{"id":27431,"url":"https://patchwork.libcamera.org/api/patches/27431/?format=json","web_url":"https://patchwork.libcamera.org/patch/27431/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260721145958.68489-8-johannes.goede@oss.qualcomm.com>","date":"2026-07-21T14:59:57","name":"[v2,7/7] camss: Add Offline Processing Engine ISP support","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"5e1d4eb879e71d3d7637a44d7049c6ba6eaa19bb","submitter":{"id":242,"url":"https://patchwork.libcamera.org/api/people/242/?format=json","name":"Hans de Goede","email":"johannes.goede@oss.qualcomm.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27431/mbox/","series":[{"id":6070,"url":"https://patchwork.libcamera.org/api/series/6070/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6070","date":"2026-07-21T14:59:50","name":"camss: Add CAMSS pipeline handler","version":2,"mbox":"https://patchwork.libcamera.org/series/6070/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27431/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27431/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 6036BBDE4C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 21 Jul 2026 15:00:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0CDD967E78;\n\tTue, 21 Jul 2026 17:00:19 +0200 (CEST)","from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com\n\t[205.220.180.131])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 047E967E72\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Jul 2026 17:00:16 +0200 (CEST)","from pps.filterd (m0279870.ppops.net [127.0.0.1])\n\tby mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n\t66LDfwtK1912530 for <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Jul 2026 15:00:15 GMT","from mail-qt1-f198.google.com (mail-qt1-f198.google.com\n\t[209.85.160.198])\n\tby mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4fj1duk0k7-1\n\t(version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT)\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Jul 2026 15:00:15 +0000 (GMT)","by mail-qt1-f198.google.com with SMTP id\n\td75a77b69052e-51c075c1e25so163191061cf.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 21 Jul 2026 08:00:15 -0700 (PDT)","from shalem\n\t(2001-1c00-0c32-7800-5bfa-a036-83f0-f9ec.cable.dynamic.v6.ziggo.nl.\n\t[2001:1c00:c32:7800:5bfa:a036:83f0:f9ec])\n\tby smtp.gmail.com with ESMTPSA id\n\ta640c23a62f3a-c1712a5f837sm615968066b.32.2026.07.21.08.00.09\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 21 Jul 2026 08:00:09 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=qualcomm.com header.i=@qualcomm.com\n\theader.b=\"aY4pvB1O\"; dkim=pass (2048-bit key;\n\tunprotected) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com\n\theader.b=\"h+HQHBUL\"; dkim-atps=neutral","DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h=\n\tcc:content-transfer-encoding:date:from:in-reply-to:message-id\n\t:mime-version:references:subject:to; s=qcppdkim1; bh=iBY8Jj61JpQ\n\t7zLskBS7JWNts/YX873WTeG1EgV3TanE=; b=aY4pvB1OHe/WYH3xrTFvFsCXcvC\n\tL7zXv1WrTiS6KuAnuCJ6MJY4YbJYhS7E3icrygRk+zPoHirEiC21mF8v/WNaag4T\n\tVijMT6eArkLtkZYUufCZM3KaAlTNFrBsKIbSMEutr1Ye84/qQPUiUSbhaN5fl5R8\n\tgTpUMWQ+ipIHhEiJkDmaMNYPOPC4RKtzx8bfJQ325zQaP2rFMYzAOLIiwzccLgbq\n\tHRxxcAb4u5GofiEpS0qx0TDa88cTUA9fsVb2QpZR8W5Tc8awG9/8ARVueHTF73IL\n\tgCryPtJv7rvprh0PBr/HD3wUySVQtAFA+dJTr42DN2T18uwvB+1bryclLqw==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=oss.qualcomm.com; s=google; t=1784646015; x=1785250815;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to:content-type;\n\tbh=iBY8Jj61JpQ7zLskBS7JWNts/YX873WTeG1EgV3TanE=;\n\tb=h+HQHBUL0RqYyrF66XQD/XsoEd3kRl5u0MmUOMreHhS4LmCHv5Tj7mmn2B53u8o4AS\n\t/nREOwwz3VmWWpvniabM8N4kFj6sU4xGjJzlbRh9pR4H4ZAJjeeBvVGP653al9oYqwVE\n\tVBhmpCrd665U3o00xjjbN3uum2Qr0+jMmg0Dshwom2uHgIeErMYbRPtB9IfnrbalzROQ\n\t1BMjs2gz4cLVStilnGWfw7OtHSyKMwbtvJnTuq17jRZ8TyAgzSO6Q8C8IzlIidvFmmne\n\tlbF8DtYLy1KCS8Di3osdvGcVrU4GQkHBzi88p4zHYMOaOpnq231DZpUvW9agwO9TP2PP\n\t54TQ=="],"X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1784646015; x=1785250815;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to:content-type;\n\tbh=iBY8Jj61JpQ7zLskBS7JWNts/YX873WTeG1EgV3TanE=;\n\tb=hxsURIYRkxPTwzpp1xnr7JAgZy5dA+A3pCEKZ4+YbMGrQesd3REABq2NhvDTXm6HiI\n\tbqHXdfAl0S/NdhrudZpnQzQJRTv7y96f0IYe4SvTwcLMw3u+MaGV3M+ZsWH42eMjm6dL\n\ta/fDj90+/pSXcmjtALvtQxFXcGItqJQ2Cg6rOcZYaNMQREGnvg/byaNFI1Y2xGWM1CcX\n\tdnvSXLck6Z/FO5BViKBWDMWTbqWpdwnXSiPTIS36hvItr90Nsgj2LP2tuiS3sjh4K38t\n\t1wFMnfi6l/YBnECMMcoIZUxuNB6ZBEDzenbBPMbqVDTb2Nv4ybrAWuZVgJ/Wgg8VDsk8\n\tO2uQ==","X-Gm-Message-State":"AOJu0YyI39sNo2f9cMgX0K6fyGV7tUpAC7pWUg9t7ffI8k7fOy3D7TOF\n\tFOJjLliYDnZe683SPBjEgIo12AwEim8Pu4Ap+/diYjwGiHW/+cnycBBRmrE45Am32DR5qPtE7lK\n\tEbL5PvsyhKTKwzgFY1yGSqmKsExrfesB+wAugEZwIuh++OIGbv/6Od7KmCv1vyb60Ctja0KiGiM\n\tKr08PdIQMl","X-Gm-Gg":"AfdE7clE9WUchXpGkYXXaTFfRbGfv80SwEoSHnRe2vIeBvzFAL921GnUdRtc8Td3ynw\n\t5ceXi0qifpRBWgiP47CU51jlzc1SMovNlc1OSsFZpFVjwug6Im8z/T3ODvhlNHwOaPB1Xne4sDo\n\tpLsiAAiKf+YRwMFd5sIMpUKxTsCFkD6AH9cXuHWS41v/lMsdDjxG3sf110/jF7LmDfeCiD7qWyW\n\toXiK73XWkPd8qMaW7V6Hb1TGdt3nKXSYPF/a8676N8KWB1Ltz8ii350Iv1v1HYqjVlVCqG4cics\n\tfhRkPkhK/X0RC8osJT4UzBBSSMEQI1K7jH1lK5ilyILhCeNB116KISLfMZsJgC8GmIEooLiIrDA\n\t4KvMklATSDgwcltkEUnEeFlAJbLR8v1oUKIVEV+s7cBBfntOFhOdlRBkGlBB2K6BefIQbIuS5wn\n\tRxgEwbJfR2uL1n09o=","X-Received":["by 2002:a05:622a:1ba2:b0:51c:6e:ef23 with SMTP id\n\td75a77b69052e-5213c9e3db3mr177448231cf.32.1784646012314; \n\tTue, 21 Jul 2026 08:00:12 -0700 (PDT)","by 2002:a05:622a:1ba2:b0:51c:6e:ef23 with SMTP id\n\td75a77b69052e-5213c9e3db3mr177445831cf.32.1784646010484; \n\tTue, 21 Jul 2026 08:00:10 -0700 (PDT)"],"From":"Hans de Goede <johannes.goede@oss.qualcomm.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Loic Poulain <loic.poulain@oss.qualcomm.com>,\n\tHans de Goede <johannes.goede@oss.qualcomm.com>","Subject":"[PATCH v2 7/7] camss: Add Offline Processing Engine ISP support","Date":"Tue, 21 Jul 2026 16:59:57 +0200","Message-ID":"<20260721145958.68489-8-johannes.goede@oss.qualcomm.com>","X-Mailer":"git-send-email 2.55.0","In-Reply-To":"<20260721145958.68489-1-johannes.goede@oss.qualcomm.com>","References":"<20260721145958.68489-1-johannes.goede@oss.qualcomm.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-Proofpoint-ORIG-GUID":"ib5jDCn2Hczbq5p99WW5TufhQDw1XYnN","X-Proofpoint-Spam-Details-Enc":"AW1haW4tMjYwNzIxMDE1OCBTYWx0ZWRfX0BcZoi8n/r3f\n\tQSPfa5rw2rltU9N5luq+gHWqEyDnBGRZ1MdSbQ/t4W2o+AToLha+hQeRVNNdviwhX4bg4BcKfni\n\tgTKFzUCdH78vLE1LgwkN9794xkEBbH8qErpkM7iKWc+xJUJ8MOpDzdrWoC9Sy39bBskypR0zpGf\n\tmbbBZmP7qcYnGWjB1LiKYHBQffeNibDOFon3EksorkbyubISUEsKZls6RM5rYY6ZttRFHgWI7Vo\n\tz0eU0ETrWPl1w9dm5ee6AEWkF5cHeUqDMDrnGDD3vWBZenatKWYSRHvUhVs9mkv0mRKqAg1XXVu\n\t6HZ5KyUz59P+0mhYJYkjyShaHjVNxI43CBO9LhuBDXzk5q2/BKBfO2n+BAzKgoztYKhTHm9trwp\n\tXxSGizbCGB+jkgTyPelX5ilzTA9SPuaTRc5Bz99jzuanXIQcAHi2aZhQHwVjDCCNGrItQ3wEqR+\n\tVUux9xkR9+MMO5IEbCw==","X-Authority-Analysis":"v=2.4 cv=I9JVgtgg c=1 sm=1 tr=0 ts=6a5f897f cx=c_pps\n\ta=mPf7EqFMSY9/WdsSgAYMbA==:117 a=xqWC_Br6kY4A:10 a=RAioF0-LDSMA:10\n\ta=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22\n\ta=gowsoOTTUOVcmtlkKump:22 a=VwQbUJbxAAAA:8 a=EUspDBNiAAAA:8\n\ta=ISZBYy2OhyTthe2ftDAA:9 a=dawVfQjAaf238kedN5IG:22","X-Proofpoint-GUID":"ib5jDCn2Hczbq5p99WW5TufhQDw1XYnN","X-Proofpoint-Spam-Info":"AW1haW4tMjYwNzIxMDE1OCBTYWx0ZWRfX3k1IkMrien8y\n\tzrpwEDxqMzLvTdbtp9nh7mcmiJ/e9xns2yOZRE5IGVWR5orH57f/mWykAgRwrnuyw/3Z4n0BkJ+\n\tuZ8HNOaowCxFC8AlrottFwpVau5no/E=","X-Proofpoint-Virus-Version":"vendor=baseguard\n\tengine=ICAP:2.0.293, Aquarius:18.0.1143, Hydra:6.1.134,\n\tFMLib:17.12.100.49\n\tdefinitions=2026-07-21_03,2026-07-20_03,2025-10-01_01","X-Proofpoint-Spam-Details":"rule=outbound_notspam policy=outbound score=0\n\tbulkscore=0 adultscore=0 spamscore=0 clxscore=1015 suspectscore=0\n\tlowpriorityscore=0 phishscore=0 priorityscore=1501 impostorscore=0\n\tmalwarescore=0 classifier=typeunknown authscore=0 authtc= authcc=\n\troute=outbound adjust=0 reason=mlx scancount=1\n\tengine=8.22.0-2606150000\n\tdefinitions=main-2607210158","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"This adds support for the OPE through the v3 kernel driver series posted\nupstream recently (See Link tag).\n\nThis is still in the proof of concept phase atm. With the biggest todo\nitem being this using the softISP simple IPA atm, while it really should\ncome with its own IPA.\n\nLink: https://lore.kernel.org/linux-media/20260508-camss-isp-ope-v3-0-bb1055274603@oss.qualcomm.com/\nSigned-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n---\n src/libcamera/pipeline/camss/camss.cpp        |  19 +-\n .../pipeline/camss/camss_isp_ope.cpp          | 679 ++++++++++++++++++\n src/libcamera/pipeline/camss/camss_isp_ope.h  | 120 ++++\n src/libcamera/pipeline/camss/camss_params.h   |   2 +-\n src/libcamera/pipeline/camss/meson.build      |   3 +\n 5 files changed, 816 insertions(+), 7 deletions(-)\n create mode 100644 src/libcamera/pipeline/camss/camss_isp_ope.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_isp_ope.h","diff":"diff --git a/src/libcamera/pipeline/camss/camss.cpp b/src/libcamera/pipeline/camss/camss.cpp\nindex 371d4f8a2..6b53bdacd 100644\n--- a/src/libcamera/pipeline/camss/camss.cpp\n+++ b/src/libcamera/pipeline/camss/camss.cpp\n@@ -38,6 +38,7 @@\n #include \"camss_csi.h\"\n #include \"camss_frames.h\"\n #include \"camss_isp.h\"\n+#include \"camss_isp_ope.h\"\n #include \"camss_isp_soft.h\"\n \n namespace libcamera {\n@@ -522,12 +523,18 @@ bool PipelineHandlerCamss::match(DeviceEnumerator *enumerator)\n \t\tdata->delayedCtrls_ =\n \t\t\tstd::make_unique<DelayedControls>(sensor->device(), params);\n \n-\t\tdata->isp_ = std::make_unique<CamssIspSoft>(this, sensor,\n-\t\t\t\t\t\t\t    &data->frameInfos_,\n-\t\t\t\t\t\t\t    &data->controlInfo_);\n-\t\tif (!data->isp_->isValid()) {\n-\t\t\tLOG(Camss, Error) << \"Failed to create software ISP\";\n-\t\t\tcontinue;\n+\t\tdata->isp_ = CamssIspOpe::match(this, enumerator, sensor,\n+\t\t\t\t\t\t&data->frameInfos_,\n+\t\t\t\t\t\t&data->controlInfo_);\n+\t\tif (data->isp_ == nullptr) {\n+\t\t\tdata->isp_ =\n+\t\t\t\tstd::make_unique<CamssIspSoft>(this, sensor,\n+\t\t\t\t\t\t\t       &data->frameInfos_,\n+\t\t\t\t\t\t\t       &data->controlInfo_);\n+\t\t\tif (!data->isp_->isValid()) {\n+\t\t\t\tLOG(Camss, Error) << \"Failed to create software ISP\";\n+\t\t\t\tcontinue;\n+\t\t\t}\n \t\t}\n \n \t\tdata->isp_->inputBufferReady.connect(data->csi_.get(),\ndiff --git a/src/libcamera/pipeline/camss/camss_isp_ope.cpp b/src/libcamera/pipeline/camss/camss_isp_ope.cpp\nnew file mode 100644\nindex 000000000..454c72795\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp_ope.cpp\n@@ -0,0 +1,679 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS OPE ISP class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#include \"camss_isp_ope.h\"\n+\n+#include <string.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/camera_manager.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/dma_buf_allocator.h\"\n+#include \"libcamera/internal/framebuffer.h\" // FIXME drop once softIPA (ab)use is gone\n+#include \"libcamera/internal/ipa_manager.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/software_isp/debayer_params.h\"\n+#include \"libcamera/internal/software_isp/swstats_cpu.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+#include \"camss_frames.h\"\n+#include \"camss_params.h\"\n+#include \"camss_util.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Camss)\n+\n+namespace {\n+\n+static const std::map<PixelFormat, uint32_t> inputFormatToMediaBus = {\n+\t{ formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },\n+\t{ formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },\n+\t{ formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },\n+\t{ formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },\n+\t{ formats::SBGGR10_CSI2P, MEDIA_BUS_FMT_SBGGR10_1X10 },\n+\t{ formats::SGBRG10_CSI2P, MEDIA_BUS_FMT_SGBRG10_1X10 },\n+\t{ formats::SGRBG10_CSI2P, MEDIA_BUS_FMT_SGRBG10_1X10 },\n+\t{ formats::SRGGB10_CSI2P, MEDIA_BUS_FMT_SRGGB10_1X10 },\n+};\n+\n+static const std::map<PixelFormat, uint32_t> outputFormatToMediaBus = {\n+\t{ formats::NV12, MEDIA_BUS_FMT_YUYV8_1_5X8 },\n+\t{ formats::NV21, MEDIA_BUS_FMT_YVYU8_1_5X8 },\n+\t{ formats::NV16, MEDIA_BUS_FMT_YUYV8_2X8 },\n+\t{ formats::NV61, MEDIA_BUS_FMT_YUYV8_2X8 },\n+\t{ formats::NV24, MEDIA_BUS_FMT_YUV8_1X24 },\n+\t{ formats::NV42, MEDIA_BUS_FMT_YUV8_1X24 },\n+\t{ formats::R8, MEDIA_BUS_FMT_Y8_1X8 },\n+};\n+\n+} /* namespace */\n+\n+/**\n+ * \\class CamssIspOpe\n+ * \\brief CAMSS ISP class for the Offline Processing Engine (OPE) ISP\n+ */\n+\n+/**\n+ * \\brief Constructs CamssIspOpe object\n+ * \\param[in] pipe The pipeline handler in use\n+ * \\param[in] opeMediaDev The OPE MediaDevice\n+ * \\param[in] sensor Pointer to the CameraSensor instance owned by the pipeline\n+ * \\param[in] frameInfos Pointer to CamssFrames instance owned by the pipeline\n+ * \\param[out] ControlInfoMap to which to add ISP provided controls\n+ */\n+CamssIspOpe::CamssIspOpe(PipelineHandler *pipe, std::shared_ptr<MediaDevice> opeMediaDev,\n+\t\t\t const CameraSensor *sensor, CamssFrames *frameInfos,\n+\t\t\t ControlInfoMap *ispControls)\n+\t: opeMediaDev_(std::move(opeMediaDev)), sensor_(sensor), frameInfos_(frameInfos)\n+{\n+\tsharedParams_ = SharedMemObject<DebayerParams>(\"softIsp_params\");\n+\tif (!sharedParams_) {\n+\t\tLOG(Camss, Error) << \"Failed to create shared memory for parameters\";\n+\t\treturn;\n+\t}\n+\n+\tconst CameraManager &cm = *pipe->cameraManager();\n+\n+\tstats_ = std::make_unique<SwStatsCpu>(cm);\n+\tif (!stats_->isValid()) {\n+\t\tLOG(Camss, Error) << \"Failed to create SwStatsCpu object\";\n+\t\treturn;\n+\t}\n+\n+\tstats_->statsReady.connect(this,\n+\t\t\t\t   [&](uint32_t frame, uint32_t statsBufferId) {\n+\t\t\t\t\t   statsReady.emit(frame, statsBufferId);\n+\t\t\t\t   });\n+\n+\tipa_ = pipe->createIPA<ipa::soft::IPAProxySoft>(\"simple\", 0, 0);\n+\tif (!ipa_) {\n+\t\tLOG(Camss, Error) << \"Creating IPA failed\";\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * The API tuning file is made from the sensor name. If the tuning file\n+\t * isn't found, fall back to the 'uncalibrated' file.\n+\t */\n+\tstd::string ipaTuningFile =\n+\t\tipa_->configurationFile(sensor->model() + \".yaml\", \"uncalibrated.yaml\");\n+\n+\tIPACameraSensorInfo sensorInfo{};\n+\tint ret = sensor->sensorInfo(&sensorInfo);\n+\tif (ret) {\n+\t\tLOG(Camss, Error) << \"Camera sensor information not available\";\n+\t\tipa_.reset();\n+\t\treturn;\n+\t}\n+\n+\tbool ccmEnabled;\n+\tret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },\n+\t\t\t stats_->getStatsFD(),\n+\t\t\t sharedParams_.fd(),\n+\t\t\t sensorInfo,\n+\t\t\t sensor->controls(),\n+\t\t\t ispControls,\n+\t\t\t &ccmEnabled);\n+\tif (ret) {\n+\t\tLOG(Camss, Error) << \"IPA init failed\";\n+\t\tipa_.reset();\n+\t\treturn;\n+\t}\n+\n+\tipa_->paramsComputed.connect(this, &CamssIspOpe::paramsComputed);\n+\tipa_->metadataReady.connect(this,\n+\t\t\t\t    [&](uint32_t frame, const ControlList &metadata) {\n+\t\t\t\t\t    metadataReady.emit(frame, metadata);\n+\t\t\t\t    });\n+\tipa_->setSensorControls.connect(this,\n+\t\t\t\t\t[&](const ControlList &sensorControls) {\n+\t\t\t\t\t\tsetSensorControls.emit(sensorControls);\n+\t\t\t\t\t});\n+}\n+\n+CamssIspOpe::~CamssIspOpe() = default;\n+\n+bool CamssIspOpe::isValid()\n+{\n+\treturn !!ipa_;\n+}\n+\n+Size CamssIspOpe::getMargins([[maybe_unused]] PixelFormat inputFormat)\n+{\n+\t/* TODO: Do we really not need any margins on OPE? */\n+\treturn Size();\n+}\n+\n+int CamssIspOpe::init()\n+{\n+\tMediaEntity *paramsEnt, *inputEnt, *procEnt, *dispEnt, *dispOutputEnt;\n+\tint ret;\n+\n+\tif (!isValid())\n+\t\treturn -ENOMEM;\n+\n+\tparamsEnt = opeMediaDev_->getEntityByName(\"ope_params\");\n+\tinputEnt = opeMediaDev_->getEntityByName(\"ope_input\");\n+\tprocEnt = opeMediaDev_->getEntityByName(\"ope_proc\");\n+\tdispEnt = opeMediaDev_->getEntityByName(\"ope_disp\");\n+\tdispOutputEnt = opeMediaDev_->getEntityByName(\"ope_disp_output\");\n+\n+\tif (!paramsEnt || !inputEnt || !procEnt || !dispEnt || !dispOutputEnt) {\n+\t\tLOG(Camss, Error) << \"Did not find expected entities\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tparams_ = std::make_unique<V4L2VideoDevice>(paramsEnt);\n+\tret = params_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tparams_->bufferReady.connect(this, &CamssIspOpe::parameterBufferReady);\n+\n+\tinput_ = std::make_unique<V4L2VideoDevice>(inputEnt);\n+\tret = input_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tinput_->bufferReady.connect(this, [&](FrameBuffer *f) {\n+\t\tinputBufferReady.emit(f);\n+\t});\n+\n+\tproc_ = std::make_unique<V4L2Subdevice>(procEnt);\n+\tret = proc_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tdisp_ = std::make_unique<V4L2Subdevice>(dispEnt);\n+\tret = disp_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\toutput_ = std::make_unique<V4L2VideoDevice>(dispOutputEnt);\n+\tret = output_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\toutput_->bufferReady.connect(this, [&](FrameBuffer *f) {\n+\t\toutputBufferReady.emit(f);\n+\t});\n+\n+\treturn 0;\n+}\n+\n+/* Note cfg is const / not modified when update_stride_and_size is false */\n+int CamssIspOpe::trySetVideoCfg(V4L2VideoDevice *v4l2Dev, StreamConfiguration &cfg,\n+\t\t\t\tbool set, bool update_stride_and_size,\n+\t\t\t\tconst char *msgPrefix) const\n+{\n+\tint ret;\n+\n+\tV4L2DeviceFormat v4l2Format;\n+\tv4l2Format.fourcc = v4l2Dev->toV4L2PixelFormat(cfg.pixelFormat);\n+\tv4l2Format.size = cfg.size;\n+\tv4l2Format.planes[0].bpl = cfg.stride;\n+\tv4l2Format.planesCount = 1;\n+\n+\tif (set)\n+\t\tret = v4l2Dev->setFormat(&v4l2Format);\n+\telse\n+\t\tret = v4l2Dev->tryFormat(&v4l2Format);\n+\n+\tif (ret < 0) {\n+\t\tLOG(Camss, Error) << msgPrefix << \" error: \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (update_stride_and_size) {\n+\t\tcfg.stride = v4l2Format.planes[0].bpl;\n+\t\tcfg.frameSize = v4l2Format.planes[0].size;\n+\t}\n+\n+\tif (!camssV4L2DeviceFormatMatchesStreamConfig(v4l2Format, cfg, msgPrefix))\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+StreamConfiguration CamssIspOpe::generateConfiguration(const StreamConfiguration &raw) const\n+{\n+\tif (!inputFormatToMediaBus.count(raw.pixelFormat)) {\n+\t\tLOG(Camss, Warning) << \"Unsupported pixel format \" << raw.pixelFormat;\n+\t\treturn {};\n+\t}\n+\n+\tif (trySetVideoCfg(input_.get(), const_cast<StreamConfiguration &>(raw),\n+\t\t\t   false, false, \"OPE input try format\"))\n+\t\treturn {};\n+\n+\t/* OPE always supports all output formats */\n+\tstd::vector<SizeRange> sizesVector = { SizeRange(kMinOutputSize, raw.size, 2, 2) };\n+\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n+\tfor (auto it = outputFormatToMediaBus.begin(); it != outputFormatToMediaBus.end(); it++)\n+\t\tformats[it->first] = sizesVector;\n+\n+\tStreamConfiguration cfg{ StreamFormats{ formats } };\n+\tcfg.size = raw.size;\n+\tcfg.pixelFormat = formats::NV12; /* Default to NV12 output */\n+\tcfg.bufferCount = kBufferCount;\n+\n+\treturn cfg;\n+}\n+\n+int CamssIspOpe::trySetSubdevFormat(V4L2Subdevice *subdev,\n+\t\t\t\t    unsigned int pad,\n+\t\t\t\t    const V4L2SubdeviceFormat &fmt,\n+\t\t\t\t    V4L2Subdevice::Whence whence,\n+\t\t\t\t    const char *msgPrefix) const\n+{\n+\tV4L2SubdeviceFormat subdevFormat = fmt;\n+\n+\tint ret = subdev->setFormat(pad, &subdevFormat, whence);\n+\tif (ret < 0) {\n+\t\tLOG(Camss, Error) << msgPrefix << \" try/set format error: \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (subdevFormat.size != fmt.size || subdevFormat.code != fmt.code) {\n+\t\tLOG(Camss, Error) << msgPrefix << \" mismatch asked \"\n+\t\t\t\t  << fmt.size << \"/\" << fmt.code << \" got \"\n+\t\t\t\t  << subdevFormat.size << \"/\" << subdevFormat.code;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int CamssIspOpe::trySetSubdevSelection(V4L2Subdevice *subdev,\n+\t\t\t\t       unsigned int pad,\n+\t\t\t\t       unsigned int target,\n+\t\t\t\t       const Rectangle &rect,\n+\t\t\t\t       V4L2Subdevice::Whence whence,\n+\t\t\t\t       const char *msgPrefix) const\n+{\n+\tstruct Rectangle r = rect;\n+\n+\tint ret = subdev->setSelection(pad, target, &r, whence);\n+\tif (ret < 0) {\n+\t\tLOG(Camss, Error) << msgPrefix << \" set selection error: \" << strerror(-ret);\n+\t\treturn ret;\n+\t}\n+\n+\tif (r != rect) {\n+\t\tLOG(Camss, Error) << msgPrefix << \" mismatch asked \"\n+\t\t\t\t  << rect << \" got \" << r;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int CamssIspOpe::trySetPipelineConfig(const StreamConfiguration &inputCfg,\n+\t\t\t\t      [[maybe_unused]] const StreamConfiguration &outputCfg,\n+\t\t\t\t      V4L2Subdevice::Whence whence) const\n+{\n+\tif (!inputFormatToMediaBus.count(inputCfg.pixelFormat)) {\n+\t\tLOG(Camss, Warning) << \"Unsupported pixel format \" << inputCfg.pixelFormat;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tV4L2SubdeviceFormat subdevFormat;\n+\tsubdevFormat.code = inputFormatToMediaBus.at(inputCfg.pixelFormat);\n+\tsubdevFormat.size = inputCfg.size;\n+\n+\tint ret = trySetSubdevFormat(proc_.get(), PROC_PAD_INPUT, subdevFormat,\n+\t\t\t\t     whence, \"OPE proc input\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * No input-crop, downscale instead to keep full field of view.\n+\t * \\todo crop in x or y dimension to match requested output aspect,\n+\t * to keep square pixels after scaling.\n+\t */\n+\tret = trySetSubdevSelection(proc_.get(), PROC_PAD_INPUT, V4L2_SEL_TGT_CROP,\n+\t\t\t\t    Rectangle(inputCfg.size), whence, \"OPE input crop\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = trySetSubdevSelection(proc_.get(), PROC_PAD_DISP, V4L2_SEL_TGT_COMPOSE,\n+\t\t\t\t    Rectangle(outputCfg.size), whence, \"OPE output compose\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tsubdevFormat.code = outputFormatToMediaBus.at(outputCfg.pixelFormat);\n+\tsubdevFormat.size = outputCfg.size;\n+\n+\tret = trySetSubdevFormat(proc_.get(), PROC_PAD_DISP, subdevFormat,\n+\t\t\t\t whence, \"OPE proc disp out\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = trySetSubdevFormat(disp_.get(), DISP_PAD_PROC, subdevFormat,\n+\t\t\t\t whence, \"OPE disp proc in\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = trySetSubdevFormat(disp_.get(), DISP_PAD_OUTPUT, subdevFormat,\n+\t\t\t\t whence, \"OPE disp out\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+StreamConfiguration CamssIspOpe::validate(const StreamConfiguration &raw, const StreamConfiguration &req) const\n+{\n+\tif (trySetVideoCfg(input_.get(), const_cast<StreamConfiguration &>(raw),\n+\t\t\t   false, false, \"OPE input try format\"))\n+\t\treturn {};\n+\n+\tStreamConfiguration cfg;\n+\n+\tfor (auto it = outputFormatToMediaBus.begin(); it != outputFormatToMediaBus.end(); it++) {\n+\t\tif (it->first == req.pixelFormat)\n+\t\t\tcfg.pixelFormat = req.pixelFormat;\n+\t}\n+\n+\tif (!cfg.pixelFormat.isValid())\n+\t\tcfg.pixelFormat = formats::NV12; /* Default to NV12 output */\n+\n+\tif (SizeRange(kMinOutputSize, raw.size, 2, 2).contains(req.size))\n+\t\tcfg.size = req.size;\n+\telse\n+\t\tcfg.size = raw.size;\n+\n+\tint ret = trySetPipelineConfig(raw, cfg, V4L2Subdevice::TryFormat);\n+\tif (ret < 0)\n+\t\treturn {};\n+\n+\tret = trySetVideoCfg(output_.get(), cfg, false, true, \"OPE output try format\");\n+\tif (ret < 0)\n+\t\treturn {};\n+\n+\tcfg.bufferCount = std::max(kBufferCount, req.bufferCount);\n+\tcfg.setStream(const_cast<Stream *>(&outStream_));\n+\n+\treturn cfg;\n+}\n+\n+int CamssIspOpe::configure(const StreamConfiguration &inputCfg,\n+\t\t\t   const StreamConfiguration &outputCfg)\n+{\n+\tipa::soft::IPAConfigInfo configInfo;\n+\tconfigInfo.sensorControls = sensor_->controls();\n+\n+\tint ret = ipa_->configure(configInfo);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = stats_->configure(inputCfg);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\t/* Use 2/3 center of image to reduce CPU load */\n+\tRectangle statsWindow;\n+\tstatsWindow.width = inputCfg.size.width * 2 / 3;\n+\tstatsWindow.height = inputCfg.size.height * 2 / 3;\n+\tstatsWindow.x = (inputCfg.size.width - statsWindow.width) / 2;\n+\tstatsWindow.y = (inputCfg.size.height - statsWindow.height) / 2;\n+\t/* stats_->setWindow() takes care of necessary alignment itself */\n+\tstats_->setWindow(statsWindow);\n+\n+\tret = trySetVideoCfg(input_.get(), const_cast<StreamConfiguration &>(inputCfg),\n+\t\t\t     true, false, \"OPE input set format\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = trySetPipelineConfig(inputCfg, outputCfg, V4L2Subdevice::ActiveFormat);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = trySetVideoCfg(output_.get(), const_cast<StreamConfiguration &>(outputCfg),\n+\t\t\t     true, false, \"OPE output set format\");\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tinputBufferCount_ = inputCfg.bufferCount;\n+\toutputBufferCount_ = outputCfg.bufferCount;\n+\n+\treturn 0;\n+}\n+\n+int CamssIspOpe::allocateBuffers(unsigned int bufferCount)\n+{\n+\tint ret;\n+\n+\tret = input_->importBuffers(inputBufferCount_);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = output_->importBuffers(outputBufferCount_);\n+\tif (ret < 0) {\n+\t\tfreeBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\tret = params_->allocateBuffers(bufferCount, &paramBuffers_);\n+\tif (ret < 0) {\n+\t\tfreeBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\t/* Map buffers to the IPA. */\n+\tunsigned int ipaBufferId = 1;\n+\n+\t/* Lambda function for future re-use with statsBuffers. */\n+\tauto pushBuffers = [&](const std::vector<std::unique_ptr<FrameBuffer>> &buffers) {\n+\t\tfor (const std::unique_ptr<FrameBuffer> &buffer : buffers) {\n+\t\t\tSpan<const FrameBuffer::Plane> planes = buffer->planes();\n+\n+\t\t\tbuffer->setCookie(ipaBufferId++);\n+\t\t\tipaBuffers_.emplace_back(buffer->cookie(),\n+\t\t\t\t\t\t std::vector<FrameBuffer::Plane>{ planes.begin(),\n+\t\t\t\t\t\t\t\t\t\t  planes.end() });\n+\t\t}\n+\t};\n+\n+\tpushBuffers(paramBuffers_);\n+\n+\t/* FIXME drop once using own camss IPA, which will map on IPA side */\n+\tfor (const IPABuffer &buffer : ipaBuffers_) {\n+\t\tconst FrameBuffer fb(buffer.planes);\n+\t\tmappedIpaBuffers_.emplace(buffer.id,\n+\t\t\t\t\t  MappedFrameBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite));\n+\t}\n+\n+\tframeInfos_->init(paramBuffers_, {}, true);\n+\treturn 0;\n+}\n+\n+void CamssIspOpe::freeBuffers()\n+{\n+\tframeInfos_->clear();\n+\n+\t/* FIXME drop once using own camss IPA, which will map on IPA side */\n+\tmappedIpaBuffers_.clear();\n+\n+\tipaBuffers_.clear();\n+\tparamBuffers_.clear();\n+\tparams_->releaseBuffers();\n+\toutput_->releaseBuffers();\n+\tinput_->releaseBuffers();\n+}\n+\n+int CamssIspOpe::exportOutputBuffers([[maybe_unused]] const Stream *stream, unsigned int count,\n+\t\t\t\t     std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\treturn output_->exportBuffers(count, buffers);\n+}\n+\n+void CamssIspOpe::queueBuffers(Request *request, FrameBuffer *inputBuffer)\n+{\n+\tuint32_t frame = request->sequence();\n+\n+\tDmaSyncer inputDmaSync(inputBuffer->planes()[0].fd, DmaSyncer::SyncType::Read);\n+\n+\tMappedFrameBuffer input(inputBuffer, MappedFrameBuffer::MapFlag::Read);\n+\tif (!input.isValid()) {\n+\t\tLOG(Camss, Error) << \"mmap-ing buffer(s) failed\";\n+\t\treturn;\n+\t}\n+\n+\tipa_->queueRequest(frame, request->controls());\n+\tstats_->processFrame(frame, 0, input);\n+\tipa_->computeParams(frame);\n+\t/* paramsComputed() will queue all the buffers once the params are known. */\n+}\n+\n+void CamssIspOpe::paramsComputed(uint32_t frame)\n+{\n+\tCamssFrames::Info *info = frameInfos_->find(frame);\n+\tif (!info)\n+\t\treturn;\n+\n+\tFrameBuffer *outputBuffer = nullptr;\n+\tfor (const auto &[stream, buffer] : info->request->buffers()) {\n+\t\tif (stream == &outStream_)\n+\t\t\toutputBuffer = buffer;\n+\t}\n+\tif (!outputBuffer) {\n+\t\tLOG(Camss, Error) << \"Missing output buffer\";\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * FIXME drop once using own camss IPA, which will do this on IPA side.\n+\t * IPA paramsComputed signal should also pass a bytesused value back.\n+\t */\n+\tuint32_t bufferId = info->paramBuffer->cookie();\n+\tipa::camss::CamssParams params(mappedIpaBuffers_.at(bufferId).planes()[0]);\n+\tauto awbGains = params.block<ipa::camss::CamssBlocks::AwbGains>();\n+\tDebayerParams debayerParams = *sharedParams_;\n+\t/* Convert gains to 15uQ10 and store */\n+\tawbGains->r_gain = std::clamp(debayerParams.gains.r(), 0.0f, 31.0f) * 1024;\n+\tawbGains->g_gain = std::clamp(debayerParams.gains.g(), 0.0f, 31.0f) * 1024;\n+\tawbGains->b_gain = std::clamp(debayerParams.gains.b(), 0.0f, 31.0f) * 1024;\n+\t/* Convert blacklevel to 0-65535 (u16) and store */\n+\tawbGains->r_sub = debayerParams.blackLevel.r() * 65535;\n+\tawbGains->g_sub = debayerParams.blackLevel.g() * 65535;\n+\tawbGains->b_sub = debayerParams.blackLevel.b() * 65535;\n+\t/* Unused */\n+\tawbGains->r_add = 0;\n+\tawbGains->g_add = 0;\n+\tawbGains->b_add = 0;\n+\n+\tinfo->paramBuffer->_d()->metadata().planes()[0].bytesused = params.bytesused();\n+\tparams_->queueBuffer(info->paramBuffer);\n+\toutput_->queueBuffer(outputBuffer);\n+\tinput_->queueBuffer(info->rawBuffer);\n+}\n+\n+void CamssIspOpe::parameterBufferReady(FrameBuffer *f)\n+{\n+\tCamssFrames::Info *info = frameInfos_->find(f);\n+\tif (!info)\n+\t\treturn;\n+\n+\tinfo->paramDequeued = true;\n+\n+\tparamBufferReady.emit(f);\n+}\n+\n+void CamssIspOpe::processStats(const uint32_t frame, const uint32_t statsBufferId,\n+\t\t\t       const ControlList &sensorControls)\n+{\n+\tipa_->processStats(frame, statsBufferId, sensorControls);\n+}\n+\n+int CamssIspOpe::start()\n+{\n+\tint ret = ipa_->start();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = input_->streamOn();\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\tret = output_->streamOn();\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\tret = params_->streamOn();\n+\tif (ret < 0) {\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void CamssIspOpe::stop()\n+{\n+\tparams_->streamOff();\n+\toutput_->streamOff();\n+\tinput_->streamOff();\n+\tipa_->stop();\n+}\n+\n+/**\n+ * \\brief Match media devices and create OPE instance if found\n+ * \\param[in] pipe The pipeline handler in use\n+ * \\param[in] sensor Pointer to the CameraSensor instance owned by the pipeline\n+ * \\param[in] frameInfos Pointer to CamssFrames instance owned by the pipeline\n+ * \\param[out] ControlInfoMap to which to add ISP provided controls\n+ */\n+std::unique_ptr<CamssIspOpe> CamssIspOpe::match(PipelineHandler *pipe,\n+\t\t\t\t\t\tDeviceEnumerator *enumerator,\n+\t\t\t\t\t\tconst CameraSensor *sensor,\n+\t\t\t\t\t\tCamssFrames *frameInfos,\n+\t\t\t\t\t\tControlInfoMap *ispControls)\n+{\n+\tstd::shared_ptr<MediaDevice> opeMediaDev;\n+\tDeviceMatch opeDm(\"qcom-camss-ope\");\n+\n+\topeDm.add(\"ope_params\");\n+\topeDm.add(\"ope_input\");\n+\topeDm.add(\"ope_proc\");\n+\topeDm.add(\"ope_disp\");\n+\topeDm.add(\"ope_disp_output\");\n+\n+\topeMediaDev = pipe->acquireMediaDevice(enumerator, opeDm);\n+\tif (!opeMediaDev) {\n+\t\tLOG(Camss, Info) << \"No OPE match for \" << sensor->entity()->name();\n+\t\treturn nullptr;\n+\t}\n+\n+\tstd::unique_ptr<CamssIspOpe> ope =\n+\t\tstd::make_unique<CamssIspOpe>(pipe, std::move(opeMediaDev), sensor,\n+\t\t\t\t\t      frameInfos, ispControls);\n+\n+\tif (ope->init() != 0)\n+\t\treturn nullptr;\n+\n+\tLOG(Camss, Info) << \"Using OPE for \" << sensor->entity()->name();\n+\n+\treturn ope;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_isp_ope.h b/src/libcamera/pipeline/camss/camss_isp_ope.h\nnew file mode 100644\nindex 000000000..8909f9bac\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp_ope.h\n@@ -0,0 +1,120 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS OPE ISP class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <vector>\n+\n+#include <libcamera/ipa/soft_ipa_interface.h>\n+#include <libcamera/ipa/soft_ipa_proxy.h>\n+\n+/* FIXME drop once using own camss IPA, which will map on IPA side */\n+#include \"libcamera/internal/mapped_framebuffer.h\"\n+#include \"libcamera/internal/shared_mem_object.h\"\n+#include \"libcamera/internal/software_isp/debayer_params.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+\n+#include \"camss_isp.h\"\n+\n+namespace libcamera {\n+\n+class CameraSensor;\n+class CamssFrames;\n+class ControlInfoMap;\n+class DeviceEnumerator;\n+class MediaDevice;\n+class PipelineHandler;\n+class Stream;\n+class SwStatsCpu;\n+class V4L2VideoDevice;\n+\n+class CamssIspOpe : public CamssIsp\n+{\n+public:\n+\tstatic std::unique_ptr<CamssIspOpe> match(PipelineHandler *pipe,\n+\t\t\t\t\t\t  DeviceEnumerator *enumerator,\n+\t\t\t\t\t\t  const CameraSensor *sensor,\n+\t\t\t\t\t\t  CamssFrames *frameInfos,\n+\t\t\t\t\t\t  ControlInfoMap *ispControls);\n+\n+\tbool isValid() override;\n+\tSize getMargins(PixelFormat inputFormat) override;\n+\n+\tCamssIspOpe(PipelineHandler *pipe, std::shared_ptr<MediaDevice> opeMediaDev,\n+\t\t    const CameraSensor *sensor, CamssFrames *frameInfos,\n+\t\t    ControlInfoMap *ispControls);\n+\t~CamssIspOpe() override;\n+\n+\tStreamConfiguration generateConfiguration(const StreamConfiguration &raw) const override;\n+\tStreamConfiguration validate(const StreamConfiguration &raw, const StreamConfiguration &req) const override;\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t      const StreamConfiguration &outputCfg) override;\n+\n+\tint allocateBuffers(unsigned int bufferCount) override;\n+\tvoid freeBuffers() override;\n+\tint exportOutputBuffers(const Stream *stream, unsigned int count,\n+\t\t\t\tstd::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\tvoid queueBuffers(Request *request, FrameBuffer *input) override;\n+\n+\tvoid processStats(const uint32_t frame, const uint32_t statsBufferId,\n+\t\t\t  const ControlList &sensorControls) override;\n+\n+\tvoid paramsComputed(uint32_t frame);\n+\tvoid parameterBufferReady(FrameBuffer *f);\n+\n+\tint start() override;\n+\tvoid stop() override;\n+\n+private:\n+\tstatic constexpr Size kMinOutputSize = Size(24, 16);\n+\tstatic constexpr unsigned int PROC_PAD_INPUT = 0;\n+\tstatic constexpr unsigned int PROC_PAD_PARAMS = 1;\n+\tstatic constexpr unsigned int PROC_PAD_DISP = 2;\n+\tstatic constexpr unsigned int DISP_PAD_PROC = 0;\n+\tstatic constexpr unsigned int DISP_PAD_OUTPUT = 1;\n+\n+\tint init();\n+\tint trySetVideoCfg(V4L2VideoDevice *v4l2Dev, StreamConfiguration &cfg,\n+\t\t\t   bool set, bool update_stride_and_size,\n+\t\t\t   const char *msgPrefix) const;\n+\tint trySetSubdevFormat(V4L2Subdevice *subdev,\n+\t\t\t       unsigned int pad,\n+\t\t\t       const V4L2SubdeviceFormat &fmt,\n+\t\t\t       V4L2Subdevice::Whence whence,\n+\t\t\t       const char *msgPrefix) const;\n+\tint trySetSubdevSelection(V4L2Subdevice *subdev,\n+\t\t\t\t  unsigned int pad,\n+\t\t\t\t  unsigned int target,\n+\t\t\t\t  const Rectangle &rect,\n+\t\t\t\t  V4L2Subdevice::Whence whence,\n+\t\t\t\t  const char *msgPrefix) const;\n+\tint trySetPipelineConfig(const StreamConfiguration &inputCfg,\n+\t\t\t\t const StreamConfiguration &outputCfg,\n+\t\t\t\t V4L2Subdevice::Whence whence) const;\n+\n+\tstd::shared_ptr<MediaDevice> opeMediaDev_;\n+\tconst CameraSensor *sensor_;\n+\tconst Stream *rawStream_;\n+\tclass CamssFrames *frameInfos_;\n+\tSharedMemObject<DebayerParams> sharedParams_;\n+\tstd::unique_ptr<SwStatsCpu> stats_;\n+\tstd::unique_ptr<V4L2Subdevice> proc_;\n+\tstd::unique_ptr<V4L2Subdevice> disp_;\n+\tstd::unique_ptr<V4L2VideoDevice> params_;\n+\tstd::unique_ptr<V4L2VideoDevice> input_;\n+\tstd::unique_ptr<V4L2VideoDevice> output_;\n+\tstd::unique_ptr<ipa::soft::IPAProxySoft> ipa_;\n+\tstd::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;\n+\tstd::vector<IPABuffer> ipaBuffers_;\n+\t/* FIXME drop once using own camss IPA, which will map on IPA side */\n+\tstd::map<unsigned int, MappedFrameBuffer> mappedIpaBuffers_;\n+\tunsigned int inputBufferCount_;\n+\tunsigned int outputBufferCount_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_params.h b/src/libcamera/pipeline/camss/camss_params.h\nindex 6dec85fbc..c09d2c4c2 100644\n--- a/src/libcamera/pipeline/camss/camss_params.h\n+++ b/src/libcamera/pipeline/camss/camss_params.h\n@@ -20,7 +20,7 @@ namespace libcamera {\n \n namespace ipa::camss {\n \n-enum class CamssBlocks {\n+enum class CamssBlocks : uint16_t {\n \tAwbGains,\n \tChromaEnh,\n \tColorCorrect,\ndiff --git a/src/libcamera/pipeline/camss/meson.build b/src/libcamera/pipeline/camss/meson.build\nindex 2f3931dd1..cffb53ef5 100644\n--- a/src/libcamera/pipeline/camss/meson.build\n+++ b/src/libcamera/pipeline/camss/meson.build\n@@ -5,6 +5,9 @@ libcamera_internal_sources += files([\n     'camss_csi.cpp',\n     'camss_frames.cpp',\n     'camss_isp.cpp',\n+    'camss_isp_ope.cpp',\n     'camss_isp_soft.cpp',\n     'camss_util.cpp',\n+    # FIXME hack\n+    '../../../ipa/libipa/v4l2_params.cpp'\n ])\n","prefixes":["v2","7/7"]}