Show a patch.

GET /api/1.1/patches/23593/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 23593,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/23593/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/23593/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20250617104642.1607118-3-niklas.soderlund+renesas@ragnatech.se>",
    "date": "2025-06-17T10:46:42",
    "name": "[2/2] libcamera: pipeline: Add R-Car Gen4 ISP pipeline",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "14768e62184b2d4e0057722afa1e61d3a80d3aef",
    "submitter": {
        "id": 230,
        "url": "https://patchwork.libcamera.org/api/1.1/people/230/?format=api",
        "name": "Niklas Söderlund",
        "email": "niklas.soderlund+renesas@ragnatech.se"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/23593/mbox/",
    "series": [
        {
            "id": 5226,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5226/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5226",
            "date": "2025-06-17T10:46:40",
            "name": "Add Renesas R-Car Gen4 pipeline with IPA support",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5226/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/23593/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/23593/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 35E31BDE6B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 17 Jun 2025 10:47:02 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D8C4C68DD1;\n\tTue, 17 Jun 2025 12:47:01 +0200 (CEST)",
            "from fhigh-b4-smtp.messagingengine.com\n\t(fhigh-b4-smtp.messagingengine.com [202.12.124.155])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 72C0E68DD1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 17 Jun 2025 12:46:59 +0200 (CEST)",
            "from phl-compute-06.internal (phl-compute-06.phl.internal\n\t[10.202.2.46])\n\tby mailfhigh.stl.internal (Postfix) with ESMTP id 53E89254016F;\n\tTue, 17 Jun 2025 06:46:58 -0400 (EDT)",
            "from phl-mailfrontend-02 ([10.202.2.163])\n\tby phl-compute-06.internal (MEProxy); Tue, 17 Jun 2025 06:46:58 -0400",
            "by mail.messagingengine.com (Postfix) with ESMTPA; Tue,\n\t17 Jun 2025 06:46:57 -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=\"Ywo0hDqp\"; dkim=pass (2048-bit key;\n\tunprotected) header.d=messagingengine.com\n\theader.i=@messagingengine.com header.b=\"lyhOldAp\"; \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=1750157218;\n\tx=1750243618; bh=3hIDjuKy0aqyG84LxYb1zsT+5Ek60fem1wls5jMI90E=; b=\n\tYwo0hDqp2FyQM7BG2kjJVNnXio4grijKB+vmAEZtBjr9fbzW97dyiA2nczPd/fwY\n\tc42WKVVo3RatzHpIbIbYvthYoclj5GWp6fjvKhOCwo7IWGZgtsRfGJ2rLi86avEA\n\tPn13NhBufzsFfWJyMe0QjLLDD2P30+ozg3EiPXH0UyQEa/UDflxFotyCmzPRgMBJ\n\tsf+5phWsjp8gaFDCP3LwLKbzbEa/gd639ZD6L4y2RjKXx4QEofL12hdl0sdIiwPv\n\tQYmWIn08Hh7b3nRq2ndLnD3QaKB3ZiTzr/bUImIAo3dtdyeZlC7sW3NKXM880XH/\n\tc/7VMGAi0B1gA/PvTU5fOg==",
            "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=1750157218; x=\n\t1750243618; bh=3hIDjuKy0aqyG84LxYb1zsT+5Ek60fem1wls5jMI90E=; b=l\n\tyhOldApVMPdgc/O4UzPi3M6sQWUjBoXyHsNQquQKKcJ5mFwn+fPwBADknKv4638Z\n\tHs7N+rwRkzu4ImvEq58aD5FNcrpjmKbs8TGw3rfwIrOB3PQ2VRXQJbJkitpZu+rL\n\tA0INhjAJWLNkjQIphRHMW2CSs6RPTQownxsJHiUefl9al4IkKyemfXLM9FFtHwD2\n\tyo5HKepbqa7LjYfG0D7ay2rxX+Gav4AUO6i+YydQDDCasvFJdGKyvjzFtiCtR9gG\n\txI/kvw7jey891Cvs2zeslVMaXp6FZJRJyMNL6PYMd7NGJN7UUQNFq9jE+Xt/4dTK\n\tSJQbIAig3jU+HiuTaH4VQ=="
        ],
        "X-ME-Sender": "<xms:okdRaMZWHL4hZKjuH_2N7wyse_NMz_MJBHPANho2znkAR1WifrBqOQ>\n\t<xme:okdRaHbyhGIuCarBMmQsDRpJ23oLlWzO5nmsyItsGYRenSz8AGoduyZGKbLyW8uVz\n\tNbrSAV0ldD9jcVDI5c>",
        "X-ME-Received": "<xmr:okdRaG-YmTRPvjJC2PwNubQmnVIMZ1icMxn8xoG0xQXsduepFnByl7EcQY9t5hd0MIIq3URw3Fi8fjwLoBTRWFVzZA>",
        "X-ME-Proxy-Cause": "gggruggvucftvghtrhhoucdtuddrgeeffedrtddugddvledtfecutefuodetggdotefrod\n\tftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdp\n\tuffrtefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivg\n\thnthhsucdlqddutddtmdenucfjughrpefhvfevufffkffojghfgggtgfesthekredtredt\n\tjeenucfhrhhomheppfhikhhlrghsucfunpguvghrlhhunhguuceonhhikhhlrghsrdhsoh\n\tguvghrlhhunhguodhrvghnvghsrghssehrrghgnhgrthgvtghhrdhsvgeqnecuggftrfgr\n\tthhtvghrnhepheeigfeuveeutdefhfehgeekvedtleeuueekveefudehhffhjeffgfegff\n\telfeegnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhep\n\tnhhikhhlrghsrdhsohguvghrlhhunhgusehrrghgnhgrthgvtghhrdhsvgdpnhgspghrtg\n\thpthhtohepgedpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepjhgrtghophhordhm\n\tohhnughisehiuggvrghsohhnsghorghrugdrtghomhdprhgtphhtthhopehlrghurhgvnh\n\thtrdhpihhntghhrghrthesihguvggrshhonhgsohgrrhgurdgtohhmpdhrtghpthhtohep\n\tlhhisggtrghmvghrrgdquggvvhgvlheslhhishhtshdrlhhisggtrghmvghrrgdrohhrgh\n\tdprhgtphhtthhopehnihhklhgrshdrshhouggvrhhluhhnugdorhgvnhgvshgrshesrhgr\n\tghhnrghtvggthhdrshgv",
        "X-ME-Proxy": "<xmx:okdRaGoNkXEqw_P3LxYnTwKgRL8_yE4ZQ4sD1Yywh3G2jt8x2uT-Ug>\n\t<xmx:okdRaHq2yzK_Jddgz5pWMmOqq1RSj48AwNirozJ-5rokMH_qFdv6hw>\n\t<xmx:okdRaETqDCxQqH2Fc8YyankPQwlertQ3iULZm3_pk67EYd8Vd3ZBmA>\n\t<xmx:okdRaHpoRr0xRlyKcX1_H0z0P-oQr9JzTdIr_DqEoC5apz5uPneEzg>\n\t<xmx:okdRaKWWo3oHxnKiyhNp85x9XlCt6XUZPRvWfLGMuRo6Q6y-jc6HuQnb>",
        "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 2/2] libcamera: pipeline: Add R-Car Gen4 ISP pipeline",
        "Date": "Tue, 17 Jun 2025 12:46:42 +0200",
        "Message-ID": "<20250617104642.1607118-3-niklas.soderlund+renesas@ragnatech.se>",
        "X-Mailer": "git-send-email 2.49.0",
        "In-Reply-To": "<20250617104642.1607118-1-niklas.soderlund+renesas@ragnatech.se>",
        "References": "<20250617104642.1607118-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 Agc, Agw and\nBlackLevelCorrection algorithms and produce a stable and good image.\n\nSigned-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>\n---\n meson.build                                   |   1 +\n meson_options.txt                             |   1 +\n src/libcamera/pipeline/rcar-gen4/frames.cpp   | 283 ++++++\n src/libcamera/pipeline/rcar-gen4/frames.h     |  87 ++\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          | 816 ++++++++++++++++++\n src/libcamera/pipeline/rcar-gen4/vin.cpp      | 386 +++++++++\n src/libcamera/pipeline/rcar-gen4/vin.h        |  68 ++\n 10 files changed, 1921 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/meson.build b/meson.build\nindex 4ed8017eba1a..478beb27e9f9 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -214,6 +214,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..9185c1e89673\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/frames.cpp\n@@ -0,0 +1,283 @@\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+RCar4Frames::RCar4Frames()\n+{\n+}\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+\tframeInfo_.clear();\n+\n+\tbufferCount = std::max({\n+\t\t\t       rawStream_.configuration().bufferCount,\n+\t\t\t       outputStream_.configuration().bufferCount,\n+\t\t\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+\tfor (std::unique_ptr<FrameBuffer> &buffer : paramBuffers_) {\n+\t\tbuffer->setCookie(ipaBufferId++);\n+\t\tipaBuffers_.emplace_back(buffer->cookie(),\n+\t\t\t\t\t buffer->planes());\n+\n+\t\tavailableParamBuffers_.push(buffer.get());\n+\t}\n+\n+\tfor (std::unique_ptr<FrameBuffer> &buffer : statBuffers_) {\n+\t\tbuffer->setCookie(ipaBufferId++);\n+\t\tipaBuffers_.emplace_back(buffer->cookie(),\n+\t\t\t\t\t buffer->planes());\n+\n+\t\tavailableStatBuffers_.push(buffer.get());\n+\t}\n+\n+\tfor (const std::unique_ptr<FrameBuffer> &buffer : outputBuffers_)\n+\t\tavailableOutputBuffers_.push(buffer.get());\n+\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+\tstd::unique_ptr<Info> info = std::make_unique<Info>();\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+\tframeInfo_[frame] = std::move(info);\n+\n+\treturn frameInfo_[frame].get();\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.get();\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 const &itInfo : frameInfo_) {\n+\t\tInfo *info = itInfo.second.get();\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..8eab3de8d91a\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/frames.h\n@@ -0,0 +1,87 @@\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+\tRCar4Frames();\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, std::unique_ptr<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..a63c2b1f7c02\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/rcar-gen4.cpp\n@@ -0,0 +1,816 @@\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()\n+{\n+\tcamera_ = camera->shared_from_this();\n+\tdata_ = data;\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<< \" 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<< \" 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+\tsensorFormat_ =\n+\t\tdata_->vin_.sensor()->getFormat({ formatToMediaBus.at(vinCfg.pixelFormat) },\n+\t\t\t\t\t\tvinCfg.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\n+\t\t= 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{ sensorInfo,\n+\t\tdata->vin_.sensor()->controls(),\n+\t\tV4L2_META_FMT_RK_ISP1_EXT_PARAMS };\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()->entity()->name();\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..46c647f9a10e\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/vin.cpp\n@@ -0,0 +1,386 @@\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+RCarVINDevice::RCarVINDevice()\n+{\n+}\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\tconst 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\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..02f98a874a7f\n--- /dev/null\n+++ b/src/libcamera/pipeline/rcar-gen4/vin.h\n@@ -0,0 +1,68 @@\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+\tRCarVINDevice();\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": [
        "2/2"
    ]
}