Re: [PATCH v7 6/9] media: qcom: camss: Add CAMSS Offline Processing Engine driver

From: Loic Poulain

Date: Tue Sep 15 2026 - 10:40:42 EST


On Tue, Sep 15, 2026 at 12:19 PM Bryan O'Donoghue
<bryan.odonoghue@xxxxxxxxxx> wrote:
>
> On 15/09/2026 09:11, Loic Poulain wrote:
> > Add an image processing driver for the Qualcomm Offline Processing Engine
> > (OPE). OPE is a memory-to-memory ISP block that converts raw Bayer
> > frames to YUV, performing white balance, demosaic, chroma enhancement,
> > color correction and downscaling.
> >
> > The hardware architecture consists of Fetch Engines and Write Engines,
> > connected through intermediate pipeline modules for pix processing.
> >
> > The driver exposes three video nodes per pipeline instance:
> > - ope_input: Bayer RAW input (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> > - ope_disp_output: YUV output (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> > - ope_params: ISP parameters (V4L2_BUF_TYPE_META_OUTPUT)
> >
> > Hardware features:
> > - Stripe-based processing (up to 336 pixels wide per stripe)
> > - White balance (CLC_WB)
> > - Demosaic / Bayer-to-RGB (CLC_DEMO)
> > - Color correction matrix (CLC_CC)
> > - Gamma LUT (CLC_GAMMA)
> > - RGB-to-YUV conversion (CLC_CHROMA_ENHAN)
> > - MN downscaler for chroma and luma 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.
> >
> > The driver is split into three source files under the ope/ directory:
> >
> > - core.c: the OPE m2m driver itself: probe, power management, V4L2/media
> > device setup, format handling, stripe generation and hardware
> > programming.
> >
> > - pipeline.c/.h: a small declarative media-controller topology builder.
> > Drivers describe their entire media graph, entities (video devices,
> > subdevs, or base entities), their pads, and the links between them, in
> > a static descriptor table. The builder validates the table, allocates
> > and registers all entities, and creates all MC pad links. It is kept
> > generic but currently only used by OPE.
> >
> > - params.c/.h: V4L2 ISP parameter buffer validation and dispatch. It
> > wraps the extensible V4L2 ISP parameters buffer format, validating
> > the buffer size and each per-block header before forwarding every
> > block to its driver-supplied handler.
> >
> > A userspace libcamera support can be found at:
> > https://github.com/jwrdegoede/libcamera/commits/camss_pipeline_v2.1/
> >
> > A test tool, camss-isp-m2m, can be found at:
> > https://github.com/loicpoulain/camss-isp-m2m-test
> >
> > Co-developed-by: Hans de Goede <johannes.goede@xxxxxxxxxxxxxxxx>
> > Signed-off-by: Hans de Goede <johannes.goede@xxxxxxxxxxxxxxxx>
> > Link: https://github.com/jwrdegoede/libcamera/commits/camss_pipeline_v2.1/
> > Link: https://github.com/loicpoulain/camss-isp-m2m-test
> > Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>
>
> Should there be an Assisted-by here ?

Yes, over the versions, I have been leveraging LLM to support
refactoring efforts and address reported patchwise/sashiko issues.
I'll add it.

>
> > ---
> > MAINTAINERS | 1 +
> > drivers/media/platform/qcom/camss/Kconfig | 2 +
> > drivers/media/platform/qcom/camss/Makefile | 2 +
> > drivers/media/platform/qcom/camss/ope/Kconfig | 16 +
> > drivers/media/platform/qcom/camss/ope/Makefile | 9 +
> > drivers/media/platform/qcom/camss/ope/core.c | 3658 ++++++++++++++++++++++
> > drivers/media/platform/qcom/camss/ope/params.c | 78 +
> > drivers/media/platform/qcom/camss/ope/params.h | 95 +
> > drivers/media/platform/qcom/camss/ope/pipeline.c | 412 +++
> > drivers/media/platform/qcom/camss/ope/pipeline.h | 239 ++
> > 10 files changed, 4512 insertions(+)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index b5593f814b60fcbcd88f4b4aad25880d19825dc0..a165b356e81d5cb362831245c18382452bac12ff 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -22365,6 +22365,7 @@ F: Documentation/admin-guide/media/qcom_camss.rst
> > F: Documentation/devicetree/bindings/media/qcom,*camss*
> > F: Documentation/userspace-api/media/v4l/metafmt-qcom-isp.rst
> > F: drivers/media/platform/qcom/camss/
> > +F: include/uapi/linux/qcom-camss-config.h
> >
> > QUALCOMM CLOCK DRIVERS
> > M: Bjorn Andersson <andersson@xxxxxxxxxx>
> > diff --git a/drivers/media/platform/qcom/camss/Kconfig b/drivers/media/platform/qcom/camss/Kconfig
> > index 4eda48cb1adf049a7fb6cb59b9da3c0870fe57f4..414dfd3d0ecea0d0c575f5a2aba31846d10deb11 100644
> > --- a/drivers/media/platform/qcom/camss/Kconfig
> > +++ b/drivers/media/platform/qcom/camss/Kconfig
> > @@ -7,3 +7,5 @@ config VIDEO_QCOM_CAMSS
> > select VIDEO_V4L2_SUBDEV_API
> > select VIDEOBUF2_DMA_SG
> > select V4L2_FWNODE
> > +
> > +source "drivers/media/platform/qcom/camss/ope/Kconfig"
> > diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
> > index 27898b3cc7d3c8f275567f81f0952e2a0e18f189..b79c40626c401767fe31b3a2fc68c1db38a5a11d 100644
> > --- a/drivers/media/platform/qcom/camss/Makefile
> > +++ b/drivers/media/platform/qcom/camss/Makefile
> > @@ -31,3 +31,5 @@ qcom-camss-objs += \
> > camss-video.o
> >
> > obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o
> > +
> > +obj-$(CONFIG_VIDEO_QCOM_CAMSS_OPE) += ope/
> > diff --git a/drivers/media/platform/qcom/camss/ope/Kconfig b/drivers/media/platform/qcom/camss/ope/Kconfig
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..822ca2188c6244eba7363565afd635e145effe50
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/Kconfig
> > @@ -0,0 +1,16 @@
> > +config VIDEO_QCOM_CAMSS_OPE
> > + tristate "Qualcomm Offline Processing Engine (OPE) driver"
> > + depends on V4L_PLATFORM_DRIVERS
> > + depends on VIDEO_DEV
> > + depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST
> > + select V4L2_ISP
> > + select VIDEOBUF2_DMA_CONTIG
> > + select VIDEOBUF2_VMALLOC
> > + help
> > + Enable support for the Qualcomm Offline Processing Engine (OPE).
> > + OPE is a memory-to-memory ISP block that converts raw Bayer frames
> > + to YUV, performing white balance, demosaic, chroma enhancement and
> > + downscaling. Found on QCM2290 and related SoCs.
> > +
> > + To compile this driver as a module, choose M here: the module
> > + will be called qcom-camss-ope.
> > diff --git a/drivers/media/platform/qcom/camss/ope/Makefile b/drivers/media/platform/qcom/camss/ope/Makefile
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..02599a7b46e17640cbdda8600c95bb268ef8ab31
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/Makefile
> > @@ -0,0 +1,9 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +# Makefile for Qualcomm CAMSS Offline Processing Engine (OPE) driver
> > +
> > +qcom-camss-ope-objs := \
> > + core.o \
> > + pipeline.o \
> > + params.o
> > +
> > +obj-$(CONFIG_VIDEO_QCOM_CAMSS_OPE) += qcom-camss-ope.o
> > diff --git a/drivers/media/platform/qcom/camss/ope/core.c b/drivers/media/platform/qcom/camss/ope/core.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..c9d3e3910b443fc265b23e3a2b1007d23d9717bc
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/core.c
> > @@ -0,0 +1,3658 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ope/core.c
> > + *
> > + * Qualcomm MSM Camera Subsystem - Offline Processing Engine
> > + *
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +/*
> > + * This driver provides driver 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:
> > + * WB: White balance for channel gain configuration
> > + * DEMO: Demosaic for Bayer to RGB conversion
> > + * CC: Color Correct, coefficient based RGB correction
> > + * GLUT: Gamma LUT, redistributes brightness levels
> > + * CHROMA_ENHAN: for RGB to YUV conversion
> > + * 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/cleanup.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/slab.h>
> > +#include <linux/units.h>
> > +
> > +#include <media/v4l2-device.h>
> > +#include <media/media-device.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-fh.h>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/v4l2-rect.h>
> > +
> > +#include "pipeline.h"
> > +
> > +#include <media/videobuf2-dma-contig.h>
> > +#include <media/videobuf2-vmalloc.h>
> > +
> > +#include <uapi/linux/qcom-camss-config.h>
> > +
> > +#include "params.h"
> > +
> > +#define OPE_NAME "qcom-camss-ope"
> > +
> > +/* Format descriptor */
> > +struct ope_fmt {
> > + u32 fourcc;
> > + unsigned int depth;
> > + unsigned int align;
> > + unsigned int num_planes;
> > + u32 mbus_code;
> > + unsigned int c_hsub;
> > + unsigned int c_vsub;
> > +};
> > +
> > +/* Per-queue format state */
> > +struct ope_fmt_state {
> > + const struct ope_fmt *fmt;
> > + unsigned int width;
> > + unsigned int height;
> > + struct v4l2_rect crop;
> > + unsigned int bytesperline;
> > + unsigned int sizeimage;
> > + enum v4l2_colorspace colorspace;
> > + enum v4l2_xfer_func xfer_func;
> > + enum v4l2_ycbcr_encoding ycbcr_enc;
> > + enum v4l2_quantization quantization;
> > + unsigned int sequence;
> > + struct v4l2_fract timeperframe;
> > +};
> > +
> > +/* -------- Register layout -------- */
> > +
> > +#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_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_CMD 0x024
> > +#define OPE_TOP_IRQ_CMD_CLEAR BIT(0)
> > +#define OPE_TOP_VIOLATION_STATUS 0x028
> > +
> > +/* Fetch engine */
> > +#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_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 GENMASK(3, 2)
> > +#define OPE_BUS_RD_INPUT_IF_IRQ_STATUS_CCIF_VIOL GENMASK(17, 16)
> > +#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_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_CMD 0x030
> > +#define OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR BIT(0)
> > +#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)
> > +
> > +/* Pipeline modules */
> > +
> > +#define OPE_PP_HW_VERSION(base) ((base) + 0x00)
> > +#define OPE_PP_HW_STATUS(base) ((base) + 0x04)
> > +#define OPE_HW_VERSION_GEN(v) ((u32)FIELD_GET(OPE_TOP_HW_VERSION_GEN, (v)))
> > +#define OPE_HW_VERSION_REV(v) ((u32)FIELD_GET(OPE_TOP_HW_VERSION_REV, (v)))
> > +#define OPE_HW_VERSION_STEP(v) ((u32)FIELD_GET(OPE_TOP_HW_VERSION_STEP, (v)))
> > +
> > +#define OPE_PP_WB_GAIN_BASE 0x200
> > +#define OPE_PP_WB_GAIN_MODULE_CFG (OPE_PP_WB_GAIN_BASE + 0x60)
> > +#define OPE_PP_WB_GAIN_MODULE_CFG_EN BIT(0)
> > +#define OPE_PP_WB_GAIN_WB_CFG(ch) (OPE_PP_WB_GAIN_BASE + 0x68 + 4 * (ch))
> > +#define OPE_PP_WB_GAIN_WB_CFG_GAIN GENMASK(14, 0)
> > +#define OPE_PP_WB_GAIN_WB_SUB_CFG(ch) (OPE_PP_WB_GAIN_BASE + 0x74 + 4 * (ch))
> > +#define OPE_PP_WB_GAIN_WB_SUB_CFG_VAL GENMASK(31, 20)
> > +#define OPE_PP_WB_GAIN_WB_ADD_CFG(ch) (OPE_PP_WB_GAIN_BASE + 0x80 + 4 * (ch))
> > +#define OPE_PP_WB_GAIN_WB_ADD_CFG_VAL GENMASK(31, 20)
> > +
> > +#define OPE_PP_CC_BASE 0xa00
> > +#define OPE_PP_CC_MODULE_CFG (OPE_PP_CC_BASE + 0x60)
> > +#define OPE_PP_CC_MODULE_CFG_EN BIT(0)
> > +#define OPE_PP_CC_COEFF_A_CFG_0 (OPE_PP_CC_BASE + 0x68)
> > +#define OPE_PP_CC_COEFF_A_CFG_0_A0 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_A_CFG_0_A1 GENMASK(27, 16)
> > +#define OPE_PP_CC_COEFF_A_CFG_1 (OPE_PP_CC_BASE + 0x6c)
> > +#define OPE_PP_CC_COEFF_A_CFG_1_A2 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_B_CFG_0 (OPE_PP_CC_BASE + 0x70)
> > +#define OPE_PP_CC_COEFF_B_CFG_0_B0 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_B_CFG_0_B1 GENMASK(27, 16)
> > +#define OPE_PP_CC_COEFF_B_CFG_1 (OPE_PP_CC_BASE + 0x74)
> > +#define OPE_PP_CC_COEFF_B_CFG_1_B2 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_C_CFG_0 (OPE_PP_CC_BASE + 0x78)
> > +#define OPE_PP_CC_COEFF_C_CFG_0_C0 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_C_CFG_0_C1 GENMASK(27, 16)
> > +#define OPE_PP_CC_COEFF_C_CFG_1 (OPE_PP_CC_BASE + 0x7c)
> > +#define OPE_PP_CC_COEFF_C_CFG_1_C2 GENMASK(11, 0)
> > +#define OPE_PP_CC_COEFF_K_CFG_0 (OPE_PP_CC_BASE + 0x80)
> > +#define OPE_PP_CC_COEFF_K_CFG_0_K0 GENMASK(31, 21)
> > +#define OPE_PP_CC_COEFF_K_CFG_1 (OPE_PP_CC_BASE + 0x84)
> > +#define OPE_PP_CC_COEFF_K_CFG_1_K1 GENMASK(31, 21)
> > +#define OPE_PP_CC_COEFF_K_CFG_2 (OPE_PP_CC_BASE + 0x88)
> > +#define OPE_PP_CC_COEFF_K_CFG_2_K2 GENMASK(31, 21)
> > +#define OPE_PP_CC_COEFF_M_CFG (OPE_PP_CC_BASE + 0x8c)
> > +#define OPE_PP_CC_COEFF_M_CFG_M GENMASK(1, 0)
> > +
> > +#define OPE_PP_GLUT_BASE 0x1000
> > +#define OPE_PP_GLUT_DMI_CFG (OPE_PP_GLUT_BASE + 0x08)
> > +#define OPE_PP_GLUT_DMI_CFG_AUTO_LOAD_EN BIT(20)
> > +#define OPE_PP_GLUT_DMI_CFG_ADDR GENMASK(7, 0)
> > +#define OPE_PP_GLUT_DMI_LUT_CFG (OPE_PP_GLUT_BASE + 0x0c)
> > +#define OPE_PP_GLUT_DMI_LUT_CFG_SEL GENMASK(1, 0)
> > +#define OPE_PP_GLUT_DMI_DATA (OPE_PP_GLUT_BASE + 0x10)
> > +#define OPE_PP_GLUT_DMI_LUT_BANK_CFG (OPE_PP_GLUT_BASE + 0x58)
> > +#define OPE_PP_GLUT_DMI_LUT_BANK_CFG_SEL BIT(0)
> > +#define OPE_PP_GLUT_MODULE_LUT_BANK_CFG (OPE_PP_GLUT_BASE + 0x5c)
> > +#define OPE_PP_GLUT_MODULE_LUT_BANK_CFG_SEL BIT(0)
> > +#define OPE_PP_GLUT_MODULE_CFG (OPE_PP_GLUT_BASE + 0x60)
> > +#define OPE_PP_GLUT_MODULE_CFG_EN BIT(0)
> > +
> > +#define OPE_PP_DEMO_BASE 0x800
> > +#define OPE_PP_DEMO_MODULE_CFG (OPE_PP_DEMO_BASE + 0x60)
> > +#define OPE_PP_DEMO_MODULE_CFG_EN BIT(0)
> > +#define OPE_PP_DEMO_MODULE_CFG_DYN_G_CLAMP_EN BIT(14)
> > +#define OPE_PP_DEMO_MODULE_CFG_DYN_RB_CLAMP_EN BIT(15)
> > +#define OPE_PP_DEMO_INTERP_COEFF_CFG (OPE_PP_DEMO_BASE + 0x68)
> > +#define OPE_PP_DEMO_INTERP_COEFF_CFG_LAMBDA_G GENMASK(7, 0)
> > +#define OPE_PP_DEMO_INTERP_COEFF_CFG_LAMBDA_RB GENMASK(23, 16)
> > +#define OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_0 (OPE_PP_DEMO_BASE + 0x6c)
> > +#define OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_0_AK GENMASK(31, 20)
> > +#define OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_1 (OPE_PP_DEMO_BASE + 0x70)
> > +#define OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_1_WK GENMASK(9, 0)
> > +
> > +#define OPE_PP_DOWNSCALE_MN_DS_C_PRE_BASE 0x1c00
> > +#define OPE_PP_DOWNSCALE_MN_DS_Y_DISP_BASE 0x3000
> > +#define OPE_PP_DOWNSCALE_MN_DS_C_DISP_BASE 0x3200
> > +#define OPE_PP_CROP_RND_CLAMP_Y_DISP_BASE 0x3400
> > +#define OPE_PP_CROP_RND_CLAMP_C_DISP_BASE 0x3600
> > +#define OPE_PP_CROP_RND_CLAMP_HW_STATUS(base) OPE_PP_HW_STATUS(base)
> > +#define OPE_PP_CROP_RND_CLAMP_HEIGHT_VIOL BIT(2)
> > +#define OPE_PP_CROP_RND_CLAMP_WIDTH_VIOL BIT(1)
> > +#define OPE_PP_CROP_RND_CLAMP_MODULE_CFG(base) ((base) + 0x60)
> > +#define OPE_PP_CROP_RND_CLAMP_EN BIT(0)
> > +#define OPE_PP_CROP_RND_CLAMP_CROP_EN BIT(9)
> > +#define OPE_PP_CROP_RND_CLAMP_CROP_LINE_CFG(base) ((base) + 0x68)
> > +#define OPE_PP_CROP_RND_CLAMP_CROP_PIXEL_CFG(base) ((base) + 0x6c)
> > +#define OPE_PP_CROP_RND_CLAMP_CROP_FIRST GENMASK(29, 16)
> > +#define OPE_PP_CROP_RND_CLAMP_CROP_LAST GENMASK(13, 0)
> > +#define OPE_PP_DOWNSCALE_MN_CFG(ds) ((ds) + 0x60)
> > +#define OPE_PP_DOWNSCALE_MN_CFG_EN BIT(0)
> > +#define OPE_PP_DOWNSCALE_MN_DS_CFG(ds) ((ds) + 0x64)
> > +#define OPE_PP_DOWNSCALE_MN_DS_CFG_H_SCALE_EN BIT(9)
> > +#define OPE_PP_DOWNSCALE_MN_DS_CFG_V_SCALE_EN BIT(10)
> > +#define OPE_PP_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(ds) ((ds) + 0x68)
> > +#define OPE_PP_DOWNSCALE_MN_DS_MN_H_CFG(ds) ((ds) + 0x6c)
> > +#define OPE_PP_DOWNSCALE_MN_DS_MN_H_PHASE_CFG(ds) ((ds) + 0x70)
> > +#define OPE_PP_DOWNSCALE_MN_DS_MN_V_CFG(ds) ((ds) + 0x74)
> > +#define OPE_PP_DOWNSCALE_MN_DS_MN_V_PHASE_CFG(ds) ((ds) + 0x78)
> > +
> > +#define OPE_PP_CHROMA_ENHAN_BASE 0x1200
> > +#define OPE_PP_CHROMA_ENHAN_MODULE_CFG (OPE_PP_CHROMA_ENHAN_BASE + 0x60)
> > +#define OPE_PP_CHROMA_ENHAN_MODULE_CFG_EN BIT(0)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_0 (OPE_PP_CHROMA_ENHAN_BASE + 0x68)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_0_V0 GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_0_V1 GENMASK(27, 16)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_1 (OPE_PP_CHROMA_ENHAN_BASE + 0x6c)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_1_K GENMASK(31, 23)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_2 (OPE_PP_CHROMA_ENHAN_BASE + 0x70)
> > +#define OPE_PP_CHROMA_ENHAN_LUMA_CFG_2_V2 GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_A_CFG (OPE_PP_CHROMA_ENHAN_BASE + 0x74)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_A_CFG_AP GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_A_CFG_AM GENMASK(27, 16)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_B_CFG (OPE_PP_CHROMA_ENHAN_BASE + 0x78)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_B_CFG_BP GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_B_CFG_BM GENMASK(27, 16)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_C_CFG (OPE_PP_CHROMA_ENHAN_BASE + 0x7C)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_C_CFG_CP GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_C_CFG_CM GENMASK(27, 16)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_D_CFG (OPE_PP_CHROMA_ENHAN_BASE + 0x80)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_D_CFG_DP GENMASK(11, 0)
> > +#define OPE_PP_CHROMA_ENHAN_COEFF_D_CFG_DM GENMASK(27, 16)
> > +#define OPE_PP_CHROMA_ENHAN_CHROMA_CFG_0 (OPE_PP_CHROMA_ENHAN_BASE + 0x84)
> > +#define OPE_PP_CHROMA_ENHAN_CHROMA_CFG_0_KCB GENMASK(31, 21)
> > +#define OPE_PP_CHROMA_ENHAN_CHROMA_CFG_1 (OPE_PP_CHROMA_ENHAN_BASE + 0x88)
> > +#define OPE_PP_CHROMA_ENHAN_CHROMA_CFG_1_KCR GENMASK(31, 21)
> > +
> > +/* -------- OPE-specific constants -------- */
> > +
> > +#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 OPE_MAX_STRIDE 0xffff
> > +#define OPE_RESET_TIMEOUT_MS 100
> > +#define DEFAULT_FRAMERATE 60
> > +#define OPE_MAX_FRAMERATE 1000
> > +
> > +/*
> > + * AXI clock is shared across CAMSS blocks and not dynamically scaled,
> > + * just request the highest supported rate.
> > + */
> > +#define OPE_AXI_CLK_RATE (300 * HZ_PER_MHZ)
> > +
> > +/* Downscaler fixed-point helpers */
> > +#define Q21(v) (((uint64_t)(v)) << 21)
> > +#define DS_Q21(n, d) ((uint32_t)(((uint64_t)(n) << 21) / (d)))
> > +#define DS_RESOLUTION(in, out) \
> > + (((out) * 128 <= (in)) ? 0x0 : \
> > + ((out) * 16 <= (in)) ? 0x1 : \
> > + ((out) * 8 <= (in)) ? 0x2 : 0x3)
> > +#define DS_OUTPUT_PIX(in, phase_init, phase_step) \
> > + ((Q21(in) - (phase_init)) / (phase_step))
> > +#define OPE_WB(n, d) (((n) << 10) / (d))
> > +
> > +enum ope_downscaler {
> > + OPE_DS_C_PRE,
> > + OPE_DS_C_DISP,
> > + OPE_DS_Y_DISP,
> > + OPE_DS_MAX,
> > +};
> > +
> > +enum ope_glut_sel {
> > + OPE_GLUT_SEL_NONE,
> > + OPE_GLUT_SEL_G,
> > + OPE_GLUT_SEL_B,
> > + OPE_GLUT_SEL_R,
> > +};
> > +
> > +static const u32 ope_ds_base[OPE_DS_MAX] = {
> > + OPE_PP_DOWNSCALE_MN_DS_C_PRE_BASE,
> > + OPE_PP_DOWNSCALE_MN_DS_C_DISP_BASE,
> > + OPE_PP_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_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_8 = 1,
> > + OPE_UNPACKER_FMT_PLAIN_16_10BPP = 2,
> > + OPE_UNPACKER_FMT_MIPI_10 = 13,
> > +};
> > +
> > +enum ope_packer_format {
> > + OPE_PACKER_FMT_PLAIN_8 = 1,
> > + OPE_PACKER_FMT_PLAIN_8_ODD_EVEN = 2,
> > + OPE_PACKER_FMT_MIPI_10 = 12,
> > +};
> > +
> > +struct ope_hw_fmt {
> > + u32 fourcc;
> > + enum ope_pixel_pattern pattern;
> > + enum ope_unpacker_format unpacker;
> > + enum ope_packer_format packer;
> > +};
> > +
> > +static const struct ope_hw_fmt ope_hw_fmts[] = {
> > + { V4L2_PIX_FMT_SBGGR10P, OPE_PIXEL_PATTERN_BGBGBG,
> > + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 },
> > + { V4L2_PIX_FMT_SGBRG10P, OPE_PIXEL_PATTERN_GBGBGB,
> > + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 },
> > + { V4L2_PIX_FMT_SGRBG10P, OPE_PIXEL_PATTERN_GRGRGR,
> > + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 },
> > + { V4L2_PIX_FMT_SRGGB10P, OPE_PIXEL_PATTERN_RGRGRG,
> > + OPE_UNPACKER_FMT_MIPI_10, OPE_PACKER_FMT_MIPI_10 },
> > + { V4L2_PIX_FMT_SRGGB8, OPE_PIXEL_PATTERN_RGRGRG,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_SBGGR8, OPE_PIXEL_PATTERN_BGBGBG,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_SGBRG8, OPE_PIXEL_PATTERN_GBGBGB,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_SGRBG8, OPE_PIXEL_PATTERN_GRGRGR,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_NV24, OPE_PIXEL_PATTERN_YCBYCR,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_NV42, OPE_PIXEL_PATTERN_YCRYCB,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN },
> > + { V4L2_PIX_FMT_NV16, OPE_PIXEL_PATTERN_CBYCRY,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_NV61, OPE_PIXEL_PATTERN_CBYCRY,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN },
> > + { V4L2_PIX_FMT_NV12, OPE_PIXEL_PATTERN_CBYCRY,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > + { V4L2_PIX_FMT_NV21, OPE_PIXEL_PATTERN_CBYCRY,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8_ODD_EVEN },
> > + { V4L2_PIX_FMT_GREY, OPE_PIXEL_PATTERN_RGRGRG,
> > + OPE_UNPACKER_FMT_PLAIN_8, OPE_PACKER_FMT_PLAIN_8 },
> > +};
> > +
> > +static const struct ope_hw_fmt *ope_find_hw_fmt(u32 fourcc)
> > +{
> > + for (unsigned int i = 0; i < ARRAY_SIZE(ope_hw_fmts); i++)
> > + if (ope_hw_fmts[i].fourcc == fourcc)
> > + return &ope_hw_fmts[i];
> > +
> > + return NULL;
> > +}
> > +
> > +static const struct ope_fmt ope_input_fmts[] = {
> > + { V4L2_PIX_FMT_SBGGR10P, 10, 2, 1, MEDIA_BUS_FMT_SBGGR10_1X10 },
> > + { V4L2_PIX_FMT_SGBRG10P, 10, 2, 1, MEDIA_BUS_FMT_SGBRG10_1X10 },
> > + { V4L2_PIX_FMT_SGRBG10P, 10, 2, 1, MEDIA_BUS_FMT_SGRBG10_1X10 },
> > + { V4L2_PIX_FMT_SRGGB10P, 10, 2, 1, MEDIA_BUS_FMT_SRGGB10_1X10 },
> > + { V4L2_PIX_FMT_SRGGB8, 8, 0, 1, MEDIA_BUS_FMT_SRGGB8_1X8 },
> > + { V4L2_PIX_FMT_SBGGR8, 8, 0, 1, MEDIA_BUS_FMT_SBGGR8_1X8 },
> > + { V4L2_PIX_FMT_SGBRG8, 8, 0, 1, MEDIA_BUS_FMT_SGBRG8_1X8 },
> > + { V4L2_PIX_FMT_SGRBG8, 8, 0, 1, MEDIA_BUS_FMT_SGRBG8_1X8 },
> > +};
> > +
> > +static const struct ope_fmt ope_output_fmts[] = {
> > + { V4L2_PIX_FMT_NV24, 24, 0, 1, MEDIA_BUS_FMT_YUV8_1X24, 1, 1 },
> > + { V4L2_PIX_FMT_NV42, 24, 0, 1, MEDIA_BUS_FMT_YUV8_1X24, 1, 1 },
> > + { V4L2_PIX_FMT_NV16, 16, 1, 1, MEDIA_BUS_FMT_YUYV8_2X8, 2, 1 },
> > + { V4L2_PIX_FMT_NV61, 16, 1, 1, MEDIA_BUS_FMT_YUYV8_2X8, 2, 1 },
> > + { V4L2_PIX_FMT_NV12, 12, 1, 1, MEDIA_BUS_FMT_YUYV8_1_5X8, 2, 2 },
> > + { V4L2_PIX_FMT_NV21, 12, 1, 1, MEDIA_BUS_FMT_YUYV8_1_5X8, 2, 2 },
> > + { V4L2_PIX_FMT_GREY, 8, 0, 1, MEDIA_BUS_FMT_Y8_1X8, 1, 1 },
> > +};
> > +
> > +struct ope_dsc_config {
> > + u32 input_width;
> > + u32 input_height;
> > + u32 output_width;
> > + u32 output_height;
> > + u32 phase_step_h;
> > + u32 phase_step_v;
> > + u32 crop_last_pixel;
> > + u32 crop_last_line;
> > +};
> > +
> > +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];
> > +};
> > +
> > +/* OPE tuning parameter state */
> > +
> > +/* Demosaic (CLC_DEMO) coefficients, internal only, not user-configurable. */
> > +#define CAMSS_PARAMS_INTERNAL (-1)
> > +
> > +struct ope_params_demo {
> > + struct v4l2_isp_params_block_header header;
> > + u8 lambda_rb;
> > + u8 lambda_g;
> > + u16 a_k;
> > + u16 w_k;
> > + u16 _pad;
> > +};
> > +
> > +/* OPE tuning parameter state */
> > +struct ope_config {
> > + struct camss_params_ope_wb_gain wb_gain;
> > + struct camss_params_ope_chroma_enhan chroma_enhan;
> > + struct camss_params_ope_color_correct color_correct;
> > + struct camss_params_ope_gamma gamma;
> > + struct ope_params_demo demo;
> > +};
> > +
> > +enum ope_entity {
> > + OPE_ENTITY_FRAME_IN,
> > + OPE_ENTITY_PARAMS,
> > + OPE_ENTITY_PROC,
> > + OPE_ENTITY_DISP,
> > + OPE_ENTITY_DISP_OUT,
> > + OPE_ENTITY_COUNT
> > +};
> > +
> > +enum ope_queue_idx {
> > + OPE_QUEUE_FRAME_IN,
> > + OPE_QUEUE_DISP_OUT,
> > + OPE_QUEUE_PARAMS,
> > + OPE_QUEUE_COUNT
> > +};
> > +
> > +struct camss_isp_buf {
> > + struct vb2_v4l2_buffer vb; /* must be first */
> > + struct list_head list;
> > +};
> > +
> > +struct ope_queue {
> > + struct vb2_queue vq;
> > + struct list_head rdy_queue;
> > + spinlock_t rdy_spinlock;
> > +};
> > +
> > +enum ope_proc_pad {
> > + OPE_PROC_PAD_SINK_IN,
> > + OPE_PROC_PAD_SINK_PAR,
> > + OPE_PROC_PAD_SOURCE,
> > + OPE_PROC_PADS_NUM,
> > +};
> > +
> > +enum ope_disp_pad {
> > + OPE_DISP_PAD_SINK,
> > + OPE_DISP_PAD_SOURCE,
> > + OPE_DISP_PADS_NUM,
> > +};
> > +
> > +/* per-context state */
> > +struct ope_ctx {
> > + bool queued;
> > + struct ope_dev *ope;
> > + struct mutex vbq_lock;
> > + struct ope_queue queues[OPE_QUEUE_COUNT];
> > +
> > + unsigned int framerate;
> > + struct ope_fmt_state fmt_in;
> > + struct ope_fmt_state fmt_out;
> > +
> > + struct list_head list;
> > + bool started;
> > + bool first_start;
> > +
> > + struct ope_config config;
> > + u8 current_stripe;
> > + struct ope_stripe stripe[OPE_MAX_STRIPE];
> > +
> > + struct v4l2_isp_params_buffer *params_buf;
> > +};
> > +
> > +/* Per OPE device state */
> > +struct ope_dev {
> > + struct device *dev;
> > + struct v4l2_device v4l2_dev;
> > + struct media_device mdev;
> > + struct camss_isp_pipeline *pipeline;
> > + struct mutex mutex;
> > + struct work_struct run_work;
> > + struct ope_ctx *curr_ctx;
> > + struct ope_ctx *prev_ctx;
> > + spinlock_t sched_lock;
> > + int irq;
> > + u32 hw_version;
> > +
> > + struct icc_path *icc_data;
> > + struct icc_path *icc_config;
> > +
> > + void __iomem *base;
> > + void __iomem *base_rd;
> > + void __iomem *base_wr;
> > + void __iomem *base_pp;
> > +
> > + struct completion reset_complete;
> > +
> > + struct list_head ctx_list;
> > + spinlock_t ctx_lock;
> > + struct ope_ctx *shared_ctx;
> > + unsigned int open_count;
> > +
> > + /* Currently active hardware context (set at job start) */
> > + struct ope_ctx *hw_ctx;
> > +
> > + /* Gamma LUTs handling */
> > + struct {
> > + struct ope_ctx *owner;
> > + bool valid;
> > + } glut_bank[2];
> > + u8 glut_cur_bank;
> > + u8 glut_mru_bank;
> > +};
> > +
> > +/* -------- Register accessors -------- */
> > +
> > +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 val)
> > +{
> > + writel(val, ope->base + reg);
> > +}
> > +
> > +static inline void ope_write_wr(struct ope_dev *ope, u32 reg, u32 val)
> > +{
> > + writel_relaxed(val, ope->base_wr + 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_rd(struct ope_dev *ope, u32 reg, u32 val)
> > +{
> > + writel_relaxed(val, ope->base_rd + reg);
> > +}
> > +
> > +static inline void ope_write_pp(struct ope_dev *ope, u32 reg, u32 val)
> > +{
> > + writel_relaxed(val, ope->base_pp + reg);
> > +}
> > +
> > +static inline u32 ope_read_pp(struct ope_dev *ope, u32 reg)
> > +{
> > + return readl_relaxed(ope->base_pp + reg);
> > +}
> > +
> > +static inline void ope_start(struct ope_dev *ope)
> > +{
> > + wmb(); /* ensure all register writes are visible before GO_CMD */
> > + ope_write_rd(ope, OPE_BUS_RD_INPUT_IF_CMD, OPE_BUS_RD_INPUT_IF_CMD_GO_CMD);
> > +}
> > +
> > +static inline bool ope_hw_available(struct ope_dev *ope)
> > +{
> > + /* v4l2_device_unregister() clear the dev pointer */
> > + return !!ope->v4l2_dev.dev;
> > +}
> > +
> > +/* -------- Stripe helpers -------- */
> > +
> > +static inline enum ope_stripe_location ope_stripe_location(unsigned int idx, unsigned int count)
> > +{
> > + if (count == 1)
> > + return OPE_STRIPE_LOCATION_FULL;
> > + if (idx == 0)
> > + return OPE_STRIPE_LOCATION_LEFT;
> > + if (idx == count - 1)
> > + return OPE_STRIPE_LOCATION_RIGHT;
> > +
> > + return OPE_STRIPE_LOCATION_MIDDLE;
> > +}
> > +
> > +static inline bool ope_stripe_is_last(const struct ope_stripe *s)
> > +{
> > + return s && (s->src.location == OPE_STRIPE_LOCATION_RIGHT ||
> > + s->src.location == OPE_STRIPE_LOCATION_FULL);
> > +}
> > +
> > +static inline struct ope_stripe *ope_current_stripe(struct ope_ctx *ctx)
> > +{
> > + if (ctx->current_stripe >= OPE_MAX_STRIPE)
> > + return NULL;
> > +
> > + return &ctx->stripe[ctx->current_stripe];
> > +}
> > +
> > +static inline unsigned int ope_stripe_index(struct ope_ctx *ctx, const 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 idx = ope_stripe_index(ctx, stripe);
> > +
> > + return idx ? &ctx->stripe[idx - 1] : NULL;
> > +}
> > +
> > +
> > +static inline unsigned int ope_last_stripe_width(unsigned int src_width, unsigned int nstripes)
> > +{
> > + unsigned int last_sw = src_width - (nstripes - 1) * OPE_STRIPE_MAX_W;
> > +
> > + if (last_sw < OPE_STRIPE_MIN_W)
> > + last_sw += OPE_STRIPE_MIN_W * 2;
> > +
> > + return last_sw;
> > +}
> > +
> > +static unsigned int ope_calc_h_scale(unsigned int src, unsigned int dst,
> > + unsigned int nstripes, unsigned int c_hsub,
> > + unsigned int sw_align)
> > +{
> > + unsigned int last_sw = ope_last_stripe_width(src, nstripes);
> > + unsigned int denom;
> > +
> > + if (src == dst)
> > + return DS_Q21(src, src);
> > +
> > + /* Find the smallest h_scale denominator >= dst such that the
> > + * last-stripe adjustment can satisfy both Y and C pixel counts.
> > + */
> > + for (denom = dst; denom <= dst + 16 * nstripes; denom++) {
> > + unsigned int hs = DS_Q21(src, denom);
> > + unsigned int tw = (nstripes - 1) * DS_OUTPUT_PIX(OPE_STRIPE_MAX_W, 0, hs);
> > + unsigned int tc = (nstripes - 1) * DS_OUTPUT_PIX(OPE_STRIPE_MAX_W / c_hsub, 0, hs);
> > + unsigned int ry = dst - tw;
> > + unsigned int rc = dst / c_hsub - tc;
> > + unsigned int s_y = DIV_ROUND_UP((u64)ry * hs, Q21(1));
> > + unsigned int s_c = DIV_ROUND_UP((u64)rc * hs, Q21(1)) * c_hsub;
> > + unsigned int s = ALIGN(max(s_y, s_c), sw_align);
> > +
> > + s = clamp(s, OPE_STRIPE_MIN_W, last_sw);
> > + tw += DS_OUTPUT_PIX(s, 0, hs);
> > + tc += DS_OUTPUT_PIX(s / c_hsub, 0, hs);
> > +
> > + if (tw == dst && tc * c_hsub == dst)
> > + return hs;
> > + }
> > +
> > + return DS_Q21(src, dst);
> > +}
> > +
> > +static unsigned int ope_calc_v_scale(unsigned int src, unsigned int dst)
> > +{
> > + return DS_Q21(src, dst);
> > +}
> > +
> > +static void ope_gen_stripe_chroma_dsc(struct ope_ctx *ctx,
> > + struct ope_stripe *stripe)
> > +{
> > + struct ope_dsc_config *dsc = &stripe->dsc[OPE_DS_C_PRE];
> > + const struct ope_fmt *fmt = ctx->fmt_out.fmt;
> > +
> > + dsc->input_width = stripe->src.width;
> > + dsc->input_height = stripe->src.height;
> > +
> > + dsc->output_width = dsc->input_width / fmt->c_hsub;
> > + dsc->output_height = dsc->input_height / fmt->c_vsub;
> > +
> > + 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_stripe_dsc(struct ope_ctx *ctx, struct ope_stripe *stripe,
> > + unsigned int h_scale, unsigned int v_scale)
> > +{
> > + struct ope_dsc_config *dsc_c = &stripe->dsc[OPE_DS_C_DISP];
> > + struct ope_dsc_config *dsc_y = &stripe->dsc[OPE_DS_Y_DISP];
> > + unsigned int sw = stripe->src.width;
> > + unsigned int sw_c = stripe->dsc[OPE_DS_C_PRE].output_width;
> > +
> > + 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;
> > +
> > + /* Clip the output so the WE always receives the expected count */
> > + dsc_y->output_width = DS_OUTPUT_PIX(sw, 0, h_scale);
> > + dsc_y->output_height = DS_OUTPUT_PIX(stripe->src.height, 0, v_scale);
> > + dsc_c->output_width = DS_OUTPUT_PIX(sw_c, 0, h_scale);
> > + dsc_c->output_height = DS_OUTPUT_PIX(stripe->dsc[OPE_DS_C_PRE].output_height, 0, v_scale);
> > +
> > + dsc_y->output_width = max_t(unsigned int, dsc_y->output_width, 1);
> > + dsc_y->output_height = max_t(unsigned int, dsc_y->output_height, 1);
> > + dsc_c->output_width = max_t(unsigned int, dsc_c->output_width, 1);
> > + dsc_c->output_height = max_t(unsigned int, dsc_c->output_height, 1);
> > +
> > + dsc_y->crop_last_pixel = dsc_y->output_width - 1;
> > + dsc_y->crop_last_line = dsc_y->output_height - 1;
> > + dsc_c->crop_last_pixel = dsc_c->output_width - 1;
> > + dsc_c->crop_last_line = dsc_c->output_height - 1;
> > +}
> > +
> > +static void ope_gen_stripe_yuv_dst(struct ope_ctx *ctx, struct ope_stripe *stripe,
> > + dma_addr_t dst, const struct v4l2_rect *compose)
> > +{
> > + const struct ope_fmt_state *fo = &ctx->fmt_out;
> > + const struct ope_hw_fmt *hw = ope_find_hw_fmt(fo->fmt->fourcc);
> > + struct ope_stripe *prev = ope_prev_stripe(ctx, stripe);
> > + unsigned int bpl = fo->bytesperline;
> > + unsigned int img_h = fo->height;
> > + unsigned int c_stride;
> > + unsigned int x_init;
> > + dma_addr_t base;
> > +
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].enabled = true;
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = true;
> > +
> > + /* Y plane */
> > + base = dst + compose->top * bpl;
> > +
> > + if (prev)
> > + x_init = prev->dst[OPE_WR_CLIENT_DISP_Y].x_init +
> > + prev->dst[OPE_WR_CLIENT_DISP_Y].width;
> > + else
> > + x_init = compose->left;
> > +
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].addr = base;
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].x_init = x_init;
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].width = stripe->dsc[OPE_DS_Y_DISP].output_width;
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].height = stripe->dsc[OPE_DS_Y_DISP].output_height;
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].stride = bpl;
> > + stripe->dst[OPE_WR_CLIENT_DISP_Y].format = OPE_PACKER_FMT_PLAIN_8;
> > +
> > + /* UV plane (appended to Y plane)*/
> > + c_stride = bpl * 2 / fo->fmt->c_hsub;
> > + base = dst + bpl * img_h + (compose->top / fo->fmt->c_vsub) * c_stride;
> > +
> > + if (prev)
> > + x_init = prev->dst[OPE_WR_CLIENT_DISP_C].x_init +
> > + prev->dst[OPE_WR_CLIENT_DISP_C].width;
> > + else
> > + x_init = compose->left * 2 / fo->fmt->c_hsub;
> > +
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].addr = base;
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].x_init = x_init;
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].format = hw ? hw->packer : OPE_PACKER_FMT_PLAIN_8;
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].width = stripe->dsc[OPE_DS_C_DISP].output_width * 2;
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].height = stripe->dsc[OPE_DS_C_DISP].output_height;
> > +
> > + switch (fo->fmt->fourcc) {
> > + case V4L2_PIX_FMT_GREY:
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].enabled = false;
> > + break;
> > + default:
> > + stripe->dst[OPE_WR_CLIENT_DISP_C].stride = c_stride;
> > + break;
> > + }
> > +}
> > +
> > +static void ope_gen_stripes(struct ope_ctx *ctx, dma_addr_t src, dma_addr_t dst)
> > +{
> > + const struct ope_fmt_state *fi = &ctx->fmt_in;
> > + const struct ope_hw_fmt *src_hw = ope_find_hw_fmt(fi->fmt->fourcc);
> > + unsigned int num_stripes, width, x_out, x_out_c, i;
> > + const struct ope_fmt_state *fo = &ctx->fmt_out;
> > + unsigned int c_hsub = fo->fmt->c_hsub;
> > + struct v4l2_subdev_state *sd_state;
> > + const struct v4l2_rect *compose;
> > + const struct v4l2_rect *crop;
> > + struct v4l2_subdev *proc_sd;
> > + unsigned int h_scale, v_scale;
> > +
> > + proc_sd = camss_isp_pipeline_get_subdev(ctx->ope->pipeline, OPE_ENTITY_PROC);
> > + sd_state = v4l2_subdev_lock_and_get_active_state(proc_sd);
> > + crop = v4l2_subdev_state_get_crop(sd_state, OPE_PROC_PAD_SINK_IN);
> > + compose = v4l2_subdev_state_get_compose(sd_state, OPE_PROC_PAD_SOURCE);
> > +
> > + /* Advance source pointer to the crop origin */
> > + src += crop->top * fi->bytesperline + crop->left * fi->fmt->depth / 8;
> > + width = crop->width;
> > + num_stripes = DIV_ROUND_UP(crop->width, OPE_STRIPE_MAX_W);
> > + h_scale = ope_calc_h_scale(crop->width, compose->width, num_stripes, c_hsub,
> > + max(2u, 1u << fi->fmt->align));
> > + v_scale = ope_calc_v_scale(crop->height, compose->height);
> > +
> > + x_out = x_out_c = 0;
> > +
> > + for (i = 0; i < num_stripes; i++) {
> > + struct ope_stripe *stripe = &ctx->stripe[i];
> > +
> > + memset(stripe, 0, sizeof(*stripe));
> > +
> > + stripe->src.addr = src;
> > + stripe->src.width = width;
> > + stripe->src.height = crop->height;
> > + stripe->src.stride = fi->bytesperline;
> > + stripe->src.location = ope_stripe_location(i, num_stripes);
> > + stripe->src.pattern = src_hw ? src_hw->pattern : 0;
> > + stripe->src.format = src_hw ? src_hw->unpacker : 0;
> > +
> > + /* Ensure last stripe is wide 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,
> > + OPE_STRIPE_MIN_W, OPE_STRIPE_MAX_W,
> > + fi->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 * fi->fmt->depth / 8;
> > +
> > + /* Last-stripe adjustment: compute the minimum input width that
> > + * delivers exactly the remaining output pixels for Y and C.
> > + */
> > + if (ope_stripe_is_last(stripe)) {
> > + unsigned int rem_y = compose->width - x_out;
> > + unsigned int rem_c = compose->width / c_hsub - x_out_c;
> > + unsigned int s_y = DIV_ROUND_UP((u64)rem_y * h_scale, Q21(1));
> > + unsigned int s_c = DIV_ROUND_UP((u64)rem_c * h_scale, Q21(1)) * c_hsub;
> > + unsigned int s = ALIGN(max(s_y, s_c), max(2u, 1u << fi->fmt->align));
> > +
> > + stripe->src.width = clamp(s, OPE_STRIPE_MIN_W, stripe->src.width);
> > + }
> > +
> > + ope_gen_stripe_chroma_dsc(ctx, stripe);
> > + ope_gen_stripe_dsc(ctx, stripe, h_scale, v_scale);
> > + ope_gen_stripe_yuv_dst(ctx, stripe, dst, compose);
> > +
> > + x_out += stripe->dsc[OPE_DS_Y_DISP].output_width;
> > + x_out_c += stripe->dsc[OPE_DS_C_DISP].output_width;
> > +
> > + /* Width in bytes for the fetch engine */
> > + stripe->src.width = stripe->src.width * fi->fmt->depth / 8;
> > + }
> > +
> > + v4l2_subdev_unlock_state(sd_state);
> > +}
> > +
> > +/* -------- Pipeline IQ module programming -------- */
> > +
> > +static bool ope_module_update(struct ope_ctx *ctx, u32 module_cfg_reg, u32 enable_mask,
> > + struct v4l2_isp_params_block_header *hdr, bool force)
> > +{
> > + bool enable = !(hdr->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE);
> > + bool dirty = hdr->flags & CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY;
> > +
> > + /* skip if neither forced (new context) nor dirty (updated params) */
> > + if (!force && !dirty)
> > + return false;
> > +
> > + hdr->flags &= ~CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY;
> > +
> > + ope_write_pp(ctx->ope, module_cfg_reg, enable ? enable_mask : 0);
> > +
> > + /* If module is disabled, not need to update content */
> > + if (!enable)
> > + return false;
> > +
> > + dev_dbg(ctx->ope->dev, "IQ module (%u) update (flags:0x%x)\n", hdr->type, hdr->flags);
> > +
> > + return true;
> > +}
> > +
> > +static void ope_prog_wb(struct ope_ctx *ctx, bool force)
> > +{
> > + struct camss_params_ope_wb_gain *wb = &ctx->config.wb_gain;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + if (!ope_module_update(ctx, OPE_PP_WB_GAIN_MODULE_CFG,
> > + OPE_PP_WB_GAIN_MODULE_CFG_EN,
> > + &wb->header, force))
> > + return;
> > +
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_CFG(0),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_CFG_GAIN, wb->g_gain));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_CFG(1),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_CFG_GAIN, wb->b_gain));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_CFG(2),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_CFG_GAIN, wb->r_gain));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_SUB_CFG(0),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_SUB_CFG_VAL, wb->g_sub));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_SUB_CFG(1),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_SUB_CFG_VAL, wb->b_sub));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_SUB_CFG(2),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_SUB_CFG_VAL, wb->r_sub));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_ADD_CFG(0),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_ADD_CFG_VAL, wb->g_add));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_ADD_CFG(1),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_ADD_CFG_VAL, wb->b_add));
> > + ope_write_pp(ope, OPE_PP_WB_GAIN_WB_ADD_CFG(2),
> > + FIELD_PREP(OPE_PP_WB_GAIN_WB_ADD_CFG_VAL, wb->r_add));
> > +}
> > +
> > +static void ope_prog_bayer2rgb(struct ope_ctx *ctx, bool force)
> > +{
> > + struct ope_params_demo *demo = &ctx->config.demo;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + if (!ope_module_update(ctx, OPE_PP_DEMO_MODULE_CFG,
> > + OPE_PP_DEMO_MODULE_CFG_EN |
> > + OPE_PP_DEMO_MODULE_CFG_DYN_G_CLAMP_EN |
> > + OPE_PP_DEMO_MODULE_CFG_DYN_RB_CLAMP_EN,
> > + &demo->header, force))
> > + return;
> > +
> > + ope_write_pp(ope, OPE_PP_DEMO_INTERP_COEFF_CFG,
> > + FIELD_PREP(OPE_PP_DEMO_INTERP_COEFF_CFG_LAMBDA_G, demo->lambda_g) |
> > + FIELD_PREP(OPE_PP_DEMO_INTERP_COEFF_CFG_LAMBDA_RB, demo->lambda_rb));
> > + ope_write_pp(ope, OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_0,
> > + FIELD_PREP(OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_0_AK, demo->a_k));
> > + ope_write_pp(ope, OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_1,
> > + FIELD_PREP(OPE_PP_DEMO_INTERP_CLASSIFIER_CFG_1_WK, demo->w_k));
> > +}
> > +
> > +static void ope_prog_rgb2yuv(struct ope_ctx *ctx, bool force)
> > +{
> > + struct camss_params_ope_chroma_enhan *cc = &ctx->config.chroma_enhan;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + if (!ope_module_update(ctx, OPE_PP_CHROMA_ENHAN_MODULE_CFG,
> > + OPE_PP_CHROMA_ENHAN_MODULE_CFG_EN,
> > + &cc->header, force))
> > + return;
> > +
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_LUMA_CFG_0,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_LUMA_CFG_0_V0, cc->luma_v0) |
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_LUMA_CFG_0_V1, cc->luma_v1));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_LUMA_CFG_1,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_LUMA_CFG_1_K, cc->luma_k));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_LUMA_CFG_2,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_LUMA_CFG_2_V2, cc->luma_v2));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_COEFF_A_CFG,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_A_CFG_AP, cc->coeff_ap) |
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_A_CFG_AM, cc->coeff_am));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_COEFF_B_CFG,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_B_CFG_BP, cc->coeff_bp) |
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_B_CFG_BM, cc->coeff_bm));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_COEFF_C_CFG,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_C_CFG_CP, cc->coeff_cp) |
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_C_CFG_CM, cc->coeff_cm));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_COEFF_D_CFG,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_D_CFG_DP, cc->coeff_dp) |
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_COEFF_D_CFG_DM, cc->coeff_dm));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_CHROMA_CFG_0,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_CHROMA_CFG_0_KCB, cc->kcb));
> > + ope_write_pp(ope, OPE_PP_CHROMA_ENHAN_CHROMA_CFG_1,
> > + FIELD_PREP(OPE_PP_CHROMA_ENHAN_CHROMA_CFG_1_KCR, cc->kcr));
> > +}
> > +
> > +static void ope_prog_color_correct(struct ope_ctx *ctx, bool force)
> > +{
> > + struct camss_params_ope_color_correct *cc = &ctx->config.color_correct;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + if (!ope_module_update(ctx, OPE_PP_CC_MODULE_CFG,
> > + OPE_PP_CC_MODULE_CFG_EN,
> > + &cc->header, force))
> > + return;
> > +
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_A_CFG_0,
> > + FIELD_PREP(OPE_PP_CC_COEFF_A_CFG_0_A0, cc->a[0]) |
> > + FIELD_PREP(OPE_PP_CC_COEFF_A_CFG_0_A1, cc->a[1]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_A_CFG_1,
> > + FIELD_PREP(OPE_PP_CC_COEFF_A_CFG_1_A2, cc->a[2]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_B_CFG_0,
> > + FIELD_PREP(OPE_PP_CC_COEFF_B_CFG_0_B0, cc->b[0]) |
> > + FIELD_PREP(OPE_PP_CC_COEFF_B_CFG_0_B1, cc->b[1]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_B_CFG_1,
> > + FIELD_PREP(OPE_PP_CC_COEFF_B_CFG_1_B2, cc->b[2]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_C_CFG_0,
> > + FIELD_PREP(OPE_PP_CC_COEFF_C_CFG_0_C0, cc->c[0]) |
> > + FIELD_PREP(OPE_PP_CC_COEFF_C_CFG_0_C1, cc->c[1]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_C_CFG_1,
> > + FIELD_PREP(OPE_PP_CC_COEFF_C_CFG_1_C2, cc->c[2]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_K_CFG_0,
> > + FIELD_PREP(OPE_PP_CC_COEFF_K_CFG_0_K0, cc->k[0]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_K_CFG_1,
> > + FIELD_PREP(OPE_PP_CC_COEFF_K_CFG_1_K1, cc->k[1]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_K_CFG_2,
> > + FIELD_PREP(OPE_PP_CC_COEFF_K_CFG_2_K2, cc->k[2]));
> > + ope_write_pp(ope, OPE_PP_CC_COEFF_M_CFG,
> > + FIELD_PREP(OPE_PP_CC_COEFF_M_CFG_M, cc->qfactor));
> > +}
> > +
> > +static void ope_glut_load_lut(struct ope_dev *ope, enum ope_glut_sel ch,
> > + u8 bank, const u16 *lut)
> > +{
> > + ope_write_pp(ope, OPE_PP_GLUT_DMI_LUT_BANK_CFG,
> > + FIELD_PREP(OPE_PP_GLUT_DMI_LUT_BANK_CFG_SEL, bank));
> > + ope_write_pp(ope, OPE_PP_GLUT_DMI_LUT_CFG,
> > + FIELD_PREP(OPE_PP_GLUT_DMI_LUT_CFG_SEL, ch));
> > + ope_write_pp(ope, OPE_PP_GLUT_DMI_CFG,
> > + OPE_PP_GLUT_DMI_CFG_AUTO_LOAD_EN |
> > + FIELD_PREP(OPE_PP_GLUT_DMI_CFG_ADDR, 0));
> > + for (unsigned int i = 0; i < CAMSS_OPE_GAMMA_LUT_SIZE; i++)
> > + ope_write_pp(ope, OPE_PP_GLUT_DMI_DATA, lut[i]);
> > +}
> > +
> > +static void ope_glut_upload(struct ope_ctx *ctx, u8 bank)
> > +{
> > + struct camss_params_ope_gamma *gamma = &ctx->config.gamma;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + ope_glut_load_lut(ope, OPE_GLUT_SEL_R, bank, gamma->rlut);
> > + ope_glut_load_lut(ope, OPE_GLUT_SEL_G, bank, gamma->glut);
> > + ope_glut_load_lut(ope, OPE_GLUT_SEL_B, bank, gamma->blut);
> > +
> > + ope_write_pp(ope, OPE_PP_GLUT_DMI_LUT_CFG,
> > + FIELD_PREP(OPE_PP_GLUT_DMI_LUT_CFG_SEL,
> > + OPE_GLUT_SEL_NONE));
> > +
> > + ope->glut_bank[bank].owner = ctx;
> > + ope->glut_bank[bank].valid = true;
> > +}
> > +
> > +static bool ope_glut_bank_resident(struct ope_dev *ope, u8 bank,
> > + struct ope_ctx *ctx)
> > +{
> > + return ope->glut_bank[bank].valid &&
> > + ope->glut_bank[bank].owner == ctx;
> > +}
> > +
> > +static void ope_prog_gamma(struct ope_ctx *ctx, bool force)
> > +{
> > + struct camss_params_ope_gamma *gamma = &ctx->config.gamma;
> > + bool enable = !(gamma->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE);
> > + bool dirty = gamma->header.flags & CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY;
> > + struct ope_dev *ope = ctx->ope;
> > + u8 bank;
> > +
> > + if (!force && !dirty)
> > + return;
> > +
> > + gamma->header.flags &= ~CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY;
> > +
> > + if (!enable) {
> > + ope_write_pp(ope, OPE_PP_GLUT_MODULE_CFG, 0);
> > + return;
> > + }
> > +
> > + if (!dirty && ope_glut_bank_resident(ope, ope->glut_cur_bank, ctx)) {
> > + /* Already active in the read bank: only (re)enable the module. */
> > + bank = ope->glut_cur_bank;
> > + } else if (!dirty && ope_glut_bank_resident(ope, !ope->glut_cur_bank, ctx)) {
> > + /* Resident in the other bank: flip to it, no upload. */
> > + bank = !ope->glut_cur_bank;
> > + } else {
> > + /* Dirty, or miss in both banks: upload into the LRU bank. */
> > + bank = !ope->glut_mru_bank;
> > + ope_glut_upload(ctx, bank);
> > + }
> > +
> > + ope->glut_mru_bank = bank;
> > +
> > + dev_dbg(ope->dev,
> > + "IQ module (%u) update (flags:0x%x) glut bank %u->%u)\n",
> > + gamma->header.type, gamma->header.flags,
> > + ope->glut_cur_bank, bank);
> > +
> > + ope_write_pp(ope, OPE_PP_GLUT_MODULE_LUT_BANK_CFG,
> > + FIELD_PREP(OPE_PP_GLUT_MODULE_LUT_BANK_CFG_SEL, bank));
> > + ope->glut_cur_bank = bank;
> > +
> > + ope_write_pp(ope, OPE_PP_GLUT_MODULE_CFG, OPE_PP_GLUT_MODULE_CFG_EN);
> > +}
> > +
> > +static void ope_glut_cache_invalidate(struct ope_dev *ope)
> > +{
> > + ope->glut_bank[0].valid = false;
> > + ope->glut_bank[1].valid = false;
> > + ope->glut_cur_bank = 0;
> > + ope->glut_mru_bank = 0;
> > +}
> > +
> > +static void ope_prog_crop_rnd_clamp(struct ope_dev *ope, const struct ope_stripe *stripe)
> > +{
> > + static const u32 crop_bases[] = {
> > + OPE_PP_CROP_RND_CLAMP_Y_DISP_BASE,
> > + OPE_PP_CROP_RND_CLAMP_C_DISP_BASE,
> > + };
> > + static const enum ope_downscaler ds_idx[] = {
> > + OPE_DS_Y_DISP,
> > + OPE_DS_C_DISP,
> > + };
> > +
> > + for (unsigned int i = 0; i < ARRAY_SIZE(crop_bases); i++) {
> > + const struct ope_dsc_config *dsc = &stripe->dsc[ds_idx[i]];
> > + u32 cbase = crop_bases[i];
> > +
> > + if (!dsc->output_width || !dsc->output_height) {
> > + ope_write_pp(ope, OPE_PP_CROP_RND_CLAMP_MODULE_CFG(cbase), 0);
> > + continue;
> > + }
> > +
> > + ope_write_pp(ope, OPE_PP_CROP_RND_CLAMP_CROP_PIXEL_CFG(cbase),
> > + FIELD_PREP(OPE_PP_CROP_RND_CLAMP_CROP_FIRST, 0) |
> > + FIELD_PREP(OPE_PP_CROP_RND_CLAMP_CROP_LAST,
> > + dsc->crop_last_pixel));
> > + ope_write_pp(ope, OPE_PP_CROP_RND_CLAMP_CROP_LINE_CFG(cbase),
> > + FIELD_PREP(OPE_PP_CROP_RND_CLAMP_CROP_FIRST, 0) |
> > + FIELD_PREP(OPE_PP_CROP_RND_CLAMP_CROP_LAST, dsc->crop_last_line));
> > + ope_write_pp(ope, OPE_PP_CROP_RND_CLAMP_MODULE_CFG(cbase),
> > + OPE_PP_CROP_RND_CLAMP_EN | OPE_PP_CROP_RND_CLAMP_CROP_EN);
> > + }
> > +}
> > +
> > +static void ope_prog_stripe(struct ope_ctx *ctx, struct ope_stripe *stripe)
> > +{
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + dev_dbg(ope->dev, "ctx=%p programming stripe %u\n",
> > + ctx, (unsigned int)(stripe - 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 (int 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 (int 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_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_DOWNSCALE_MN_DS_CFG_V_SCALE_EN;
> > + }
> > +
> > + ope_write_pp(ope, OPE_PP_DOWNSCALE_MN_DS_CFG(base), cfg);
> > + ope_write_pp(ope, OPE_PP_DOWNSCALE_MN_DS_IMAGE_SIZE_CFG(base),
> > + ((dsc->input_width - 1) << 16) | (dsc->input_height - 1));
> > + ope_write_pp(ope, OPE_PP_DOWNSCALE_MN_DS_MN_H_CFG(base),
> > + dsc->phase_step_h);
> > + ope_write_pp(ope, OPE_PP_DOWNSCALE_MN_DS_MN_V_CFG(base),
> > + dsc->phase_step_v);
> > + ope_write_pp(ope, OPE_PP_DOWNSCALE_MN_CFG(base),
> > + cfg ? OPE_PP_DOWNSCALE_MN_CFG_EN : 0);
> > +
> > + dev_dbg(ope->dev, "DS[%d] cfg=0x%x in=%ux%u out=%ux%u\n",
> > + i, cfg, dsc->input_width, dsc->input_height,
> > + dsc->output_width, dsc->output_height);
> > + }
> > +
> > + ope_prog_crop_rnd_clamp(ope, stripe);
> > +}
> > +
> > +static void ope_params_apply_wb(void *priv, const union camss_isp_params_block *block)
> > +{
> > + struct ope_ctx *ctx = priv;
> > +
> > + if (!camss_isp_params_copy_block(&ctx->config.wb_gain, block, sizeof(ctx->config.wb_gain)))
> > + return;
> > +
> > + ctx->config.wb_gain.g_sub = block->wb_gain.g_sub >> 4;
> > + ctx->config.wb_gain.b_sub = block->wb_gain.b_sub >> 4;
> > + ctx->config.wb_gain.r_sub = block->wb_gain.r_sub >> 4;
> > + ctx->config.wb_gain.g_add = block->wb_gain.g_add >> 4;
> > + ctx->config.wb_gain.b_add = block->wb_gain.b_add >> 4;
> > + ctx->config.wb_gain.r_add = block->wb_gain.r_add >> 4;
> > +}
> > +
> > +static void ope_params_apply_chroma_enhan(void *priv, const union camss_isp_params_block *block)
> > +{
> > + struct ope_ctx *ctx = priv;
> > +
> > + camss_isp_params_copy_block(&ctx->config.chroma_enhan, block,
> > + sizeof(ctx->config.chroma_enhan));
> > +}
> > +
> > +static void ope_params_apply_color_correct(void *priv, const union camss_isp_params_block *block)
> > +{
> > + struct ope_ctx *ctx = priv;
> > +
> > + camss_isp_params_copy_block(&ctx->config.color_correct, block,
> > + sizeof(ctx->config.color_correct));
> > +}
> > +
> > +static void ope_params_apply_gamma(void *priv, const union camss_isp_params_block *block)
> > +{
> > + struct ope_ctx *ctx = priv;
> > +
> > + camss_isp_params_copy_block(&ctx->config.gamma, block, sizeof(ctx->config.gamma));
> > +}
> > +
> > +static const struct v4l2_isp_params_block_type_info ope_params_type_info[] = {
> > + [CAMSS_PARAMS_OPE_WB_GAIN] = { sizeof(struct camss_params_ope_wb_gain) },
> > + [CAMSS_PARAMS_OPE_CHROMA_ENHAN] = { sizeof(struct camss_params_ope_chroma_enhan) },
> > + [CAMSS_PARAMS_OPE_COLOR_CORRECT] = { sizeof(struct camss_params_ope_color_correct) },
> > + [CAMSS_PARAMS_OPE_GAMMA] = { sizeof(struct camss_params_ope_gamma) },
> > +};
> > +
> > +static const camss_isp_params_handler_fn ope_params_handlers[] = {
> > + [CAMSS_PARAMS_OPE_WB_GAIN] = ope_params_apply_wb,
> > + [CAMSS_PARAMS_OPE_CHROMA_ENHAN] = ope_params_apply_chroma_enhan,
> > + [CAMSS_PARAMS_OPE_COLOR_CORRECT] = ope_params_apply_color_correct,
> > + [CAMSS_PARAMS_OPE_GAMMA] = ope_params_apply_gamma,
> > +};
> > +
> > +static struct vb2_v4l2_buffer *ope_queue_remove(struct ope_ctx *ctx,
> > + unsigned int idx)
> > +{
> > + struct ope_queue *q = &ctx->queues[idx];
> > + struct camss_isp_buf *buf;
> > +
> > + scoped_guard(spinlock_irqsave, &q->rdy_spinlock) {
> > + buf = list_first_entry_or_null(&q->rdy_queue, struct camss_isp_buf, list);
> > + if (buf)
> > + list_del(&buf->list);
> > + }
> > +
> > + return buf ? &buf->vb : NULL;
> > +}
> > +
> > +static struct vb2_v4l2_buffer *ope_queue_peek(struct ope_ctx *ctx,
> > + unsigned int idx)
> > +{
> > + struct ope_queue *q = &ctx->queues[idx];
> > + struct camss_isp_buf *buf;
> > +
> > + scoped_guard(spinlock_irqsave, &q->rdy_spinlock)
> > + buf = list_first_entry_or_null(&q->rdy_queue, struct camss_isp_buf, list);
> > +
> > + return buf ? &buf->vb : NULL;
> > +}
> > +
> > +static void ope_queue_drain(struct ope_ctx *ctx, unsigned int idx,
> > + enum vb2_buffer_state state)
> > +{
> > + struct vb2_v4l2_buffer *vbuf;
> > +
> > + while ((vbuf = ope_queue_remove(ctx, idx)))
> > + vb2_buffer_done(&vbuf->vb2_buf, state);
> > +}
> > +
> > +static int ope_apply_params(struct ope_ctx *ctx)
> > +{
> > + struct vb2_v4l2_buffer *vbuf;
> > +
> > + vbuf = ope_queue_peek(ctx, OPE_QUEUE_PARAMS);
> > + if (!vbuf)
> > + return 0;
> > +
> > + return camss_isp_params_apply(ctx->ope->dev, &vbuf->vb2_buf,
> > + ctx->params_buf,
> > + ope_params_type_info,
> > + ope_params_handlers,
> > + ARRAY_SIZE(ope_params_handlers),
> > + ctx);
> > +}
> > +
> > +/* Default/initial tuning parameters */
> > +static const struct ope_config ope_default_config = {
> > + .wb_gain = {
> > + .header.type = CAMSS_PARAMS_OPE_WB_GAIN,
> > + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE |
> > + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY,
> > + .g_gain = OPE_WB(1, 1),
> > + .b_gain = OPE_WB(3, 2),
> > + .r_gain = OPE_WB(3, 2),
> > + },
> > + .demo = {
> > + .header.type = CAMSS_PARAMS_INTERNAL,
> > + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE |
> > + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY,
> > + .lambda_rb = 0,
> > + .lambda_g = 128,
> > + .a_k = 128,
> > + .w_k = 102,
> > + },
> > + .chroma_enhan = { /* RGB -> YUV values from BT.601/Full-Range */
> > + .header.type = CAMSS_PARAMS_OPE_CHROMA_ENHAN,
> > + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_ENABLE |
> > + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY,
> > + .luma_v0 = 0x04d, /* 0.299 */
> > + .luma_v1 = 0x096, /* 0.587 */
> > + .luma_v2 = 0x01d, /* 0.114 */
> > + .luma_k = 0,
> > + .coeff_ap = 0x077, /* 0.464 */
> > + .coeff_am = 0x070, /* 0.438 */
> > + .coeff_bp = 0xfac, /* -0.329 */
> > + .coeff_bm = 0xfc4, /* -0.234 */
> > + .coeff_cp = 0x086, /* 0.524 */
> > + .coeff_cm = 0x089, /* 0.535 */
> > + .coeff_dp = 0xfff, /* -0.004 */
> > + .coeff_dm = 0xfdf, /* -0.130 */
> > + .kcb = 128,
> > + .kcr = 128,
> > + },
> > + .color_correct = {
> > + .header.type = CAMSS_PARAMS_OPE_COLOR_CORRECT,
> > + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_DISABLE |
> > + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY,
> > + },
> > + .gamma = {
> > + .header.type = CAMSS_PARAMS_OPE_GAMMA,
> > + .header.flags = V4L2_ISP_PARAMS_FL_BLOCK_DISABLE |
> > + CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY,
> > + },
> > +};
> > +
> > +static void ope_try_schedule(struct ope_ctx *ctx);
> > +
> > +/* -------- Job scheduling -------- */
> > +static bool ope_job_ready(void *priv)
> > +{
> > + struct ope_ctx *ctx = priv;
> > +
> > + return ctx->started &&
> > + !list_empty(&ctx->queues[OPE_QUEUE_FRAME_IN].rdy_queue) &&
> > + !list_empty(&ctx->queues[OPE_QUEUE_DISP_OUT].rdy_queue);
> > +}
> > +
> > +static void ope_job_finish(struct ope_ctx *ctx, enum vb2_buffer_state state)
> > +{
> > + struct vb2_v4l2_buffer *src, *dst, *params;
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + src = ope_queue_remove(ctx, OPE_QUEUE_FRAME_IN);
> > + dst = ope_queue_remove(ctx, OPE_QUEUE_DISP_OUT);
> > + params = ope_queue_remove(ctx, OPE_QUEUE_PARAMS);
> > +
> > + if (dst) {
> > + dst->sequence = ctx->fmt_in.sequence++;
> > + if (src)
> > + dst->vb2_buf.timestamp = src->vb2_buf.timestamp;
> > + }
> > +
> > + if (dst)
> > + vb2_buffer_done(&dst->vb2_buf, state);
> > + if (src)
> > + vb2_buffer_done(&src->vb2_buf, state);
> > + if (params)
> > + vb2_buffer_done(&params->vb2_buf, state);
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock) {
> > + ope->hw_ctx = NULL;
> > + ope->curr_ctx = NULL;
> > + ctx->queued = false;
> > +
> > + if (state != VB2_BUF_STATE_DONE)
> > + ope->prev_ctx = NULL;
> > + }
> > +
> > + dev_dbg(ctx->ope->dev, "done ctx=%p state=%u\n", ctx, state);
> > +
> > + ope_try_schedule(ctx);
> > +}
> > +
> > +static dma_addr_t ope_buf_dma_addr(struct ope_ctx *ctx, unsigned int queue_idx)
> > +{
> > + struct vb2_v4l2_buffer *vbuf = ope_queue_peek(ctx, queue_idx);
> > +
> > + if (!vbuf)
> > + return 0;
> > +
> > + return vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0);
> > +}
> > +
> > +static void ope_run_job(void *priv, bool ctx_changed)
> > +{
> > + struct ope_ctx *ctx = priv;
> > + struct ope_dev *ope = ctx->ope;
> > + dma_addr_t src, dst;
> > +
> > + src = ope_buf_dma_addr(ctx, OPE_QUEUE_FRAME_IN);
> > + dst = ope_buf_dma_addr(ctx, OPE_QUEUE_DISP_OUT);
>
> ope_hw_reset_sync() can schedule this function with !src && dst ->
> ope_job_finish() runs with dst true.
>
> ope_sched_cancel() and ope_v4l2_unregister() call ope_hw_reset_sync();
>
> ->
>
> ope_job_finish() will rescind the buffer with the hardware thinking it
> still owns them ?
>
> You should proably do a OPE_TOP_RESET_CMD
>
> > + if (!src || !dst) {
> > + dev_warn(ope->dev, "Job cannot run, missing buffer\n");
> > + ope_job_finish(ctx, VB2_BUF_STATE_ERROR);
> > + return;
> > + }
> > +
> > + dev_dbg(ope->dev, "start ctx=%p->%p src=%pad dst=%pad\n",
> > + ope->hw_ctx, ctx, &src, &dst);
> > +
> > + if (ope_apply_params(ctx))
> > + dev_warn_ratelimited(ope->dev, "Invalid parameters\n");
> > +
> > + if (ctx->first_start) {
> > + ctx_changed = true;
> > + ctx->first_start = false;
> > + }
> > +
> > + ope_gen_stripes(ctx, src, dst);
> > +
> > + ope_prog_wb(ctx, ctx_changed);
> > + ope_prog_bayer2rgb(ctx, ctx_changed);
> > + ope_prog_color_correct(ctx, ctx_changed);
> > + ope_prog_gamma(ctx, ctx_changed);
> > + ope_prog_rgb2yuv(ctx, ctx_changed);
> > +
> > + ctx->current_stripe = 0;
> > + ope->hw_ctx = ctx;
> > + ope_prog_stripe(ctx, &ctx->stripe[0]);
> > +
> > + ope_start(ope);
> > +}
> > +
> > +static int ope_hw_reset_sync(struct ope_dev *ope)
> > +{
> > + 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)))
> > + return -ETIMEDOUT;
> > +
> > + return 0;
> > +}
> > +
> > +static void ope_sched_cancel(struct ope_ctx *ctx)
> > +{
> > + struct ope_dev *ope = ctx->ope;
> > + bool running;
> > +
> > + disable_work_sync(&ope->run_work);
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock)
> > + running = (ope->curr_ctx == ctx);
> > +
> > + if (running && ope_hw_reset_sync(ope)) {
> > + dev_err(ope->dev, "reset timeout during cancel\n");
> > + synchronize_irq(ope->irq);
> > + ope_job_finish(ctx, VB2_BUF_STATE_ERROR);
> > + }
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock)
> > + ctx->queued = false;
> > +}
> > +
> > +static void ope_sched_resume(struct ope_ctx *ctx)
> > +{
> > + if (enable_work(&ctx->ope->run_work))
> > + ope_try_schedule(ctx);
> > +}
> > +
> > +static void ope_run_work(struct work_struct *work)
> > +{
> > + struct ope_dev *ope = container_of(work, struct ope_dev, run_work);
> > + struct ope_ctx *ctx;
> > + bool ctx_changed;
> > +
> > + if (unlikely(!ope_hw_available(ope)))
> > + return;
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock) {
> > + ctx = ope->shared_ctx;
>
> shared_ctx is written holding ope->mutex but read holding sched_lock
> > + if (!ctx || !ctx->queued || ope->curr_ctx)
> > + return;
> > + if (!ope_job_ready(ctx))
> > + return;
> > + ctx_changed = (ope->prev_ctx != ctx);
> > + ope->prev_ctx = ctx;
> > + ope->curr_ctx = ctx;
> > + }
> > +
> > + ope_run_job(ctx, ctx_changed);
> > +}
> > +
> > +static void ope_try_schedule(struct ope_ctx *ctx)
> > +{
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + if (!ope_job_ready(ctx))
> > + return;
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock) {
> > + if (ctx->queued || ope->curr_ctx)
> > + return;
> > + ctx->queued = true;
>
>
> > + }
> > +
> > + schedule_work(&ope->run_work);
>
>
> > +}
> > +
> > +/* -------- Interrupt handlers -------- */
> > +
> > +static void ope_fe_irq(struct ope_dev *ope)
> > +{
> > + u32 status = readl_relaxed(ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_STATUS);
> > +
> > + writel_relaxed(status, ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_CLEAR);
> > + writel_relaxed(OPE_BUS_RD_INPUT_IF_IRQ_CMD_CLEAR,
> > + ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_CMD);
> > +
> > + if (status & OPE_BUS_RD_INPUT_IF_IRQ_STATUS_CCIF_VIOL)
> > + dev_err_ratelimited(ope->dev, "FE: CCIF violation (status=0x%08x)\n", status);
> > +}
> > +
> > +static bool ope_we_irq(struct ope_dev *ope, struct ope_ctx *ctx)
> > +{
> > + bool reset = false;
> > + u32 status;
> > +
> > + status = ope_read_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0);
> > + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CLEAR_0, status);
> > + ope_write_wr(ope, OPE_BUS_WR_INPUT_IF_IRQ_CMD, OPE_BUS_WR_INPUT_IF_IRQ_CMD_CLEAR);
> > +
> > + if (!ctx)
> > + return false;
> > +
> > + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_CONS_VIOL) {
> > + u32 viol = ope_read_wr(ope, OPE_BUS_WR_VIOLATION_STATUS);
> > +
> > + dev_err_ratelimited(ope->dev, "constraint violation (clients=0x%x)\n", viol);
> > + reset = true;
> > + }
> > +
> > + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_IMG_SZ_VIOL) {
> > + u32 viol = ope_read_wr(ope, OPE_BUS_WR_IMAGE_SIZE_VIOLATION_STATUS);
> > +
> > + for (int i = 0; i < OPE_WR_CLIENT_MAX; i++) {
> > + if (BIT(i) & viol)
> > + dev_err_ratelimited(ope->dev, "WE%d: image size violation\n", i);
> > + }
> > + reset = true;
> > + }
> > +
> > + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_VIOL) {
> > + u32 viol = ope_read_wr(ope, OPE_BUS_WR_VIOLATION_STATUS);
> > +
> > + dev_err_ratelimited(ope->dev, "fatal violation (status=0x%08x)\n", viol);
> > + reset = true;
> > + }
> > +
> > + if (reset) {
> > + ope_write(ctx->ope, OPE_TOP_RESET_CMD, OPE_TOP_RESET_CMD_SW);
> > + return true;
> > + }
> > +
> > + if (status & OPE_BUS_WR_INPUT_IF_IRQ_STATUS_0_RUP_DONE) {
> > + /* Register update done: program next stripe (double-buffered) */
> > + struct ope_stripe *stripe = ope_current_stripe(ctx);
> > +
> > + if (stripe && !ope_stripe_is_last(stripe))
> > + ope_prog_stripe(ctx, stripe + 1);
> > + }
> > +
> > + return false;
> > +}
> > +
> > +
> > +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_FE |
> > + 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);
> > + /* The fetch engine only needs to report bus errors */
> > + writel_relaxed(OPE_BUS_RD_INPUT_IF_IRQ_STATUS_CCIF_VIOL,
> > + ope->base_rd + OPE_BUS_RD_INPUT_IF_IRQ_MASK);
> > +}
> > +
> > +static irqreturn_t ope_irq(int irq, void *dev_id)
> > +{
> > + struct ope_dev *ope = dev_id;
> > + struct ope_ctx *ctx;
> > + u32 status;
> > +
> > + /*
> > + * hw_ctx is safe to read here without a lock: ope_run_job() sets it
> > + * before calling ope_start(), so the hardware cannot raise an IRQ
> > + * before hw_ctx is visible. ope_job_finish() clears it in IRQ context
> > + * here, so the clear is serialised with this read by the IRQ itself.
> > + */
> > + ctx = ope->hw_ctx;
> > +
> > + status = ope_read(ope, OPE_TOP_IRQ_STATUS);
>
> if (!status)
> return IRQ_NONE;
>
> > + 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) {
> > + dev_dbg(ope->dev, "reset done ctx=%p\n", ctx);
> > + ope_glut_cache_invalidate(ope);
> > + if (ctx)
> > + ope_job_finish(ctx, VB2_BUF_STATE_ERROR);
> > +
> > + complete(&ope->reset_complete);
> > +
> > + return IRQ_HANDLED;
> > + }
> > +
> > + if (status & OPE_TOP_IRQ_STATUS_VIOL)
> > + dev_warn(ope->dev, "OPE violation: 0x%08x\n",
> > + ope_read(ope, OPE_TOP_VIOLATION_STATUS));
> > +
> > + if (status & OPE_TOP_IRQ_STATUS_FE)
> > + ope_fe_irq(ope);
> > +
> > + if (status & OPE_TOP_IRQ_STATUS_WE) {
> > + if (ope_we_irq(ope, ctx))
> > + return IRQ_HANDLED;
> > + }
> > +
> > + if ((status & OPE_TOP_IRQ_STATUS_IDLE) && ctx) {
> > + struct ope_stripe *stripe = ope_current_stripe(ctx);
> > +
> > + dev_dbg(ope->dev, "stripe %u done ctx=%p\n", ctx->current_stripe, ctx);
> > +
> > + if (unlikely(!stripe)) {
> > + dev_err(ope->dev, "stripe index %u out of range\n", ctx->current_stripe);
> > + ctx->current_stripe = 0;
> > + ope_job_finish(ctx, VB2_BUF_STATE_ERROR);
> > + } else if (ope_stripe_is_last(stripe)) {
> > + ctx->current_stripe = 0;
> > + ope_job_finish(ctx, VB2_BUF_STATE_DONE);
> > + } else {
> > + ctx->current_stripe++;
> > + ope_start(ope);
> > + }
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +/* -------- vb2 queue private data -------- */
> > +
> > +struct ope_vq_priv {
> > + struct ope_ctx *ctx;
> > + unsigned int queue_idx;
> > + struct video_device *vdev;
> > +};
> > +
> > +static inline struct ope_ctx *ope_ctx_from_vq(struct vb2_queue *q)
> > +{
> > + return ((struct ope_vq_priv *)vb2_get_drv_priv(q))->ctx;
> > +}
> > +
> > +static inline unsigned int ope_idx_from_vq(struct vb2_queue *q)
> > +{
> > + return ((struct ope_vq_priv *)vb2_get_drv_priv(q))->queue_idx;
> > +}
> > +
> > +static inline struct video_device *ope_vdev_from_vq(struct vb2_queue *q)
> > +{
> > + return ((struct ope_vq_priv *)vb2_get_drv_priv(q))->vdev;
> > +}
> > +
> > +/* -------- vb2 ops -------- */
> > +
> > +static int ope_queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
> > + unsigned int *nplanes, unsigned int sizes[],
> > + struct device *alloc_devs[])
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_vq(q);
> > + unsigned int idx = ope_idx_from_vq(q);
> > + unsigned int size;
> > +
> > + if (idx == OPE_QUEUE_FRAME_IN)
> > + size = ctx->fmt_in.sizeimage ? ctx->fmt_in.sizeimage : PAGE_SIZE;
> > + else if (idx == OPE_QUEUE_DISP_OUT)
> > + size = ctx->fmt_out.sizeimage ? ctx->fmt_out.sizeimage : PAGE_SIZE;
> > + else
> > + size = v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD);
> > +
> > + if (*nplanes) {
> > + if (*nplanes != 1 || sizes[0] < size)
> > + return -EINVAL;
> > + } else {
> > + *nplanes = 1;
> > + sizes[0] = size;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_buf_prepare(struct vb2_buffer *vb)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_vq(vb->vb2_queue);
> > + unsigned int idx = ope_idx_from_vq(vb->vb2_queue);
> > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> > + unsigned int sizeimage;
> > +
> > + if (idx == OPE_QUEUE_FRAME_IN)
> > + sizeimage = ctx->fmt_in.sizeimage;
> > + else if (idx == OPE_QUEUE_DISP_OUT)
> > + sizeimage = ctx->fmt_out.sizeimage;
> > + else
> > + sizeimage = v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD);
> > +
> > + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
> > + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> > + if (vbuf->field == V4L2_FIELD_ANY)
> > + vbuf->field = V4L2_FIELD_NONE;
> > + if (vbuf->field != V4L2_FIELD_NONE)
> > + return -EINVAL;
> > + }
> > + }
> > +
> > + if (vb2_plane_size(vb, 0) < sizeimage)
> > + return -EINVAL;
> > +
> > + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type))
> > + vb2_set_plane_payload(vb, 0, sizeimage);
> > +
> > + return 0;
> > +}
> > +
> > +static void ope_buf_queue(struct vb2_buffer *vb)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_vq(vb->vb2_queue);
> > + unsigned int idx = ope_idx_from_vq(vb->vb2_queue);
> > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> > + struct ope_queue *q = &ctx->queues[idx];
> > + struct camss_isp_buf *buf = container_of(vbuf, struct camss_isp_buf, vb);
> > +
> > + guard(spinlock_irqsave)(&q->rdy_spinlock);
> > + list_add_tail(&buf->list, &q->rdy_queue);
> > +
> > + ope_try_schedule(ctx);
> > +}
> > +
> > +/* -------- Power scaling -------- */
> > +
> > +static inline unsigned long ope_pixclk(const struct ope_fmt_state *fs, unsigned int fps)
> > +{
> > + return (unsigned long)fs->width * fs->height * fps;
> > +}
> > +
> > +static inline unsigned int ope_load_avg(const struct ope_fmt_state *fs, unsigned int fps)
> > +{
> > + return mult_frac(ope_pixclk(fs, fps), fs->fmt->depth, 1000) / 8;
> > +}
> > +
> > +static inline unsigned int ope_load_peak(const struct ope_fmt_state *fs, unsigned int fps)
> > +{
> > + return ope_load_avg(fs, fps) * 2;
> > +}
> > +
> > +static inline unsigned int ope_load_config(const struct ope_fmt_state *fs, unsigned int fps)
> > +{
> > + unsigned int stripe_count = fs->width / OPE_STRIPE_MAX_W + 1;
> > +
> > + /* In worst case we have ~50 32-bit registers to write */
> > + return mult_frac(stripe_count * 50 * 4, fps, 1000);
> > +}
> > +
> > +static void ope_adjust_power(struct ope_dev *ope)
> > +{
> > + unsigned int loadavg = 0, loadpeak = 0, loadconfig = 0;
> > + unsigned long pixclk = 0;
> > + struct dev_pm_opp *opp;
> > + struct ope_ctx *ctx;
> > + int ret;
> > +
> > + if (unlikely(!ope_hw_available(ope)))
> > + return;
> > +
> > + scoped_guard(spinlock, &ope->ctx_lock) {
> > + list_for_each_entry(ctx, &ope->ctx_list, list) {
> > + unsigned int fps;
> > +
> > + if (!ctx->started)
> > + continue;
> > +
> > + fps = ctx->framerate ? ctx->framerate : DEFAULT_FRAMERATE;
> > +
> > + pixclk += ope_pixclk(&ctx->fmt_in, fps);
> > + loadavg += ope_load_avg(&ctx->fmt_in, fps);
> > + loadavg += ope_load_avg(&ctx->fmt_out, fps);
> > + loadpeak += ope_load_peak(&ctx->fmt_in, fps);
> > + loadpeak += ope_load_peak(&ctx->fmt_out, fps);
> > + loadconfig += ope_load_config(&ctx->fmt_in, fps);
> > + }
> > + }
> > +
> > + /* 30% margin for overhead */
> > + pixclk = mult_frac(pixclk, 13, 10);
> > +
> > + dev_dbg(ope->dev, "adjust power: clk=%luHz avg=%uKBps peak=%uKBps cfg=%uKBps\n",
> > + pixclk, loadavg, loadpeak, loadconfig);
> > +
> > + opp = dev_pm_opp_find_freq_ceil(ope->dev, &pixclk);
> > + if (IS_ERR(opp))
> > + dev_warn(ope->dev, "Requested pixel clock %luHz exceeds hardware limit\n", pixclk);
> > + else
> > + dev_pm_opp_put(opp);
> > +
> > + ret = dev_pm_opp_set_rate(ope->dev, pixclk);
> > + if (ret)
> > + dev_warn(ope->dev, "Failed to set 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 BW: %d\n", ret);
> > +
> > + ret = icc_set_bw(ope->icc_config, loadconfig, loadconfig * 5);
> > + if (ret)
> > + dev_warn(ope->dev, "Failed to set config path BW: %d\n", ret);
> > +}
> > +
> > +static int ope_start_streaming(struct vb2_queue *q, unsigned int count)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_vq(q);
> > + struct video_device *vdev = ope_vdev_from_vq(q);
> > + unsigned int idx = ope_idx_from_vq(q);
> > + int ret;
> > +
> > + dev_dbg(ctx->ope->dev, "streaming start ctx=%p queue=%u\n", ctx, idx);
> > +
> > + if (!ope_hw_available(ctx->ope)) {
> > + ret = -ENODEV;
> > + goto err_drain;
> > + }
> > +
> > + ret = video_device_pipeline_alloc_start(vdev);
> > + if (ret)
> > + goto err_drain;
> > +
> > + ret = pm_runtime_resume_and_get(ctx->ope->dev);
> > + if (ret) {
> > + dev_err(ctx->ope->dev, "resume failed (%d)\n", ret);
> > + goto err_pipeline_stop;
> > + }
> > +
> > + if (idx == OPE_QUEUE_FRAME_IN) {
> > + ctx->fmt_in.sequence = 0;
> > + ctx->started = true;
> > + ctx->first_start = true;
> > + ope_adjust_power(ctx->ope);
> > + __ope_irq_init(ctx->ope);
> > + }
> > +
> > + ope_try_schedule(ctx);
> > +
> > + return 0;
> > +
> > +err_pipeline_stop:
> > + video_device_pipeline_stop(vdev);
> > +err_drain:
> > + ope_queue_drain(ctx, idx, VB2_BUF_STATE_QUEUED);
> > + return ret;
> > +}
> > +
> > +static void ope_stop_streaming(struct vb2_queue *q)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_vq(q);
> > + struct video_device *vdev = ope_vdev_from_vq(q);
> > + unsigned int idx = ope_idx_from_vq(q);
> > +
> > + dev_dbg(ctx->ope->dev, "streaming stop ctx=%p queue=%u\n", ctx, idx);
> > +
> > + if (idx == OPE_QUEUE_FRAME_IN) {
> > + ctx->started = false;
> > + ope_adjust_power(ctx->ope);
> > + }
> > +
> > + /* Drain with the scheduler held off, so no job can pick up a buffer */
> > + ope_sched_cancel(ctx);
> > + ope_queue_drain(ctx, idx, VB2_BUF_STATE_ERROR);
> > + ope_sched_resume(ctx);
>
> ope->hw_ctx = NULL;
> ope->curr_ctx = NULL;
> ctx->queued = false;
> ope->prev_ctx = NULL; ?
>
> > +
> > + pm_runtime_put(ctx->ope->dev);
> > +
> > + if (ope_hw_available(ctx->ope))
> > + video_device_pipeline_stop(vdev);
> > +}
> > +
> > +static const struct vb2_ops ope_vb2_ops = {
> > + .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 const unsigned int ope_queue_to_entity[] = {
> > + [OPE_QUEUE_FRAME_IN] = OPE_ENTITY_FRAME_IN,
> > + [OPE_QUEUE_DISP_OUT] = OPE_ENTITY_DISP_OUT,
> > + [OPE_QUEUE_PARAMS] = OPE_ENTITY_PARAMS,
> > +};
> > +
> > +static int ope_init_vq(struct ope_ctx *ctx, unsigned int idx)
> > +{
> > + struct ope_dev *ope = ctx->ope;
> > + struct ope_vq_priv *qpriv;
> > + struct vb2_queue *q = &ctx->queues[idx].vq;
> > + int ret;
> > +
> > + qpriv = kzalloc_obj(*qpriv);
> > + if (!qpriv)
> > + return -ENOMEM;
> > +
> > + qpriv->ctx = ctx;
> > + qpriv->queue_idx = idx;
> > + qpriv->vdev = camss_isp_pipeline_get_vdev(ope->pipeline, ope_queue_to_entity[idx]);
> > + q->drv_priv = qpriv;
> > + q->ops = &ope_vb2_ops;
> > + q->lock = &ctx->vbq_lock;
> > + q->dev = ope->dev;
> > + q->buf_struct_size = sizeof(struct camss_isp_buf);
> > + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> > + q->min_queued_buffers = (idx == OPE_QUEUE_PARAMS) ? 0 : 1;
> > +
> > + if (idx == OPE_QUEUE_PARAMS) {
> > + q->type = V4L2_BUF_TYPE_META_OUTPUT;
> > + q->mem_ops = &vb2_vmalloc_memops;
> > + q->io_modes = VB2_MMAP | VB2_USERPTR;
> > + } else if (idx == OPE_QUEUE_DISP_OUT) {
> > + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> > + q->mem_ops = &vb2_dma_contig_memops;
> > + q->io_modes = VB2_MMAP | VB2_DMABUF;
> > + } else {
> > + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> > + q->mem_ops = &vb2_dma_contig_memops;
> > + q->io_modes = VB2_MMAP | VB2_DMABUF;
> > + }
> > +
> > + ret = vb2_queue_init(q);
> > + if (ret) {
> > + q->drv_priv = NULL;
> > + kfree(qpriv);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/* -------- Format helpers -------- */
> > +
> > +static const struct ope_fmt *ope_find_fmt(const struct ope_fmt *fmts,
> > + unsigned int n, u32 fourcc)
> > +{
> > + for (unsigned int i = 0; i < n; i++)
> > + if (fmts[i].fourcc == fourcc)
> > + return &fmts[i];
> > +
> > + return NULL;
> > +}
> > +
> > +static const struct ope_fmt *ope_fmt_try(struct ope_dev *ope, bool is_output,
> > + struct v4l2_pix_format_mplane *pix)
> > +{
> > + const struct ope_fmt *fmts = is_output ? ope_output_fmts : ope_input_fmts;
> > + unsigned int n = is_output ? ARRAY_SIZE(ope_output_fmts) : ARRAY_SIZE(ope_input_fmts);
> > + unsigned int max_w = OPE_MAX_W, max_h = OPE_MAX_H;
> > + const struct ope_fmt *fmt;
> > + unsigned int bytesperline;
> > + unsigned int align;
> > +
> > + fmt = ope_find_fmt(fmts, n, pix->pixelformat);
> > + if (!fmt) {
> > + fmt = &fmts[0];
> > + pix->pixelformat = fmt->fourcc;
> > + }
> > +
> > + v4l_bound_align_image(&pix->width, OPE_MIN_W, max_w, fmt->align,
> > + &pix->height, OPE_MIN_H, max_h, 0, 0);
> > +
> > + pix->num_planes = 1;
> > + pix->field = V4L2_FIELD_NONE;
> > + if (!pix->colorspace)
> > + pix->colorspace = is_output ? V4L2_COLORSPACE_SRGB : V4L2_COLORSPACE_RAW;
> > + if (is_output) {
> > + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
> > + pix->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> > + }
> > +
> > + /*
> > + * Output formats are semi-planar or grey-scale (Y plane only) always
> > + * using 1 byte per Y value. pix->bytesperline stores the Y-plane bpl.
> > + * Depth tracks the total storage size including the second combined
> > + * Cb + Cr plane which directly follows the Y plane and is used for
> > + * sizeimage calculations rather then for bytesperline.
> > + */
> > + if (is_output)
> > + bytesperline = pix->width;
> > + else
> > + bytesperline = pix->width * fmt->depth / 8;
> > +
> > + if (pix->plane_fmt[0].bytesperline > bytesperline)
> > + bytesperline = pix->plane_fmt[0].bytesperline;
> > +
> > + /*
> > + * The WE requires the C plane base address to be 16-byte aligned.
> > + * c_base = bpl * img_h + (compose_top / c_vsub) * c_stride where
> > + * c_stride = bpl * 2 / c_hsub. To guarantee alignment for any
> > + * compose_top, bpl must be a multiple of 16 / c_hsub:
> > + * NV12/NV16 (c_hsub=2): bpl must be multiple of 8... but c_stride=bpl
> > + * so we need bpl % 16 == 0.
> > + * NV24/NV42 (c_hsub=1): c_stride=bpl*2, need bpl*2 % 16 == 0,
> > + * so bpl must be a multiple of 8.
> > + * GREY: no C plane, no constraint.
> > + */
> > + if (is_output && fmt->fourcc != V4L2_PIX_FMT_GREY)
> > + align = 8 * fmt->c_hsub;
> > + else
> > + align = 1;
> > +
> > + bytesperline = min(bytesperline, ALIGN_DOWN(OPE_MAX_STRIDE, align));
> > + bytesperline = ALIGN(bytesperline, align);
> > +
> > + pix->plane_fmt[0].bytesperline = bytesperline;
> > +
> > + if (is_output)
> > + pix->plane_fmt[0].sizeimage = (u64)bytesperline * pix->height * fmt->depth / 8;
> > + else
> > + pix->plane_fmt[0].sizeimage = (u64)bytesperline * pix->height;
> > +
> > + return fmt;
> > +}
> > +
> > +/* -------- ioctl helpers/ops -------- */
> > +
> > +static inline unsigned int ope_queue_idx_from_file(struct file *file)
> > +{
> > + return (unsigned int)(uintptr_t)video_get_drvdata(video_devdata(file));
> > +}
> > +
> > +static struct vb2_queue *ope_vq_from_file(struct file *file, struct ope_ctx *ctx)
> > +{
> > + unsigned int idx = ope_queue_idx_from_file(file);
> > +
> > + if (idx >= OPE_QUEUE_COUNT)
> > + return NULL;
> > +
> > + return &ctx->queues[idx].vq;
> > +}
> > +
> > +static struct ope_ctx *ope_ctx_from_file(struct file *file)
> > +{
> > + struct ope_dev *ope = container_of(video_devdata(file)->v4l2_dev,
> > + struct ope_dev, v4l2_dev);
>
> lockdep_assert_held(ope->mutex);
>
> > + return ope->shared_ctx;
> > +}
> > +
> > +static int ope_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
> > +{
> > + strscpy(cap->driver, OPE_NAME, sizeof(cap->driver));
> > + strscpy(cap->card, "Qualcomm CAMSS OPE", sizeof(cap->card));
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_enum_fmt_vid_out(struct file *file, void *priv, struct v4l2_fmtdesc *f)
> > +{
> > + unsigned int i, n = 0;
> > +
> > + for (i = 0; i < ARRAY_SIZE(ope_input_fmts); i++) {
> > + if (f->mbus_code && ope_input_fmts[i].mbus_code != f->mbus_code)
> > + continue;
> > + if (n++ == f->index) {
> > + f->pixelformat = ope_input_fmts[i].fourcc;
> > + return 0;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int ope_g_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct ope_fmt_state *fs = &ctx->fmt_in;
> > + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> > +
> > + pix->pixelformat = fs->fmt->fourcc;
> > + pix->width = fs->width;
> > + pix->height = fs->height;
> > + pix->num_planes = 1;
> > + pix->field = V4L2_FIELD_NONE;
> > + pix->colorspace = fs->colorspace;
> > + pix->xfer_func = fs->xfer_func;
> > + pix->ycbcr_enc = fs->ycbcr_enc;
> > + pix->quantization = fs->quantization;
> > + pix->plane_fmt[0].bytesperline = fs->bytesperline;
> > + pix->plane_fmt[0].sizeimage = fs->sizeimage;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > +
> > + ope_fmt_try(ctx->ope, false, &f->fmt.pix_mp);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + const struct ope_fmt *fmt;
> > +
> > + if (vb2_is_busy(&ctx->queues[OPE_QUEUE_FRAME_IN].vq))
> > + return -EBUSY;
> > +
> > + fmt = ope_fmt_try(ctx->ope, false, &f->fmt.pix_mp);
> > + ctx->fmt_in.fmt = fmt;
> > + ctx->fmt_in.width = f->fmt.pix_mp.width;
> > + ctx->fmt_in.height = f->fmt.pix_mp.height;
> > + ctx->fmt_in.bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline;
> > + ctx->fmt_in.sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage;
> > + ctx->fmt_in.colorspace = f->fmt.pix_mp.colorspace;
> > + ctx->fmt_in.xfer_func = f->fmt.pix_mp.xfer_func;
> > + ctx->fmt_in.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
> > + ctx->fmt_in.quantization = f->fmt.pix_mp.quantization;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
> > +{
> > + unsigned int i, n = 0;
> > +
> > + for (i = 0; i < ARRAY_SIZE(ope_output_fmts); i++) {
> > + if (f->mbus_code && ope_output_fmts[i].mbus_code != f->mbus_code)
> > + continue;
> > + if (n++ == f->index) {
> > + f->pixelformat = ope_output_fmts[i].fourcc;
> > + return 0;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int ope_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct ope_fmt_state *fs = &ctx->fmt_out;
> > + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
> > +
> > + pix->pixelformat = fs->fmt->fourcc;
> > + pix->width = fs->width;
> > + pix->height = fs->height;
> > + pix->num_planes = 1;
> > + pix->field = V4L2_FIELD_NONE;
> > + pix->colorspace = fs->colorspace;
> > + pix->xfer_func = fs->xfer_func;
> > + pix->ycbcr_enc = fs->ycbcr_enc;
> > + pix->quantization = fs->quantization;
> > + pix->plane_fmt[0].bytesperline = fs->bytesperline;
> > + pix->plane_fmt[0].sizeimage = fs->sizeimage;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > +
> > + ope_fmt_try(ctx->ope, true, &f->fmt.pix_mp);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + const struct ope_fmt *fmt;
> > +
> > + if (vb2_is_busy(&ctx->queues[OPE_QUEUE_DISP_OUT].vq))
> > + return -EBUSY;
> > +
> > + fmt = ope_fmt_try(ctx->ope, true, &f->fmt.pix_mp);
> > +
> > + ctx->fmt_out.fmt = fmt;
> > + ctx->fmt_out.width = f->fmt.pix_mp.width;
> > + ctx->fmt_out.height = f->fmt.pix_mp.height;
> > + ctx->fmt_out.bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline;
> > + ctx->fmt_out.sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage;
> > + ctx->fmt_out.colorspace = f->fmt.pix_mp.colorspace;
> > + ctx->fmt_out.xfer_func = f->fmt.pix_mp.xfer_func;
> > + ctx->fmt_out.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
> > + ctx->fmt_out.quantization = f->fmt.pix_mp.quantization;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_g_fmt_meta(struct file *file, void *priv, struct v4l2_format *f)
> > +{
> > + f->fmt.meta.dataformat = V4L2_META_FMT_QCOM_ISP_PARAMS;
> > + f->fmt.meta.buffersize = v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_enum_fmt_meta_out(struct file *file, void *priv, struct v4l2_fmtdesc *f)
> > +{
> > + if (f->index > 0)
> > + return -EINVAL;
> > +
> > + f->pixelformat = V4L2_META_FMT_QCOM_ISP_PARAMS;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_g_parm(struct file *file, void *priv, struct v4l2_streamparm *sp)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > +
> > + if (!V4L2_TYPE_IS_OUTPUT(sp->type))
> > + return -EINVAL;
> > +
> > + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
> > + sp->parm.output.timeperframe = ctx->fmt_in.timeperframe;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_s_parm(struct file *file, void *priv, struct v4l2_streamparm *sp)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct v4l2_fract *tpf = &sp->parm.output.timeperframe;
> > +
> > + if (!V4L2_TYPE_IS_OUTPUT(sp->type))
> > + return -EINVAL;
> > +
> > + if (vb2_is_busy(&ctx->queues[OPE_QUEUE_FRAME_IN].vq))
> > + return -EBUSY;
> > +
> > + if (!tpf->denominator)
> > + tpf->denominator = 1;
> > + if (!tpf->numerator)
> > + tpf->numerator = 1;
> > +
> > + ctx->framerate = clamp(tpf->denominator / tpf->numerator, 1U, (u32)OPE_MAX_FRAMERATE);
> > + tpf->numerator = 1;
> > + tpf->denominator = ctx->framerate;
> > +
> > + ctx->fmt_in.timeperframe = *tpf;
> > + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
> > +
> > + ope_adjust_power(ctx->ope);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize)
> > +{
> > + unsigned int idx = ope_queue_idx_from_file(file);
> > + const struct ope_fmt *fmts;
> > + unsigned int n;
> > +
> > + if (idx == OPE_QUEUE_FRAME_IN) {
> > + fmts = ope_input_fmts;
> > + n = ARRAY_SIZE(ope_input_fmts);
> > + } else {
> > + fmts = ope_output_fmts;
> > + n = ARRAY_SIZE(ope_output_fmts);
> > + }
> > +
> > + if (fsize->index > 0 || !ope_find_fmt(fmts, n, 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;
> > + fsize->stepwise.min_height = OPE_MIN_H;
> > + fsize->stepwise.max_height = OPE_MAX_H;
> > + fsize->stepwise.step_height = 1;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *rb)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > + int ret;
> > +
> > + if (!vq)
> > + return -EINVAL;
> > +
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + ret = vb2_reqbufs(vq, rb);
> > + if (!ret)
> > + vq->owner = rb->count ? file->private_data : NULL;
> > +
> > + return ret;
> > +}
> > +
> > +static int ope_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + return vq ? vb2_querybuf(vq, buf) : -EINVAL;
> > +}
> > +
> > +static int ope_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct video_device *vdev = video_devdata(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + if (!vq)
> > + return -EINVAL;
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + return vb2_qbuf(vq, vdev->v4l2_dev->mdev, buf);
> > +}
> > +
> > +static int ope_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + return vq ? vb2_dqbuf(vq, buf, file->f_flags & O_NONBLOCK) : -EINVAL;
> > +}
> > +
> > +static int ope_prepare_buf(struct file *file, void *priv, struct v4l2_buffer *buf)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct video_device *vdev = video_devdata(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + if (!vq)
> > + return -EINVAL;
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + return vb2_prepare_buf(vq, vdev->v4l2_dev->mdev, buf);
> > +}
> > +
> > +static int ope_create_bufs(struct file *file, void *priv, struct v4l2_create_buffers *create)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > + int ret;
> > +
> > + if (!vq)
> > + return -EINVAL;
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > + ret = vb2_create_bufs(vq, create);
> > + if (!ret && create->count)
> > + vq->owner = file->private_data;
> > +
> > + return ret;
> > +}
> > +
> > +static int ope_expbuf(struct file *file, void *priv, struct v4l2_exportbuffer *eb)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + if (!vq)
> > + return -EINVAL;
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + return vb2_expbuf(vq, eb);
> > +}
> > +
> > +static int ope_streamon(struct file *file, void *priv, enum v4l2_buf_type type)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + if (!vq)
> > + return -EINVAL;
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + return vb2_streamon(vq, type);
> > +}
> > +
> > +static int ope_streamoff(struct file *file, void *priv, enum v4l2_buf_type type)
> > +{
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct vb2_queue *vq = ope_vq_from_file(file, ctx);
> > +
> > + if (!vq)
> > + return -EINVAL;
> > +
> > + if (vb2_queue_is_busy(vq, file))
> > + return -EBUSY;
> > +
> > + return vb2_streamoff(vq, type);
> > +}
> > +
> > +/* -------- proc subdev ops -------- */
> > +
> > +static const struct v4l2_rect ope_sd_min_rect = {
> > + .width = OPE_MIN_W,
> > + .height = OPE_MIN_H,
> > +};
> > +
> > +static void ope_sd_adjust_crop_rect(struct v4l2_rect *crop, const struct v4l2_rect *bounds)
> > +{
> > + v4l2_rect_set_min_size(crop, &ope_sd_min_rect);
> > + v4l2_rect_map_inside(crop, bounds);
> > +}
> > +
> > +static void ope_sd_adjust_crop(struct v4l2_rect *crop, const struct v4l2_mbus_framefmt *bounds)
> > +{
> > + struct v4l2_rect crop_bounds = {
> > + .left = 0, .top = 0,
> > + .width = bounds->width, .height = bounds->height,
> > + };
> > +
> > + ope_sd_adjust_crop_rect(crop, &crop_bounds);
> > +}
> > +
> > +static void ope_proc_set_src_fmt(struct v4l2_subdev_state *state,
> > + struct v4l2_mbus_framefmt *format)
> > +{
> > + struct v4l2_mbus_framefmt *src_fmt;
> > + const struct v4l2_rect *compose;
> > +
> > + src_fmt = v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SOURCE);
> > + compose = v4l2_subdev_state_get_compose(state, OPE_PROC_PAD_SOURCE);
> > +
> > + src_fmt->code = MEDIA_BUS_FMT_YUYV8_1_5X8;
> > + for (unsigned int i = 0; i < ARRAY_SIZE(ope_output_fmts); i++) {
> > + if (ope_output_fmts[i].mbus_code == format->code) {
> > + src_fmt->code = format->code;
> > + break;
> > + }
> > + }
> > +
> > + src_fmt->width = compose->width;
> > + src_fmt->height = compose->height;
> > + src_fmt->field = V4L2_FIELD_NONE;
> > + src_fmt->colorspace = V4L2_COLORSPACE_SRGB;
> > + *format = *src_fmt;
> > +}
> > +
> > +static void ope_proc_set_src_compose(struct v4l2_subdev_state *state, struct v4l2_rect *r)
> > +{
> > + struct v4l2_rect *compose = v4l2_subdev_state_get_compose(state, OPE_PROC_PAD_SOURCE);
> > + struct v4l2_rect bounds;
> > +
> > + const struct v4l2_rect *sink_crop =
> > + v4l2_subdev_state_get_crop(state, OPE_PROC_PAD_SINK_IN);
> > +
> > + bounds = (struct v4l2_rect){ 0, 0, sink_crop->width, sink_crop->height };
> > + *compose = *r;
> > + ope_sd_adjust_crop_rect(compose, &bounds);
> > + *r = *compose;
> > +
> > + /* Propagate to out format */
> > + ope_proc_set_src_fmt(state, v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SOURCE));
> > +}
> > +
> > +static void ope_proc_set_sink_crop(struct v4l2_subdev_state *state, struct v4l2_rect *r)
> > +{
> > + struct v4l2_rect *crop;
> > +
> > + crop = v4l2_subdev_state_get_crop(state, OPE_PROC_PAD_SINK_IN);
> > +
> > + *crop = *r;
> > + ope_sd_adjust_crop(crop, v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SINK_IN));
> > + *r = *crop;
> > +
> > + /* Propagate to out format */
> > + ope_proc_set_src_compose(state, v4l2_subdev_state_get_compose(state, OPE_PROC_PAD_SOURCE));
> > +}
> > +
> > +static void ope_proc_set_sink_fmt(struct v4l2_subdev_state *state,
> > + struct v4l2_mbus_framefmt *format)
> > +{
> > + struct v4l2_mbus_framefmt *sink_fmt;
> > + struct v4l2_rect *crop;
> > +
> > + sink_fmt = v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SINK_IN);
> > + crop = v4l2_subdev_state_get_crop(state, OPE_PROC_PAD_SINK_IN);
> > +
> > + sink_fmt->code = ope_input_fmts[0].mbus_code;
> > + for (unsigned int i = 0; i < ARRAY_SIZE(ope_input_fmts); i++) {
> > + if (ope_input_fmts[i].mbus_code == format->code) {
> > + sink_fmt->code = format->code;
> > + break;
> > + }
> > + }
> > +
> > + sink_fmt->width = clamp(format->width, (u32)OPE_MIN_W, (u32)OPE_MAX_W);
> > + sink_fmt->height = clamp(format->height, (u32)OPE_MIN_H, (u32)OPE_MAX_H);
> > + sink_fmt->field = V4L2_FIELD_NONE;
> > + sink_fmt->colorspace = V4L2_COLORSPACE_RAW;
> > + *format = *sink_fmt;
> > +
> > + /* Propagate to in crop */
> > + ope_proc_set_sink_crop(state, crop);
> > +}
> > +
> > +static int ope_proc_init_state(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct v4l2_mbus_framefmt fmt = {
> > + .width = OPE_MIN_W,
> > + .height = OPE_MIN_H,
> > + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> > + };
> > +
> > + /* Params sink: fixed format, width carries the payload size in bytes */
> > + *v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SINK_PAR) =
> > + (struct v4l2_mbus_framefmt){
> > + .code = MEDIA_BUS_FMT_FIXED,
> > + .width = v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD),
> > + .height = 1,
> > + .field = V4L2_FIELD_NONE,
> > + .colorspace = V4L2_COLORSPACE_DEFAULT,
> > + };
> > +
> > + /* Initialise source compose default before cascade */
> > + *v4l2_subdev_state_get_compose(state, OPE_PROC_PAD_SOURCE) =
> > + (struct v4l2_rect){ 0, 0, OPE_MIN_W, OPE_MIN_H };
> > + v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SOURCE)->code =
> > + MEDIA_BUS_FMT_YUYV8_1_5X8;
> > +
> > + /* Drive the full cascade from sink-in fmt */
> > + ope_proc_set_sink_fmt(state, &fmt);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_proc_set_fmt(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_format *fmt)
> > +{
> > + if (fmt->pad == OPE_PROC_PAD_SINK_PAR)
> > + return v4l2_subdev_get_fmt(sd, state, fmt);
> > +
> > + if (fmt->pad == OPE_PROC_PAD_SINK_IN) {
> > + ope_proc_set_sink_fmt(state, &fmt->format);
> > + return 0;
> > + }
> > +
> > + ope_proc_set_src_fmt(state, &fmt->format);
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_proc_enum_mbus_code(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_mbus_code_enum *code)
> > +{
> > + const struct ope_fmt *fmts;
> > + unsigned int nfmts;
> > + unsigned int n = 0;
> > +
> > + if (code->pad == OPE_PROC_PAD_SINK_PAR) {
> > + if (code->index)
> > + return -EINVAL;
> > + code->code = MEDIA_BUS_FMT_FIXED;
> > + return 0;
> > + }
> > +
> > + if (code->pad == OPE_PROC_PAD_SOURCE) {
> > + fmts = ope_output_fmts;
> > + nfmts = ARRAY_SIZE(ope_output_fmts);
> > + } else {
> > + fmts = ope_input_fmts;
> > + nfmts = ARRAY_SIZE(ope_input_fmts);
> > + }
> > +
> > + for (unsigned int i = 0; i < nfmts; i++) {
> > + u32 mc = fmts[i].mbus_code;
> > + bool seen = false;
> > +
> > + for (unsigned int j = 0; j < i; j++) {
> > + if (fmts[j].mbus_code == mc) {
> > + seen = true;
> > + break;
> > + }
> > + }
> > + if (!seen && n++ == code->index) {
> > + code->code = mc;
> > + return 0;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int ope_proc_get_selection(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_selection *sel)
> > +{
> > + if (sel->pad == OPE_PROC_PAD_SINK_IN) {
> > + const struct v4l2_mbus_framefmt *sink_fmt =
> > + v4l2_subdev_state_get_format(state, OPE_PROC_PAD_SINK_IN);
> > +
> > + switch (sel->target) {
> > + case V4L2_SEL_TGT_CROP:
> > + sel->r = *v4l2_subdev_state_get_crop(state, OPE_PROC_PAD_SINK_IN);
> > + return 0;
> > + case V4L2_SEL_TGT_CROP_DEFAULT:
> > + case V4L2_SEL_TGT_CROP_BOUNDS:
> > + sel->r = (struct v4l2_rect){ 0, 0, sink_fmt->width, sink_fmt->height };
> > + return 0;
> > + default:
> > + return -EINVAL;
> > + }
> > + }
> > +
> > + if (sel->pad == OPE_PROC_PAD_SOURCE) {
> > + const struct v4l2_rect *crop =
> > + v4l2_subdev_state_get_crop(state, OPE_PROC_PAD_SINK_IN);
> > +
> > + switch (sel->target) {
> > + case V4L2_SEL_TGT_COMPOSE:
> > + sel->r = *v4l2_subdev_state_get_compose(state, OPE_PROC_PAD_SOURCE);
> > + return 0;
> > + case V4L2_SEL_TGT_COMPOSE_DEFAULT:
> > + case V4L2_SEL_TGT_COMPOSE_BOUNDS:
> > + sel->r = *crop;
> > + return 0;
> > + default:
> > + return -EINVAL;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static int ope_proc_set_selection(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_selection *sel)
> > +{
> > + if (sel->pad == OPE_PROC_PAD_SINK_IN) {
> > + if (sel->target != V4L2_SEL_TGT_CROP)
> > + return -EINVAL;
> > + ope_proc_set_sink_crop(state, &sel->r);
> > + return 0;
> > + }
> > +
> > + if (sel->pad == OPE_PROC_PAD_SOURCE) {
> > + if (sel->target != V4L2_SEL_TGT_COMPOSE)
> > + return -EINVAL;
> > + ope_proc_set_src_compose(state, &sel->r);
> > + return 0;
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops ope_proc_pad_ops = {
> > + .get_fmt = v4l2_subdev_get_fmt,
> > + .set_fmt = ope_proc_set_fmt,
> > + .enum_mbus_code = ope_proc_enum_mbus_code,
> > + .get_selection = ope_proc_get_selection,
> > + .set_selection = ope_proc_set_selection,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops ope_proc_internal_ops = {
> > + .init_state = ope_proc_init_state,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ope_proc_ops = {
> > + .pad = &ope_proc_pad_ops,
> > +};
> > +
> > +/* -------- disp subdev ops -------- */
> > +
> > +static int ope_disp_set_fmt(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_format *fmt)
> > +{
> > + struct v4l2_mbus_framefmt *pad_fmt;
> > + u32 code = fmt->format.code;
> > +
> > + /* The disp subdev is pass-through: the source pad always mirrors the
> > + * sink pad, so source format is read-only and derived from the sink.
> > + */
> > + if (fmt->pad == OPE_DISP_PAD_SOURCE)
> > + return v4l2_subdev_get_fmt(sd, state, fmt);
> > +
> > + fmt->format.code = MEDIA_BUS_FMT_YUYV8_1_5X8;
> > + for (unsigned int i = 0; i < ARRAY_SIZE(ope_output_fmts); i++) {
> > + if (ope_output_fmts[i].mbus_code == code) {
> > + fmt->format.code = code;
> > + break;
> > + }
> > + }
> > +
> > + v4l_bound_align_image(&fmt->format.width, OPE_MIN_W, OPE_MAX_W, 0,
> > + &fmt->format.height, OPE_MIN_H, OPE_MAX_H, 0, 0);
> > + fmt->format.field = V4L2_FIELD_NONE;
> > + fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
> > +
> > + pad_fmt = v4l2_subdev_state_get_format(state, fmt->pad);
> > + *pad_fmt = fmt->format;
> > +
> > + /* Propagate to source */
> > + *v4l2_subdev_state_get_format(state, OPE_DISP_PAD_SOURCE) = fmt->format;
> > +
> > + return 0;
> > +}
> > +
> > +static int ope_disp_init_state(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state)
> > +{
> > + struct v4l2_mbus_framefmt def = {
> > + .width = OPE_MIN_W,
> > + .height = OPE_MIN_H,
> > + .code = MEDIA_BUS_FMT_YUYV8_1_5X8,
> > + .field = V4L2_FIELD_NONE,
> > + .colorspace = V4L2_COLORSPACE_SRGB,
> > + };
> > +
> > + *v4l2_subdev_state_get_format(state, OPE_DISP_PAD_SINK) = def;
> > + *v4l2_subdev_state_get_format(state, OPE_DISP_PAD_SOURCE) = def;
> > + return 0;
> > +}
> > +
> > +static const struct v4l2_subdev_pad_ops ope_disp_pad_ops = {
> > + .get_fmt = v4l2_subdev_get_fmt,
> > + .set_fmt = ope_disp_set_fmt,
> > + .link_validate = v4l2_subdev_link_validate_default,
> > +};
> > +
> > +static const struct media_entity_operations ope_disp_entity_ops = {
> > + .link_validate = v4l2_subdev_link_validate,
> > +};
> > +
> > +static const struct v4l2_subdev_internal_ops ope_disp_internal_ops = {
> > + .init_state = ope_disp_init_state,
> > +};
> > +
> > +static const struct v4l2_subdev_ops ope_disp_ops = {
> > + .pad = &ope_disp_pad_ops,
> > +};
> > +
> > +/* -------- disp-output video ops -------- */
> > +
> > +static int ope_disp_output_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 ope_dev *ope = container_of(vdev->v4l2_dev, struct ope_dev, v4l2_dev);
>
> lockdep_assert_held(ope->mutex);
>
> > + struct ope_ctx *ctx = ope->shared_ctx;
> > + struct v4l2_subdev_state *sd_state;
> > + struct v4l2_subdev_format sd_fmt = {
> > + .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> > + .pad = link->source->index,
> > + };
> > + int ret;
> > +
> > + if (!ctx)
> > + return 0;
> > +
> > + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> > + ret = v4l2_subdev_call(sd, pad, get_fmt, sd_state, &sd_fmt);
> > + v4l2_subdev_unlock_state(sd_state);
> > + if (ret)
> > + return ret;
> > +
> > + if (ctx->fmt_out.fmt->mbus_code != sd_fmt.format.code) {
> > + dev_dbg(ctx->ope->dev, "link validate: mbus 0x%04x incompatible with %p4cc\n",
> > + sd_fmt.format.code, &ctx->fmt_out.fmt->fourcc);
> > + return -EPIPE;
> > + }
> > +
> > + if (sd_fmt.format.width > ctx->fmt_out.width ||
> > + sd_fmt.format.height > ctx->fmt_out.height) {
> > + dev_dbg(ctx->ope->dev,
> > + "link validate: compose %ux%u exceeds output buffer %ux%u\n",
> > + sd_fmt.format.width, sd_fmt.format.height,
> > + ctx->fmt_out.width, ctx->fmt_out.height);
> > + return -EPIPE;
> > + }
> > +
> > + /* Check compose rect (including offset) fits within output buffer */
> > + {
> > + struct v4l2_subdev *proc_sd =
> > + camss_isp_pipeline_get_subdev(ctx->ope->pipeline,
> > + OPE_ENTITY_PROC);
> > + struct v4l2_subdev_state *proc_state =
> > + v4l2_subdev_lock_and_get_active_state(proc_sd);
> > + const struct v4l2_rect *compose =
> > + v4l2_subdev_state_get_compose(proc_state,
> > + OPE_PROC_PAD_SOURCE);
> > +
> > + if (compose->left + compose->width > ctx->fmt_out.width ||
> > + compose->top + compose->height > ctx->fmt_out.height) {
> > + dev_dbg(ctx->ope->dev,
> > + "link: compose %ux%u+%u+%u overflows output buffer %ux%u\n",
> > + compose->width, compose->height,
> > + compose->left, compose->top,
> > + ctx->fmt_out.width, ctx->fmt_out.height);
> > + v4l2_subdev_unlock_state(proc_state);
> > + return -EPIPE;
> > + }
> > + v4l2_subdev_unlock_state(proc_state);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct media_entity_operations ope_disp_output_entity_ops = {
> > + .link_validate = ope_disp_output_link_validate,
> > +};
> > +
> > +static int ope_input_link_validate(struct media_link *link)
> > +{
> > + struct video_device *vdev = media_entity_to_video_device(link->source->entity);
> > + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(link->sink->entity);
> > + struct ope_dev *ope = container_of(vdev->v4l2_dev, struct ope_dev, v4l2_dev);
>
> lockdep_assert_held(ope->mutex);
>
> > + struct ope_ctx *ctx = ope->shared_ctx;
>
> > + struct v4l2_subdev_state *sd_state;
> > + struct v4l2_subdev_format sd_fmt = {
> > + .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> > + .pad = link->sink->index,
> > + };
> > + int ret;
> > +
> > + if (!ctx)
> > + return 0;
> > +
> > + sd_state = v4l2_subdev_lock_and_get_active_state(sd);
> > + ret = v4l2_subdev_call(sd, pad, get_fmt, sd_state, &sd_fmt);
> > + v4l2_subdev_unlock_state(sd_state);
> > + if (ret)
> > + return ret;
> > +
> > + if (ctx->fmt_in.fmt->mbus_code != sd_fmt.format.code ||
> > + ctx->fmt_in.width != sd_fmt.format.width ||
> > + ctx->fmt_in.height != sd_fmt.format.height) {
> > + dev_dbg(ctx->ope->dev,
> > + "link validate: input %dx%d/0x%04x != pad %dx%d/0x%04x\n",
> > + ctx->fmt_in.width, ctx->fmt_in.height,
> > + ctx->fmt_in.fmt->mbus_code,
> > + sd_fmt.format.width, sd_fmt.format.height,
> > + sd_fmt.format.code);
> > + return -EPIPE;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct media_entity_operations ope_input_entity_ops = {
> > + .link_validate = ope_input_link_validate,
> > +};
> > +
> > +static const struct v4l2_ioctl_ops ope_video_ioctl_ops = {
> > + .vidioc_querycap = ope_querycap,
> > + .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_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_framesizes = ope_enum_framesizes,
> > + .vidioc_g_parm = ope_g_parm,
> > + .vidioc_s_parm = ope_s_parm,
> > + .vidioc_reqbufs = ope_reqbufs,
> > + .vidioc_querybuf = ope_querybuf,
> > + .vidioc_qbuf = ope_qbuf,
> > + .vidioc_dqbuf = ope_dqbuf,
> > + .vidioc_prepare_buf = ope_prepare_buf,
> > + .vidioc_create_bufs = ope_create_bufs,
> > + .vidioc_expbuf = ope_expbuf,
> > + .vidioc_streamon = ope_streamon,
> > + .vidioc_streamoff = ope_streamoff,
> > + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> > +};
> > +
> > +static const struct v4l2_ioctl_ops ope_meta_ioctl_ops = {
> > + .vidioc_querycap = ope_querycap,
> > + .vidioc_enum_fmt_meta_out = ope_enum_fmt_meta_out,
> > + .vidioc_g_fmt_meta_out = ope_g_fmt_meta,
> > + .vidioc_s_fmt_meta_out = ope_g_fmt_meta,
> > + .vidioc_try_fmt_meta_out = ope_g_fmt_meta,
> > + .vidioc_reqbufs = ope_reqbufs,
> > + .vidioc_querybuf = ope_querybuf,
> > + .vidioc_qbuf = ope_qbuf,
> > + .vidioc_dqbuf = ope_dqbuf,
> > + .vidioc_prepare_buf = ope_prepare_buf,
> > + .vidioc_create_bufs = ope_create_bufs,
> > + .vidioc_expbuf = ope_expbuf,
> > + .vidioc_streamon = ope_streamon,
> > + .vidioc_streamoff = ope_streamoff,
> > +};
> > +
> > +/* -------- File/Context ops -------- */
> > +
> > +static struct ope_ctx *ope_ctx_create(struct ope_dev *ope)
> > +{
> > + struct v4l2_pix_format_mplane pix_in = { .pixelformat = ope_input_fmts[0].fourcc };
> > + struct v4l2_pix_format_mplane pix_out = { .pixelformat = ope_output_fmts[0].fourcc };
>
> lockdep_assert_held(ope->mutex);
>
> > + struct ope_ctx *ctx;
> > + unsigned int i;
> > + int ret;
> > +
> > + ctx = kvzalloc_obj(*ctx);
> > + if (!ctx)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + ctx->ope = ope;
> > +
> > + mutex_init(&ctx->vbq_lock);
> > +
> > + ctx->params_buf = kvzalloc(v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD),
> > + GFP_KERNEL);
> > + if (!ctx->params_buf) {
> > + ret = -ENOMEM;
> > + goto err_destroy_lock;
> > + }
> > +
> > + ctx->config = ope_default_config;
> > +
> > + ctx->fmt_in.fmt = ope_fmt_try(ope, false, &pix_in);
> > + ctx->fmt_in.width = pix_in.width;
> > + ctx->fmt_in.height = pix_in.height;
> > + ctx->fmt_in.bytesperline = pix_in.plane_fmt[0].bytesperline;
> > + ctx->fmt_in.sizeimage = pix_in.plane_fmt[0].sizeimage;
> > + ctx->fmt_in.colorspace = pix_in.colorspace;
> > + ctx->fmt_in.xfer_func = pix_in.xfer_func;
> > + ctx->fmt_in.ycbcr_enc = pix_in.ycbcr_enc;
> > + ctx->fmt_in.quantization = pix_in.quantization;
> > + ctx->fmt_in.timeperframe.numerator = 1;
> > + ctx->fmt_in.timeperframe.denominator = DEFAULT_FRAMERATE;
> > +
> > + ctx->fmt_out.fmt = ope_fmt_try(ope, true, &pix_out);
> > + ctx->fmt_out.width = pix_out.width;
> > + ctx->fmt_out.height = pix_out.height;
> > + ctx->fmt_out.bytesperline = pix_out.plane_fmt[0].bytesperline;
> > + ctx->fmt_out.sizeimage = pix_out.plane_fmt[0].sizeimage;
> > + ctx->fmt_out.colorspace = pix_out.colorspace;
> > + ctx->fmt_out.xfer_func = pix_out.xfer_func;
> > + ctx->fmt_out.ycbcr_enc = pix_out.ycbcr_enc;
> > + ctx->fmt_out.quantization = pix_out.quantization;
> > +
> > + for (i = 0; i < OPE_QUEUE_COUNT; i++) {
> > + INIT_LIST_HEAD(&ctx->queues[i].rdy_queue);
> > + spin_lock_init(&ctx->queues[i].rdy_spinlock);
> > + ret = ope_init_vq(ctx, i);
> > + if (ret)
> > + goto err_release_vq;
> > + }
> > +
> > + INIT_LIST_HEAD(&ctx->list);
> > +
> > + scoped_guard(spinlock, &ope->ctx_lock)
> > + list_add(&ctx->list, &ope->ctx_list);
> > +
> > + ope->shared_ctx = ctx;
>
> > +
> > + return ctx;
> > +
> > +err_release_vq:
> > + while (i--) {
> > + vb2_queue_release(&ctx->queues[i].vq);
> > + kfree(ctx->queues[i].vq.drv_priv);
> > + }
> > + kvfree(ctx->params_buf);
> > +err_destroy_lock:
> > + mutex_destroy(&ctx->vbq_lock);
> > + kvfree(ctx);
> > + return ERR_PTR(ret);
> > +}
> > +
> > +static void ope_ctx_destroy(struct ope_ctx *ctx)
> > +{
> > + ctx->started = false;
> > +
> > + ope_sched_cancel(ctx);
> > + for (unsigned int i = 0; i < OPE_QUEUE_COUNT; i++) {
> > + vb2_queue_release(&ctx->queues[i].vq);
> > + kfree(ctx->queues[i].vq.drv_priv);
> > + }
> > + ope_sched_resume(ctx);
>
> ope->hw_ctx = NULL;
> ope->curr_ctx = NULL;
> ctx->queued = false;
> ctx->prev_ctx = NULL;
>
> > +
> > + /* Do not leave the LUT cache pointing at a context about to be freed */
> > + ope_glut_cache_invalidate(ctx->ope);
> > +
> > + kvfree(ctx->params_buf);
> > + mutex_destroy(&ctx->vbq_lock);
> > + kvfree(ctx);
> > +}
> > +
> > +static int ope_open(struct file *file)
> > +{
> > + struct video_device *vdev = video_devdata(file);
> > + struct ope_dev *ope = container_of(vdev->v4l2_dev, struct ope_dev, v4l2_dev);
> > + struct v4l2_fh *fh;
> > + int ret = 0;
> > +
> > + fh = kzalloc_obj(*fh);
> > + if (!fh)
> > + return -ENOMEM;
> > +
> > + if (mutex_lock_interruptible(&ope->mutex)) {
> > + kfree(fh);
> > + return -ERESTARTSYS;
> > + }
> > +
> > + v4l2_fh_init(fh, vdev);
> > + v4l2_fh_add(fh, file);
> > +
> > + /*
> > + * For now, only a single shared context is supported,
> > + * until media multi-context support is available.
> > + */
> > + if (!ope->shared_ctx) {
> > + struct ope_ctx *ctx = ope_ctx_create(ope);
> > +
> > + if (IS_ERR(ctx)) {
> > + ret = PTR_ERR(ctx);
> > + goto err_fh_del;
> > + }
> > + }
> > +
> > + ope->open_count++;
> > + mutex_unlock(&ope->mutex);
> > +
> > + return 0;
> > +
> > +err_fh_del:
> > + v4l2_fh_del(fh, file);
> > + v4l2_fh_exit(fh);
> > + kfree(fh);
> > + mutex_unlock(&ope->mutex);
>
> This is not in the same order as allocation/lock above.
>
> > + return ret;
> > +}
> > +
> > +static int ope_release(struct file *file)
> > +{
> > + struct v4l2_fh *fh = file_to_v4l2_fh(file);
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct ope_dev *ope = ctx->ope;
> > +
> > + v4l2_fh_del(fh, file);
> > + v4l2_fh_exit(fh);
> > + kfree(fh);
> > +
> > + scoped_guard(mutex, &ope->mutex) {
> > + if (--ope->open_count != 0)
> > + return 0;
> > + ope->shared_ctx = NULL;
> > + scoped_guard(spinlock, &ope->ctx_lock)
> > + list_del(&ctx->list);
> > + }
> > +
> > + ope_ctx_destroy(ctx);
> > +
> > + return 0;
> > +}
> > +
> > +static __poll_t ope_poll(struct file *file, poll_table *wait)
> > +{
> > + unsigned int idx = ope_queue_idx_from_file(file);
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > + struct v4l2_fh *fh = file_to_v4l2_fh(file);
> > + struct vb2_queue *vq;
> > + unsigned long flags;
> > + __poll_t rc = 0;
> > +
> > + if (idx >= OPE_QUEUE_COUNT)
> > + return EPOLLERR;
> > +
> > + vq = &ctx->queues[idx].vq;
> > +
> > + poll_wait(file, &vq->done_wq, wait);
> > + poll_wait(file, &fh->wait, wait);
> > +
> > + spin_lock_irqsave(&vq->done_lock, flags);
> > + if (!list_empty(&vq->done_list)) {
> > + if (V4L2_TYPE_IS_OUTPUT(vq->type))
> > + rc |= EPOLLOUT | EPOLLWRNORM;
> > + else
> > + rc |= EPOLLIN | EPOLLRDNORM;
> > + }
> > + spin_unlock_irqrestore(&vq->done_lock, flags);
> > +
> > + if (v4l2_event_pending(fh))
> > + rc |= EPOLLPRI;
> > +
> > + return rc;
> > +}
> > +
> > +static int ope_mmap(struct file *file, struct vm_area_struct *vma)
> > +{
> > + unsigned int idx = ope_queue_idx_from_file(file);
> > + struct ope_ctx *ctx = ope_ctx_from_file(file);
> > +
> > + if (idx >= OPE_QUEUE_COUNT)
> > + return -EINVAL;
> > +
> > + return vb2_mmap(&ctx->queues[idx].vq, vma);
> > +}
> > +
> > +static const struct v4l2_file_operations ope_fops = {
> > + .owner = THIS_MODULE,
> > + .open = ope_open,
> > + .release = ope_release,
> > + .poll = ope_poll,
> > + .unlocked_ioctl = video_ioctl2,
> > + .mmap = ope_mmap,
> > +};
> > +
> > +/* Pipeline descriptor */
> > +static const struct camss_isp_entity_desc ope_entity_descs[] = {
> > + [OPE_ENTITY_FRAME_IN] = {
> > + .name = "ope_input",
> > + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE,
> > + .function = MEDIA_ENT_F_IO_V4L,
> > + .vdev.caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING,
> > + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_FRAME_IN,
> > + .vdev.fops = &ope_fops,
> > + .vdev.ioctl_ops = &ope_video_ioctl_ops,
> > + .vdev.entity_ops = &ope_input_entity_ops,
> > + .pads = (const struct camss_isp_pad_desc[]) {
> > + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_PROC, OPE_PROC_PAD_SINK_IN, 0 },
> > + { }
> > + },
> > + },
> > + [OPE_ENTITY_DISP_OUT] = {
> > + .name = "ope_disp_output",
> > + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE,
> > + .function = MEDIA_ENT_F_IO_V4L,
> > + .vdev.caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING,
> > + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_DISP_OUT,
> > + .vdev.fops = &ope_fops,
> > + .vdev.ioctl_ops = &ope_video_ioctl_ops,
> > + .vdev.entity_ops = &ope_disp_output_entity_ops,
> > + .pads = (const struct camss_isp_pad_desc[]) {
> > + { MEDIA_PAD_FL_SINK, OPE_ENTITY_DISP, OPE_DISP_PAD_SOURCE, 0 },
> > + { }
> > + },
> > + },
> > + [OPE_ENTITY_PARAMS] = {
> > + .name = "ope_params",
> > + .obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE,
> > + .function = MEDIA_ENT_F_IO_V4L,
> > + .vdev.caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING,
> > + .vdev.drvdata = (void *)(uintptr_t)OPE_QUEUE_PARAMS,
> > + .vdev.fops = &ope_fops,
> > + .vdev.ioctl_ops = &ope_meta_ioctl_ops,
> > + .pads = (const struct camss_isp_pad_desc[]) {
> > + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_PROC, OPE_PROC_PAD_SINK_PAR, 0 },
> > + { }
> > + },
> > + },
> > + [OPE_ENTITY_PROC] = {
> > + .name = "ope_proc",
> > + .obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV,
> > + .function = MEDIA_ENT_F_PROC_VIDEO_ISP,
> > + .subdev.ops = &ope_proc_ops,
> > + .subdev.internal_ops = &ope_proc_internal_ops,
> > + .pads = (const struct camss_isp_pad_desc[]) {
> > + { MEDIA_PAD_FL_SINK, OPE_ENTITY_FRAME_IN, 0, 0 },
> > + { MEDIA_PAD_FL_SINK, OPE_ENTITY_PARAMS, 0, 0 },
> > + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_DISP, OPE_DISP_PAD_SINK,
> > + MEDIA_LNK_FL_ENABLED },
> > + { }
> > + },
> > + },
> > + [OPE_ENTITY_DISP] = {
> > + .name = "ope_disp",
> > + .obj_type = MEDIA_ENTITY_TYPE_V4L2_SUBDEV,
> > + .function = MEDIA_ENT_F_PROC_VIDEO_SCALER,
> > + .subdev.ops = &ope_disp_ops,
> > + .subdev.internal_ops = &ope_disp_internal_ops,
> > + .subdev.entity_ops = &ope_disp_entity_ops,
> > + .pads = (const struct camss_isp_pad_desc[]) {
> > + { MEDIA_PAD_FL_SINK, OPE_ENTITY_PROC, OPE_PROC_PAD_SOURCE, 0 },
> > + { MEDIA_PAD_FL_SOURCE, OPE_ENTITY_DISP_OUT, 0, 0 },
> > + { }
> > + },
> > + },
> > +};
> > +
> > +static void ope_v4l2_dev_release(struct v4l2_device *v4l2_dev)
> > +{
> > + struct ope_dev *ope = container_of(v4l2_dev, struct ope_dev, v4l2_dev);
> > +
> > + /* Reached once the V4L2 core has released every video and subdev node */
> > + camss_isp_pipeline_free(ope->pipeline);
> > + media_device_cleanup(&ope->mdev);
> > + mutex_destroy(&ope->mutex);
> > + kfree(ope);
> > +}
> > +
> > +static int ope_v4l2_register(struct ope_dev *ope)
> > +{
> > + int ret;
> > +
> > + ope->mdev.dev = ope->dev;
> > + strscpy(ope->mdev.model, OPE_NAME, sizeof(ope->mdev.model));
> > + ope->mdev.hw_revision = ope->hw_version;
> > + media_device_init(&ope->mdev);
> > + ope->v4l2_dev.mdev = &ope->mdev;
> > + ope->v4l2_dev.release = ope_v4l2_dev_release;
> > +
> > + ret = v4l2_device_register(ope->dev, &ope->v4l2_dev);
> > + if (ret) {
> > + /* No v4l2_device reference exists yet, so release @ope directly */
> > + ope_v4l2_dev_release(&ope->v4l2_dev);
> > + return ret;
> > + }
> > +
> > + ret = media_device_register(&ope->mdev);
> > + if (ret)
> > + goto err_v4l2;
> > +
> > + ope->pipeline = camss_isp_pipeline_alloc(OPE_ENTITY_COUNT);
> > + if (IS_ERR(ope->pipeline)) {
> > + ret = PTR_ERR(ope->pipeline);
> > + ope->pipeline = NULL;
> > + goto err_media;
> > + }
> > +
> > + ret = camss_isp_pipeline_register(ope->pipeline, &ope->v4l2_dev, ope_entity_descs,
> > + ARRAY_SIZE(ope_entity_descs));
> > + if (ret)
> > + goto err_media;
> > +
> > + ope->pipeline->drv_priv = ope;
> > + return 0;
> > +
> > +err_media:
> > + media_device_unregister(&ope->mdev);
> > +err_v4l2:
> > + /* Rely on v4l2 dev release callback to free ope/resources */
> > + v4l2_device_unregister(&ope->v4l2_dev);
> > + v4l2_device_put(&ope->v4l2_dev);
> > + return ret;
> > +}
> > +
> > +static void ope_v4l2_unregister(struct ope_dev *ope)
> > +{
> > + struct ope_ctx *ctx;
> > +
> > + if (ope->pipeline)
> > + camss_isp_pipeline_unregister(ope->pipeline);
> > +
> > + media_device_unregister(&ope->mdev);
> > +
> > + /* Clears v4l2_dev.dev, after which no new job can reach the hardware */
> > + v4l2_device_unregister(&ope->v4l2_dev);
> > +
> > + scoped_guard(spinlock_irqsave, &ope->sched_lock)
> > + ctx = ope->curr_ctx;
>
> Its either ope->mutex that owns this pointer or sched_lock not both.
>
> > +
> > + /* A job may still be in flight. */
> > + if (ctx && ope_hw_reset_sync(ope)) {
> > + dev_err(ope->dev, "reset timeout on unbind\n");
> > + synchronize_irq(ope->irq);
> > + ope_job_finish(ctx, VB2_BUF_STATE_ERROR);
> > + }
>
> You should finish the jobs before unregistering the pipeline not the
> reverse ..
>
> > +
> > + synchronize_irq(ope->irq);
> > + cancel_work_sync(&ope->run_work);
> > +
> > + /* media_device_cleanup() is deferred to ope_v4l2_dev_release() */
> > +}
> > +
> > +static void ope_dump_pipeline(struct ope_dev *ope)
> > +{
> > + static const struct {
> > + const char *name;
> > + u32 base;
> > + } blocks[] = {
> > + { "wb_gain", OPE_PP_WB_GAIN_BASE },
> > + { "demo", OPE_PP_DEMO_BASE },
> > + { "cc", OPE_PP_CC_BASE },
> > + { "glut", OPE_PP_GLUT_BASE },
> > + { "chroma_enhan", OPE_PP_CHROMA_ENHAN_BASE },
> > + { "ds_c_pre", OPE_PP_DOWNSCALE_MN_DS_C_PRE_BASE },
> > + { "ds_y_disp", OPE_PP_DOWNSCALE_MN_DS_Y_DISP_BASE },
> > + { "ds_c_disp", OPE_PP_DOWNSCALE_MN_DS_C_DISP_BASE },
> > + { "crop_y_disp", OPE_PP_CROP_RND_CLAMP_Y_DISP_BASE },
> > + { "crop_c_disp", OPE_PP_CROP_RND_CLAMP_C_DISP_BASE },
> > + };
> > +
> > + for (unsigned int i = 0; i < ARRAY_SIZE(blocks); i++) {
> > + u32 version = ope_read_pp(ope, OPE_PP_HW_VERSION(blocks[i].base));
> > +
> > + dev_dbg(ope->dev, "pipeline %-13s HW version %u.%u.%u\n",
> > + blocks[i].name,
> > + OPE_HW_VERSION_GEN(version),
> > + OPE_HW_VERSION_REV(version),
> > + OPE_HW_VERSION_STEP(version));
> > + }
> > +}
> > +
> > +static int ope_soft_reset(struct ope_dev *ope)
> > +{
> > + u32 version;
> > + int ret;
> > +
> > + ret = pm_runtime_resume_and_get(ope->dev);
> > + if (ret)
> > + return dev_err_probe(ope->dev, ret, "resume failed\n");
> > +
> > + version = ope_read(ope, OPE_TOP_HW_VERSION);
> > + ope->hw_version = version;
> > + dev_dbg(ope->dev, "HW version %u.%u.%u\n",
> > + OPE_HW_VERSION_GEN(version),
> > + OPE_HW_VERSION_REV(version),
> > + OPE_HW_VERSION_STEP(version));
> > +
> > + ope_dump_pipeline(ope);
> > +
> > + __ope_irq_init(ope);
> > +
> > + ret = ope_hw_reset_sync(ope);
> > + if (ret)
> > + dev_err(ope->dev, "Reset timeout\n");
> > +
> > + pm_runtime_put_sync(ope->dev);
> > +
> > + return ret;
> > +}
> > +
> > +static const char * const ope_pds[] = { "camss" };
> > +static const char * const ope_opp_pds[] = { "cx" };
> > +
> > +static const struct dev_pm_domain_attach_data ope_pd_data = {
> > + .pd_names = ope_pds,
> > + .num_pd_names = ARRAY_SIZE(ope_pds),
> > +};
> > +
> > +static const struct dev_pm_domain_attach_data ope_opp_pd_data = {
> > + .pd_names = ope_opp_pds,
> > + .num_pd_names = ARRAY_SIZE(ope_opp_pds),
> > + .pd_flags = PD_FLAG_DEV_LINK_ON | PD_FLAG_REQUIRED_OPP,
> > +};
> > +
> > +static int ope_init_power(struct ope_dev *ope)
> > +{
> > + struct device *dev = ope->dev;
> > + struct dev_pm_domain_list *pmdomains;
> > + struct dev_pm_domain_list *opp_pmdomains;
> > + struct clk *axi_clk;
> > + 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");
> > +
> > + ret = devm_pm_domain_attach_list(dev, &ope_pd_data, &pmdomains);
> > + if (ret < 0 && ret != -EEXIST)
> > + return dev_err_probe(dev, ret, "failed to attach power domains\n");
> > +
> > + ret = devm_pm_domain_attach_list(dev, &ope_opp_pd_data, &opp_pmdomains);
> > + if (ret < 0 && ret != -EEXIST)
> > + return dev_err_probe(dev, ret, "failed to attach OPP power domain\n");
> > +
> > + axi_clk = devm_clk_get_optional(dev, "axi");
> > + if (IS_ERR(axi_clk))
> > + return dev_err_probe(dev, PTR_ERR(axi_clk), "failed to get AXI clock\n");
> > +
> > + ret = clk_set_rate(axi_clk, OPE_AXI_CLK_RATE);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "failed to set AXI clock rate\n");
> > +
> > + 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 ope_dev *ope;
> > + int ret, irq;
> > +
> > + /* OPE lifetime is refcounted through the embedded v4l2_device */
> > + ope = kzalloc_obj(*ope);
> > + if (!ope)
> > + return -ENOMEM;
> > +
> > + ope->dev = dev;
> > + init_completion(&ope->reset_complete);
> > + mutex_init(&ope->mutex);
> > + INIT_LIST_HEAD(&ope->ctx_list);
> > + spin_lock_init(&ope->ctx_lock);
> > + spin_lock_init(&ope->sched_lock);
> > + INIT_WORK(&ope->run_work, ope_run_work);
> > +
> > + ret = dma_set_mask(dev, DMA_BIT_MASK(32));
> > + if (ret) {
> > + ret = dev_err_probe(dev, ret, "Failed to set DMA mask\n");
> > + goto err_free;
> > + }
> > +
> > + ret = ope_init_power(ope);
> > + if (ret) {
> > + ret = dev_err_probe(dev, ret, "Power init failed\n");
> > + goto err_free;
> > + }
> > +
> > + ret = ope_init_mmio(ope);
> > + if (ret) {
> > + ret = dev_err_probe(dev, ret, "MMIO init failed\n");
> > + goto err_free;
> > + }
> > +
> > + irq = platform_get_irq(pdev, 0);
> > + if (irq < 0) {
> > + ret = dev_err_probe(dev, irq, "Unable to get IRQ\n");
> > + goto err_free;
> > + }
> > +
> > + ret = devm_request_irq(dev, irq, ope_irq, 0, "camss-ope", ope);
> > + if (ret < 0) {
> > + ret = dev_err_probe(dev, ret, "Requesting IRQ failed\n");
> > + goto err_free;
> > + }
> > +
> > + ope->irq = irq;
> > +
> > + ret = ope_soft_reset(ope);
> > + if (ret)
> > + goto err_free;
>
>
> > +
> > + /* ope ownership is transferred to ope_v4l2_register(), even on failure */
> > + ret = ope_v4l2_register(ope);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "V4L2 registration failed\n");
> > +
> > + platform_set_drvdata(pdev, ope);
> > +
> > + return 0;
> > +
> > +err_free:
>
> I believe *ope as passed to opq_irq is racy with your reset here - why
> not just devm_ the ope pointer allocation.
>
> devm_free_irq();
>
> > + mutex_destroy(&ope->mutex);
> > + kfree(ope);
> > + return ret;
> > +}
> > +
> > +static void ope_remove(struct platform_device *pdev)
> > +{
> > + struct ope_dev *ope = platform_get_drvdata(pdev);
> > +
> > + ope_v4l2_unregister(ope);
> > + v4l2_device_put(&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 = OPE_NAME,
> > + .of_match_table = ope_dt_ids,
> > + .pm = pm_ptr(&ope_pm_ops),
> > + },
> > +};
> > +
> > +module_platform_driver(ope_driver);
> > +
> > +MODULE_DESCRIPTION("CAMSS Offline Processing Engine");
> > +MODULE_AUTHOR("Loic Poulain <loic.poulain@xxxxxxxxxxxxxxxx>");
> > +MODULE_LICENSE("GPL");
> > diff --git a/drivers/media/platform/qcom/camss/ope/params.c b/drivers/media/platform/qcom/camss/ope/params.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..55f9a9002e34818e828ce51f5cde25955926018c
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/params.c
> > @@ -0,0 +1,78 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * ope/params.c
> > + *
> > + * CAMSS ISP parameter buffer parser.
> > + *
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/errno.h>
> > +#include <linux/kernel.h>
> > +#include <media/videobuf2-core.h>
> > +#include <media/v4l2-isp.h>
> > +
> > +#include "params.h"
> > +
> > +int camss_isp_params_apply(struct device *dev,
> > + struct vb2_buffer *vb,
> > + struct v4l2_isp_params_buffer *scratch,
> > + const struct v4l2_isp_params_block_type_info *type_info,
> > + const camss_isp_params_handler_fn *handlers,
> > + unsigned int num_handlers,
> > + void *priv)
> > +{
> > + const struct v4l2_isp_params_buffer *buf = scratch;
> > + const void *src;
> > + unsigned int remaining;
> > + unsigned int offset = 0;
> > + int ret;
> > +
> > + ret = v4l2_isp_params_validate_buffer_size(dev, vb,
> > + v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD));
> > + if (ret)
> > + return ret;
> > +
> > + src = vb2_plane_vaddr(vb, 0);
> > +
> > + if (!src) {
> > + dev_dbg(dev, "params: buffer has no kernel mapping\n");
> > + return -EFAULT;
> > + }
> > +
> > + /*
> > + * The payload lives in a vmalloc'd buffer that userspace keeps mapped
> > + * while the buffer is queued, so it must not be validated in place:
> > + * copy it into kernel-only memory first, then work exclusively on the
> > + * copy. See the v4l2_isp_params_validate_buffer() documentation.
> > + */
> > + memcpy(scratch, src, vb2_get_plane_payload(vb, 0));
> > +
> > + ret = v4l2_isp_params_validate_buffer(dev, vb, buf, type_info, num_handlers);
> > + if (ret)
> > + return ret;
> > +
> > + dev_dbg(dev, "params: version=%u data_size=%u\n", buf->version, buf->data_size);
> > +
> > + remaining = buf->data_size;
> > +
> > + while (remaining >= sizeof(struct v4l2_isp_params_block_header)) {
> > + const union camss_isp_params_block *block =
> > + (const union camss_isp_params_block *)&buf->data[offset];
> > + u16 type = block->header.type;
> > + u32 bsize = block->header.size;
>
> You should bounds check this against remaining and as non-zero.
>
> > +
> > + if (type < num_handlers && handlers[type])
> > + handlers[type](priv, block);
> > + else
> > + dev_dbg(dev, "params: no handler for block type %u\n", type);
> > +
> > + offset += bsize;
> > + remaining -= bsize;
> > + }
> > +
> > + dev_dbg(dev, "params: buffer parsed successfully\n");
> > +
> > + return 0;
> > +}
> > diff --git a/drivers/media/platform/qcom/camss/ope/params.h b/drivers/media/platform/qcom/camss/ope/params.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..5bc1984a67b8d31e5c84238cf93076158f367032
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/params.h
> > @@ -0,0 +1,95 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * ope/params.h
> > + *
> > + * CAMSS ISP parameter buffer parser.
> > + *
> > + * Wraps the upstream v4l2_isp_params_validate_buffer() validation and adds
> > + * a dispatch layer: after validation each block is forwarded to a
> > + * driver-supplied handler.
> > + *
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#ifndef CAMSS_PARAMS_H
> > +#define CAMSS_PARAMS_H
> > +
> > +#include <linux/string.h>
> > +#include <linux/types.h>
> > +#include <media/v4l2-isp.h>
> > +#include <uapi/linux/qcom-camss-config.h>
> > +
> > +#define CAMSS_ISP_PARAMS_FMT_INIT \
> > + { .fourcc = V4L2_META_FMT_QCOM_ISP_PARAMS, .depth = 8, .align = 0, .num_planes = 1 }
> > +
> > +#define CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY (1U << V4L2_ISP_FL_DRIVER_FLAGS(0))
> > +
> > +struct device;
> > +struct vb2_buffer;
> > +struct camss_isp_fmt;
> > +
> > +union camss_isp_params_block {
> > + struct v4l2_isp_params_block_header header;
> > + struct camss_params_ope_wb_gain wb_gain;
> > + struct camss_params_ope_chroma_enhan chroma_enhan;
> > + struct camss_params_ope_color_correct color_correct;
> > + struct camss_params_ope_gamma gamma;
> > +};
> > +
> > +typedef void (*camss_isp_params_handler_fn)(void *priv, const union camss_isp_params_block *block);
> > +
> > +/**
> > + * camss_isp_params_copy_block - copy one validated block into driver state
> > + *
> > + * @dst: destination block in the driver's shadow configuration
> > + * @block: validated source block
> > + * @size: size of the destination block
> > + *
> > + * Returns true if the payload was copied, false if only the header was.
> > + */
> > +static inline bool camss_isp_params_copy_block(void *dst,
> > + const union camss_isp_params_block *block,
> > + size_t size)
> > +{
> > + struct v4l2_isp_params_block_header *hdr = dst;
> > + bool header_only = block->header.size == sizeof(block->header);
> > +
> > + if (header_only)
> > + *hdr = block->header;
> > + else
> > + memcpy(dst, block, size);
> > +
> > + hdr->flags |= CAMSS_ISP_PARAMS_FL_BLOCK_DIRTY;
> > +
> > + return !header_only;
> > +}
> > +
> > +/**
> > + * camss_isp_params_apply - validate and dispatch a params buffer
> > + *
> > + * @dev: device for error logging
> > + * @vb: the vb2 buffer (used for size validation)
> > + * @scratch: kernel-only bounce buffer, at least
> > + * v4l2_isp_buffer_size(CAMSS_PARAMS_OPE_MAX_PAYLOAD) bytes
> > + * @type_info: per-block-type validation info, indexed by block type
> > + * @handlers: per-block-type handlers, indexed by block type
> > + * @num_handlers: number of entries in @type_info and @handlers
> > + * @priv: opaque pointer forwarded to each handler
> > + *
> > + * Copies the buffer payload into @scratch, calls
> > + * v4l2_isp_params_validate_buffer_size(), then
> > + * v4l2_isp_params_validate_buffer(), then walks the validated block stream
> > + * dispatching each block to its handler. Validation and dispatch operate
> > + * only on @scratch, never on the userspace-visible mapping.
> > + *
> > + * Returns 0 on success, negative errno on validation failure.
> > + */
> > +int camss_isp_params_apply(struct device *dev,
> > + struct vb2_buffer *vb,
> > + struct v4l2_isp_params_buffer *scratch,
> > + const struct v4l2_isp_params_block_type_info *type_info,
> > + const camss_isp_params_handler_fn *handlers,
> > + unsigned int num_handlers,
> > + void *priv);
> > +
> > +#endif /* CAMSS_PARAMS_H */
> > diff --git a/drivers/media/platform/qcom/camss/ope/pipeline.c b/drivers/media/platform/qcom/camss/ope/pipeline.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..bbb076717ef77718045b6ac0008b4dc6ed84673a
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/pipeline.c
> > @@ -0,0 +1,412 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * CAMSS ISP pipeline helper — declarative MC topology builder
> > + *
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#include <linux/slab.h>
> > +
> > +#include <media/media-device.h>
> > +#include <media/media-entity.h>
> > +#include <media/v4l2-dev.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-subdev.h>
> > +#include "pipeline.h"
> > +
> > +#if !IS_ENABLED(CONFIG_MEDIA_CONTROLLER)
> > +static inline int media_entity_pads_init(struct media_entity *e, u16 n,
> > + struct media_pad *p) { return 0; }
> > +static inline void media_entity_remove_links(struct media_entity *e) {}
> > +static inline int media_create_pad_link(struct media_entity *src, u16 sp,
> > + struct media_entity *sink, u16 dp,
> > + u32 flags) { return 0; }
> > +#endif
> > +
> > +/* -------- Internal elpers -------- */
> > +
> > +static enum vfl_devnode_direction isp_caps_to_vfl_dir(u32 caps)
> > +{
> > + if (caps & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))
> > + return VFL_DIR_M2M;
> > + if (caps & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
> > + V4L2_CAP_META_OUTPUT | V4L2_CAP_VBI_OUTPUT | V4L2_CAP_SDR_OUTPUT))
> > + return VFL_DIR_TX;
> > + return VFL_DIR_RX;
> > +}
> > +
> > +static unsigned int isp_count_pads(const struct camss_isp_pad_desc *pads)
> > +{
> > + unsigned int n = 0;
> > +
> > + if (!pads)
> > + return 0;
> > + while (pads[n].flags)
> > + n++;
> > + return n;
> > +}
> > +
> > +static struct media_entity *isp_pipeline_media_entity(struct camss_isp_pipeline *pipeline,
> > + unsigned int idx)
> > +{
> > + struct camss_isp_pipeline_entity *slot = &pipeline->entities[idx];
> > +
> > + switch (slot->obj_type) {
> > + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
> > + return &slot->vdev.entity;
> > + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
> > + return &slot->subdev.entity;
> > + default:
> > + return &slot->entity;
> > + }
> > +}
> > +
> > +/* -------- Validation -------- */
> > +
> > +static int isp_pipeline_validate(struct device *dev,
> > + const struct camss_isp_entity_desc *descs,
> > + unsigned int num_entities)
> > +{
> > + unsigned int i, pi;
> > +
> > + for (i = 0; i < num_entities; i++) {
> > + const struct camss_isp_pad_desc *pads = descs[i].pads;
> > + unsigned int num_pads = isp_count_pads(pads);
> > +
> > + for (pi = 0; pi < num_pads; pi++) {
> > + const struct camss_isp_pad_desc *pad = &pads[pi];
> > + const struct camss_isp_pad_desc *peer_pad;
> > + unsigned int peer_num_pads;
> > + int peer_ent = pad->peer_entity;
> > +
> > + if (peer_ent < 0)
> > + continue;
> > +
> > + if ((unsigned int)peer_ent >= num_entities) {
> > + dev_err(dev, "entity[%u].p%u: peer_entity %d out of range\n",
> > + i, pi, peer_ent);
> > + return -EINVAL;
> > + }
> > +
> > + peer_num_pads = isp_count_pads(descs[peer_ent].pads);
> > + if (pad->peer_pad >= peer_num_pads) {
> > + dev_err(dev, "entity[%u].p%u: peer_pad %u out of range\n",
> > + i, pi, pad->peer_pad);
> > + return -EINVAL;
> > + }
> > +
> > + peer_pad = &descs[peer_ent].pads[pad->peer_pad];
> > +
> > + /* Links are SOURCE->SINK; reject SOURCE->SOURCE or SINK->SINK */
> > + if (((pad->flags & MEDIA_PAD_FL_SOURCE) &&
> > + (peer_pad->flags & MEDIA_PAD_FL_SOURCE)) ||
> > + ((pad->flags & MEDIA_PAD_FL_SINK) &&
> > + (peer_pad->flags & MEDIA_PAD_FL_SINK))) {
> > + dev_err(dev, "entity[%u].p%u -> entity[%d].p%u: invalid\n",
> > + i, pi, peer_ent, pad->peer_pad);
> > + return -EINVAL;
> > + }
> > +
> > + /* A link must be described symmetrically from both ends */
> > + if ((unsigned int)peer_pad->peer_entity != i ||
> > + peer_pad->peer_pad != pi) {
> > + dev_err(dev, "entity[%u].p%u <-> entity[%d].p%u: mismatch\n",
> > + i, pi, peer_ent, pad->peer_pad);
> > + return -EINVAL;
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/* -------- Allocation / Release -------- */
> > +
> > +struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities)
> > +{
> > + struct camss_isp_pipeline *pipeline;
> > +
> > + pipeline = kzalloc(struct_size(pipeline, entities, num_entities),
> > + GFP_KERNEL);
> > + if (!pipeline)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + pipeline->num_entities = num_entities;
> > + mutex_init(&pipeline->vdev_lock);
> > + return pipeline;
> > +}
> > +
> > +void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline)
> > +{
> > + unsigned int i;
> > +
> > + if (!pipeline)
> > + return;
> > +
> > + /* Release pads and subdevs state */
> > + for (i = 0; i < pipeline->num_entities; i++) {
> > + struct camss_isp_pipeline_entity *slot = &pipeline->entities[i];
> > +
> > + if (slot->obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV)
> > + v4l2_subdev_cleanup(&slot->subdev);
> > +
> > + kfree(slot->pads);
> > + slot->pads = NULL;
> > + }
> > +
> > + mutex_destroy(&pipeline->vdev_lock);
> > + kfree(pipeline);
> > +}
> > +
> > +/* -------- Registration -------- */
> > +
> > +void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline)
> > +{
> > + int i;
> > +
> > + /* Unregister entities in reverse order. */
> > + for (i = (int)pipeline->num_entities - 1; i >= 0; i--) {
> > + struct camss_isp_pipeline_entity *slot = &pipeline->entities[i];
> > +
> > + if (slot->registered) {
> > + switch (slot->obj_type) {
> > + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
> > + video_unregister_device(&slot->vdev);
> > + break;
> > + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
> > + v4l2_device_unregister_subdev(&slot->subdev);
> > + break;
> > + case MEDIA_ENTITY_TYPE_BASE:
> > + media_entity_remove_links(&slot->entity);
> > + media_device_unregister_entity(&slot->entity);
> > + slot->entity.name = NULL;
> > + break;
> > + }
> > +
> > + slot->registered = false;
> > + }
> > + }
> > +
> > + pipeline->v4l2_dev = NULL;
> > +}
> > +
> > +static int isp_register_vdev(struct camss_isp_pipeline *pipeline,
> > + struct camss_isp_pipeline_entity *slot,
> > + const struct camss_isp_entity_desc *desc,
> > + struct v4l2_device *v4l2_dev)
> > +{
> > + struct video_device *vdev = &slot->vdev;
> > + int ret;
> > +
> > + if (!desc->name || !desc->name[0] || !desc->vdev.fops)
> > + return -EINVAL;
> > +
> > + strscpy(vdev->name, desc->name, sizeof(vdev->name));
> > + vdev->vfl_dir = isp_caps_to_vfl_dir(desc->vdev.caps);
> > + vdev->v4l2_dev = v4l2_dev;
> > + vdev->device_caps = desc->vdev.caps | V4L2_CAP_IO_MC;
> > + vdev->release = video_device_release_empty;
> > + vdev->fops = desc->vdev.fops;
> > + vdev->lock = &pipeline->vdev_lock;
> > + if (desc->vdev.ioctl_ops)
> > + vdev->ioctl_ops = desc->vdev.ioctl_ops;
> > + if (desc->vdev.entity_ops)
> > + vdev->entity.ops = desc->vdev.entity_ops;
> > +
> > + vdev->entity.obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE;
> > + vdev->entity.function = desc->function ? desc->function : MEDIA_ENT_F_IO_V4L;
> > +
> > + ret = media_entity_pads_init(&vdev->entity, slot->num_pads, slot->pads);
> > + if (ret)
> > + return ret;
> > +
> > + video_set_drvdata(vdev, desc->vdev.drvdata);
> > +
> > + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> > + if (ret)
> > + return ret;
> > +
> > + return 0;
> > +}
> > +
> > +static int isp_register_subdev(struct camss_isp_pipeline_entity *slot,
> > + const struct camss_isp_entity_desc *desc,
> > + struct v4l2_device *v4l2_dev)
> > +{
> > + struct v4l2_subdev *sd = &slot->subdev;
> > + int ret;
> > +
> > + if (!desc->name || !desc->name[0] || !desc->subdev.ops)
> > + return -EINVAL;
> > +
> > + v4l2_subdev_init(sd, desc->subdev.ops);
> > + strscpy(sd->name, desc->name, sizeof(sd->name));
> > + sd->entity.function = desc->function ?
> > + desc->function : MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN;
> > + /* Create a /dev/v4l-subdevN node so userspace can query pad formats */
> > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> > + if (desc->subdev.internal_ops)
> > + sd->internal_ops = desc->subdev.internal_ops;
> > + if (desc->subdev.entity_ops)
> > + sd->entity.ops = desc->subdev.entity_ops;
> > +
> > + ret = media_entity_pads_init(&sd->entity, slot->num_pads, slot->pads);
> > + if (ret)
> > + return ret;
> > +
> > + ret = v4l2_subdev_init_finalize(sd);
> > + if (ret)
> > + return ret;
> > +
> > + ret = v4l2_device_register_subdev(v4l2_dev, sd);
> > + if (ret) {
> > + v4l2_subdev_cleanup(sd);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int isp_register_base_entity(struct camss_isp_pipeline_entity *slot,
> > + const struct camss_isp_entity_desc *desc,
> > + struct v4l2_device *v4l2_dev)
> > +{
> > + struct media_entity *entity = &slot->entity;
> > + int ret;
> > +
> > + if (!desc->name || !desc->name[0])
> > + return -EINVAL;
> > +
> > + entity->obj_type = MEDIA_ENTITY_TYPE_BASE;
> > + entity->name = desc->name;
> > + entity->function = desc->function;
> > +
> > + ret = media_entity_pads_init(entity, slot->num_pads, slot->pads);
> > + if (ret) {
> > + entity->name = NULL;
> > + return ret;
> > + }
> > +
> > + ret = media_device_register_entity(v4l2_dev->mdev, entity);
> > + if (ret) {
> > + entity->name = NULL;
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int isp_alloc_pads(struct camss_isp_pipeline_entity *slot,
> > + const struct camss_isp_entity_desc *desc)
> > +{
> > + unsigned int num_pads = isp_count_pads(desc->pads);
> > + unsigned int i;
> > +
> > + if (!num_pads)
> > + goto done;
> > +
> > + slot->pads = kcalloc(num_pads, sizeof(*slot->pads), GFP_KERNEL);
> > + if (!slot->pads)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < num_pads; i++)
> > + slot->pads[i].flags = desc->pads[i].flags;
> > +done:
> > + slot->num_pads = num_pads;
> > + return 0;
> > +}
> > +
> > +int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline,
> > + struct v4l2_device *v4l2_dev,
> > + const struct camss_isp_entity_desc *descs,
> > + unsigned int num_entities)
> > +{
> > + unsigned int i, pi;
> > + int ret;
> > +
> > + if (WARN_ON(num_entities != pipeline->num_entities))
> > + return -EINVAL;
> > +
> > + if (WARN_ON(!v4l2_dev || !v4l2_dev->mdev))
> > + return -EINVAL;
> > +
> > + ret = isp_pipeline_validate(v4l2_dev->dev, descs, num_entities);
> > + if (ret)
> > + return ret;
> > +
> > + pipeline->v4l2_dev = v4l2_dev;
> > +
> > + /* Register each entity */
> > + for (i = 0; i < num_entities; i++) {
> > + const struct camss_isp_entity_desc *desc = &descs[i];
> > + struct camss_isp_pipeline_entity *slot = &pipeline->entities[i];
> > +
> > + slot->obj_type = desc->obj_type;
> > +
> > + ret = isp_alloc_pads(slot, desc);
> > + if (ret)
> > + goto err_unregister;
> > +
> > + switch (desc->obj_type) {
> > + case MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
> > + ret = isp_register_vdev(pipeline, slot, desc, v4l2_dev);
> > + break;
> > + case MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
> > + ret = isp_register_subdev(slot, desc, v4l2_dev);
> > + break;
> > + case MEDIA_ENTITY_TYPE_BASE:
> > + default:
> > + ret = isp_register_base_entity(slot, desc, v4l2_dev);
> > + break;
> > + }
> > + if (ret)
> > + goto err_unregister;
> > +
> > + slot->registered = true;
> > + }
> > +
> > + /* Create links — only from SOURCE side to avoid duplicates */
> > + for (i = 0; i < num_entities; i++) {
> > + const struct camss_isp_entity_desc *desc = &descs[i];
> > + unsigned int num_pads = isp_count_pads(desc->pads);
> > +
> > + for (pi = 0; pi < num_pads; pi++) {
> > + const struct camss_isp_pad_desc *pad = &desc->pads[pi];
> > + struct media_entity *src_entity, *sink_entity;
> > + unsigned int src_pad_idx, sink_pad_idx;
> > + u32 lflags;
> > +
> > + if (!(pad->flags & MEDIA_PAD_FL_SOURCE))
> > + continue;
> > + if (pad->peer_entity < 0)
> > + continue;
> > +
> > + src_entity = isp_pipeline_media_entity(pipeline, i);
> > + sink_entity = isp_pipeline_media_entity(pipeline,
> > + (unsigned int)pad->peer_entity);
> > + src_pad_idx = pi;
> > + sink_pad_idx = pad->peer_pad;
> > +
> > + lflags = pad->link_flags ?
> > + pad->link_flags :
> > + (MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED);
> > +
> > + ret = media_create_pad_link(src_entity, src_pad_idx,
> > + sink_entity, sink_pad_idx,
> > + lflags);
> > + if (ret)
> > + goto err_unregister;
> > + }
> > + }
> > +
> > + /* Create /dev/v4l-subdevN nodes for all registered subdevs */
> > + ret = v4l2_device_register_subdev_nodes(v4l2_dev);
> > + if (ret)
> > + goto err_unregister;
> > +
> > + return 0;
> > +
> > +err_unregister:
> > + camss_isp_pipeline_unregister(pipeline);
> > + return ret;
> > +}
> > diff --git a/drivers/media/platform/qcom/camss/ope/pipeline.h b/drivers/media/platform/qcom/camss/ope/pipeline.h
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..f7d50734ed61f45e988328de980ac5793f7d38dd
> > --- /dev/null
> > +++ b/drivers/media/platform/qcom/camss/ope/pipeline.h
> > @@ -0,0 +1,239 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * CAMSS ISP pipeline helper — declarative MC topology builder
> > + *
> > + * Drivers describe their entire media graph — entities (video devices,
> > + * subdevs, or base entities), their pads, and the links between them —
> > + * in a single static descriptor table. The builder validates the table,
> > + * allocates and registers all entities, and creates all MC links.
> > + *
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + */
> > +
> > +#ifndef _CAMSS_PIPELINE_H
> > +#define _CAMSS_PIPELINE_H
> > +
> > +#include <linux/mutex.h>
> > +#include <media/media-device.h>
> > +#include <media/media-entity.h>
> > +#include <media/v4l2-dev.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-subdev.h>
> > +
> > +/**
> > + * struct camss_isp_pad_desc - descriptor for one pad and its optional link
> > + *
> > + * @flags: Pad flags: MEDIA_PAD_FL_SINK, MEDIA_PAD_FL_SOURCE,
> > + * MEDIA_PAD_FL_MUST_CONNECT. A zero @flags value acts as
> > + * the sentinel that terminates the pad list.
> > + * @peer_entity: Index of the peer entity in the descriptor array, or -1
> > + * if this pad has no link.
> > + * @peer_pad: Pad index on the peer entity to link to.
> > + * @link_flags: MC link flags (MEDIA_LNK_FL_*). Defaults to
> > + * MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED when zero.
> > + *
> > + * Links are described from both sides (each endpoint references the other),
> > + * but the builder only creates each link once — from the SOURCE side.
> > + */
> > +struct camss_isp_pad_desc {
> > + u32 flags;
> > + int peer_entity;
> > + unsigned int peer_pad;
> > + u32 link_flags;
> > +};
> > +
> > +/**
> > + * struct camss_isp_entity_desc - descriptor for one entity in the pipeline
> > + *
> > + * @name: Human-readable entity name (also used as video device name
> > + * suffix when @obj_type is MEDIA_ENTITY_TYPE_VIDEO_DEVICE).
> > + * Mandatory and must be non-empty.
> > + * @obj_type: MEDIA_ENTITY_TYPE_VIDEO_DEVICE, MEDIA_ENTITY_TYPE_V4L2_SUBDEV,
> > + * or MEDIA_ENTITY_TYPE_BASE.
> > + * @function: MEDIA_ENT_F_* function identifier.
> > + * @pads: Sentinel-terminated (flags == 0) array of pad descriptors.
> > + *
> > + * Fields used only for MEDIA_ENTITY_TYPE_VIDEO_DEVICE:
> > + * @vdev.caps: V4L2_CAP_* device capabilities.
> > + * The video device direction (VFL_DIR_RX/TX/M2M) is derived
> > + * automatically from @caps by the builder.
> > + * @vdev.drvdata: Opaque pointer set via video_set_drvdata() after registration.
> > + * @vdev.fops: File operations (mandatory).
> > + * @vdev.ioctl_ops: ioctl operations (may be NULL).
> > + *
> > + * Fields used only for MEDIA_ENTITY_TYPE_V4L2_SUBDEV:
> > + * @subdev.ops: Subdev operations (mandatory).
> > + * @subdev.internal_ops: Internal subdev operations (may be NULL).
> > + */
> > +struct camss_isp_entity_desc {
> > + const char *name;
> > + u32 obj_type;
> > + u32 function;
> > + const struct camss_isp_pad_desc *pads;
> > +
> > + union {
> > + /* MEDIA_ENTITY_TYPE_VIDEO_DEVICE */
> > + struct {
> > + u32 caps;
> > + void *drvdata;
> > + const struct v4l2_file_operations *fops;
> > + const struct v4l2_ioctl_ops *ioctl_ops;
> > + const struct media_entity_operations *entity_ops;
> > + } vdev;
> > + /* MEDIA_ENTITY_TYPE_V4L2_SUBDEV */
> > + struct {
> > + const struct v4l2_subdev_ops *ops;
> > + const struct v4l2_subdev_internal_ops *internal_ops;
> > + const struct media_entity_operations *entity_ops;
> > + } subdev;
> > + };
> > +};
> > +
> > +/**
> > + * struct camss_isp_pipeline_entity - one registered entity slot
> > + *
> > + * Internal to the pipeline; drivers access entities via the accessor helpers.
> > + *
> > + * @obj_type: mirrors the descriptor's @obj_type.
> > + * @registered: set once the entity has been successfully registered, so that
> > + * teardown only undoes what actually succeeded.
> > + * @pads: allocated pad array for this entity.
> > + * @num_pads: number of entries in @pads.
> > + * @vdev: valid when @obj_type == MEDIA_ENTITY_TYPE_VIDEO_DEVICE.
> > + * @subdev: valid when @obj_type == MEDIA_ENTITY_TYPE_V4L2_SUBDEV.
> > + * @entity: valid when @obj_type == MEDIA_ENTITY_TYPE_BASE.
> > + */
> > +struct camss_isp_pipeline_entity {
> > + u32 obj_type;
> > + bool registered;
> > + struct media_pad *pads;
> > + unsigned int num_pads;
> > + union {
> > + struct video_device vdev;
> > + struct v4l2_subdev subdev;
> > + struct media_entity entity;
> > + };
> > +};
> > +
> > +/**
> > + * struct camss_isp_pipeline - registered ISP pipeline topology
> > + *
> > + * Allocate with camss_isp_pipeline_alloc(), register with
> > + * camss_isp_pipeline_register(), tear down with
> > + * camss_isp_pipeline_unregister(), free with camss_isp_pipeline_free().
> > + *
> > + * @v4l2_dev: Pointer to the caller-provided V4L2 device.
> > + * @drv_priv: Driver-private pointer; not touched by the framework.
> > + * @vdev_lock: Pipeline-wide mutex assigned to video_device.lock of every
> > + * registered video device, so the V4L2 core serialises ioctl2
> > + * handlers across all of the pipeline's video nodes.
> > + * @num_entities: Number of entries in @entities.
> > + * @entities: Per-entity state; flexible array.
> > + */
> > +struct camss_isp_pipeline {
> > + struct v4l2_device *v4l2_dev;
> > + void *drv_priv;
> > + struct mutex vdev_lock;
> > +
> > + unsigned int num_entities;
> > + struct camss_isp_pipeline_entity entities[] __counted_by(num_entities);
> > +};
> > +
> > +/**
> > + * camss_isp_pipeline_alloc() - allocate a pipeline for @num_entities entities
> > + *
> > + * Returns a pointer to the new pipeline or ERR_PTR on failure.
> > + * Free with camss_isp_pipeline_free().
> > + */
> > +struct camss_isp_pipeline *camss_isp_pipeline_alloc(unsigned int num_entities);
> > +
> > +/**
> > + * camss_isp_pipeline_free() - free a pipeline
> > + * @pipeline: pipeline to free (may be NULL)
> > + */
> > +void camss_isp_pipeline_free(struct camss_isp_pipeline *pipeline);
> > +
> > +/**
> > + * camss_isp_pipeline_register() - validate descriptors and register the graph
> > + * @pipeline: pipeline (allocated with camss_isp_pipeline_alloc())
> > + * @v4l2_dev: caller-owned and already-registered V4L2 device; its
> > + * associated media_device (v4l2_dev->mdev) must also be
> > + * initialised and registered before this call.
> > + * @descs: array of @num_entities entity descriptors
> > + * @num_entities: number of entities; must equal pipeline->num_entities
> > + *
> > + * Validates the descriptor table (link direction consistency, index bounds),
> > + * then registers all entities into the provided v4l2_device / media_device
> > + * and creates all MC pad links.
> > + *
> > + * Returns 0 on success or a negative error code.
> > + */
> > +int camss_isp_pipeline_register(struct camss_isp_pipeline *pipeline,
> > + struct v4l2_device *v4l2_dev,
> > + const struct camss_isp_entity_desc *descs,
> > + unsigned int num_entities);
> > +
> > +/**
> > + * camss_isp_pipeline_unregister() - tear down a registered pipeline
> > + * @pipeline: pipeline to unregister.
> > + */
> > +void camss_isp_pipeline_unregister(struct camss_isp_pipeline *pipeline);
> > +
> > +/**
> > + * camss_isp_pipeline_get_vdev() - return the video_device for entity @idx
> > + * @pipeline: registered pipeline
> > + * @idx: entity index (must be MEDIA_ENTITY_TYPE_VIDEO_DEVICE)
> > + *
> > + * Returns NULL if @idx is out of range or the entity is not a video device.
> > + */
> > +static inline struct video_device *
> > +camss_isp_pipeline_get_vdev(struct camss_isp_pipeline *pipeline,
> > + unsigned int idx)
> > +{
> > + if (WARN_ON(idx >= pipeline->num_entities))
> > + return NULL;
> > + if (WARN_ON(pipeline->entities[idx].obj_type !=
> > + MEDIA_ENTITY_TYPE_VIDEO_DEVICE))
> > + return NULL;
> > + return &pipeline->entities[idx].vdev;
> > +}
> > +
> > +/**
> > + * camss_isp_pipeline_get_subdev() - return the v4l2_subdev for entity @idx
> > + * @pipeline: registered pipeline
> > + * @idx: entity index (must be MEDIA_ENTITY_TYPE_V4L2_SUBDEV)
> > + *
> > + * Returns NULL if @idx is out of range or the entity is not a subdev.
> > + */
> > +static inline struct v4l2_subdev *
> > +camss_isp_pipeline_get_subdev(struct camss_isp_pipeline *pipeline,
> > + unsigned int idx)
> > +{
> > + if (WARN_ON(idx >= pipeline->num_entities))
> > + return NULL;
> > + if (WARN_ON(pipeline->entities[idx].obj_type !=
> > + MEDIA_ENTITY_TYPE_V4L2_SUBDEV))
> > + return NULL;
> > + return &pipeline->entities[idx].subdev;
> > +}
> > +
> > +/**
> > + * camss_isp_pipeline_get_entity() - return the media_entity for entity @idx
> > + * @pipeline: registered pipeline
> > + * @idx: entity index (must be MEDIA_ENTITY_TYPE_BASE)
> > + *
> > + * Returns NULL if @idx is out of range or the entity is not a base entity.
> > + */
> > +static inline struct media_entity *
> > +camss_isp_pipeline_get_entity(struct camss_isp_pipeline *pipeline,
> > + unsigned int idx)
> > +{
> > + if (WARN_ON(idx >= pipeline->num_entities))
> > + return NULL;
> > + if (WARN_ON(pipeline->entities[idx].obj_type !=
> > + MEDIA_ENTITY_TYPE_BASE))
> > + return NULL;
> > + return &pipeline->entities[idx].entity;
> > +}
> > +
> > +#endif /* _CAMSS_PIPELINE_H */
> >
>