From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-ej2-f12.google.com (mail-ej2-f12.google.com [74.125.228.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DAC4D472F9B for ; Tue, 15 Sep 2026 10:19:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.228.140 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789467601; cv=none; b=Br7S1ZKhe23oNFe3OBaRvszh8cg3cGeOM6ik1PlFWBd9dGsr7BnSuTbVITnAYdA1Nr4lVq6rocpCpi7Njmbr0/z3iiCieMsxWGQez+ZzPfjAd0+AS/M90MfUTDD3eRAR0hOdLMcBb8s2QLFHpSlj3rQscLTphVKrGi08rz9JToU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789467601; c=relaxed/simple; bh=LEM2ZQ5EB+V3OF5/5bjhYbbb2+DpBnjp/T3fXAfjvzI=; h=Message-ID:Date:MIME-Version:Subject:To:Cc:References:From: In-Reply-To:Content-Type; b=NG7fc092ilyQKPd0N3T5tRuDaWc2sLarK+l2k3Ux1DFS4RqUdLwimvtynPpxSsvZSdjDHqI+u5PQN3k4LjsvwiszQ3mWkXRveI/LS/hzJVnjRhW414YvIlQTjBElH1tOSrOI/wFe7VzxAwZaZRY63w+y+k5q9hrj5NR8W4FemyQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linaro.org; spf=pass smtp.mailfrom=linaro.org; dkim=pass (2048-bit key) header.d=linaro.org header.i=@linaro.org header.b=vN46Ql9C; arc=none smtp.client-ip=74.125.228.140 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linaro.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linaro.org header.i=@linaro.org header.b="vN46Ql9C" Received: by mail-ej2-f12.google.com with SMTP id a640c23a62f3a-c254f705536so709843266b.1 for ; Tue, 15 Sep 2026 03:19:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1789467586; x=1790072386; darn=vger.kernel.org; h=content-transfer-encoding:content-type:in-reply-to:content-language :from:references:cc:to:subject:user-agent:mime-version:date :message-id:from:to:cc:subject:date:message-id:reply-to:content-type; bh=p0Wg2RvVB0Gay2atXJpcXr39i+ZAIDROBlOySqTHDVs=; b=vN46Ql9CtoQXXPsRGA7s7aNww5WsK7p1kde/LtCk0zXVCMXm5lA6+I0StA4ajqyKiL NJI2hDyW0rWy21Nvm8aVVU5eEBjF5UNt2vJLUihKSOF+6XTLf/9kukWltmExMrRMfJlD D4yvb+Ge1QxUQmcC6zyOGSmGTltF93xAWyeFQqsMPOOPszZvWbRgX2KI11x+SllJmtsn x7sbEQkSo2qC8UfphxvZMpwAiEpaDmxNBR+eoDGRe1ciMwVLnwNYPpivCPS+THU+Vwmv k5/LvDWSt3r3esI7Y2TxIpdWBAjUD9Be42KtMNaDUUTAACeMjcL1TReJkEJ9kzxXrtXk d6AA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789467586; x=1790072386; h=content-transfer-encoding:content-type:in-reply-to:content-language :from:references:cc:to:subject:user-agent:mime-version:date :message-id:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=p0Wg2RvVB0Gay2atXJpcXr39i+ZAIDROBlOySqTHDVs=; b=AbuXF1amRMiuvrGUTqr2Ov87FO9D2DgfR2sue9u2rtL/htv3lPH01+bCZdnxoHHwps VWEfYXlvaN9fRxN1Sl37ZDOlDrh2Fubo2Yo2usURQ3u9c+siCj+h6EjmVjl+BBbKoFNh 5Wsx2hn3CHeUKkXT4m6tiwaXqdsNMwUYCUXFeWu0a9scB9m6VwXirCUAp3ghIQpZfqpx ZVhOJLVvsX+H3G+X7RqZ4L+RxeXu9rDER56oklFEQ8LgI5nNOI/D0alLOm4rdArTfUHk mnAIXtx7yKZTDz7uXT3Yk1rKYbjRP3wn2AFDuqatBC9JXdP12EYOndfDDyhy2mUp1GLw KGrg== X-Forwarded-Encrypted: i=1; AKwUvBxT4LcL7rUohjWhqi2mbQmp4JzOGsMOb1+gkCuo7TLD5wNliAXlVWvdKfi1zW69c0U5lkll1SAC8KLG@vger.kernel.org X-Gm-Message-State: AFuF++m5F/fZjmKY+1ynStOykAx8q+v72iU5bNr6z3jm2XGGxyDOmQm+ QSp+yUaKCFWLDaLgP+Mr5NsqjjRykc3OPHCP9fijWfLKrSFl7B7c/6LS6O7f3D4kRQQ= X-Gm-Gg: AYBFou0XdK8L0Ef2NAljjp7nYHQSwYx4KApYuQ2lsLQ6qHe7YaWr0GEOb0p08uqjZKp 7mLwjl4Qdg7xXBSNE0VfXId7nkv/p/p3nd1KvQnPhPimn++WV9N3rhJRsp7gvmkkimLJMZnJe94 mmsnBp7ni7IUHuLWLwbyuWHVUq9ieczng8+IgxKnf6LZ0krOqzvdZYQ5MCVAPd54G62YKRBrHwp EbO72T8eEzY8R0Jnht1rqn/8b0GmckhTyClR+T80lPkj7yty4yk74zbWFDDDA4JzCIh62uaOBjk Hk/CWmPAN95zTQIYXCfCGnnWZhoPXQwA9QgN6vSYMkV4HqbasXeGHp5+/9hYE0V8m7H7OysUK1w 5pFEdq887kLWOeqb2/hmhrE2mDmBp5aK9BFs6O4/4NL16OWVt5PSXU7FMgnXi0gJGe7TIupNlio i4sS+f04boQYM9ssvlAzmEAjRdNn2L6ywhvXRs6ZrWMxbWLViY65PkE1/0LsJMMH7UDg6BnRltf oyKjwLJJVhmv9X8g4XLNLE= X-Received: by 2002:a17:907:3ccc:b0:c26:1648:a069 with SMTP id a640c23a62f3a-c29b875479bmr419378866b.36.1789467584340; Tue, 15 Sep 2026 03:19:44 -0700 (PDT) Received: from [192.168.0.167] ([109.76.15.202]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c2965c4f77asm557610366b.8.2026.09.15.03.19.22 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 15 Sep 2026 03:19:43 -0700 (PDT) Message-ID: Date: Tue, 15 Sep 2026 11:19:21 +0100 Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v7 6/9] media: qcom: camss: Add CAMSS Offline Processing Engine driver To: Loic Poulain , Mauro Carvalho Chehab , Vladimir Zapolskiy , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Kees Cook , "Gustavo A. R. Silva" , Bjorn Andersson , Konrad Dybcio Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org, linux-hardening@vger.kernel.org, Hans de Goede References: <20260915-camss-isp-ope-v7-0-77b13d131d3d@oss.qualcomm.com> <20260915-camss-isp-ope-v7-6-77b13d131d3d@oss.qualcomm.com> From: Bryan O'Donoghue Content-Language: en-GB In-Reply-To: <20260915-camss-isp-ope-v7-6-77b13d131d3d@oss.qualcomm.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit 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 > Signed-off-by: Hans de Goede > 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 Should there be an Assisted-by here ? > --- > 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 > 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "pipeline.h" > + > +#include > +#include > + > +#include > + > +#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(¶ms->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 "); > +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 > +#include > +#include > +#include > +#include > + > +#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 > +#include > +#include > +#include > + > +#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 > + > +#include > +#include > +#include > +#include > +#include > +#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 > +#include > +#include > +#include > +#include > +#include > + > +/** > + * 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 */ >