{"id":24306,"url":"https://patchwork.libcamera.org/api/patches/24306/?format=json","web_url":"https://patchwork.libcamera.org/patch/24306/","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":"<20250911090547.1860863-3-niklas.soderlund+renesas@ragnatech.se>","date":"2025-09-11T09:05:47","name":"[v2,2/2] libcamera: pipeline: Add R-Car Gen4 ISP pipeline","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"7c21e42357a60e670e7d772ef4c5cfb53a647bb4","submitter":{"id":230,"url":"https://patchwork.libcamera.org/api/people/230/?format=json","name":"Niklas Söderlund","email":"niklas.soderlund+renesas@ragnatech.se"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/24306/mbox/","series":[{"id":5430,"url":"https://patchwork.libcamera.org/api/series/5430/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5430","date":"2025-09-11T09:05:45","name":"Add Renesas R-Car Gen4 pipeline with IPA support","version":2,"mbox":"https://patchwork.libcamera.org/series/5430/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/24306/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/24306/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 16BA1BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 11 Sep 2025 09:06:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A30A96936F;\n\tThu, 11 Sep 2025 11:06:45 +0200 (CEST)","from fhigh-b3-smtp.messagingengine.com\n\t(fhigh-b3-smtp.messagingengine.com [202.12.124.154])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2313569367\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 11 Sep 2025 11:06:40 +0200 (CEST)","from phl-compute-06.internal (phl-compute-06.internal\n\t[10.202.2.46])\n\tby mailfhigh.stl.internal (Postfix) with ESMTP id 094FD7A0084;\n\tThu, 11 Sep 2025 05:06:39 -0400 (EDT)","from phl-mailfrontend-02 ([10.202.2.163])\n\tby phl-compute-06.internal (MEProxy); Thu, 11 Sep 2025 05:06:39 -0400","by mail.messagingengine.com (Postfix) with ESMTPA; Thu,\n\t11 Sep 2025 05:06:37 -0400 (EDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=ragnatech.se header.i=@ragnatech.se\n\theader.b=\"QqnIOx0V\"; dkim=pass (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"P7FtWy2H\"; \n\tdkim-atps=neutral","DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/relaxed; d=ragnatech.se; h=\n\tcc:cc:content-transfer-encoding:content-type:content-type:date\n\t:date:from:from:in-reply-to:in-reply-to:message-id:mime-version\n\t:references:reply-to:subject:subject:to:to; s=fm3; t=1757581598;\n\tx=1757667998; bh=Jnu2Z22fs4fwa5o7KxBnWD+w6kcF3m78ye34K3rGdaQ=; b=\n\tQqnIOx0VAV9N0BON5Nc++gR8mnP2gwkLg0jnkyoeTyOENnahf/uoghVwW6zcsrhP\n\tVDaMSDeee+Ka2JYAG1s07g71U67GdTyCYRmlR1+XV8GZmQsv+rBk2UolK5lucTcm\n\tBOF0ijJS9wUGZlbXtYBZn+4mn8XDHq1dr+Vz92u2NSSYqqChcZEm/MgnUh5L4BK2\n\tIWDStkKWUyn2cIlKdVKK/4RnJ8SbdihV1GfIIQysCQD9tqiLsCQNFSTwzDVjbTAq\n\ty51XZobNOm2rLpDgPtlDo3ejchyNRKEh9OG72+6qMgkXCBgO4jOQp508Orp4zoMu\n\tVpuVXCGeZkEoZO5tLmnNyA==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=\n\tmessagingengine.com; h=cc:cc:content-transfer-encoding\n\t:content-type:content-type:date:date:feedback-id:feedback-id\n\t:from:from:in-reply-to:in-reply-to:message-id:mime-version\n\t:references:reply-to:subject:subject:to:to:x-me-proxy\n\t:x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t=1757581598; x=\n\t1757667998; bh=Jnu2Z22fs4fwa5o7KxBnWD+w6kcF3m78ye34K3rGdaQ=; b=P\n\t7FtWy2HJkkbgkkf/V64tMoId/6Yn+OLFAzdzJUcGysYaY6eMClDnf6PmJJZ5x+7f\n\tNQ8TaRRfqVvNHrwjQbejQht/Fr3d4I7Dt1bm3myFV5k3q9U1zjlBwzv8m2/33+16\n\tpT0qnLhzGl/FIWq/iZ2D+ZqK0m5xong7IOD6SvYlFVdaigpnnQBcTzZXFAUU6tW1\n\tYwbnBaq/qQl7NEW073UEaaeuOcWrxXTyp8CjW8cjNhk3J+B+loADoGzuX5WjJQsx\n\tab4C8aEgRznDQwn9YubOo9jSA2nYiDA8NvrxWd/Is1II/D4xsHbth9xpZJfUytBS\n\tGOAoMAIIbNew2Oogrmf2w=="],"X-ME-Sender":"<xms:HpHCaBLPFJhX3SYnrMH1gHRIFhrBnAeRma8YJ6_vzT2WPypXboXtpQ>\n\t<xme:HpHCaHtkaGHFdFhhuHtXqOn7bfVWS0A1tQLV-BO9HY-yoKxNnELzRCP7qjT6tCLrP\n\twdxc__9FRJCcbATKfk>","X-ME-Received":"<xmr:HpHCaGRYPWgGtiD8938k2vmBM1mJJ_RtI0ZSpj20R-3L7pqDb2kN16hDYL7fXab_7pbEcll_9clM8k9lsLEAhqg_Vw>","X-ME-Proxy-Cause":"gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddvheejjecutefuodetggdotefrod\n\tftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr\n\tihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenucfjug\n\thrpefhvfevufffkffojghfgggtgfesthekredtredtjeenucfhrhhomheppfhikhhlrghs\n\tucfunpguvghrlhhunhguuceonhhikhhlrghsrdhsohguvghrlhhunhguodhrvghnvghsrg\n\thssehrrghgnhgrthgvtghhrdhsvgeqnecuggftrfgrthhtvghrnhepheeigfeuveeutdef\n\thfehgeekvedtleeuueekveefudehhffhjeffgfegffelfeegnecuvehluhhsthgvrhfuih\n\tiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhepnhhikhhlrghsrdhsohguvghrlhhu\n\tnhgusehrrghgnhgrthgvtghhrdhsvgdpnhgspghrtghpthhtohepgedpmhhouggvpehsmh\n\thtphhouhhtpdhrtghpthhtohepjhgrtghophhordhmohhnughisehiuggvrghsohhnsgho\n\trghrugdrtghomhdprhgtphhtthhopehlrghurhgvnhhtrdhpihhntghhrghrthesihguvg\n\tgrshhonhgsohgrrhgurdgtohhmpdhrtghpthhtoheplhhisggtrghmvghrrgdquggvvhgv\n\tlheslhhishhtshdrlhhisggtrghmvghrrgdrohhrghdprhgtphhtthhopehnihhklhgrsh\n\tdrshhouggvrhhluhhnugdorhgvnhgvshgrshesrhgrghhnrghtvggthhdrshgv","X-ME-Proxy":"<xmx:HpHCaFOw-WCf57FfX6aBb15GSRb8tWs7hE6-uO7RIxbI6u4wEh964Q>\n\t<xmx:HpHCaDbz1HPqEgqEm6g5obqNQom5gvBrwsaFCPA49mPkKDJkCV6qBg>\n\t<xmx:HpHCaHwl4TXztTH_4L9QuOAWA8_CReG-14GM9XcXQoScrsTenpH5IA>\n\t<xmx:HpHCaGJEB2ry4hE8IbnMJQ0l4Bd2WneDKxbjJJZvwN1uFZowc_llaw>\n\t<xmx:HpHCaP44TldpDz-y6DfePfKWTfmGvk91MBCzEUAMfneNf55Z_pRZS8rG>","Feedback-ID":"i80c9496c:Fastmail","From":"=?utf-8?q?Niklas_S=C3=B6derlund?=\n\t<niklas.soderlund+renesas@ragnatech.se>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Cc":"=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund+renesas@ragnatech.se>","Subject":"[PATCH v2 2/2] libcamera: pipeline: Add R-Car Gen4 ISP pipeline","Date":"Thu, 11 Sep 2025 11:05:47 +0200","Message-ID":"<20250911090547.1860863-3-niklas.soderlund+renesas@ragnatech.se>","X-Mailer":"git-send-email 2.51.0","In-Reply-To":"<20250911090547.1860863-1-niklas.soderlund+renesas@ragnatech.se>","References":"<20250911090547.1860863-1-niklas.soderlund+renesas@ragnatech.se>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","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":"Add a pipeline handler for R-Car Gen4. The pipeline makes use of the\nRkISP1 IPA and, depending on the kernel R-Car ISP driver, support the\nsame features as the RkISP1 pipeline handler.\n\nThe pipeline and IPA is tested with the AWB measurement, Histogram\nMeasurement, Auto Exposure Measurement, AWB gain, Black Level\nCorrection, Color Correction Matrix, Lens Shade Correction and Gamma\nCorrection algorithms and produce a stable and good image.\n\nTested on R-Car V4H Sparrow-Hawk together with IMX219 and IMX462\nsensors.\n\nSigned-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>\n---\n* Changes since v1\n- Fix camera names. Was 'imx462 2-001a', is now '/base/soc/i2c@e6510000/cam@1a'.\n- Fix a compiler issue with some versions of gcc.\n- Add dependency on rkisp1.mojom.\n- Rebase on latest master branch which requires reworking some\n  interfaces.\n---\n include/libcamera/ipa/meson.build             |   1 +\n meson.build                                   |   1 +\n meson_options.txt                             |   1 +\n src/libcamera/pipeline/rcar-gen4/frames.cpp   | 280 ++++++\n src/libcamera/pipeline/rcar-gen4/frames.h     |  85 ++\n src/libcamera/pipeline/rcar-gen4/isp.cpp      | 227 +++++\n src/libcamera/pipeline/rcar-gen4/isp.h        |  44 +\n src/libcamera/pipeline/rcar-gen4/meson.build  |   8 +\n .../pipeline/rcar-gen4/rcar-gen4.cpp          | 814 ++++++++++++++++++\n src/libcamera/pipeline/rcar-gen4/vin.cpp      | 391 +++++++++\n src/libcamera/pipeline/rcar-gen4/vin.h        |  66 ++\n 11 files changed, 1918 insertions(+)\n create mode 100644 src/libcamera/pipeline/rcar-gen4/frames.cpp\n create mode 100644 src/libcamera/pipeline/rcar-gen4/frames.h\n create mode 100644 src/libcamera/pipeline/rcar-gen4/isp.cpp\n create mode 100644 src/libcamera/pipeline/rcar-gen4/isp.h\n create mode 100644 src/libcamera/pipeline/rcar-gen4/meson.build\n create mode 100644 src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp\n create mode 100644 src/libcamera/pipeline/rcar-gen4/vin.cpp\n create mode 100644 src/libcamera/pipeline/rcar-gen4/vin.h","diff":"diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build\nindex 3ee3ada303c0..48eb19b2f0f6 100644\n--- a/include/libcamera/ipa/meson.build\n+++ b/include/libcamera/ipa/meson.build\n@@ -66,6 +66,7 @@ pipeline_ipa_mojom_mapping = {\n     'ipu3': 'ipu3.mojom',\n     'mali-c55': 'mali-c55.mojom',\n     'rkisp1': 'rkisp1.mojom',\n+    'rcar-gen4': 'rkisp1.mojom',\n     'rpi/pisp': 'raspberrypi.mojom',\n     'rpi/vc4': 'raspberrypi.mojom',\n     'simple': 'soft.mojom',\ndiff --git a/meson.build b/meson.build\nindex fd508fd7f6b5..054e209e8b92 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -218,6 +218,7 @@ pipelines_support = {\n     'imx8-isi':     arch_arm,\n     'ipu3':         arch_x86,\n     'mali-c55':     arch_arm,\n+    'rcar-gen4':    arch_arm,\n     'rkisp1':       arch_arm,\n     'rpi/pisp':     arch_arm,\n     'rpi/vc4':      arch_arm,\ndiff --git a/meson_options.txt b/meson_options.txt\nindex 2104469e3793..eba9458487ff 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -51,6 +51,7 @@ option('pipelines',\n             'imx8-isi',\n             'ipu3',\n             'mali-c55',\n+            'rcar-gen4',\n             'rkisp1',\n             'rpi/pisp',\n             'rpi/vc4',\ndiff --git a/src/libcamera/pipeline/rcar-gen4/frames.cpp b/src/libcamera/pipeline/rcar-gen4/frames.cpp\nnew file mode 100644\nindex 000000000000..c604924b7127\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/frames.cpp\n@@ -0,0 +1,280 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 VIN pipeline\n+ */\n+\n+#include \"frames.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/framebuffer.h>\n+#include <libcamera/request.h>\n+\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(RCar4)\n+\n+int RCar4Frames::start(class RCarISPDevice *isp, class ipa::rkisp1::IPAProxyRkISP1 *ipa)\n+{\n+\tunsigned int ipaBufferId = 1;\n+\tunsigned int bufferCount;\n+\tint ret;\n+\n+\tauto pushBuffers = [&](const std::vector<std::unique_ptr<FrameBuffer>> &buffers,\n+\t\t\t       std::queue<FrameBuffer *> &queue) {\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\tqueue.push(buffer.get());\n+\t\t}\n+\t};\n+\n+\tframeInfo_.clear();\n+\n+\tbufferCount = std::max({\n+\t\trawStream_.configuration().bufferCount,\n+\t\toutputStream_.configuration().bufferCount,\n+\t});\n+\n+\tret = isp->input_->exportBuffers(bufferCount, &inputBuffers_);\n+\tif (ret < 0) {\n+\t\tLOG(RCar4, Error) << \"Failed to allocate ISP input buffers\";\n+\t\tgoto error;\n+\t}\n+\n+\tret = isp->param_->allocateBuffers(bufferCount, &paramBuffers_);\n+\tif (ret < 0) {\n+\t\tLOG(RCar4, Error) << \"Failed to allocate ISP param buffers\";\n+\t\tgoto error;\n+\t}\n+\n+\tret = isp->stat_->allocateBuffers(bufferCount, &statBuffers_);\n+\tif (ret < 0) {\n+\t\tLOG(RCar4, Error) << \"Failed to allocate ISP stat buffers\";\n+\t\tgoto error;\n+\t}\n+\n+\tret = isp->output_->exportBuffers(bufferCount, &outputBuffers_);\n+\tif (ret < 0) {\n+\t\tLOG(RCar4, Error) << \"Failed to allocate ISP output buffers\";\n+\t\tgoto error;\n+\t}\n+\n+\tfor (const std::unique_ptr<FrameBuffer> &buffer : inputBuffers_)\n+\t\tavailableInputBuffers_.push(buffer.get());\n+\n+\tpushBuffers(paramBuffers_, availableParamBuffers_);\n+\tpushBuffers(statBuffers_, availableStatBuffers_);\n+\n+\tfor (const std::unique_ptr<FrameBuffer> &buffer : outputBuffers_)\n+\t\tavailableOutputBuffers_.push(buffer.get());\n+\n+\tipa->mapBuffers(ipaBuffers_);\n+\n+\treturn 0;\n+error:\n+\tstop(isp, ipa);\n+\treturn ret;\n+}\n+\n+void RCar4Frames::stop(class RCarISPDevice *isp, class ipa::rkisp1::IPAProxyRkISP1 *ipa)\n+{\n+\tstd::vector<unsigned int> ids;\n+\n+\tavailableInputBuffers_ = {};\n+\tavailableParamBuffers_ = {};\n+\tavailableStatBuffers_ = {};\n+\tavailableOutputBuffers_ = {};\n+\n+\toutputBuffers_.clear();\n+\tstatBuffers_.clear();\n+\tparamBuffers_.clear();\n+\tinputBuffers_.clear();\n+\n+\tfor (IPABuffer &ipabuf : ipaBuffers_)\n+\t\tids.push_back(ipabuf.id);\n+\n+\tipa->unmapBuffers(ids);\n+\tipaBuffers_.clear();\n+\n+\tif (isp->output_->releaseBuffers())\n+\t\tLOG(RCar4, Error) << \"Failed to release ISP output buffers\";\n+\n+\tif (isp->stat_->releaseBuffers())\n+\t\tLOG(RCar4, Error) << \"Failed to release ISP stat buffers\";\n+\n+\tif (isp->param_->releaseBuffers())\n+\t\tLOG(RCar4, Error) << \"Failed to release ISP param buffers\";\n+\n+\tif (isp->input_->releaseBuffers())\n+\t\tLOG(RCar4, Error) << \"Failed to release ISP input buffers\";\n+}\n+\n+RCar4Frames::Info *RCar4Frames::create(Request *request)\n+{\n+\tunsigned int frame = request->sequence();\n+\n+\t/* Try to get input and output buffers from request. */\n+\tFrameBuffer *inputBuffer = request->findBuffer(&rawStream_);\n+\tFrameBuffer *outputBuffer = request->findBuffer(&outputStream_);\n+\n+\t/* Make sure we have enough internal buffers. */\n+\tif (!inputBuffer && availableInputBuffers_.empty()) {\n+\t\tLOG(RCar4, Debug) << \"Input buffer underrun\";\n+\t\treturn nullptr;\n+\t}\n+\n+\tif (availableParamBuffers_.empty()) {\n+\t\tLOG(RCar4, Debug) << \"Parameters buffer underrun\";\n+\t\treturn nullptr;\n+\t}\n+\n+\tif (availableStatBuffers_.empty()) {\n+\t\tLOG(RCar4, Debug) << \"Statistics buffer underrun\";\n+\t\treturn nullptr;\n+\t}\n+\n+\tif (!outputBuffer && availableOutputBuffers_.empty()) {\n+\t\tLOG(RCar4, Debug) << \"Output buffer underrun\";\n+\t\treturn nullptr;\n+\t}\n+\n+\t/* Select buffers to use. */\n+\tif (!inputBuffer) {\n+\t\tinputBuffer = availableInputBuffers_.front();\n+\t\tavailableInputBuffers_.pop();\n+\t\tinputBuffer->_d()->setRequest(request);\n+\t}\n+\n+\tFrameBuffer *paramBuffer = availableParamBuffers_.front();\n+\tavailableParamBuffers_.pop();\n+\tparamBuffer->_d()->setRequest(request);\n+\n+\tFrameBuffer *statBuffer = availableStatBuffers_.front();\n+\tavailableStatBuffers_.pop();\n+\tstatBuffer->_d()->setRequest(request);\n+\n+\tif (!outputBuffer) {\n+\t\toutputBuffer = availableOutputBuffers_.front();\n+\t\tavailableOutputBuffers_.pop();\n+\t\toutputBuffer->_d()->setRequest(request);\n+\t}\n+\n+\t/* Recored the info needed to process one frame. */\n+\tauto [it, inserted] = frameInfo_.try_emplace(frame);\n+\tif (!inserted)\n+\t\treturn nullptr;\n+\n+\tauto &info = it->second;\n+\n+\tinfo.frame = frame;\n+\tinfo.request = request;\n+\tinfo.inputBuffer = inputBuffer;\n+\tinfo.paramBuffer = paramBuffer;\n+\tinfo.statBuffer = statBuffer;\n+\tinfo.outputBuffer = outputBuffer;\n+\tinfo.rawDequeued = false;\n+\tinfo.paramDequeued = false;\n+\tinfo.metadataProcessed = false;\n+\tinfo.outputDequeued = false;\n+\n+\treturn &info;\n+}\n+\n+void RCar4Frames::remove(RCar4Frames::Info *info)\n+{\n+\t/* If internal input buffer used, return for reuse. */\n+\tfor (const std::unique_ptr<FrameBuffer> &buf : inputBuffers_) {\n+\t\tif (info->inputBuffer == buf.get()) {\n+\t\t\tavailableInputBuffers_.push(info->inputBuffer);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* Return param and stat buffer for reuse. */\n+\tavailableParamBuffers_.push(info->paramBuffer);\n+\tavailableStatBuffers_.push(info->statBuffer);\n+\n+\t/* If internal output buffer used, return for reuse. */\n+\tfor (const std::unique_ptr<FrameBuffer> &buf : outputBuffers_) {\n+\t\tif (info->outputBuffer == buf.get()) {\n+\t\t\tavailableOutputBuffers_.push(info->outputBuffer);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* Delete the extended frame information. */\n+\tframeInfo_.erase(info->frame);\n+}\n+\n+bool RCar4Frames::tryComplete(RCar4Frames::Info *info)\n+{\n+\tRequest *request = info->request;\n+\n+\tif (request->hasPendingBuffers())\n+\t\treturn false;\n+\n+\tif (!info->rawDequeued)\n+\t\treturn false;\n+\n+\tif (!info->metadataProcessed)\n+\t\treturn false;\n+\n+\tif (!info->paramDequeued)\n+\t\treturn false;\n+\n+\tif (!info->outputDequeued)\n+\t\treturn false;\n+\n+\tremove(info);\n+\n+\t/* Signal new internal buffers available. */\n+\tbufferAvailable.emit();\n+\n+\treturn true;\n+}\n+\n+RCar4Frames::Info *RCar4Frames::find(unsigned int frame)\n+{\n+\tconst auto &itInfo = frameInfo_.find(frame);\n+\n+\tif (itInfo != frameInfo_.end())\n+\t\treturn &itInfo->second;\n+\n+\tLOG(RCar4, Fatal) << \"Can't find tracking information for frame \" << frame;\n+\n+\treturn nullptr;\n+}\n+\n+RCar4Frames::Info *RCar4Frames::find(FrameBuffer *buffer)\n+{\n+\tfor (auto &itInfo : frameInfo_) {\n+\t\tInfo *info = &itInfo.second;\n+\n+\t\tfor (auto const itBuffers : info->request->buffers())\n+\t\t\tif (itBuffers.second == buffer)\n+\t\t\t\treturn info;\n+\n+\t\tif (info->inputBuffer == buffer ||\n+\t\t    info->paramBuffer == buffer ||\n+\t\t    info->statBuffer == buffer ||\n+\t\t    info->outputBuffer == buffer)\n+\t\t\treturn info;\n+\t}\n+\n+\tLOG(RCar4, Fatal) << \"Can't find tracking information from buffer\";\n+\n+\treturn nullptr;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/frames.h b/src/libcamera/pipeline/rcar-gen4/frames.h\nnew file mode 100644\nindex 000000000000..24573fe07a25\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/frames.h\n@@ -0,0 +1,85 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 VIN pipeline\n+ */\n+\n+#pragma once\n+\n+#include <map>\n+#include <memory>\n+#include <queue>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/stream.h>\n+\n+#include <libcamera/ipa/core_ipa_interface.h>\n+#include <libcamera/ipa/rkisp1_ipa_interface.h>\n+#include <libcamera/ipa/rkisp1_ipa_proxy.h>\n+\n+#include \"isp.h\"\n+\n+namespace libcamera {\n+\n+class FrameBuffer;\n+class Request;\n+\n+class RCar4Frames\n+{\n+public:\n+\tstruct Info {\n+\t\tunsigned int frame;\n+\t\tRequest *request;\n+\n+\t\tFrameBuffer *inputBuffer;\n+\t\tFrameBuffer *paramBuffer;\n+\t\tFrameBuffer *statBuffer;\n+\t\tFrameBuffer *outputBuffer;\n+\n+\t\tControlList effectiveSensorControls;\n+\n+\t\tbool rawDequeued;\n+\t\tbool paramDequeued;\n+\t\tbool metadataProcessed;\n+\t\tbool outputDequeued;\n+\t};\n+\n+\tint start(class RCarISPDevice *isp, class ipa::rkisp1::IPAProxyRkISP1 *ipa);\n+\tvoid stop(class RCarISPDevice *isp, class ipa::rkisp1::IPAProxyRkISP1 *ipa);\n+\n+\tInfo *create(Request *request);\n+\tvoid remove(Info *info);\n+\tbool tryComplete(Info *info);\n+\n+\tInfo *find(unsigned int frame);\n+\tInfo *find(FrameBuffer *buffer);\n+\n+\tSignal<> bufferAvailable;\n+\n+\tStream rawStream_;\n+\tStream outputStream_;\n+private:\n+\tstd::map<unsigned int, Info> frameInfo_;\n+\n+\t/* Buffers for internal use, if none is provided in request. */\n+\tstd::vector<std::unique_ptr<FrameBuffer>> inputBuffers_;\n+\tstd::vector<std::unique_ptr<FrameBuffer>> paramBuffers_;\n+\tstd::vector<std::unique_ptr<FrameBuffer>> statBuffers_;\n+\tstd::vector<std::unique_ptr<FrameBuffer>> outputBuffers_;\n+\n+\t/* Queues of available internal buffers. */\n+\tstd::queue<FrameBuffer *> availableInputBuffers_;\n+\tstd::queue<FrameBuffer *> availableParamBuffers_;\n+\tstd::queue<FrameBuffer *> availableStatBuffers_;\n+\tstd::queue<FrameBuffer *> availableOutputBuffers_;\n+\n+\t/* Buffers mapped and shared with IPA. */\n+\tstd::vector<IPABuffer> ipaBuffers_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/isp.cpp b/src/libcamera/pipeline/rcar-gen4/isp.cpp\nnew file mode 100644\nindex 000000000000..1d9d7f98429c\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/isp.cpp\n@@ -0,0 +1,227 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 ISP pipeline\n+ */\n+\n+#include \"isp.h\"\n+\n+#include <algorithm>\n+#include <cmath>\n+#include <limits>\n+\n+#include <linux/media-bus-format.h>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(RCar4)\n+\n+int RCarISPDevice::init(const MediaDevice *media, const std::string &pipeId)\n+{\n+\tconst MediaEntity *entity;\n+\tconst MediaPad *pad, *next;\n+\tint ret;\n+\n+\t/* Locate IPSCORE, e.g. rcar_isp fed00000.isp core */\n+\tstd::unique_ptr<V4L2Subdevice> core =\n+\t\tV4L2Subdevice::fromEntityName(media, pipeId + \" core\");\n+\tif (!core) {\n+\t\tLOG(RCar4, Error) << \"Failed to find ISPCORE \" << pipeId;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tentity = core->entity();\n+\n+\t/* Use the media links to find all video devices. */\n+\tpad = entity->getPadByIndex(0);\n+\tnext = pad->links()[0]->source();\n+\tinput_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name());\n+\tif (!input_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find ISP input entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpad = entity->getPadByIndex(1);\n+\tnext = pad->links()[0]->source();\n+\tparam_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name());\n+\tif (!param_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find ISP param entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpad = entity->getPadByIndex(2);\n+\tnext = pad->links()[0]->sink();\n+\tstat_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name());\n+\tif (!stat_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find ISP stat entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpad = entity->getPadByIndex(3);\n+\tnext = pad->links()[0]->sink();\n+\toutput_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name());\n+\tif (!output_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find ISP output entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Open all devices. */\n+\tret = input_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = param_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = stat_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = output_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+std::vector<PixelFormat> RCarISPDevice::formats() const\n+{\n+\tstd::vector<PixelFormat> formats;\n+\tfor (const auto &[format, sizes] : output_->formats())\n+\t\tformats.push_back(format.toPixelFormat());\n+\n+\treturn formats;\n+}\n+\n+StreamConfiguration\n+RCarISPDevice::generateConfiguration(PixelFormat format, Size size) const\n+{\n+\tStreamConfiguration cfg;\n+\n+\tbool found = false;\n+\tfor (const auto &pixelFormat : formats()) {\n+\t\tif (pixelFormat == format)\n+\t\t\tfound = true;\n+\t}\n+\n+\tcfg.size = size;\n+\tcfg.bufferCount = kBufferCount;\n+\tcfg.pixelFormat = found ? format : formats::XRGB8888;\n+\n+\t/* Get stride and frame size from device. */\n+\tV4L2DeviceFormat fmt;\n+\tfmt.fourcc = output_->toV4L2PixelFormat(cfg.pixelFormat);\n+\tfmt.size = cfg.size;\n+\n+\tif (output_->tryFormat(&fmt))\n+\t\treturn {};\n+\n+\tcfg.stride = fmt.planes[0].bpl;\n+\tcfg.frameSize = fmt.planes[0].size;\n+\n+\treturn cfg;\n+}\n+\n+int RCarISPDevice::configure(V4L2DeviceFormat *inputFormat,\n+\t\t\t     const PixelFormat &outputPixelFormat)\n+{\n+\tint ret;\n+\n+\t/* Configure the RAW input. */\n+\tret = input_->setFormat(inputFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tLOG(RCar4, Debug) << \"ISP input format = \" << *inputFormat;\n+\n+\t/* Configure the image output. */\n+\tV4L2DeviceFormat outputFormat;\n+\toutputFormat.fourcc = output_->toV4L2PixelFormat(outputPixelFormat);\n+\toutputFormat.size = inputFormat->size;\n+\tret = output_->setFormat(&outputFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tLOG(RCar4, Debug) << \"ISP output format = \" << outputFormat;\n+\n+\t/* Configure paramaters. */\n+\tV4L2DeviceFormat paramFormat;\n+\tparamFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS);\n+\tret = param_->setFormat(&paramFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Configure statistics. */\n+\tV4L2DeviceFormat statFormat;\n+\tstatFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_STAT_3A);\n+\tret = stat_->setFormat(&statFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+int RCarISPDevice::start()\n+{\n+\tint ret;\n+\n+\tret = input_->importBuffers(kBufferCount);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to import ISP input buffers\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = output_->importBuffers(kBufferCount);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to import ISP output buffers\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = output_->streamOn();\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to start ISP output\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = param_->streamOn();\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to start ISP param\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = stat_->streamOn();\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to start ISP stat\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = input_->streamOn();\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to start ISP input\";\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void RCarISPDevice::stop()\n+{\n+\toutput_->streamOff();\n+\tparam_->streamOff();\n+\tstat_->streamOff();\n+\tinput_->streamOff();\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/isp.h b/src/libcamera/pipeline/rcar-gen4/isp.h\nnew file mode 100644\nindex 000000000000..57148dc43041\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/isp.h\n@@ -0,0 +1,44 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 ISP pipeline\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <string>\n+\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+class MediaDevice;\n+class Size;\n+struct StreamConfiguration;\n+\n+class RCarISPDevice\n+{\n+public:\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\n+\tstd::vector<PixelFormat> formats() const;\n+\n+\tint init(const MediaDevice *media, const std::string &pipeId);\n+\n+\tStreamConfiguration generateConfiguration(PixelFormat format, Size size) const;\n+\n+\tint configure(V4L2DeviceFormat *inputFormat, const PixelFormat &outputPixelFormat);\n+\n+\tint start();\n+\tvoid stop();\n+\n+\tstd::unique_ptr<V4L2VideoDevice> input_;\n+\tstd::unique_ptr<V4L2VideoDevice> param_;\n+\tstd::unique_ptr<V4L2VideoDevice> stat_;\n+\tstd::unique_ptr<V4L2VideoDevice> output_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/meson.build b/src/libcamera/pipeline/rcar-gen4/meson.build\nnew file mode 100644\nindex 000000000000..431eb54e2803\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/meson.build\n@@ -0,0 +1,8 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_sources += files([\n+    'frames.cpp',\n+    'isp.cpp',\n+    'rcar-gen4.cpp',\n+    'vin.cpp',\n+])\ndiff --git a/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp b/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp\nnew file mode 100644\nindex 000000000000..b75197b24ae3\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp\n@@ -0,0 +1,814 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 ISP pipeline\n+ */\n+\n+#include <memory>\n+#include <queue>\n+#include <string>\n+#include <vector>\n+\n+#include <linux/rkisp1-config.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/stream.h>\n+\n+#include <libcamera/ipa/core_ipa_interface.h>\n+#include <libcamera/ipa/rkisp1_ipa_interface.h>\n+#include <libcamera/ipa/rkisp1_ipa_proxy.h>\n+\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/delayed_controls.h\"\n+#include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/ipa_manager.h\"\n+#include \"libcamera/internal/mapped_framebuffer.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/v4l2_device.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+#include \"frames.h\"\n+#include \"isp.h\"\n+#include \"vin.h\"\n+\n+namespace libcamera {\n+\n+namespace {\n+\n+const std::map<PixelFormat, uint32_t> formatToMediaBus = {\n+\t{ formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n+\t{ formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n+\t{ formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n+\t{ formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n+};\n+\n+/* Max supported resolution of VIN and ISP. */\n+static constexpr Size MaxResolution = { 4096, 4096 };\n+static constexpr unsigned int nBuffers = 4;\n+\n+} /* namespace */\n+\n+LOG_DEFINE_CATEGORY(RCar4)\n+\n+/* -----------------------------------------------------------------------------\n+ * Camera Data\n+ */\n+\n+class RCar4CameraData : public Camera::Private\n+{\n+public:\n+\tRCar4CameraData(PipelineHandler *pipe)\n+\t\t: Camera::Private(pipe)\n+\t{\n+\t}\n+\n+\tRCarVINDevice vin_;\n+\tRCarISPDevice isp_;\n+\tstd::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;\n+\n+\tRCar4Frames frames_;\n+\tstd::unique_ptr<DelayedControls> delayedCtrls_;\n+\tControlInfoMap ipaControls_;\n+\n+\tvoid queuePendingRequests();\n+\tvoid cancelPendingRequests();\n+\n+\t/* Requests for which no buffer has been queued to the VIN device yet. */\n+\tstd::queue<Request *> pendingRequests_;\n+\n+\t/* Slots for processing ready buffers. */\n+\tvoid vinBufferReady(FrameBuffer *buffer);\n+\tvoid inputBufferReady(FrameBuffer *buffer);\n+\tvoid paramBufferReady(FrameBuffer *buffer);\n+\tvoid statBufferReady(FrameBuffer *buffer);\n+\tvoid outputBufferReady(FrameBuffer *buffer);\n+\n+\t/* Slots for processing IPA interactions. */\n+\tvoid paramsComputed(unsigned int frame, unsigned int bytesused);\n+\tvoid setSensorControls(unsigned int frame,\n+\t\t\t       const ControlList &sensorControls);\n+\tvoid metadataReady(unsigned int frame, const ControlList &metadata);\n+};\n+\n+void RCar4CameraData::queuePendingRequests()\n+{\n+\twhile (!pendingRequests_.empty()) {\n+\t\tRequest *request = pendingRequests_.front();\n+\n+\t\tRCar4Frames::Info *info = frames_.create(request);\n+\t\tif (!info)\n+\t\t\tbreak;\n+\n+\t\tif (vin_.queueBuffer(info->inputBuffer)) {\n+\t\t\t/* Remove if raw buffer failed, should not happen. */\n+\t\t\tframes_.remove(info);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tipa_->queueRequest(info->frame, request->controls());\n+\n+\t\tpendingRequests_.pop();\n+\t}\n+}\n+\n+void RCar4CameraData::cancelPendingRequests()\n+{\n+\twhile (!pendingRequests_.empty()) {\n+\t\tRequest *request = pendingRequests_.front();\n+\n+\t\tfor (auto it : request->buffers()) {\n+\t\t\tFrameBuffer *buffer = it.second;\n+\t\t\tbuffer->_d()->cancel();\n+\t\t\tpipe()->completeBuffer(request, buffer);\n+\t\t}\n+\n+\t\tpipe()->completeRequest(request);\n+\t\tpendingRequests_.pop();\n+\t}\n+}\n+\n+void RCar4CameraData::vinBufferReady(FrameBuffer *buffer)\n+{\n+\tRCar4Frames::Info *info = frames_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\t/* If the buffer is cancelled force a complete of the whole request. */\n+\tif (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n+\t\tfor (auto it : request->buffers()) {\n+\t\t\tFrameBuffer *b = it.second;\n+\t\t\tb->_d()->cancel();\n+\t\t\tpipe()->completeBuffer(request, b);\n+\t\t}\n+\n+\t\tframes_.remove(info);\n+\t\tpipe()->completeRequest(request);\n+\t\treturn;\n+\t}\n+\n+\t/* Record the sensor's timestamp in the request metadata. */\n+\trequest->metadata().set(controls::SensorTimestamp,\n+\t\t\t\tbuffer->metadata().timestamp);\n+\n+\tipa_->computeParams(info->frame, info->paramBuffer->cookie());\n+}\n+\n+void RCar4CameraData::inputBufferReady(FrameBuffer *buffer)\n+{\n+\tRCar4Frames::Info *info = frames_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tif (request->findBuffer(&frames_.rawStream_))\n+\t\tpipe()->completeBuffer(request, buffer);\n+\n+\tinfo->rawDequeued = true;\n+\n+\tif (frames_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+void RCar4CameraData::paramBufferReady(FrameBuffer *buffer)\n+{\n+\tRCar4Frames::Info *info = frames_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tinfo->paramDequeued = true;\n+\n+\tif (frames_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+void RCar4CameraData::statBufferReady(FrameBuffer *buffer)\n+{\n+\tRCar4Frames::Info *info = frames_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tif (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n+\t\tinfo->metadataProcessed = true;\n+\n+\t\tif (frames_.tryComplete(info))\n+\t\t\tpipe()->completeRequest(request);\n+\n+\t\treturn;\n+\t}\n+\n+\tipa_->processStats(info->frame, info->statBuffer->cookie(),\n+\t\t\t   delayedCtrls_->get(buffer->metadata().sequence));\n+}\n+\n+void RCar4CameraData::outputBufferReady(FrameBuffer *buffer)\n+{\n+\tRCar4Frames::Info *info = frames_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tif (request->findBuffer(&frames_.outputStream_))\n+\t\tpipe()->completeBuffer(request, buffer);\n+\n+\trequest->metadata().set(controls::draft::PipelineDepth, 3);\n+\n+\tinfo->outputDequeued = true;\n+\n+\tif (frames_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+void RCar4CameraData::paramsComputed(unsigned int frame, unsigned int bytesused)\n+{\n+\tRCar4Frames::Info *info = frames_.find(frame);\n+\tif (!info)\n+\t\treturn;\n+\n+\tinfo->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;\n+\n+\tisp_.output_->queueBuffer(info->outputBuffer);\n+\tisp_.param_->queueBuffer(info->paramBuffer);\n+\tisp_.stat_->queueBuffer(info->statBuffer);\n+\tisp_.input_->queueBuffer(info->inputBuffer);\n+}\n+\n+void RCar4CameraData::setSensorControls([[maybe_unused]] unsigned int frame,\n+\t\t\t\t\tconst ControlList &sensorControls)\n+{\n+\tdelayedCtrls_->push(sensorControls);\n+}\n+\n+void RCar4CameraData::metadataReady(unsigned int frame, const ControlList &metadata)\n+{\n+\tRCar4Frames::Info *info = frames_.find(frame);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tinfo->request->metadata().merge(metadata);\n+\tinfo->metadataProcessed = true;\n+\n+\tif (frames_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Camera Configuration\n+ */\n+\n+class RCar4CameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tRCar4CameraConfiguration(Camera *camera, RCar4CameraData *data);\n+\n+\tStatus validate() override;\n+\n+\tconst V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }\n+\tconst Transform &combinedTransform() { return combinedTransform_; }\n+\tconst PixelFormat &ispOutputFormat() { return ispOutputFormat_; }\n+private:\n+\tstd::shared_ptr<Camera> camera_;\n+\tRCar4CameraData *data_;\n+\n+\tV4L2SubdeviceFormat sensorFormat_;\n+\tTransform combinedTransform_;\n+\tPixelFormat ispOutputFormat_;\n+};\n+\n+RCar4CameraConfiguration::RCar4CameraConfiguration(Camera *camera,\n+\t\t\t\t\t\t   RCar4CameraData *data)\n+\t: CameraConfiguration(), camera_(camera->shared_from_this()), data_(data)\n+{\n+}\n+\n+CameraConfiguration::Status RCar4CameraConfiguration::validate()\n+{\n+\tStatus status;\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\tstatus = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);\n+\n+\t/* Cap the number of entries to the available streams. */\n+\tif (config_.size() > 2) {\n+\t\tconfig_.resize(2);\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\tOrientation requestedOrientation = orientation;\n+\tcombinedTransform_ = data_->vin_.sensor()->computeTransform(&orientation);\n+\tif (orientation != requestedOrientation)\n+\t\tstatus = Adjusted;\n+\n+\t/* Figure out the VIN configuration based on the first stream size. */\n+\tStreamConfiguration vinCfg = data_->vin_.generateConfiguration(config_.at(0).size);\n+\n+\t/* Default ISP output format. */\n+\tispOutputFormat_ = formats::XRGB8888;\n+\n+\t/*\n+\t * Validate there are at max two streams, one output and one RAW. The\n+\t * size of two streams must match each other and the sensor output as we\n+\t * have no scaler.\n+\t */\n+\tunsigned int outputStreams = 0;\n+\tunsigned int rawStreams = 0;\n+\tfor (unsigned int i = 0; i < config_.size(); i++) {\n+\t\tStreamConfiguration &cfg = config_.at(i);\n+\t\tStreamConfiguration newCfg = {};\n+\t\tconst StreamConfiguration originalCfg = cfg;\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);\n+\n+\t\tLOG(RCar4, Debug) << \"Validating stream: \" << cfg.toString();\n+\n+\t\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {\n+\t\t\tif (rawStreams++) {\n+\t\t\t\tLOG(RCar4, Error)\n+\t\t\t\t\t<< \"Camera configuration support only one RAW stream\";\n+\t\t\t\treturn Invalid;\n+\t\t\t}\n+\n+\t\t\tnewCfg = vinCfg;\n+\n+\t\t\tcfg.setStream(&data_->frames_.rawStream_);\n+\t\t\tLOG(RCar4, Debug) << \"Assigned \" << newCfg.toString()\n+\t\t\t\t\t  << \" to the raw stream\";\n+\t\t} else {\n+\t\t\tif (outputStreams++) {\n+\t\t\t\tLOG(RCar4, Error)\n+\t\t\t\t\t<< \"Camera configuration support only one output stream\";\n+\t\t\t\treturn Invalid;\n+\t\t\t}\n+\n+\t\t\tnewCfg = data_->isp_.generateConfiguration(cfg.pixelFormat, vinCfg.size);\n+\t\t\tispOutputFormat_ = newCfg.pixelFormat;\n+\n+\t\t\tcfg.setStream(&data_->frames_.outputStream_);\n+\t\t\tLOG(RCar4, Debug) << \"Assigned \" << newCfg.toString()\n+\t\t\t\t\t  << \" to the output stream\";\n+\t\t}\n+\n+\t\tcfg.size = newCfg.size;\n+\t\tcfg.bufferCount = newCfg.bufferCount;\n+\t\tcfg.pixelFormat = newCfg.pixelFormat;\n+\t\tcfg.stride = newCfg.stride;\n+\t\tcfg.frameSize = newCfg.frameSize;\n+\n+\t\tif (!cfg.pixelFormat.isValid()) {\n+\t\t\tLOG(RCar4, Error)\n+\t\t\t\t<< \"Stream \" << i << \" can not generate cfg\";\n+\t\t\treturn Invalid;\n+\t\t}\n+\n+\t\tif (cfg.pixelFormat != originalCfg.pixelFormat ||\n+\t\t    cfg.size != originalCfg.size) {\n+\t\t\tLOG(RCar4, Debug)\n+\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n+\t\t\t\t<< cfg.toString();\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\t}\n+\n+\t/* Select the sensor format. */\n+\tstd::vector<unsigned int> mbusCodes = { formatToMediaBus.at(vinCfg.pixelFormat) };\n+\tsensorFormat_ =\n+\t\tdata_->vin_.sensor()->getFormat(mbusCodes, vinCfg.size, vinCfg.size);\n+\n+\treturn status;\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Pipeline Handler\n+ */\n+\n+class PipelineHandlerRCar4 : public PipelineHandler\n+{\n+public:\n+\tPipelineHandlerRCar4(CameraManager *manager);\n+\n+\tstd::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n+\t\t\t\t\t\t\t\t   Span<const StreamRole> roles) override;\n+\tint configure(Camera *camera, CameraConfiguration *config) override;\n+\n+\tint exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\n+\tint start(Camera *camera, const ControlList *controls) override;\n+\tvoid stopDevice(Camera *camera) override;\n+\n+\tint queueRequestDevice(Camera *camera, Request *request) override;\n+\n+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+\tint updateControls(RCar4CameraData *data);\n+\n+private:\n+\tRCar4CameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<RCar4CameraData *>(camera->_d());\n+\t}\n+\n+\tint createCamera(MediaDevice *mdev, const std::string &pipeId);\n+\n+\tStreamConfiguration generateStreamConfiguration(RCar4CameraData *data,\n+\t\t\t\t\t\t\tStreamRole role);\n+};\n+\n+PipelineHandlerRCar4::PipelineHandlerRCar4(CameraManager *manager)\n+\t: PipelineHandler(manager)\n+{\n+}\n+\n+StreamConfiguration\n+PipelineHandlerRCar4::generateStreamConfiguration(RCar4CameraData *data,\n+\t\t\t\t\t\t  StreamRole role)\n+{\n+\tconst std::vector<unsigned int> &mbusCodes = data->vin_.sensor()->mbusCodes();\n+\n+\t/* Create the list of supported RAW stream formats. */\n+\tstd::map<PixelFormat, std::vector<SizeRange>> rawFormats;\n+\tunsigned int rawBitsPerPixel = 0;\n+\tPixelFormat rawFormat;\n+\tSize rawSize = { 0, 0 };\n+\tstd::vector<SizeRange> rawSizes;\n+\n+\tfor (const auto &format : data->vin_.formats()) {\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(format);\n+\n+\t\t/* Populate stream formats for RAW configurations. */\n+\t\tuint32_t mbusCode = formatToMediaBus.at(format);\n+\n+\t\t/* Skip formats not supported by sensor. */\n+\t\tif (std::find(mbusCodes.begin(), mbusCodes.end(), mbusCode) == mbusCodes.end())\n+\t\t\tcontinue;\n+\n+\t\t/* Add all the RAW sizes the sensor can produce for this code. */\n+\t\tfor (const auto &rawSizeByCode : data->vin_.sensor()->sizes(mbusCode)) {\n+\t\t\tif (rawSizeByCode.width > MaxResolution.width ||\n+\t\t\t    rawSizeByCode.height > MaxResolution.height)\n+\t\t\t\tcontinue;\n+\n+\t\t\trawSizes.push_back({ rawSizeByCode, rawSizeByCode });\n+\n+\t\t\trawFormats[format].push_back({ rawSizeByCode, rawSizeByCode });\n+\n+\t\t\t/* Cache for later default format. */\n+\t\t\tif (info.bitsPerPixel >= rawBitsPerPixel) {\n+\t\t\t\trawBitsPerPixel = info.bitsPerPixel;\n+\t\t\t\trawFormat = format;\n+\n+\t\t\t\tif (rawSizeByCode > rawSize)\n+\t\t\t\t\trawSize = rawSizeByCode;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* If generating for RAW role we are done. */\n+\tif (role == StreamRole::Raw) {\n+\t\tStreamFormats rawStreamFormats(rawFormats);\n+\t\tStreamConfiguration rawCfg(rawStreamFormats);\n+\t\trawCfg.pixelFormat = rawFormat;\n+\t\trawCfg.size = rawSize;\n+\t\trawCfg.bufferCount = nBuffers;\n+\n+\t\treturn rawCfg;\n+\t}\n+\n+\t/* Create the list of supported other stream formats. */\n+\tstd::map<PixelFormat, std::vector<SizeRange>> outputFormats;\n+\tstd::vector<SizeRange> outputSizes(rawSizes.begin(), rawSizes.end());\n+\n+\tfor (const auto &format : data->isp_.formats()) {\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(format);\n+\n+\t\t/* Skip RAW formats. */\n+\t\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)\n+\t\t\tcontinue;\n+\n+\t\toutputFormats[format] = { outputSizes };\n+\t}\n+\n+\tStreamFormats outputStreamFormats(outputFormats);\n+\tStreamConfiguration outputCfg(outputStreamFormats);\n+\toutputCfg.pixelFormat = formats::XRGB8888;\n+\toutputCfg.size = rawSize;\n+\n+\treturn outputCfg;\n+}\n+\n+std::unique_ptr<CameraConfiguration>\n+PipelineHandlerRCar4::generateConfiguration(Camera *camera,\n+\t\t\t\t\t    Span<const StreamRole> roles)\n+{\n+\tRCar4CameraData *data = cameraData(camera);\n+\n+\tstd::unique_ptr<CameraConfiguration> config =\n+\t\tstd::make_unique<RCar4CameraConfiguration>(camera, data);\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tfor (const StreamRole role : roles) {\n+\t\tstd::optional<ColorSpace> colorSpace;\n+\n+\t\tswitch (role) {\n+\t\tcase StreamRole::Raw:\n+\t\t\tcolorSpace = ColorSpace::Raw;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tcolorSpace = ColorSpace::Rec709;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tStreamConfiguration cfg =\n+\t\t\tgenerateStreamConfiguration(data, role);\n+\t\tif (!cfg.pixelFormat.isValid())\n+\t\t\treturn nullptr;\n+\n+\t\tcfg.colorSpace = colorSpace;\n+\t\tcfg.bufferCount = nBuffers;\n+\t\tconfig->addConfiguration(cfg);\n+\t}\n+\n+\tif (config->validate() == CameraConfiguration::Invalid)\n+\t\treturn nullptr;\n+\n+\treturn config;\n+}\n+\n+int PipelineHandlerRCar4::configure(Camera *camera, CameraConfiguration *c)\n+{\n+\tRCar4CameraConfiguration *config = static_cast<RCar4CameraConfiguration *>(c);\n+\tRCar4CameraData *data = cameraData(camera);\n+\n+\tV4L2DeviceFormat vinFormat;\n+\tint ret;\n+\n+\t/* Configure VIN and propagate format to ISP. */\n+\tret = data->vin_.configure(config->sensorFormat().size,\n+\t\t\t\t   config->combinedTransform(), &vinFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = data->isp_.configure(&vinFormat, config->ispOutputFormat());\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Inform IPA of stream configuration and sensor controls. */\n+\tIPACameraSensorInfo sensorInfo;\n+\tret = data->vin_.sensor()->sensorInfo(&sensorInfo);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tipa::rkisp1::IPAConfigInfo ipaConfig{\n+\t\tsensorInfo, data->vin_.sensor()->controls(),\n+\t\tV4L2_META_FMT_RK_ISP1_EXT_PARAMS\n+\t};\n+\n+\tstd::map<unsigned int, IPAStream> streamConfig;\n+\tstreamConfig[0] =\n+\t\tIPAStream(PixelFormat(config->ispOutputFormat().fourcc()),\n+\t\t\t  config->sensorFormat().size);\n+\n+\tret = data->ipa_->configure(ipaConfig, streamConfig, &data->ipaControls_);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"failed configuring IPA (\" << ret << \")\";\n+\t\treturn ret;\n+\t}\n+\n+\treturn updateControls(data);\n+}\n+\n+int PipelineHandlerRCar4::exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t\t\t     std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tRCar4CameraData *data = cameraData(camera);\n+\tunsigned int count = stream->configuration().bufferCount;\n+\n+\tif (stream == &data->frames_.outputStream_)\n+\t\treturn data->isp_.output_->exportBuffers(count, buffers);\n+\n+\tif (stream == &data->frames_.rawStream_)\n+\t\treturn data->isp_.input_->exportBuffers(count, buffers);\n+\n+\treturn -EINVAL;\n+}\n+\n+int PipelineHandlerRCar4::start(Camera *camera,\n+\t\t\t\t[[maybe_unused]] const ControlList *controls)\n+{\n+\tRCar4CameraData *data = cameraData(camera);\n+\tint ret;\n+\n+\tdata->delayedCtrls_->reset();\n+\n+\tret = data->frames_.start(&data->isp_, data->ipa_.get());\n+\tif (ret)\n+\t\tgoto error;\n+\n+\tret = data->ipa_->start();\n+\tif (ret)\n+\t\tgoto error;\n+\n+\tret = data->vin_.start();\n+\tif (ret)\n+\t\tgoto error;\n+\n+\tret = data->isp_.start();\n+\tif (ret)\n+\t\tgoto error;\n+\n+\treturn 0;\n+error:\n+\tstop(camera);\n+\n+\treturn ret;\n+}\n+\n+void PipelineHandlerRCar4::stopDevice(Camera *camera)\n+{\n+\tRCar4CameraData *data = cameraData(camera);\n+\n+\tdata->cancelPendingRequests();\n+\n+\tdata->isp_.stop();\n+\tdata->vin_.stop();\n+\tdata->ipa_->stop();\n+\n+\tdata->frames_.stop(&data->isp_, data->ipa_.get());\n+}\n+\n+int PipelineHandlerRCar4::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tRCar4CameraData *data = cameraData(camera);\n+\n+\tdata->pendingRequests_.push(request);\n+\tdata->queuePendingRequests();\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerRCar4::updateControls(RCar4CameraData *data)\n+{\n+\tControlInfoMap::Map controls;\n+\n+\tfor (const auto &ipaControl : data->ipaControls_)\n+\t\tcontrols[ipaControl.first] = ipaControl.second;\n+\n+\tdata->controlInfo_ = ControlInfoMap(std::move(controls),\n+\t\t\t\t\t    controls::controls);\n+\treturn 0;\n+}\n+\n+int PipelineHandlerRCar4::createCamera(MediaDevice *mdev,\n+\t\t\t\t       const std::string &pipeId)\n+{\n+\tstd::unique_ptr<RCar4CameraData> data = std::make_unique<RCar4CameraData>(this);\n+\tIPACameraSensorInfo sensorInfo{};\n+\tint ret;\n+\n+\tret = data->vin_.init(mdev, pipeId);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = data->isp_.init(mdev, pipeId);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Load RkISP1 IPA for use with RCar4\n+\t */\n+\tdata->ipa_ = IPAManager::createIPA<ipa::rkisp1::IPAProxyRkISP1>(this, 1, 1, \"rkisp1\");\n+\tif (!data->ipa_) {\n+\t\tLOG(RCar4, Error) << \"No IPA module found\";\n+\t\treturn -ENOENT;\n+\t}\n+\n+\t/* The IPA tuning file is made from the sensor name. */\n+\tstd::string ipaTuningFile =\n+\t\tdata->ipa_->configurationFile(data->vin_.sensor()->model() + \".yaml\", \"uncalibrated.yaml\");\n+\n+\tret = data->vin_.sensor()->sensorInfo(&sensorInfo);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Camera sensor information not available\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = data->ipa_->init({ ipaTuningFile, data->vin_.sensor()->model() },\n+\t\t\t       libcamera::ipa::rkisp1::HwRevisionExternalRppX1,\n+\t\t\t       sensorInfo, data->vin_.sensor()->controls(), &data->ipaControls_);\n+\tif (ret < 0) {\n+\t\tLOG(RCar4, Error) << \"IPA initialization failure\";\n+\t\treturn ret;\n+\t}\n+\n+\tupdateControls(data.get());\n+\n+\t/*\n+\t * Initialize the camera properties.\n+\t */\n+\tdata->properties_ = data->vin_.sensor()->properties();\n+\tconst CameraSensorProperties::SensorDelays &delays = data->vin_.sensor()->sensorDelays();\n+\tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n+\t\t{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },\n+\t\t{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },\n+\t\t{ V4L2_CID_VBLANK, { delays.vblankDelay, false } },\n+\t};\n+\n+\tdata->delayedCtrls_ =\n+\t\tstd::make_unique<DelayedControls>(data->vin_.sensor()->device(),\n+\t\t\t\t\t\t  params);\n+\n+\tstd::set<Stream *> streams{\n+\t\t&data->frames_.rawStream_,\n+\t\t&data->frames_.outputStream_,\n+\t};\n+\n+\t/*\n+\t * Connect signals to slots to drive the pipeline.\n+\t */\n+\n+\t/* When internal buffers become available try to queue more jobs. */\n+\tdata->frames_.bufferAvailable.connect(data.get(),\n+\t\t\t\t\t      &RCar4CameraData::queuePendingRequests);\n+\n+\t/* Connect bufferReady for each video device to a handler. */\n+\tdata->vin_.bufferReady().connect(data.get(),\n+\t\t\t\t\t &RCar4CameraData::vinBufferReady);\n+\tdata->isp_.input_->bufferReady.connect(data.get(),\n+\t\t\t\t\t       &RCar4CameraData::inputBufferReady);\n+\tdata->isp_.param_->bufferReady.connect(data.get(),\n+\t\t\t\t\t       &RCar4CameraData::paramBufferReady);\n+\tdata->isp_.stat_->bufferReady.connect(data.get(),\n+\t\t\t\t\t      &RCar4CameraData::statBufferReady);\n+\tdata->isp_.output_->bufferReady.connect(data.get(),\n+\t\t\t\t\t\t&RCar4CameraData::outputBufferReady);\n+\n+\t/* Connect IPA signals. */\n+\tdata->ipa_->setSensorControls.connect(data.get(),\n+\t\t\t\t\t      &RCar4CameraData::setSensorControls);\n+\tdata->ipa_->paramsComputed.connect(data.get(),\n+\t\t\t\t\t   &RCar4CameraData::paramsComputed);\n+\tdata->ipa_->metadataReady.connect(data.get(),\n+\t\t\t\t\t  &RCar4CameraData::metadataReady);\n+\n+\t/* Apply controls at start at exposure. */\n+\tdata->vin_.frameStart().connect(data->delayedCtrls_.get(),\n+\t\t\t\t\t&DelayedControls::applyControls);\n+\n+\t/*\n+\t * Register the camera.\n+\t */\n+\tconst std::string &id = data->vin_.sensor()->id();\n+\tstd::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);\n+\n+\tregisterCamera(std::move(camera));\n+\n+\treturn 0;\n+}\n+\n+bool PipelineHandlerRCar4::match(DeviceEnumerator *enumerator)\n+{\n+\tDeviceMatch dm(\"rcar_vin\");\n+\n+\tMediaDevice *media = acquireMediaDevice(enumerator, dm);\n+\tif (!media)\n+\t\treturn false;\n+\n+\tbool registered = false;\n+\tfor (const MediaEntity *entity : media->entities()) {\n+\t\tif (entity->name().substr(0, 8) == \"rcar_isp\" &&\n+\t\t    entity->name().rfind(\"core\") != std::string::npos) {\n+\t\t\t/*\n+\t\t\t * Isolate the unit address that identifies one ISP\n+\t\t\t * instance. pipeId will look like\n+\t\t\t * 'rcar_isp fed00000.isp'.\n+\t\t\t */\n+\t\t\tstd::string pipeId = entity->name().substr(0, 21);\n+\t\t\tif (!createCamera(media, pipeId))\n+\t\t\t\tregistered = true;\n+\t\t}\n+\t}\n+\n+\treturn registered;\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerRCar4, \"rcar-gen4\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/vin.cpp b/src/libcamera/pipeline/rcar-gen4/vin.cpp\nnew file mode 100644\nindex 000000000000..76b94a434d84\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/vin.cpp\n@@ -0,0 +1,391 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 VIN pipeline\n+ */\n+\n+#include \"vin.h\"\n+\n+#include <cmath>\n+#include <limits>\n+\n+#include <linux/media-bus-format.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/stream.h>\n+#include <libcamera/transform.h>\n+\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(RCar4)\n+\n+namespace {\n+\n+const std::map<uint32_t, PixelFormat> mbusCodesToPixelFormat = {\n+\t{ MEDIA_BUS_FMT_SBGGR10_1X10, formats::SBGGR10 },\n+\t{ MEDIA_BUS_FMT_SGBRG10_1X10, formats::SGBRG10 },\n+\t{ MEDIA_BUS_FMT_SGRBG10_1X10, formats::SGRBG10 },\n+\t{ MEDIA_BUS_FMT_SRGGB10_1X10, formats::SRGGB10 },\n+};\n+\n+} /* namespace */\n+\n+/**\n+ * \\brief Retrieve the list of supported PixelFormats\n+ *\n+ * Retrieve the list of supported pixel formats by matching the sensor produced\n+ * media bus codes with the formats supported by the VIN unit.\n+ *\n+ * \\return The list of supported PixelFormat\n+ */\n+std::vector<PixelFormat> RCarVINDevice::formats() const\n+{\n+\tif (!sensor_)\n+\t\treturn {};\n+\n+\tstd::vector<PixelFormat> formats;\n+\tfor (unsigned int code : sensor_->mbusCodes()) {\n+\t\tauto it = mbusCodesToPixelFormat.find(code);\n+\t\tif (it != mbusCodesToPixelFormat.end())\n+\t\t\tformats.push_back(it->second);\n+\t}\n+\n+\treturn formats;\n+}\n+\n+/**\n+ * \\brief Retrieve the list of supported size ranges\n+ * \\param[in] format The pixel format\n+ *\n+ * Retrieve the list of supported sizes for a particular \\a format by matching\n+ * the sensor produced media bus codes formats supported by the VIN unit.\n+ *\n+ * \\return A list of supported sizes for the \\a format or an empty list\n+ * otherwise\n+ */\n+std::vector<SizeRange> RCarVINDevice::sizes(const PixelFormat &format) const\n+{\n+\tint mbusCode = -1;\n+\n+\tif (!sensor_)\n+\t\treturn {};\n+\n+\tstd::vector<SizeRange> sizes;\n+\tfor (const auto &iter : mbusCodesToPixelFormat) {\n+\t\tif (iter.second != format)\n+\t\t\tcontinue;\n+\n+\t\tmbusCode = iter.first;\n+\t\tbreak;\n+\t}\n+\n+\tif (mbusCode == -1)\n+\t\treturn {};\n+\n+\tfor (const Size &sz : sensor_->sizes(mbusCode))\n+\t\tsizes.emplace_back(sz);\n+\n+\treturn sizes;\n+}\n+\n+int RCarVINDevice::init(const MediaDevice *media, const std::string &pipeId)\n+{\n+\tconst MediaEntity *entity;\n+\tconst MediaPad *pad, *next;\n+\tint ret;\n+\n+\t/* Locate IPS Channel Selector, e.g. rcar_isp fed00000.isp */\n+\tcsisp_ = V4L2Subdevice::fromEntityName(media, pipeId);\n+\tif (!csisp_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find Channel Selector \" << pipeId;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Use the Channel Selector links to find CSI-2 Rx and Sensor. */\n+\tentity = csisp_->entity();\n+\tpad = entity->getPadByIndex(0);\n+\tnext = pad->links()[0]->source();\n+\tcsi2_ = V4L2Subdevice::fromEntityName(media, next->entity()->name());\n+\tif (!csi2_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find CSI-2 Rx entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tentity = csi2_->entity();\n+\tpad = entity->getPadByIndex(0);\n+\tnext = pad->links()[0]->source();\n+\tsensor_ = CameraSensorFactoryBase::create(next->entity());\n+\tif (!sensor_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find sensor entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Make sure the sensor produces at least one format compatible with\n+\t * the VIN requirements.\n+\t */\n+\tstd::vector<unsigned int> vinCodes = utils::map_keys(mbusCodesToPixelFormat);\n+\tconst std::vector<unsigned int> &sensorCodes = sensor_->mbusCodes();\n+\tif (!utils::set_overlap(sensorCodes.begin(), sensorCodes.end(),\n+\t\t\t\tvinCodes.begin(), vinCodes.end())) {\n+\t\tLOG(RCar4, Error)\n+\t\t\t<< \"Sensor \" << sensor_->entity()->name()\n+\t\t\t<< \" has not format compatible with the VIN\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Use the Channel Selector links to find VIN. */\n+\tentity = csisp_->entity();\n+\tpad = entity->getPadByIndex(1);\n+\tnext = pad->links()[0]->sink();\n+\toutput_ = V4L2VideoDevice::fromEntityName(media, next->entity()->name());\n+\tif (!output_) {\n+\t\tLOG(RCar4, Error) << \"Failed to find VIN entity\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Open all devices. */\n+\tret = csi2_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = csisp_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = output_->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+int RCarVINDevice::configure(const Size &size, const Transform &transform,\n+\t\t\t     V4L2DeviceFormat *outputFormat)\n+{\n+\tV4L2SubdeviceFormat sensorFormat;\n+\tint ret;\n+\n+\t/* Configure sensor */\n+\tstd::vector<unsigned int> mbusCodes = utils::map_keys(mbusCodesToPixelFormat);\n+\tsensorFormat = getSensorFormat(mbusCodes, size);\n+\tret = sensor_->setFormat(&sensorFormat, transform);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Configure CSI-2 */\n+\tret = csi2_->setFormat(0, &sensorFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (mbusCodesToPixelFormat.find(sensorFormat.code) == mbusCodesToPixelFormat.end())\n+\t\treturn -EINVAL;\n+\n+\t/* Configure Channel selector. */\n+\tret = csisp_->setFormat(0, &sensorFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (mbusCodesToPixelFormat.find(sensorFormat.code) == mbusCodesToPixelFormat.end())\n+\t\treturn -EINVAL;\n+\n+\t/* Configure VIN */\n+\tconst auto &itInfo = mbusCodesToPixelFormat.find(sensorFormat.code);\n+\toutputFormat->fourcc = output_->toV4L2PixelFormat(itInfo->second);\n+\toutputFormat->size = sensorFormat.size;\n+\toutputFormat->planesCount = 1;\n+\n+\tret = output_->setFormat(outputFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tLOG(RCar4, Debug) << \"VIN output format \" << *outputFormat;\n+\n+\treturn 0;\n+}\n+\n+StreamConfiguration RCarVINDevice::generateConfiguration(Size size) const\n+{\n+\tStreamConfiguration cfg;\n+\n+\t/* If no desired size use the sensor resolution. */\n+\tif (size.isNull())\n+\t\tsize = sensor_->resolution();\n+\n+\t/* Query the sensor static information for closest match. */\n+\tstd::vector<unsigned int> mbusCodes = utils::map_keys(mbusCodesToPixelFormat);\n+\tV4L2SubdeviceFormat sensorFormat = getSensorFormat(mbusCodes, size);\n+\tif (!sensorFormat.code) {\n+\t\tLOG(RCar4, Error) << \"Sensor does not support mbus code\";\n+\t\treturn {};\n+\t}\n+\n+\tcfg.size = sensorFormat.size;\n+\tcfg.pixelFormat = mbusCodesToPixelFormat.at(sensorFormat.code);\n+\tcfg.bufferCount = kBufferCount;\n+\n+\t/* Get stride and frame size from device. */\n+\tV4L2DeviceFormat fmt;\n+\tfmt.fourcc = output_->toV4L2PixelFormat(cfg.pixelFormat);\n+\tfmt.size = cfg.size;\n+\n+\tint ret = output_->tryFormat(&fmt);\n+\tif (ret)\n+\t\treturn {};\n+\n+\tcfg.stride = fmt.planes[0].bpl;\n+\tcfg.frameSize = fmt.planes[0].size;\n+\n+\treturn cfg;\n+}\n+\n+/**\n+ * \\brief Retrieve the best sensor format for a desired output\n+ * \\param[in] mbusCodes The list of acceptable media bus codes\n+ * \\param[in] size The desired size\n+ *\n+ * Media bus codes are selected from \\a mbusCodes, which lists all acceptable\n+ * codes in decreasing order of preference. Media bus codes supported by the\n+ * sensor but not listed in \\a mbusCodes are ignored. If none of the desired\n+ * codes is supported, it returns an error.\n+ *\n+ * \\a size indicates the desired size at the output of the sensor. This method\n+ * selects the best media bus code and size supported by the sensor according\n+ * to the following criteria.\n+ *\n+ * - The desired \\a size shall fit in the sensor output size to avoid the need\n+ *   to up-scale.\n+ * - The aspect ratio of sensor output size shall be as close as possible to\n+ *   the sensor's native resolution field of view.\n+ * - The sensor output size shall be as small as possible to lower the required\n+ *   bandwidth.\n+ * - The desired \\a size shall be supported by one of the media bus code listed\n+ *   in \\a mbusCodes.\n+ *\n+ * When multiple media bus codes can produce the same size, the code at the\n+ * lowest position in \\a mbusCodes is selected.\n+ *\n+ * The returned sensor output format is guaranteed to be acceptable by the\n+ * setFormat() method without any modification.\n+ *\n+ * \\return The best sensor output format matching the desired media bus codes\n+ * and size on success, or an empty format otherwise.\n+ */\n+V4L2SubdeviceFormat RCarVINDevice::getSensorFormat(const std::vector<unsigned int> &mbusCodes,\n+\t\t\t\t\t\t   const Size &size) const\n+{\n+\tunsigned int desiredArea = size.width * size.height;\n+\tunsigned int bestArea = std::numeric_limits<unsigned int>::max();\n+\tconst Size &resolution = sensor_->resolution();\n+\tfloat desiredRatio = static_cast<float>(resolution.width) /\n+\t\t\t     resolution.height;\n+\tfloat bestRatio = std::numeric_limits<float>::max();\n+\tSize bestSize;\n+\tuint32_t bestCode = 0;\n+\n+\tfor (unsigned int code : mbusCodes) {\n+\t\tconst auto sizes = sensor_->sizes(code);\n+\t\tif (!sizes.size())\n+\t\t\tcontinue;\n+\n+\t\tfor (const Size &sz : sizes) {\n+\t\t\t/* No need to check ratios if we have an exact match. */\n+\t\t\tif (sz == size) {\n+\t\t\t\tbestRatio = 0;\n+\t\t\t\tbestArea = 0;\n+\t\t\t\tbestSize = sz;\n+\t\t\t\tbestCode = code;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (sz.width < size.width || sz.height < size.height)\n+\t\t\t\tcontinue;\n+\n+\t\t\tfloat ratio = static_cast<float>(sz.width) / sz.height;\n+\t\t\t/*\n+\t\t\t * Ratios can differ by small mantissa difference which\n+\t\t\t * can affect the selection of the sensor output size\n+\t\t\t * wildly. We are interested in selection of the closest\n+\t\t\t * size with respect to the desired output size, hence\n+\t\t\t * comparing it with a single precision digit is enough.\n+\t\t\t */\n+\t\t\tratio = static_cast<unsigned int>(ratio * 10) / 10.0;\n+\t\t\tfloat ratioDiff = std::abs(ratio - desiredRatio);\n+\t\t\tunsigned int area = sz.width * sz.height;\n+\t\t\tunsigned int areaDiff = area - desiredArea;\n+\n+\t\t\tif (ratioDiff > bestRatio)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (ratioDiff < bestRatio || areaDiff < bestArea) {\n+\t\t\t\tbestRatio = ratioDiff;\n+\t\t\t\tbestArea = areaDiff;\n+\t\t\t\tbestSize = sz;\n+\t\t\t\tbestCode = code;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (bestSize.isNull()) {\n+\t\tLOG(RCar4, Debug) << \"No supported format or size found\";\n+\t\treturn {};\n+\t}\n+\n+\tV4L2SubdeviceFormat format{};\n+\tformat.code = bestCode;\n+\tformat.size = bestSize;\n+\n+\treturn format;\n+}\n+\n+int RCarVINDevice::start()\n+{\n+\tint ret;\n+\n+\tret = output_->importBuffers(kBufferCount);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to import VIN buffers\";\n+\t\treturn ret;\n+\t}\n+\n+\tret = output_->streamOn();\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to start VIN\";\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\tret = output_->setFrameStartEnabled(true);\n+\tif (ret) {\n+\t\tLOG(RCar4, Error) << \"Failed to enable Frame Start\";\n+\t\tstop();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void RCarVINDevice::stop()\n+{\n+\toutput_->setFrameStartEnabled(false);\n+\n+\toutput_->streamOff();\n+\n+\tif (output_->releaseBuffers())\n+\t\tLOG(RCar4, Error) << \"Failed to release VIN buffers\";\n+}\n+\n+int RCarVINDevice::queueBuffer(FrameBuffer *buffer)\n+{\n+\treturn output_->queueBuffer(buffer);\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/rcar-gen4/vin.h b/src/libcamera/pipeline/rcar-gen4/vin.h\nnew file mode 100644\nindex 000000000000..5ac97be0e4bc\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/vin.h\n@@ -0,0 +1,66 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright 2025 Renesas Electronics Co\n+ * Copyright 2025 Niklas Söderlund <niklas.soderlund@ragnatech.se>\n+ *\n+ * Renesas R-Car Gen4 VIN pipeline\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <queue>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+class CameraSensor;\n+class FrameBuffer;\n+class MediaDevice;\n+class PixelFormat;\n+class Request;\n+class Size;\n+class SizeRange;\n+struct StreamConfiguration;\n+enum class Transform;\n+\n+class RCarVINDevice\n+{\n+public:\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\n+\tstd::vector<PixelFormat> formats() const;\n+\tstd::vector<SizeRange> sizes(const PixelFormat &format) const;\n+\n+\tint init(const MediaDevice *media, const std::string &pipeId);\n+\tint configure(const Size &size, const Transform &transform,\n+\t\t      V4L2DeviceFormat *outputFormat);\n+\n+\tStreamConfiguration generateConfiguration(Size size) const;\n+\n+\tint start();\n+\tvoid stop();\n+\n+\tCameraSensor *sensor() { return sensor_.get(); }\n+\tconst CameraSensor *sensor() const { return sensor_.get(); }\n+\n+\tint queueBuffer(FrameBuffer *buffer);\n+\n+\tSignal<FrameBuffer *> &bufferReady() { return output_->bufferReady; }\n+\tSignal<uint32_t> &frameStart() { return output_->frameStart; }\n+private:\n+\tV4L2SubdeviceFormat getSensorFormat(const std::vector<unsigned int> &mbusCodes,\n+\t\t\t\t\t    const Size &size) const;\n+\n+\tstd::unique_ptr<CameraSensor> sensor_;\n+\tstd::unique_ptr<V4L2Subdevice> csi2_;\n+\tstd::unique_ptr<V4L2Subdevice> csisp_;\n+\tstd::unique_ptr<V4L2VideoDevice> output_;\n+};\n+\n+} /* namespace libcamera */\n","prefixes":["v2","2/2"]}