Patch Detail
Show a patch.
GET /api/patches/27508/?format=api
{ "id": 27508, "url": "https://patchwork.libcamera.org/api/patches/27508/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27508/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/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": "<20260725135445.14555-1-magdum.foss@gmail.com>", "date": "2026-07-25T13:49:54", "name": "[v2,RFC,v2,1/1] pipeline: tegra: Add NVIDIA Tegra VI pipeline handler", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "f50f5fba80a8bb7daa4fa2103c4c2bbc9bfa1d7c", "submitter": { "id": 453, "url": "https://patchwork.libcamera.org/api/people/453/?format=api", "name": "Magdum", "email": "magdum.foss@gmail.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27508/mbox/", "series": [ { "id": 6083, "url": "https://patchwork.libcamera.org/api/series/6083/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6083", "date": "2026-07-25T13:49:54", "name": "[v2,RFC,v2,1/1] pipeline: tegra: Add NVIDIA Tegra VI pipeline handler", "version": 2, "mbox": "https://patchwork.libcamera.org/series/6083/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/27508/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27508/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 02406C3264\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 25 Jul 2026 13:54:51 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4779367F48;\n\tSat, 25 Jul 2026 15:54:51 +0200 (CEST)", "from mail-wm1-x330.google.com (mail-wm1-x330.google.com\n\t[IPv6:2a00:1450:4864:20::330])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CAC5F67F42\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 25 Jul 2026 15:54:49 +0200 (CEST)", "by mail-wm1-x330.google.com with SMTP id\n\t5b1f17b1804b1-49545ba3d4eso7476475e9.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 25 Jul 2026 06:54:49 -0700 (PDT)", "from magdum-System-Product-Name.vodafone.ultrahub\n\t([2a02:810d:4b14:4600:4c66:91af:4d7a:df5e])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-496b485f65csm71146145e9.5.2026.07.25.06.54.47\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tSat, 25 Jul 2026 06:54:48 -0700 (PDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"CRTZY9JU\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=gmail.com; s=20251104; t=1784987689; x=1785592489;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:content-type:mime-version:references\n\t:in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject\n\t:date:message-id:reply-to:content-type;\n\tbh=FGM57AsKLVEIQin8DQRzUee9c7ro3dp0SaE+FtGHE5s=;\n\tb=CRTZY9JUXYh3Vge0v9uoYkTGiSnnpSgD33rqfQlIc53E4xmmOpSRycEJW/6OcPbF/Q\n\tcxNhcRy+rl3v1t5jP2B6xfx7drvzJVWbnKDq68x2CWqyopqYGbAd3cVzdGH1PZ7uJyI/\n\t6Z7f3ipUy6EDC6GtKjRrVrmkKnbiW0dbLMGbqAfBw/Aw50AwmEf70r2379Qw3urrCjAj\n\tfDj8Ta2LF0ssJpW0ucJVjPcna7VBqtGGxiQm/2oOeiGMfxMU1GucKKOFXaIs30nNnqdZ\n\tPUivPukdUL7G+8ms/mUP+mnbqkaAOGjrs7UcU04ch28SEoCFWxPPUB54LYcWpB21wlD0\n\tdQqQ==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1784987689; x=1785592489;\n\th=content-transfer-encoding:content-type:mime-version:references\n\t:in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to\n\t:content-type;\n\tbh=FGM57AsKLVEIQin8DQRzUee9c7ro3dp0SaE+FtGHE5s=;\n\tb=IpJsLzr9jwFy1/4Yhho3/ZsJOoy764vEPK4Lj69oo33t9sBFmqMGHv8QgP0zBg05Iz\n\tUiTyGeP9OZ8YWHvVYP900iEbEC28sLQjbzJvHfauybvMZIVe6p/bGIjso2eNg/TwELBk\n\tvMbElFoWguqfSzFMlef4/WKUONEQRRhM0gL6WjN9soDPhLxF86aofKdEoDqehFPSA+Zu\n\ttPl08vfL6yHGPEhj5bsFuOs4Xf3nDGJx4dJf0/aB3/kZTd78I5cwyxTORGLoaSjSvHvy\n\tAXLdl5Uy9Qp4P/Hg/TC6d0rEbC2rE3LXwPk4XpRxCDhlBpWO644O2brPuh/BWzaDfXQY\n\t76cw==", "X-Gm-Message-State": "AOJu0YyvBza2QfI7YvNZvBcmyltlH4KJQevOLT5Xb7kjI5sDOq5o0KXc\n\tX8Tb+8nRnruZmPonfyWWs1C2vkADZZjQTfboa5zUtkIPBH0OuFe0f/V6aH9RLgZ2SKY=", "X-Gm-Gg": "AR+sD12yavW1bvYmRKWRLoq4p+gCFJI3wb+RqOmElY09PNmG6YsLT8FsYJyF3Mu4rTa\n\th0ecH452VjordhQkCn5lnqJUAZFCGE+d4HzOmQlBFX89tJb11x38P1cm6c3YrgAEnSgHxsM05Ze\n\tkUlOr9cpU4qzYn8/yRtfQ866AznIdOgM0GphxEY1iwC4KCN8FIM35iH043URbmbYkTk8H6wRZPz\n\tuxVOj6zRSiy9rbTGw3K6AUDCirTn1TM+F96igUtervykphS7T/hcGS2KRxA2vAIsrMAL9ZJCPVF\n\t5TYSAqiRP9+4v0JjGVt5URWyt+lfzOSgB4+MB6/mv61GnroSrCgTYNgZSp14TRmdKA4H0mlr7Aw\n\tTkttDcBtlPjOUpN/Do+bvVbdItElhb1FKZzSZF8yJm7m/Cg2AmDW0YvomKDJgIvBzdNvXILpeBr\n\tNIOsyrRpLKxag5AvIBBl4fYbm7uqQ9R4uEuE3Ui3mV8tv/lh8L2r/7", "X-Received": "by 2002:a05:600c:4d06:b0:495:7a23:1eee with SMTP id\n\t5b1f17b1804b1-496b56f39d0mr17843775e9.12.1784987688596; \n\tSat, 25 Jul 2026 06:54:48 -0700 (PDT)", "From": "Magdum <magdum.foss@gmail.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "magdum.foss@gmail.com", "Subject": "[PATCH v2] [RFC v2 1/1] pipeline: tegra: Add NVIDIA Tegra VI\n\tpipeline handler", "Date": "Sat, 25 Jul 2026 15:49:54 +0200", "Message-ID": "<20260725135445.14555-1-magdum.foss@gmail.com>", "X-Mailer": "git-send-email 2.43.0", "In-Reply-To": "<20260724174705.9411-2-magdum.foss@gmail.com>", "References": "<20260724174705.9411-2-magdum.foss@gmail.com>", "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": "Introduce a new pipeline handler for the upstream tegra-video\nV4L2/Media Controller driver on Jetson platforms.\n\nThe handler builds a raw Bayer capture path from sensor -> CSI -> VI and\nfeeds frames into SoftwareISP for debayer and IPA-driven processing before\ncompleting user requests.\n\nKey points:\n Route raw Bayer frames from the VI engine through SoftwareISP\n Enable SoftISP automatically in Meson when the tegra pipeline is selected\n Handle per-frame synchronization using DelayedControls\n Match the upstream media driver name tegra-vi\n This keeps the implementation aligned with libcamera upstream policy by\n targeting upstream kernel driver naming only.\n\nSigned-off-by: Magdum <magdum.foss@gmail.com>\n\nChanges in v2:\n\nDrop tegra234-vi matching\nMatch only tegra-vi\n---\nHello Laurent,\n\nThanks for the review feedback on v1.\n\nThis v2 updates the Tegra pipeline proposal to align with libcamera upstream policy: support is restricted to upstream kernel drivers only.\nIn particular, v2 removes matching for tegra234-vi and keeps discovery limited to the upstream tegra-vi media driver.\nNo downstream-only driver names are used.\n\nThe intent of this series remains unchanged: add a Tegra VI + SoftwareISP pipeline for upstream tegra-video based platforms.\nIf a distinct Tegra234 driver is introduced upstream later, support for it will be proposed separately in a follow-up series\nonce the kernel side is in place.\n\nChanges in v2:\nDrop tegra234-vi from driver matching.\nMatch only upstream tegra-vi.\nClarify commit message wording around upstream-only support.\n\n include/libcamera/ipa/meson.build | 1 +\n meson_options.txt | 1 +\n src/libcamera/pipeline/tegra/meson.build | 5 +\n src/libcamera/pipeline/tegra/tegra.cpp | 1100 ++++++++++++++++++++++\n src/libcamera/software_isp/meson.build | 2 +-\n 5 files changed, 1108 insertions(+), 1 deletion(-)\n create mode 100644 src/libcamera/pipeline/tegra/meson.build\n create mode 100644 src/libcamera/pipeline/tegra/tegra.cpp", "diff": "diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build\nindex 3ee3ada30..ed0acd133 100644\n--- a/include/libcamera/ipa/meson.build\n+++ b/include/libcamera/ipa/meson.build\n@@ -69,6 +69,7 @@ pipeline_ipa_mojom_mapping = {\n 'rpi/pisp': 'raspberrypi.mojom',\n 'rpi/vc4': 'raspberrypi.mojom',\n 'simple': 'soft.mojom',\n+ 'tegra': 'soft.mojom',\n 'vimc': 'vimc.mojom',\n }\n \ndiff --git a/meson_options.txt b/meson_options.txt\nindex 20baacc4f..8f7ac1dbf 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -85,6 +85,7 @@ option('pipelines',\n 'rpi/pisp',\n 'rpi/vc4',\n 'simple',\n+ 'tegra',\n 'uvcvideo',\n 'vimc',\n 'virtual'\ndiff --git a/src/libcamera/pipeline/tegra/meson.build b/src/libcamera/pipeline/tegra/meson.build\nnew file mode 100644\nindex 000000000..c8cb0ff97\n--- /dev/null\n+++ b/src/libcamera/pipeline/tegra/meson.build\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_sources += files([\n+ 'tegra.cpp',\n+])\ndiff --git a/src/libcamera/pipeline/tegra/tegra.cpp b/src/libcamera/pipeline/tegra/tegra.cpp\nnew file mode 100644\nindex 000000000..39ca8470f\n--- /dev/null\n+++ b/src/libcamera/pipeline/tegra/tegra.cpp\n@@ -0,0 +1,1100 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, Magdum <magdum.foss@gmail.com>\n+ *\n+ * Pipeline handler for NVIDIA Tegra VI (Video Input)\n+ *\n+ * Supports Jetson platforms (Nano / TX2 / Xavier / Orin) using the\n+ * upstream tegra-video V4L2 / Media Controller driver.\n+ *\n+ * Pipeline topology\n+ * -----------------\n+ *\n+ * [Camera Sensor (I2C)]\n+ * | (MIPI CSI-2)\n+ * [tegra-csi V4L2 subdev] <-- sets Bayer bus format and lane count\n+ * |\n+ * [tegra-vi V4L2 capture] <-- DMA of raw Bayer frames into memory\n+ * |\n+ * [SoftwareIsp] <-- debayer, AGC, AWB, CCM, gamma\n+ * |\n+ * [User FrameBuffer]\n+ *\n+ * The capture video device captures raw Bayer frames into a small ring of\n+ * internal buffers. Each completed raw frame is handed to SoftwareIsp,\n+ * which debayers it into the user-supplied output buffer and then\n+ * completes the associated Request.\n+ *\n+ * Hardware ISP support (ISP7 on Tegra234) can be added in a follow-up\n+ * once the in-kernel tegra-isp driver is upstreamed.\n+ */\n+\n+#include <algorithm>\n+#include <map>\n+#include <memory>\n+#include <queue>\n+#include <set>\n+#include <string>\n+#include <unordered_map>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/color_space.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/pixel_format.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/camera_sensor_properties.h\"\n+#include \"libcamera/internal/delayed_controls.h\"\n+#include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/formats.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/request.h\"\n+#include \"libcamera/internal/software_isp/software_isp.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(TegraPipeline)\n+\n+/* Number of internal raw capture buffers in the VI ring. */\n+static constexpr unsigned int kNumRawBuffers = 4;\n+\n+/*\n+ * Select the smallest sensor size that covers the requested output size.\n+ * Falls back to the largest available size if none is large enough.\n+ */\n+static bool selectSensorSize(const std::vector<Size> &sensorSizes,\n+\t\t\t const Size &requested, Size *sensorSize)\n+{\n+\tif (sensorSizes.empty())\n+\t\treturn false;\n+\n+\tfor (const Size &size : sensorSizes) {\n+\t\tif (size.width >= requested.width &&\n+\t\t size.height >= requested.height) {\n+\t\t\t*sensorSize = size;\n+\t\t\treturn true;\n+\t\t}\n+\t}\n+\n+\t*sensorSize = sensorSizes.back();\n+\treturn true;\n+}\n+\n+/* -------------------------------------------------------------------------\n+ * Per-frame bookkeeping\n+ * -------------------------------------------------------------------------\n+ *\n+ * We need to know, for every in-flight frame sequence number:\n+ * - which Request it belongs to\n+ * - whether the SoftwareIsp will emit metadata (so we wait for it)\n+ * - whether that metadata has already arrived\n+ */\n+struct TegraFrameInfo {\n+\tTegraFrameInfo(Request *r, bool m)\n+\t\t: request(r), metadataRequired(m),\n+\t\t metadataProcessed(false)\n+\t{\n+\t}\n+\n+\tRequest *request;\n+\tbool metadataRequired;\n+\tbool metadataProcessed;\n+};\n+\n+/* -------------------------------------------------------------------------\n+ * Forward declarations\n+ * -------------------------------------------------------------------------*/\n+class PipelineHandlerTegra;\n+\n+/* -------------------------------------------------------------------------\n+ * TegraCameraData\n+ * -------------------------------------------------------------------------\n+ *\n+ * Per-camera state: owns the sensor, CSI subdev, VI video device and the\n+ * software ISP instance.\n+ */\n+class TegraCameraData : public Camera::Private\n+{\n+public:\n+\tTegraCameraData(PipelineHandlerTegra *pipe,\n+\t\t\tstd::shared_ptr<MediaDevice> media);\n+\n+\tint init();\n+\n+\t/* Callbacks */\n+\tvoid imageBufferReady(FrameBuffer *buffer);\n+\tvoid ispOutputBufferReady(FrameBuffer *buffer);\n+\tvoid clearIncompleteRequests();\n+\tvoid ispStatsReady(uint32_t frame, uint32_t bufferId);\n+\tvoid metadataReady(uint32_t frame, const ControlList &metadata);\n+\tvoid setSensorControls(const ControlList &sensorControls);\n+\n+\tPipelineHandlerTegra *pipe();\n+\n+\t/* Hardware resources */\n+\tstd::shared_ptr<MediaDevice> media_;\n+\tstd::unique_ptr<CameraSensor> sensor_;\n+\tstd::unique_ptr<V4L2Subdevice> csi_;\n+\tstd::unique_ptr<V4L2VideoDevice> video_;\n+\tconst MediaEntity *videoEntity_;\n+\n+\t/* Software ISP (debayer + IPA) */\n+\tstd::unique_ptr<SoftwareIsp> swIsp_;\n+\n+\t/* Delayed sensor controls (exposure / gain) */\n+\tstd::unique_ptr<DelayedControls> delayedCtrls_;\n+\n+\t/* The single output stream exposed to the application */\n+\tStream stream_;\n+\n+\t/*\n+\t * Ring of internal raw capture buffers.\n+\t * These are allocated once at start() and pre-queued to the VI node.\n+\t */\n+\tstd::vector<std::unique_ptr<FrameBuffer>> rawBuffers_;\n+\n+\t/*\n+\t * Queue of pending requests: each entry pairs a Request with the\n+\t * user-supplied output FrameBuffer. The head entry is consumed when\n+\t * the next raw frame arrives from the VI device.\n+\t */\n+\tstruct PendingRequest {\n+\t\tRequest *request;\n+\t\tFrameBuffer *outputBuffer;\n+\t};\n+\tstd::queue<PendingRequest> pendingRequests_;\n+\n+\t/* Per-frame metadata tracking */\n+\tstd::map<uint32_t, TegraFrameInfo> frameInfos_;\n+\n+private:\n+\tvoid tryCompleteRequest(Request *request);\n+};\n+\n+/* -------------------------------------------------------------------------\n+ * TegraCameraConfiguration\n+ * -------------------------------------------------------------------------\n+ *\n+ * Validates and adjusts the stream configuration requested by the\n+ * application. Only a single output stream is supported.\n+ */\n+class TegraCameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tTegraCameraConfiguration(Camera *camera, TegraCameraData *data);\n+\n+\tStatus validate() override;\n+\n+\tstatic constexpr unsigned int kNumBuffersDefault = 4;\n+\tstatic constexpr unsigned int kNumBuffersMax = 32;\n+\n+private:\n+\tstd::shared_ptr<Camera> camera_;\n+\tTegraCameraData *data_;\n+};\n+\n+/* -------------------------------------------------------------------------\n+ * PipelineHandlerTegra\n+ * -------------------------------------------------------------------------*/\n+class PipelineHandlerTegra : public PipelineHandler\n+{\n+public:\n+\texplicit PipelineHandlerTegra(CameraManager *manager);\n+\n+\tstd::unique_ptr<CameraConfiguration>\n+\tgenerateConfiguration(Camera *camera,\n+\t\t\t Span<const StreamRole> roles) override;\n+\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+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+protected:\n+\tint queueRequestDevice(Camera *camera, Request *request) override;\n+\n+private:\n+\tTegraCameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<TegraCameraData *>(camera->_d());\n+\t}\n+\n+\t/*\n+\t * Traverse the media graph starting from a camera sensor entity and\n+\t * return the first CSI bridge and VI capture node found on the path.\n+\t * Returns true on success.\n+\t */\n+\tstatic bool findPipelineEntities(MediaEntity *sensorEntity,\n+\t\t\t\t\t MediaEntity **csiEntity,\n+\t\t\t\t\t MediaEntity **viEntity);\n+\n+\tbool createCamera(std::shared_ptr<MediaDevice> media,\n+\t\t\t MediaEntity *sensorEntity);\n+};\n+\n+/* =========================================================================\n+ * TegraCameraData implementation\n+ * =========================================================================*/\n+\n+TegraCameraData::TegraCameraData(PipelineHandlerTegra *pipe,\n+\t\t\t\t std::shared_ptr<MediaDevice> media)\n+\t: Camera::Private(pipe), media_(media), videoEntity_(nullptr)\n+{\n+}\n+\n+PipelineHandlerTegra *TegraCameraData::pipe()\n+{\n+\treturn static_cast<PipelineHandlerTegra *>(Camera::Private::pipe());\n+}\n+\n+/*\n+ * Open all V4L2 devices, create the CameraSensor and SoftwareIsp instances,\n+ * and set up the DelayedControls helper.\n+ */\n+int TegraCameraData::init()\n+{\n+\tint ret;\n+\n+\t/* Open the CSI bridge subdev. */\n+\tret = csi_->open();\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to open CSI subdev: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/* Open the VI video device. */\n+\tret = video_->open();\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to open VI video device: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/* Build the DelayedControls helper for sensor exposure / gain. */\n+\tconst CameraSensorProperties::SensorDelays &delays =\n+\t\tsensor_->sensorDelays();\n+\n+\tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n+\t\t{ V4L2_CID_ANALOGUE_GAIN,\n+\t\t { delays.gainDelay, false } },\n+\t\t{ V4L2_CID_EXPOSURE,\n+\t\t { delays.exposureDelay, false } },\n+\t};\n+\tdelayedCtrls_ =\n+\t\tstd::make_unique<DelayedControls>(sensor_->device(), params);\n+\n+\t/* Create the Software ISP (provides debayering + AGC/AWB via IPA). */\n+\tswIsp_ = std::make_unique<SoftwareIsp>(pipe(), sensor_.get(),\n+\t\t\t\t\t &controlInfo_);\n+\tif (!swIsp_->isValid()) {\n+\t\tLOG(TegraPipeline, Error) << \"Failed to create software ISP\";\n+\t\tswIsp_.reset();\n+\t\treturn -ENODEV;\n+\t}\n+\n+\t/* Wire software ISP signals. */\n+\tswIsp_->outputBufferReady.connect(this,\n+\t\t\t\t\t &TegraCameraData::ispOutputBufferReady);\n+\tswIsp_->ispStatsReady.connect(this, &TegraCameraData::ispStatsReady);\n+\tswIsp_->metadataReady.connect(this, &TegraCameraData::metadataReady);\n+\tswIsp_->setSensorControls.connect(this,\n+\t\t\t\t\t &TegraCameraData::setSensorControls);\n+\n+\treturn 0;\n+}\n+\n+/* Called by the VI video device when a raw frame DMA is complete. */\n+void TegraCameraData::imageBufferReady(FrameBuffer *buffer)\n+{\n+\tPipelineHandlerTegra *pipe = TegraCameraData::pipe();\n+\n+\t/* On error or cancellation just complete the request immediately. */\n+\tif (buffer->metadata().status != FrameMetadata::FrameSuccess) {\n+\t\tif (pendingRequests_.empty()) {\n+\t\t\t/* Stale buffer with no associated request – requeue. */\n+\t\t\tvideo_->queueBuffer(buffer);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tPendingRequest &pending = pendingRequests_.front();\n+\t\tRequest *request = pending.request;\n+\n+\t\tauto it = frameInfos_.find(request->sequence());\n+\t\tif (it != frameInfos_.end())\n+\t\t\tit->second.metadataRequired = false;\n+\n+\t\tif (pipe->completeBuffer(request, pending.outputBuffer))\n+\t\t\ttryCompleteRequest(request);\n+\t\tpendingRequests_.pop();\n+\n+\t\t/*\n+\t\t * If the buffer was cancelled we stop; otherwise it is an\n+\t\t * error frame so requeue the internal buffer for the next\n+\t\t * capture.\n+\t\t */\n+\t\tif (buffer->metadata().status != FrameMetadata::FrameCancelled)\n+\t\t\tvideo_->queueBuffer(buffer);\n+\n+\t\treturn;\n+\t}\n+\n+\t/* Stamp the sensor timestamp into request metadata via the correct sequence lookup. */\n+\tif (!pendingRequests_.empty()) {\n+\t\tRequest *request = pendingRequests_.front().request;\n+\t\tauto it = frameInfos_.find(request->sequence());\n+\t\tif (it != frameInfos_.end()) {\n+\t\t\trequest->_d()->metadata().set(controls::SensorTimestamp,\n+\t\t\t\t\t\t buffer->metadata().timestamp);\n+\t\t}\n+\t}\n+\n+\t/* No pending request: requeue the internal buffer and wait. */\n+\tif (pendingRequests_.empty()) {\n+\t\tvideo_->queueBuffer(buffer);\n+\t\treturn;\n+\t}\n+\n+\t/* Hand the raw frame and the output buffer to the software ISP. */\n+\tPendingRequest &pending = pendingRequests_.front();\n+\tstd::map<const Stream *, FrameBuffer *> outputs{\n+\t\t{ &stream_, pending.outputBuffer }\n+\t};\n+\n+\tconst int ret = swIsp_->queueBuffers(pending.request->sequence(),\n+\t\t\t\t\t buffer, outputs);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to queue buffers to software ISP: \" << ret;\n+\t\tif (auto it = frameInfos_.find(pending.request->sequence());\n+\t\t it != frameInfos_.end())\n+\t\t\tit->second.metadataRequired = false;\n+\t\tif (pipe->completeBuffer(pending.request, pending.outputBuffer))\n+\t\t\ttryCompleteRequest(pending.request);\n+\t\tpendingRequests_.pop();\n+\t\tvideo_->queueBuffer(buffer);\n+\t\treturn;\n+\t}\n+\n+\tpendingRequests_.pop();\n+}\n+\n+void TegraCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId)\n+{\n+\tswIsp_->processStats(frame, bufferId, delayedCtrls_->get(frame));\n+}\n+\n+void TegraCameraData::metadataReady(uint32_t frame,\n+\t\t\t\t const ControlList &metadata)\n+{\n+\tauto it = frameInfos_.find(frame);\n+\tif (it == frameInfos_.end())\n+\t\treturn;\n+\n+\tTegraFrameInfo &info = it->second;\n+\tinfo.request->_d()->metadata().merge(metadata);\n+\tinfo.metadataProcessed = true;\n+\ttryCompleteRequest(info.request);\n+}\n+\n+void TegraCameraData::setSensorControls(\n+\tconst ControlList &sensorControls)\n+{\n+\tdelayedCtrls_->push(sensorControls);\n+}\n+\n+void TegraCameraData::ispOutputBufferReady(FrameBuffer *buffer)\n+{\n+\tRequest *request = buffer->request();\n+\tif (!request)\n+\t\treturn;\n+\n+\tif (pipe()->completeBuffer(request, buffer))\n+\t\ttryCompleteRequest(request);\n+}\n+\n+void TegraCameraData::tryCompleteRequest(Request *request)\n+{\n+\tif (request->hasPendingBuffers())\n+\t\treturn;\n+\n+\tauto it = frameInfos_.find(request->sequence());\n+\tif (it == frameInfos_.end())\n+\t\treturn;\n+\n+\tconst TegraFrameInfo &info = it->second;\n+\tif (info.metadataRequired && !info.metadataProcessed)\n+\t\treturn;\n+\n+\tframeInfos_.erase(it);\n+\tpipe()->completeRequest(request);\n+}\n+\n+void TegraCameraData::clearIncompleteRequests()\n+{\n+\twhile (!pendingRequests_.empty()) {\n+\t\tpipe()->cancelRequest(pendingRequests_.front().request);\n+\t\tpendingRequests_.pop();\n+\t}\n+}\n+\n+/* =========================================================================\n+ * TegraCameraConfiguration implementation\n+ * =========================================================================*/\n+\n+TegraCameraConfiguration::TegraCameraConfiguration(Camera *camera,\n+\t\t\t\t\t\t TegraCameraData *data)\n+\t: CameraConfiguration(),\n+\t camera_(camera->shared_from_this()),\n+\t data_(data)\n+{\n+}\n+\n+CameraConfiguration::Status TegraCameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\t/* Only a single output stream is supported. */\n+\tif (config_.size() > 1) {\n+\t\tconfig_.resize(1);\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/*\n+\t * Fix the orientation: the Tegra VI pipeline does not support\n+\t * hardware transforms, so only Rotate0 is valid.\n+\t */\n+\tif (orientation != Orientation::Rotate0) {\n+\t\torientation = Orientation::Rotate0;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\tStreamConfiguration &cfg = config_[0];\n+\n+\t/* Validate that the requested pixel format is supported by swIsp. */\n+\tif (!data_->swIsp_)\n+\t\treturn Invalid;\n+\n+\t/*\n+\t * Use the first sensor format to probe what swIsp can produce, then\n+\t * check whether the requested format is in that list.\n+\t */\n+\tconst std::vector<uint32_t> mbusCodes = data_->sensor_->mbusCodes();\n+\tif (mbusCodes.empty())\n+\t\treturn Invalid;\n+\n+\tconst uint32_t primaryCode = mbusCodes.front();\n+\tconst std::vector<Size> &sensorSizes = data_->sensor_->sizes(primaryCode);\n+\tif (sensorSizes.empty())\n+\t\treturn Invalid;\n+\n+\tconst Size &maxSensorSize = sensorSizes.back();\n+\tconst BayerFormat bayerFmt = BayerFormat::fromMbusCode(primaryCode);\n+\tconst std::vector<PixelFormat> supportedFormats =\n+\t\tdata_->swIsp_->formats(\n+\t\t\tbayerFmt.isValid() ? bayerFmt.toPixelFormat()\n+\t\t\t\t\t : formats::SRGGB10);\n+\n+\tif (!supportedFormats.empty()) {\n+\t\tauto it = std::find(supportedFormats.begin(),\n+\t\t\t\t supportedFormats.end(),\n+\t\t\t\t cfg.pixelFormat);\n+\t\tif (it == supportedFormats.end()) {\n+\t\t\tcfg.pixelFormat = supportedFormats.front();\n+\t\t\tLOG(TegraPipeline, Debug)\n+\t\t\t\t<< \"Adjusting pixel format to \"\n+\t\t\t\t<< cfg.pixelFormat;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\t}\n+\n+\t/* Clamp size to sensor output range. */\n+\tSizeRange outputSizes =\n+\t\tdata_->swIsp_->sizes(\n+\t\t\tbayerFmt.isValid() ? bayerFmt.toPixelFormat()\n+\t\t\t\t\t : formats::SRGGB10,\n+\t\t\tmaxSensorSize);\n+\n+\tif (!outputSizes.contains(cfg.size)) {\n+\t\tcfg.size = cfg.size.boundedTo(outputSizes.max)\n+\t\t\t\t .expandedTo(outputSizes.min);\n+\t\tLOG(TegraPipeline, Debug)\n+\t\t\t<< \"Adjusting size to \" << cfg.size;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/* Fix up color space. */\n+\tif (!cfg.colorSpace) {\n+\t\tconst PixelFormatInfo &info =\n+\t\t\tPixelFormatInfo::info(cfg.pixelFormat);\n+\t\tswitch (info.colourEncoding) {\n+\t\tcase PixelFormatInfo::ColourEncodingRGB:\n+\t\t\tcfg.colorSpace = ColorSpace::Srgb;\n+\t\t\tbreak;\n+\t\tcase PixelFormatInfo::ColourEncodingYUV:\n+\t\t\tcfg.colorSpace = ColorSpace::Sycc;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tcfg.colorSpace = ColorSpace::Raw;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tcfg.colorSpace->adjust(cfg.pixelFormat);\n+\n+\t/* Determine stride and frame size via swIsp. */\n+\tauto [stride, frameSize] =\n+\t\tdata_->swIsp_->strideAndFrameSize(cfg.pixelFormat, cfg.size);\n+\tif (stride == 0)\n+\t\treturn Invalid;\n+\n+\tcfg.stride = stride;\n+\tcfg.frameSize = frameSize;\n+\n+\tif (!cfg.bufferCount)\n+\t\tcfg.bufferCount = TegraCameraConfiguration::kNumBuffersDefault;\n+\telse if (cfg.bufferCount > TegraCameraConfiguration::kNumBuffersMax)\n+\t\tcfg.bufferCount = TegraCameraConfiguration::kNumBuffersMax;\n+\n+\treturn status;\n+}\n+\n+/* =========================================================================\n+ * PipelineHandlerTegra implementation\n+ * =========================================================================*/\n+\n+PipelineHandlerTegra::PipelineHandlerTegra(CameraManager *manager)\n+\t: PipelineHandler(manager)\n+{\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* generateConfiguration */\n+/* ---------------------------------------------------------------------- */\n+\n+std::unique_ptr<CameraConfiguration>\n+PipelineHandlerTegra::generateConfiguration(Camera *camera,\n+\t\t\t\t\t Span<const StreamRole> roles)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\n+\tauto config = std::make_unique<TegraCameraConfiguration>(camera, data);\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\t/*\n+\t * Build the set of output formats / sizes that swIsp can produce\n+\t * for the primary sensor media bus code.\n+\t */\n+\tif (!data->swIsp_ || data->sensor_->mbusCodes().empty())\n+\t\treturn nullptr;\n+\n+\tconst uint32_t primaryCode = data->sensor_->mbusCodes().front();\n+\tconst BayerFormat bayer = BayerFormat::fromMbusCode(primaryCode);\n+\tif (!bayer.isValid())\n+\t\treturn nullptr;\n+\tconst PixelFormat bayerFmt = bayer.toPixelFormat();\n+\n+\tconst std::vector<PixelFormat> outFormats =\n+\t\tdata->swIsp_->formats(bayerFmt);\n+\tif (outFormats.empty())\n+\t\treturn nullptr;\n+\n+\t/* Use the largest available sensor size for the default. */\n+\tconst std::vector<Size> &sensorSizes =\n+\t\tdata->sensor_->sizes(primaryCode);\n+\tif (sensorSizes.empty())\n+\t\treturn nullptr;\n+\n+\tconst Size &maxSize = sensorSizes.back();\n+\tSizeRange outSizes = data->swIsp_->sizes(bayerFmt, maxSize);\n+\n+\t/* Build StreamFormats map for the processed stream. */\n+\tstd::map<PixelFormat, std::vector<SizeRange>> fmtMap;\n+\tfor (const PixelFormat &fmt : outFormats)\n+\t\tfmtMap[fmt] = { outSizes };\n+\n+\t/*\n+\t * Only a single output stream is supported; silently accept any role\n+\t * and return one stream configuration.\n+\t */\n+\tStreamConfiguration cfg{ StreamFormats{ fmtMap } };\n+\tcfg.pixelFormat = outFormats.front();\n+\tcfg.size = outSizes.max;\n+\tcfg.bufferCount = TegraCameraConfiguration::kNumBuffersDefault;\n+\n+\tconfig->addConfiguration(cfg);\n+\tconfig->validate();\n+\n+\treturn config;\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* configure */\n+/* ---------------------------------------------------------------------- */\n+\n+int PipelineHandlerTegra::configure(Camera *camera,\n+\t\t\t\t CameraConfiguration *c)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\tStreamConfiguration &cfg = c->at(0);\n+\tint ret;\n+\n+\t/*\n+\t * Disable all media links on the board to start from a clean state,\n+\t * then re-enable the sensor → CSI → VI path.\n+\t */\n+\tret = data->media_->disableLinks();\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to disable media links: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/* Enable sensor → CSI link. */\n+\tconst MediaEntity *sensorEntity = data->sensor_->entity();\n+\tfor (MediaPad *pad : sensorEntity->pads()) {\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SOURCE))\n+\t\t\tcontinue;\n+\t\tfor (MediaLink *link : pad->links()) {\n+\t\t\tif (link->sink()->entity() ==\n+\t\t\t data->csi_->entity()) {\n+\t\t\t\tret = link->setEnabled(true);\n+\t\t\t\tif (ret)\n+\t\t\t\t\treturn ret;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Enable CSI → VI link. */\n+\tfor (MediaPad *pad : data->csi_->entity()->pads()) {\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SOURCE))\n+\t\t\tcontinue;\n+\t\tfor (MediaLink *link : pad->links()) {\n+\t\t\tif (link->sink()->entity() ==\n+\t\t\t data->videoEntity_) {\n+\t\t\t\tret = link->setEnabled(true);\n+\t\t\t\tif (ret)\n+\t\t\t\t\treturn ret;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Select the best sensor format: smallest resolution that covers\n+\t * the requested output size.\n+\t */\n+\tif (data->sensor_->mbusCodes().empty()) {\n+\t\tLOG(TegraPipeline, Error) << \"Sensor has no media bus codes\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tconst uint32_t primaryCode = data->sensor_->mbusCodes().front();\n+\tconst std::vector<Size> &sensorSizes = data->sensor_->sizes(primaryCode);\n+\tSize sensorSize;\n+\tif (!selectSensorSize(sensorSizes, cfg.size, &sensorSize)) {\n+\t\tLOG(TegraPipeline, Error) << \"Sensor has no supported sizes\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tV4L2SubdeviceFormat sensorFmt{};\n+\tsensorFmt.code = primaryCode;\n+\tsensorFmt.size = sensorSize;\n+\n+\tret = data->sensor_->setFormat(&sensorFmt);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to set sensor format: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Propagate the format through the CSI bridge.\n+\t * Set it on sink pad 0, then read back the source pad 1 format.\n+\t */\n+\tret = data->csi_->setFormat(0, &sensorFmt);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to set CSI sink format: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\tV4L2SubdeviceFormat csiFmt{};\n+\tret = data->csi_->getFormat(1, &csiFmt);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to get CSI source format: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Set the raw Bayer format on the VI capture node.\n+\t * The stride hint from swIsp allows it to use its preferred layout.\n+\t */\n+\tconst BayerFormat captureBayer = BayerFormat::fromMbusCode(csiFmt.code);\n+\tif (!captureBayer.isValid()) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"CSI source format is not a Bayer format: \"\n+\t\t\t<< csiFmt.code;\n+\t\treturn -EINVAL;\n+\t}\n+\tconst PixelFormat captureFmt = captureBayer.toPixelFormat();\n+\n+\tV4L2DeviceFormat videoFmt{};\n+\tvideoFmt.fourcc = data->video_->toV4L2PixelFormat(captureFmt);\n+\tvideoFmt.size = csiFmt.size;\n+\tvideoFmt.planes[0].bpl =\n+\t\tdata->swIsp_->preferredInputStride(captureFmt, csiFmt.size);\n+\n+\tret = data->video_->setFormat(&videoFmt);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to set VI format: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\t/* Configure the software ISP using the dynamically negotiated buffer count. */\n+\tStreamConfiguration inputCfg{};\n+\tinputCfg.pixelFormat = captureFmt;\n+\tinputCfg.size = csiFmt.size;\n+\tinputCfg.stride = videoFmt.planes[0].bpl;\n+\tinputCfg.bufferCount = cfg.bufferCount;\n+\n+\tipa::soft::IPAConfigInfo ipaConfig{};\n+\tipaConfig.sensorControls = data->sensor_->controls();\n+\n+\tstd::vector<std::reference_wrapper<const StreamConfiguration>>\n+\t\toutputCfgs{ cfg };\n+\n+\tret = data->swIsp_->configure(inputCfg, outputCfgs, ipaConfig);\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to configure software ISP: \" << ret;\n+\t\treturn ret;\n+\t}\n+\n+\tcfg.setStream(&data->stream_);\n+\n+\tLOG(TegraPipeline, Debug)\n+\t\t<< \"Configured: sensor=\" << sensorFmt\n+\t\t<< \" CSI=\" << csiFmt\n+\t\t<< \" capture=\" << captureFmt\n+\t\t<< \"@\" << csiFmt.size\n+\t\t<< \" -> output=\" << cfg.pixelFormat\n+\t\t<< \"@\" << cfg.size;\n+\n+\treturn 0;\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* exportFrameBuffers */\n+/* ---------------------------------------------------------------------- */\n+\n+int PipelineHandlerTegra::exportFrameBuffers(\n+\tCamera *camera, Stream *stream,\n+\tstd::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\tunsigned int count = stream->configuration().bufferCount;\n+\treturn data->swIsp_->exportBuffers(stream, count, buffers);\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* start */\n+/* ---------------------------------------------------------------------- */\n+\n+int PipelineHandlerTegra::start(Camera *camera,\n+\t\t\t\t[[maybe_unused]] const ControlList *controls)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\tint ret;\n+\n+\t/* Allocate internal raw ring-buffer. */\n+\tret = data->video_->allocateBuffers(kNumRawBuffers,\n+\t\t\t\t\t &data->rawBuffers_);\n+\tif (ret < static_cast<int>(kNumRawBuffers)) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to allocate raw buffers: \" << ret;\n+\t\treturn ret < 0 ? ret : -ENOMEM;\n+\t}\n+\n+\t/* Connect the VI buffer-ready signal. */\n+\tdata->video_->bufferReady.connect(data,\n+\t\t\t\t\t &TegraCameraData::imageBufferReady);\n+\n+\t/* Connect the swIsp input-buffer-ready signal to requeue raw buffers. */\n+\tdata->swIsp_->inputBufferReady.connect(\n+\t\tdata->video_.get(),\n+\t\t[data](FrameBuffer *buf) {\n+\t\t\tdata->video_->queueBuffer(buf);\n+\t\t});\n+\n+\t/* Start the software ISP worker. */\n+\tret = data->swIsp_->start();\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to start software ISP: \" << ret;\n+\t\tdata->video_->bufferReady.disconnect();\n+\t\tdata->swIsp_->inputBufferReady.disconnect();\n+\t\tdata->video_->releaseBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\tdata->delayedCtrls_->reset();\n+\n+\t/* Start the VI video device. */\n+\tret = data->video_->streamOn();\n+\tif (ret) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to start VI streaming: \" << ret;\n+\t\tdata->swIsp_->stop();\n+\t\tdata->video_->bufferReady.disconnect();\n+\t\tdata->swIsp_->inputBufferReady.disconnect();\n+\t\tdata->video_->releaseBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\t/* Pre-queue all internal raw buffers to keep the DMA pipeline full. */\n+\tfor (auto &buf : data->rawBuffers_)\n+\t\tdata->video_->queueBuffer(buf.get());\n+\n+\treturn 0;\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* stopDevice */\n+/* ---------------------------------------------------------------------- */\n+\n+void PipelineHandlerTegra::stopDevice(Camera *camera)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\n+\t/* Disconnect signals immediately so no late asynchronous callbacks fire. */\n+\tdata->video_->bufferReady.disconnect(data, &TegraCameraData::imageBufferReady);\n+\tdata->swIsp_->inputBufferReady.disconnect();\n+\tdata->swIsp_->outputBufferReady.disconnect();\n+\n+\t/* Gracefully stop worker processing threads and clear active pipelines. */\n+\tdata->swIsp_->stop();\n+\tdata->video_->streamOff();\n+\n+\t/* Safely release buffers and clear state tracking structures. */\n+\tdata->video_->releaseBuffers();\n+\tdata->rawBuffers_.clear();\n+\tdata->clearIncompleteRequests();\n+\tdata->frameInfos_.clear();\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* queueRequestDevice */\n+/* ---------------------------------------------------------------------- */\n+\n+int PipelineHandlerTegra::queueRequestDevice(Camera *camera,\n+\t\t\t\t\t Request *request)\n+{\n+\tTegraCameraData *data = cameraData(camera);\n+\n+\tFrameBuffer *outBuf = request->findBuffer(&data->stream_);\n+\tif (!outBuf) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Request has no buffer for the output stream\";\n+\t\treturn -ENOENT;\n+\t}\n+\n+\t/* Record metadata tracking for this frame. */\n+\tauto [it, inserted] = data->frameInfos_.try_emplace(\n+\t\trequest->sequence(),\n+\t\trequest, /*metadataRequired=*/true);\n+\tif (!inserted) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Duplicate sequence number \" << request->sequence();\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Forward any per-request controls to the IPA. */\n+\tdata->swIsp_->queueRequest(request->sequence(),\n+\t\t\t\t request->controls());\n+\n+\t/* Enqueue the request output for the next raw-frame arrival. */\n+\tdata->pendingRequests_.push({ request, outBuf });\n+\n+\treturn 0;\n+}\n+\n+/* ---------------------------------------------------------------------- */\n+/* match */\n+/* ---------------------------------------------------------------------- */\n+\n+/*\n+ * Given a camera sensor entity, walk downstream through the media graph to\n+ * find the first CSI bridge (MEDIA_ENT_F_VID_IF_BRIDGE) and then the VI\n+ * capture node (MEDIA_ENT_F_IO_V4L).\n+ *\n+ * Returns true when both entities are found.\n+ */\n+bool PipelineHandlerTegra::findPipelineEntities(\n+\tMediaEntity *sensorEntity,\n+\tMediaEntity **csiEntity,\n+\tMediaEntity **viEntity)\n+{\n+\t*csiEntity = nullptr;\n+\t*viEntity = nullptr;\n+\n+\tfor (MediaPad *pad : sensorEntity->pads()) {\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SOURCE))\n+\t\t\tcontinue;\n+\n+\t\tfor (MediaLink *link : pad->links()) {\n+\t\t\tMediaEntity *downstream = link->sink()->entity();\n+\n+\t\t\tif (downstream->function() ==\n+\t\t\t MEDIA_ENT_F_VID_IF_BRIDGE) {\n+\t\t\t\t*csiEntity = downstream;\n+\n+\t\t\t\t/* Follow one more hop to find the VI node. */\n+\t\t\t\tfor (MediaPad *csiPad : downstream->pads()) {\n+\t\t\t\t\tif (!(csiPad->flags() &\n+\t\t\t\t\t MEDIA_PAD_FL_SOURCE))\n+\t\t\t\t\t\tcontinue;\n+\n+\t\t\t\t\tfor (MediaLink *csiLink :\n+\t\t\t\t\t csiPad->links()) {\n+\t\t\t\t\t\tMediaEntity *vi =\n+\t\t\t\t\t\t\tcsiLink->sink()\n+\t\t\t\t\t\t\t\t->entity();\n+\t\t\t\t\t\tif (vi->function() ==\n+\t\t\t\t\t\t MEDIA_ENT_F_IO_V4L) {\n+\t\t\t\t\t\t\t*viEntity = vi;\n+\t\t\t\t\t\t\treturn true;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn false;\n+}\n+\n+bool PipelineHandlerTegra::createCamera(\n+\tstd::shared_ptr<MediaDevice> media,\n+\tMediaEntity *sensorEntity)\n+{\n+\tMediaEntity *csiEntity = nullptr;\n+\tMediaEntity *viEntity = nullptr;\n+\n+\tif (!findPipelineEntities(sensorEntity, &csiEntity, &viEntity)) {\n+\t\tLOG(TegraPipeline, Debug)\n+\t\t\t<< \"No complete sensor→CSI→VI path from \"\n+\t\t\t<< sensorEntity->name();\n+\t\treturn false;\n+\t}\n+\n+\tauto data =\n+\t\tstd::make_unique<TegraCameraData>(this, media);\n+\n+\t/* Create the camera sensor. */\n+\tdata->sensor_ =\n+\t\tCameraSensorFactoryBase::create(sensorEntity);\n+\tif (!data->sensor_) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to create sensor for \"\n+\t\t\t<< sensorEntity->name();\n+\t\treturn false;\n+\t}\n+\n+\t/* Open the CSI bridge subdev. */\n+\tdata->csi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n+\n+\t/* Open the VI video device. */\n+\tdata->video_ =\n+\t\tstd::make_unique<V4L2VideoDevice>(viEntity);\n+\tdata->videoEntity_ = viEntity;\n+\n+\tif (data->init()) {\n+\t\tLOG(TegraPipeline, Error)\n+\t\t\t<< \"Failed to initialise camera data for \"\n+\t\t\t<< sensorEntity->name();\n+\t\treturn false;\n+\t}\n+\n+\t/* Assemble the camera and register it. */\n+\tstd::set<Stream *> streams{ &data->stream_ };\n+\tconst std::string id = data->sensor_->id();\n+\n+\tstd::shared_ptr<Camera> camera =\n+\t\tCamera::create(std::move(data), id, streams);\n+\tregisterCamera(std::move(camera));\n+\n+\tLOG(TegraPipeline, Info)\n+\t\t<< \"Registered Tegra camera: \" << id\n+\t\t<< \" (\" << sensorEntity->name() << \" → \"\n+\t\t<< csiEntity->name() << \" → \"\n+\t\t<< viEntity->name() << \")\";\n+\n+\treturn true;\n+}\n+\n+bool PipelineHandlerTegra::match(DeviceEnumerator *enumerator)\n+{\n+\t/*\n+\t * Match the upstream Tegra VI driver name.\n+\t * \"tegra-vi\" – upstream tegra-video media driver\n+\t */\n+\tstatic const char *const kDriverNames[] = {\n+\t\t\"tegra-vi\",\n+\t};\n+\n+\tbool matched = false;\n+\n+\tfor (const char *driverName : kDriverNames) {\n+\t\tDeviceMatch dm(driverName);\n+\t\tstd::shared_ptr<MediaDevice> media =\n+\t\t\tacquireMediaDevice(enumerator, dm);\n+\t\tif (!media)\n+\t\t\tcontinue;\n+\n+\t\tLOG(TegraPipeline, Debug)\n+\t\t\t<< \"Found Tegra VI media device (\"\n+\t\t\t<< driverName << \")\";\n+\n+\t\t/*\n+\t\t * Enumerate all camera sensor entities and attempt to build a\n+\t\t * camera for each one.\n+\t\t */\n+\t\tfor (MediaEntity *entity : media->entities()) {\n+\t\t\tif (entity->function() != MEDIA_ENT_F_CAM_SENSOR)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (createCamera(media, entity))\n+\t\t\t\tmatched = true;\n+\t\t}\n+\t}\n+\n+\treturn matched;\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerTegra, \"tegra\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\nindex 15f9e4032..08522fc30 100644\n--- a/src/libcamera/software_isp/meson.build\n+++ b/src/libcamera/software_isp/meson.build\n@@ -1,6 +1,6 @@\n # SPDX-License-Identifier: CC0-1.0\n \n-softisp_enabled = pipelines.contains('simple')\n+softisp_enabled = pipelines.contains('simple') or pipelines.contains('tegra')\n summary({'SoftISP support' : softisp_enabled},\n bool_yn : true,\n section : 'Configuration')\n", "prefixes": [ "v2", "RFC", "v2", "1/1" ] }