Show a patch.

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

{
    "id": 22527,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/22527/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/22527/",
    "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": "<20250113093532.4054142-5-naush@raspberrypi.com>",
    "date": "2025-01-13T09:24:46",
    "name": "[v1,4/4] pipeline: rpi: Add support for Raspberry Pi 5",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "5ece47892f7604a7d5b5c6c1074736679c7a0adc",
    "submitter": {
        "id": 34,
        "url": "https://patchwork.libcamera.org/api/1.1/people/34/?format=api",
        "name": "Naushir Patuck",
        "email": "naush@raspberrypi.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/22527/mbox/",
    "series": [
        {
            "id": 4944,
            "url": "https://patchwork.libcamera.org/api/1.1/series/4944/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4944",
            "date": "2025-01-13T09:24:42",
            "name": "Raspberry Pi: Add support for Pi 5 (PiSP)",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/4944/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/22527/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/22527/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 8D74BC32F6\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 13 Jan 2025 09:35:55 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 40A576851D;\n\tMon, 13 Jan 2025 10:35:55 +0100 (CET)",
            "from mail-wr1-x42f.google.com (mail-wr1-x42f.google.com\n\t[IPv6:2a00:1450:4864:20::42f])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2CB30684F3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 13 Jan 2025 10:35:42 +0100 (CET)",
            "by mail-wr1-x42f.google.com with SMTP id\n\tffacd0b85a97d-385e0d47720so292949f8f.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 13 Jan 2025 01:35:42 -0800 (PST)",
            "from NAUSH-P-DELL.pitowers.org ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-38a8e38c6dbsm11904724f8f.55.2025.01.13.01.35.39\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 13 Jan 2025 01:35:39 -0800 (PST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"D4DZt0LJ\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1736760941; x=1737365741;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=lskSZQtS9XAjFWiyybqGHHQIOUWfNQODMbaA7pcrDmY=;\n\tb=D4DZt0LJb6UXzbXTFLDH+JagPAVPBtUfssT73iNqrL2SAoaGJCNn3ZhWTL1WJb3WYM\n\tdwZoHCtBpeBO5EXEpk5op0yRFHDZL4BA01JV9kq5Xe/NSMarJsZPUzq/+KtLk09yNSl9\n\t5RAEcwL056yPS+FTdIHjeVZJaTh383ycFdQE1/wh9+a1m1w7uQf6lXa53jXC97eGgzRq\n\tQUGT3kAg6qWCLBQp0t3B14llfKRZUJQK9toMkmi4GXu8FGcAFOe5Y9ODN6x0hXn9KOd/\n\tCcDGnzMTYEf1OQHo7ddQ//Fl/zP3ZTiHq/23s7DyeMEOWSbq+9MjUGoKjdE2/+J93AHH\n\trwww==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1736760941; x=1737365741;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=lskSZQtS9XAjFWiyybqGHHQIOUWfNQODMbaA7pcrDmY=;\n\tb=F9qi9vMnIyUj8C5FMIqsI5uc9RpvlooeSIg8EGRY39RFZTSusAZB5R14SUDCmwBai5\n\tYWUtE17EAza92bpe4NR9UTkJR/Xx9/WrDCtH5KHpSCGJVPcYT6L+pXtavHAXoD2Hl6j6\n\t7kJzvSRthSdcxgLuU1poCICRFQwwQ5CTJjMHLAtDQw0c/bPUHBNWlmPVdtvyMhjEBfa6\n\t/jD5vaf9mhiCEa+4i5WfRYD3KlwhQsAQFX9M7zVRGXcQFV9Nwq8OZSHrPmHug9pvcrBe\n\tloJaIklf00kGNTAjhpJbyA7r2+NeEVMcMWFurEA75NncN6bT7wfvIDynRLBI6eiDoVYe\n\toVFQ==",
        "X-Gm-Message-State": "AOJu0YyNFTgnEOVNSBN/wo9OTmuekcZ8WDuD1zy8FFs0Ay8pnLlvmitK\n\tHW84GMQ5yb1nYMk9WZXPxV97dU6H0l2QoYT6wg0B6J9HmJtG/87CFoRT+lV17e/nHQZGIQ67FpU\n\t0QuY=",
        "X-Gm-Gg": "ASbGncszQ2lvWlF4rNkErn9ZTfXF8JXzqacPzPURSS4UIs4Ykf4/2yA6ZmtBaX2mxgu\n\thZ3UBbCM8OObT2uA0VpRez2Un6vG0wXtAsXLDDktpohL6CyATKmFnX/2AmWWTESz3Fd4rY0Je2h\n\tuVJ2Zz56V/FR+fQQMKT6RwGbj4tfXTQtNhwGgxOis4zabXkklInHKz+5Dn5Ysak+ddO5MGAAh+7\n\tGrOagS4VoW2+r7v7y+fje1/KxXm4RjOfD77AEJZHpU3tALbpHyCbYsjAAFGtbpJan0ddumM3KuC",
        "X-Google-Smtp-Source": "AGHT+IFWqEUzkvBUX4AUUaAP0qXiwrYYYbLnC8Vs00l4+cE5p7YCunTayLcxh3cgPysJz5PsAxXD6w==",
        "X-Received": "by 2002:a05:600c:b90:b0:435:192:63eb with SMTP id\n\t5b1f17b1804b1-436e26a8160mr73081405e9.3.1736760940031; \n\tMon, 13 Jan 2025 01:35:40 -0800 (PST)",
        "From": "Naushir Patuck <naush@raspberrypi.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Naushir Patuck <naush@raspberrypi.com>,\n\tDavid Plowman <david.plowman@raspberrypi.com>",
        "Subject": "[PATCH v1 4/4] pipeline: rpi: Add support for Raspberry Pi 5",
        "Date": "Mon, 13 Jan 2025 09:24:46 +0000",
        "Message-ID": "<20250113093532.4054142-5-naush@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20250113093532.4054142-1-naush@raspberrypi.com>",
        "References": "<20250113093532.4054142-1-naush@raspberrypi.com>",
        "MIME-Version": "1.0",
        "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 the Raspberry Pi 5 ISP (PiSP) pipeline handler to libcamera. To\ninclude this pipeline handler in the build, set the following meson\noption:\n\nmeson configure -Dpipelines=rpi/pisp\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nReviewed-by: David Plowman <david.plowman@raspberrypi.com>\n---\n Documentation/guides/pipeline-handler.rst     |    2 +-\n include/libcamera/ipa/meson.build             |    1 +\n include/libcamera/meson.build                 |    1 +\n meson.build                                   |    1 +\n meson_options.txt                             |    1 +\n .../pipeline/rpi/pisp/data/example.yaml       |   45 +\n .../pipeline/rpi/pisp/data/meson.build        |    8 +\n src/libcamera/pipeline/rpi/pisp/meson.build   |   12 +\n src/libcamera/pipeline/rpi/pisp/pisp.cpp      | 2372 +++++++++++++++++\n 9 files changed, 2442 insertions(+), 1 deletion(-)\n create mode 100644 src/libcamera/pipeline/rpi/pisp/data/example.yaml\n create mode 100644 src/libcamera/pipeline/rpi/pisp/data/meson.build\n create mode 100644 src/libcamera/pipeline/rpi/pisp/meson.build\n create mode 100644 src/libcamera/pipeline/rpi/pisp/pisp.cpp",
    "diff": "diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst\nindex 69e832a5587e..9a15c20ab5b0 100644\n--- a/Documentation/guides/pipeline-handler.rst\n+++ b/Documentation/guides/pipeline-handler.rst\n@@ -186,7 +186,7 @@ to the libcamera build options in the top level ``meson_options.txt``.\n \n    option('pipelines',\n            type : 'array',\n-           choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'uvcvideo', 'vimc', 'vivid'],\n+           choices : ['ipu3', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple', 'uvcvideo', 'vimc', 'vivid'],\n            description : 'Select which pipeline handlers to include')\n \n \ndiff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build\nindex 3129f1193d5a..3ee3ada303c0 100644\n--- a/include/libcamera/ipa/meson.build\n+++ b/include/libcamera/ipa/meson.build\n@@ -66,6 +66,7 @@ pipeline_ipa_mojom_mapping = {\n     'ipu3': 'ipu3.mojom',\n     'mali-c55': 'mali-c55.mojom',\n     'rkisp1': 'rkisp1.mojom',\n+    'rpi/pisp': 'raspberrypi.mojom',\n     'rpi/vc4': 'raspberrypi.mojom',\n     'simple': 'soft.mojom',\n     'vimc': 'vimc.mojom',\ndiff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\nindex fd69a5172b6a..9297f3fa3aea 100644\n--- a/include/libcamera/meson.build\n+++ b/include/libcamera/meson.build\n@@ -37,6 +37,7 @@ controls_map = {\n         'core': 'control_ids_core.yaml',\n         'debug': 'control_ids_debug.yaml',\n         'draft': 'control_ids_draft.yaml',\n+        'rpi/pisp': 'control_ids_rpi.yaml',\n         'rpi/vc4': 'control_ids_rpi.yaml',\n     },\n \ndiff --git a/meson.build b/meson.build\nindex 5270f626f2fb..7a0e09f1c48f 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -213,6 +213,7 @@ pipelines_support = {\n     'ipu3':         arch_x86,\n     'mali-c55':     arch_arm,\n     'rkisp1':       arch_arm,\n+    'rpi/pisp':     arch_arm,\n     'rpi/vc4':      arch_arm,\n     'simple':       ['any'],\n     'uvcvideo':     ['any'],\ndiff --git a/meson_options.txt b/meson_options.txt\nindex 4db05a68325f..1877d80057dd 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -51,6 +51,7 @@ option('pipelines',\n             'ipu3',\n             'mali-c55',\n             'rkisp1',\n+            'rpi/pisp',\n             'rpi/vc4',\n             'simple',\n             'uvcvideo',\ndiff --git a/src/libcamera/pipeline/rpi/pisp/data/example.yaml b/src/libcamera/pipeline/rpi/pisp/data/example.yaml\nnew file mode 100644\nindex 000000000000..d67e654a8b9e\n--- /dev/null\n+++ b/src/libcamera/pipeline/rpi/pisp/data/example.yaml\n@@ -0,0 +1,45 @@\n+{\n+        \"version\": 1.0,\n+        \"target\": \"pisp\",\n+\n+        \"pipeline_handler\":\n+        {\n+                # Number of CFE config and stats buffers to allocate and use. A\n+                # larger number minimises the possibility of dropping frames,\n+                # but increases the latency for updating the HW configuration.\n+                #\n+                # \"num_cfe_config_stats_buffers\": 12,\n+\n+                # Number of jobs to queue ahead to the CFE on startup. A larger\n+                # number will increase latency for 3A changes, but may reduce \n+                # avoidable frame drops.\n+                #\n+                # \"num_cfe_config_queue\": 2,\n+\n+                # Override any request from the IPA to drop a number of startup\n+                # frames.\n+                #\n+                # \"disable_startup_frame_drops\": false,\n+\n+                # Custom timeout value (in ms) for camera to use. This overrides\n+                # the value computed by the pipeline handler based on frame\n+                # durations.\n+                #\n+                # Set this value to 0 to use the pipeline handler computed\n+                # timeout value.\n+                #\n+                # \"camera_timeout_value_ms\": 0,\n+\n+                # Disables temporal denoise functionality in the ISP pipeline.\n+                # Disabling temporal denoise avoids allocating 2 additional\n+                # Bayer framebuffers required for its operation.\n+                #\n+                # \"disable_tdn\": false,\n+\n+                # Disables multiframe HDR functionality in the ISP pipeline.\n+                # Disabling multiframe HDR avoids allocating 2 additional Bayer\n+                # framebuffers required for its operation.\n+                #\n+                # \"disable_hdr\": false,\n+        }\n+}\ndiff --git a/src/libcamera/pipeline/rpi/pisp/data/meson.build b/src/libcamera/pipeline/rpi/pisp/data/meson.build\nnew file mode 100644\nindex 000000000000..17dfc435be6f\n--- /dev/null\n+++ b/src/libcamera/pipeline/rpi/pisp/data/meson.build\n@@ -0,0 +1,8 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+conf_files = files([\n+    'example.yaml',\n+])\n+\n+install_data(conf_files,\n+             install_dir : pipeline_data_dir / 'rpi' / 'pisp')\ndiff --git a/src/libcamera/pipeline/rpi/pisp/meson.build b/src/libcamera/pipeline/rpi/pisp/meson.build\nnew file mode 100644\nindex 000000000000..178df94c2622\n--- /dev/null\n+++ b/src/libcamera/pipeline/rpi/pisp/meson.build\n@@ -0,0 +1,12 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_sources += files([\n+    'pisp.cpp',\n+])\n+\n+librt = cc.find_library('rt', required : true)\n+libpisp_dep = dependency('libpisp', fallback : ['libpisp', 'libpisp_dep'])\n+\n+libcamera_deps += [libpisp_dep, librt]\n+\n+subdir('data')\ndiff --git a/src/libcamera/pipeline/rpi/pisp/pisp.cpp b/src/libcamera/pipeline/rpi/pisp/pisp.cpp\nnew file mode 100644\nindex 000000000000..0f9e332667c0\n--- /dev/null\n+++ b/src/libcamera/pipeline/rpi/pisp/pisp.cpp\n@@ -0,0 +1,2372 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Raspberry Pi Ltd\n+ *\n+ * pisp.cpp - Pipeline handler for PiSP based Raspberry Pi devices\n+ */\n+\n+#include <algorithm>\n+#include <fstream>\n+#include <memory>\n+#include <mutex>\n+#include <numeric>\n+#include <queue>\n+#include <set>\n+#include <sstream>\n+#include <string>\n+#include <sys/ioctl.h>\n+#include <unordered_map>\n+#include <vector>\n+\n+#include <linux/dma-buf.h>\n+#include <linux/v4l2-controls.h>\n+#include <linux/videodev2.h>\n+\n+#include <libcamera/base/shared_fd.h>\n+#include <libcamera/formats.h>\n+\n+#include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/shared_mem_object.h\"\n+\n+#include \"libpisp/backend/backend.hpp\"\n+#include \"libpisp/common/logging.hpp\"\n+#include \"libpisp/common/utils.hpp\"\n+#include \"libpisp/common/version.hpp\"\n+#include \"libpisp/frontend/frontend.hpp\"\n+#include \"libpisp/variants/variant.hpp\"\n+\n+#include \"../common/pipeline_base.h\"\n+#include \"../common/rpi_stream.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(RPI)\n+\n+using StreamFlag = RPi::Stream::StreamFlag;\n+using StreamParams = RPi::RPiCameraConfiguration::StreamParams;\n+\n+namespace {\n+\n+enum class Cfe : unsigned int { Output0, Embedded, Stats, Config };\n+enum class Isp : unsigned int { Input, Output0, Output1, TdnInput, TdnOutput,\n+\t\t\t\tStitchInput, StitchOutput, Config };\n+\n+/* Offset for all compressed buffers; mode for TDN and Stitch. */\n+constexpr unsigned int DefaultCompressionOffset = 2048;\n+constexpr unsigned int DefaultCompressionMode = 1;\n+\n+const std::vector<std::pair<BayerFormat, unsigned int>> BayerToMbusCodeMap{\n+\t{ { BayerFormat::BGGR, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR8_1X8, },\n+\t{ { BayerFormat::GBRG, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG8_1X8, },\n+\t{ { BayerFormat::GRBG, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG8_1X8, },\n+\t{ { BayerFormat::RGGB, 8, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB8_1X8, },\n+\t{ { BayerFormat::BGGR, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR10_1X10, },\n+\t{ { BayerFormat::GBRG, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG10_1X10, },\n+\t{ { BayerFormat::GRBG, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG10_1X10, },\n+\t{ { BayerFormat::RGGB, 10, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB10_1X10, },\n+\t{ { BayerFormat::BGGR, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR12_1X12, },\n+\t{ { BayerFormat::GBRG, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG12_1X12, },\n+\t{ { BayerFormat::GRBG, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG12_1X12, },\n+\t{ { BayerFormat::RGGB, 12, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB12_1X12, },\n+\t{ { BayerFormat::BGGR, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR14_1X14, },\n+\t{ { BayerFormat::GBRG, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG14_1X14, },\n+\t{ { BayerFormat::GRBG, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG14_1X14, },\n+\t{ { BayerFormat::RGGB, 14, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB14_1X14, },\n+\t{ { BayerFormat::BGGR, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SBGGR16_1X16, },\n+\t{ { BayerFormat::GBRG, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGBRG16_1X16, },\n+\t{ { BayerFormat::GRBG, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SGRBG16_1X16, },\n+\t{ { BayerFormat::RGGB, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_SRGGB16_1X16, },\n+\t{ { BayerFormat::BGGR, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SBGGR16_1X16, },\n+\t{ { BayerFormat::GBRG, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SGBRG16_1X16, },\n+\t{ { BayerFormat::GRBG, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SGRBG16_1X16, },\n+\t{ { BayerFormat::RGGB, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SRGGB16_1X16, },\n+\t{ { BayerFormat::RGGB, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_SRGGB16_1X16, },\n+\t{ { BayerFormat::MONO, 16, BayerFormat::Packing::None }, MEDIA_BUS_FMT_Y16_1X16, },\n+\t{ { BayerFormat::MONO, 16, BayerFormat::Packing::PISP1 }, MEDIA_BUS_FMT_Y16_1X16, },\n+};\n+\n+unsigned int bayerToMbusCode(const BayerFormat &bayer)\n+{\n+\tconst auto it = std::find_if(BayerToMbusCodeMap.begin(), BayerToMbusCodeMap.end(),\n+\t\t\t\t     [bayer](const std::pair<BayerFormat, unsigned int> &match) {\n+\t\t\t\t\t\treturn bayer == match.first;\n+\t\t\t\t     });\n+\n+\tif (it != BayerToMbusCodeMap.end())\n+\t\treturn it->second;\n+\n+\treturn 0;\n+}\n+\n+uint32_t mbusCodeUnpacked16(unsigned int code)\n+{\n+\tBayerFormat bayer = BayerFormat::fromMbusCode(code);\n+\tBayerFormat bayer16(bayer.order, 16, BayerFormat::Packing::None);\n+\n+\treturn bayerToMbusCode(bayer16);\n+}\n+\n+uint8_t toPiSPBayerOrder(V4L2PixelFormat format)\n+{\n+\tBayerFormat bayer = BayerFormat::fromV4L2PixelFormat(format);\n+\n+\tswitch (bayer.order) {\n+\tcase BayerFormat::Order::BGGR:\n+\t\treturn PISP_BAYER_ORDER_BGGR;\n+\tcase BayerFormat::Order::GBRG:\n+\t\treturn PISP_BAYER_ORDER_GBRG;\n+\tcase BayerFormat::Order::GRBG:\n+\t\treturn PISP_BAYER_ORDER_GRBG;\n+\tcase BayerFormat::Order::RGGB:\n+\t\treturn PISP_BAYER_ORDER_RGGB;\n+\tcase BayerFormat::Order::MONO:\n+\t\treturn PISP_BAYER_ORDER_GREYSCALE;\n+\tdefault:\n+\t\tASSERT(0);\n+\t\treturn -1;\n+\t}\n+}\n+\n+pisp_image_format_config toPiSPImageFormat(V4L2DeviceFormat &format)\n+{\n+\tpisp_image_format_config image = {};\n+\n+\timage.width = format.size.width;\n+\timage.height = format.size.height;\n+\timage.stride = format.planes[0].bpl;\n+\n+\tPixelFormat pix = format.fourcc.toPixelFormat();\n+\n+\tif (RPi::PipelineHandlerBase::isRaw(pix)) {\n+\t\tBayerFormat bayer = BayerFormat::fromPixelFormat(pix);\n+\t\tswitch (bayer.packing) {\n+\t\tcase BayerFormat::Packing::None:\n+\t\t\timage.format = PISP_IMAGE_FORMAT_BPS_16 +\n+\t\t\t\t       PISP_IMAGE_FORMAT_UNCOMPRESSED;\n+\t\t\tbreak;\n+\t\tcase BayerFormat::Packing::PISP1:\n+\t\t\timage.format = PISP_IMAGE_FORMAT_COMPRESSION_MODE_1;\n+\t\t\tbreak;\n+\t\tcase BayerFormat::Packing::PISP2:\n+\t\t\timage.format = PISP_IMAGE_FORMAT_COMPRESSION_MODE_2;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tASSERT(0);\n+\t\t}\n+\t\treturn image;\n+\t}\n+\n+\tswitch (pix) {\n+\tcase formats::YUV420:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_420 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_PLANAR;\n+\t\timage.stride2 = image.stride / 2;\n+\t\tbreak;\n+\tcase formats::NV12:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_420 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR;\n+\t\timage.stride2 = image.stride;\n+\t\tbreak;\n+\tcase formats::NV21:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_420 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR +\n+\t\t\t       PISP_IMAGE_FORMAT_ORDER_SWAPPED;\n+\t\timage.stride2 = image.stride;\n+\t\tbreak;\n+\tcase formats::YUYV:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_422 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED;\n+\t\tbreak;\n+\tcase formats::UYVY:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_422 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED +\n+\t\t\t       PISP_IMAGE_FORMAT_ORDER_SWAPPED;\n+\t\tbreak;\n+\tcase formats::NV16:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_422 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR;\n+\t\timage.stride2 = image.stride;\n+\t\tbreak;\n+\tcase formats::NV61:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL +\n+\t\t\t       PISP_IMAGE_FORMAT_BPS_8 +\n+\t\t\t       PISP_IMAGE_FORMAT_SAMPLING_422 +\n+\t\t\t       PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR +\n+\t\t\t       PISP_IMAGE_FORMAT_ORDER_SWAPPED;\n+\t\timage.stride2 = image.stride;\n+\t\tbreak;\n+\tcase formats::RGB888:\n+\tcase formats::BGR888:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL;\n+\t\tbreak;\n+\tcase formats::XRGB8888:\n+\tcase formats::XBGR8888:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32;\n+\t\tbreak;\n+\tcase formats::RGBX8888:\n+\tcase formats::BGRX8888:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPP_32 +\n+\t\t\t       PISP_IMAGE_FORMAT_ORDER_SWAPPED;\n+\t\tbreak;\n+\tcase formats::RGB161616:\n+\tcase formats::BGR161616:\n+\t\timage.format = PISP_IMAGE_FORMAT_THREE_CHANNEL + PISP_IMAGE_FORMAT_BPS_16;\n+\t\tbreak;\n+\tdefault:\n+\t\tLOG(RPI, Error) << \"Pixel format \" << pix << \" unsupported\";\n+\t\tASSERT(0);\n+\t}\n+\n+\treturn image;\n+}\n+\n+void computeOptimalStride(V4L2DeviceFormat &format)\n+{\n+\tpisp_image_format_config fmt = toPiSPImageFormat(format);\n+\n+\tlibpisp::compute_optimal_stride(fmt);\n+\n+\tuint32_t fourcc = format.fourcc.fourcc();\n+\n+\t/*\n+\t * For YUV420/422 non-multiplanar formats, double the U/V stride for the\n+\t * Y-plane to ensure we get the optimal alignment on all three planes.\n+\t */\n+\tif (fourcc == V4L2_PIX_FMT_YUV420 || fourcc == V4L2_PIX_FMT_YUV422P ||\n+\t    fourcc == V4L2_PIX_FMT_YVU420)\n+\t\tfmt.stride = fmt.stride2 * 2;\n+\n+\tformat.planes[0].bpl = fmt.stride;\n+\tformat.planes[1].bpl = fmt.stride2;\n+\tformat.planes[2].bpl = fmt.stride2;\n+\n+\t/*\n+\t * Need to set planesCount correctly so that V4L2VideoDevice::trySetFormatMultiplane()\n+\t * copies the bpl fields correctly.\n+\t */\n+\tconst PixelFormat &pixFormat = format.fourcc.toPixelFormat();\n+\tconst PixelFormatInfo &info = PixelFormatInfo::info(pixFormat);\n+\tformat.planesCount = info.numPlanes();\n+}\n+\n+void setupOutputClipping(const V4L2DeviceFormat &v4l2Format,\n+\t\t\t pisp_be_output_format_config &outputFormat)\n+{\n+\tconst PixelFormat &pixFormat = v4l2Format.fourcc.toPixelFormat();\n+\tconst PixelFormatInfo &info = PixelFormatInfo::info(pixFormat);\n+\n+\tif (info.colourEncoding != PixelFormatInfo::ColourEncodingYUV)\n+\t\treturn;\n+\n+\tif (v4l2Format.colorSpace == ColorSpace::Sycc) {\n+\t\toutputFormat.lo = 0;\n+\t\toutputFormat.hi = 65535;\n+\t\toutputFormat.lo2 = 0;\n+\t\toutputFormat.hi2 = 65535;\n+\t} else if (v4l2Format.colorSpace == ColorSpace::Smpte170m ||\n+\t\t\tv4l2Format.colorSpace == ColorSpace::Rec709) {\n+\t\toutputFormat.lo = 16 << 8;\n+\t\toutputFormat.hi = 235 << 8;\n+\t\toutputFormat.lo2 = 16 << 8;\n+\t\toutputFormat.hi2 = 240 << 8;\n+\t} else {\n+\t\tLOG(RPI, Warning)\n+\t\t\t<< \"Unrecognised colour space \"\n+\t\t\t<< ColorSpace::toString(v4l2Format.colorSpace)\n+\t\t\t<< \", using full range\";\n+\t\toutputFormat.lo = 0;\n+\t\toutputFormat.hi = 65535;\n+\t\toutputFormat.lo2 = 0;\n+\t\toutputFormat.hi2 = 65535;\n+\t}\n+}\n+\n+int dmabufSyncStart(const SharedFD &fd)\n+{\n+\tstruct dma_buf_sync dma_sync {};\n+\tdma_sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;\n+\n+\tint ret = ::ioctl(fd.get(), DMA_BUF_IOCTL_SYNC, &dma_sync);\n+\tif (ret)\n+\t\tLOG(RPI, Error) << \"failed to lock-sync-write dma buf\";\n+\n+\treturn ret;\n+}\n+\n+int dmabufSyncEnd(const SharedFD &fd)\n+{\n+\tstruct dma_buf_sync dma_sync {};\n+\tdma_sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;\n+\n+\tint ret = ::ioctl(fd.get(), DMA_BUF_IOCTL_SYNC, &dma_sync);\n+\n+\tif (ret)\n+\t\tLOG(RPI, Error) << \"failed to unlock-sync-write dma buf\";\n+\n+\treturn ret;\n+}\n+\n+void do32BitConversion(void *mem, unsigned int width, unsigned int height,\n+\t\t       unsigned int stride)\n+{\n+\t/*\n+\t * The arm64 version is actually not that much quicker because the\n+\t * vast bulk of the time is spent waiting for memory.\n+\t */\n+#if __aarch64__\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\t\tuint64_t count = (width + 15) / 16;\n+\t\tuint8_t *dest = ptr + count * 64;\n+\t\tuint8_t *src = ptr + count * 48;\n+\n+\t\t/* Pre-decrement would have been nice. */\n+\t\tasm volatile(\"movi v3.16b, #255 \\n\"\n+\t\t\t\t\"1: \\n\"\n+\t\t\t\t\"sub %[src], %[src], #48 \\n\"\n+\t\t\t\t\"sub %[dest], %[dest], #64 \\n\"\n+\t\t\t\t\"subs %[count], %[count], #1 \\n\"\n+\t\t\t\t\"ld3 {v0.16b, v1.16b, v2.16b}, [%[src]] \\n\"\n+\t\t\t\t\"st4 {v0.16b, v1.16b, v2.16b, v3.16b}, [%[dest]] \\n\"\n+\t\t\t\t\"b.gt 1b \\n\"\n+\t\t\t\t: [count]\"+r\" (count)\n+\t\t\t\t: [src]\"r\" (src), [dest]\"r\" (dest)\n+\t\t\t\t: \"cc\", \"v1\", \"v2\", \"v3\", \"v4\", \"memory\"\n+\t\t\t\t);\n+\t}\n+#else\n+\tstd::vector<uint8_t> incache(3 * width);\n+\tstd::vector<uint8_t> outcache(4 * width);\n+\n+\tmemcpy(incache.data(), mem, 3 * width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *ptr3 = incache.data();\n+\t\tuint8_t *ptr4 = outcache.data();\n+\t\tfor (unsigned int i = 0; i < width; i++) {\n+\t\t\t*(ptr4++) = *(ptr3++);\n+\t\t\t*(ptr4++) = *(ptr3++);\n+\t\t\t*(ptr4++) = *(ptr3++);\n+\t\t\t*(ptr4++) = 255;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 3 * width);\n+\t\tmemcpy(ptr, outcache.data(), 4 * width);\n+\t}\n+#endif\n+}\n+\n+void do16BitEndianSwap([[maybe_unused]] void *mem, [[maybe_unused]] unsigned int width,\n+\t\t       [[maybe_unused]] unsigned int height, [[maybe_unused]] unsigned int stride)\n+{\n+#if __aarch64__\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\t\tuint64_t count = (width + 7) / 8;\n+\n+\t\tasm volatile(\"1: \\n\"\n+\t\t\t\t\"ld1 {v1.16b}, [%[ptr]] \\n\"\n+\t\t\t\t\"rev16 v1.16b, v1.16b \\n\"\n+\t\t\t\t\"st1 {v1.16b}, [%[ptr]], #16 \\n\"\n+\t\t\t\t\"subs %[count], %[count], #1 \\n\"\n+\t\t\t\t\"b.gt 1b \\n\"\n+\t\t\t\t: [count]\"+r\" (count), [ptr]\"+r\" (ptr)\n+\t\t\t\t:\n+\t\t\t\t: \"cc\", \"v1\", \"memory\"\n+\t\t\t\t);\n+\t}\n+#endif\n+}\n+\n+void do14bitUnpack(void *mem, unsigned int width, unsigned int height,\n+\t\t   unsigned int stride)\n+{\n+\tstd::vector<uint8_t> cache(stride);\n+\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tconst uint8_t *in = ((uint8_t *)mem) + j * stride;\n+\t\tuint8_t *out = ((uint8_t *)mem) + j * stride;\n+\t\tuint8_t *p = cache.data();\n+\n+\t\tstd::memcpy(p, in, stride);\n+\t\tfor (unsigned int i = 0; i < width; i += 4, p += 7) {\n+\t\t\tuint16_t p0 = (p[0] << 8) | ((p[4] & 0x3f) << 2);\n+\t\t\tuint16_t p1 = (p[1] << 8) | ((p[4] & 0xc0) >> 4) | ((p[5] & 0x0f) << 4);\n+\t\t\tuint16_t p2 = (p[2] << 8) | ((p[5] & 0xf0) >> 2) | ((p[6] & 0x03) << 6);\n+\t\t\tuint16_t p3 = (p[3] << 8) | (p[6] & 0xfc);\n+\n+\t\t\t*(uint16_t *)(out + i * 2 + 0) = p0;\n+\t\t\t*(uint16_t *)(out + i * 2 + 2) = p1;\n+\t\t\t*(uint16_t *)(out + i * 2 + 4) = p2;\n+\t\t\t*(uint16_t *)(out + i * 2 + 6) = p3;\n+\t\t}\n+\t}\n+}\n+\n+void downscaleInterleaved3(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t   unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(3 * src_width);\n+\tunsigned int dst_width = src_width / 2;\n+\tstd::vector<uint8_t> outcache(3 * dst_width);\n+\n+\tmemcpy(incache.data(), mem, 3 * src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 6, dst += 3) {\n+\t\t\tdst[0] = ((int)src[0] + (int)src[3] + 1) >> 1;\n+\t\t\tdst[1] = ((int)src[1] + (int)src[4] + 1) >> 1;\n+\t\t\tdst[2] = ((int)src[2] + (int)src[5] + 1) >> 1;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 3 * src_width);\n+\t\tmemcpy(ptr, outcache.data(), 3 * dst_width);\n+\t}\n+}\n+\n+void downscaleInterleaved4(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t   unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(4 * src_width);\n+\tunsigned int dst_width = src_width / 2;\n+\tstd::vector<uint8_t> outcache(4 * dst_width);\n+\n+\tmemcpy(incache.data(), mem, 4 * src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) {\n+\t\t\tdst[0] = ((int)src[0] + (int)src[4] + 1) >> 1;\n+\t\t\tdst[1] = ((int)src[1] + (int)src[5] + 1) >> 1;\n+\t\t\tdst[2] = ((int)src[2] + (int)src[6] + 1) >> 1;\n+\t\t\tdst[3] = ((int)src[3] + (int)src[7] + 1) >> 1;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 4 * src_width);\n+\t\tmemcpy(ptr, outcache.data(), 4 * dst_width);\n+\t}\n+}\n+\n+void downscalePlaneInternal(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t    unsigned int stride, std::vector<uint8_t> &incache,\n+\t\t\t    std::vector<uint8_t> &outcache)\n+{\n+\tunsigned int dst_width = src_width / 2;\n+\tmemcpy(incache.data(), mem, src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 2, dst++)\n+\t\t\t*dst = ((int)src[0] + (int)src[1] + 1) >> 1;\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, src_width);\n+\t\tmemcpy(ptr, outcache.data(), dst_width);\n+\t}\n+}\n+\n+void downscalePlanar420(void *memY, void *memU, void *memV, unsigned int height,\n+\t\t\tunsigned int src_width, unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(src_width);\n+\tstd::vector<uint8_t> outcache(src_width / 2);\n+\n+\tdownscalePlaneInternal(memY, height, src_width, stride, incache, outcache);\n+\tdownscalePlaneInternal(memU, height / 2, src_width / 2, stride / 2, incache, outcache);\n+\tdownscalePlaneInternal(memV, height / 2, src_width / 2, stride / 2, incache, outcache);\n+}\n+\n+void downscalePlanar422(void *memY, void *memU, void *memV,\n+\t\t\tunsigned int height, unsigned int src_width, unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(src_width);\n+\tstd::vector<uint8_t> outcache(src_width / 2);\n+\n+\tdownscalePlaneInternal(memY, height, src_width, stride, incache, outcache);\n+\tdownscalePlaneInternal(memU, height, src_width / 2, stride / 2, incache, outcache);\n+\tdownscalePlaneInternal(memV, height, src_width / 2, stride / 2, incache, outcache);\n+}\n+\n+void downscaleInterleavedYuyv(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t      unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(2 * src_width);\n+\tunsigned int dst_width = src_width / 2;\n+\tstd::vector<uint8_t> outcache(2 * dst_width);\n+\n+\tmemcpy(incache.data(), mem, 2 * src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) {\n+\t\t\tdst[0] = ((int)src[0] + (int)src[2] + 1) >> 1;\n+\t\t\tdst[1] = ((int)src[1] + (int)src[5] + 1) >> 1;\n+\t\t\tdst[2] = ((int)src[4] + (int)src[6] + 1) >> 1;\n+\t\t\tdst[3] = ((int)src[3] + (int)src[7] + 1) >> 1;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 4 * src_width);\n+\t\tmemcpy(ptr, outcache.data(), 2 * dst_width);\n+\t}\n+}\n+\n+void downscaleInterleavedUyvy(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t      unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(2 * src_width);\n+\tunsigned int dst_width = src_width / 2;\n+\tstd::vector<uint8_t> outcache(2 * dst_width);\n+\n+\tmemcpy(incache.data(), mem, 2 * src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 8, dst += 4) {\n+\t\t\tdst[0] = ((int)src[0] + (int)src[4] + 1) >> 1;\n+\t\t\tdst[1] = ((int)src[1] + (int)src[3] + 1) >> 1;\n+\t\t\tdst[2] = ((int)src[2] + (int)src[6] + 1) >> 1;\n+\t\t\tdst[3] = ((int)src[5] + (int)src[7] + 1) >> 1;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 4 * src_width);\n+\t\tmemcpy(ptr, outcache.data(), 2 * dst_width);\n+\t}\n+}\n+\n+void downscaleInterleaved2Internal(void *mem, unsigned int height, unsigned int src_width,\n+\t\t\t\t   unsigned int stride, std::vector<uint8_t> &incache,\n+\t\t\t\t   std::vector<uint8_t> &outcache)\n+{\n+\tunsigned int dst_width = src_width / 2;\n+\tmemcpy(incache.data(), mem, 2 * src_width);\n+\tfor (unsigned int j = 0; j < height; j++) {\n+\t\tuint8_t *ptr = (uint8_t *)mem + j * stride;\n+\n+\t\tuint8_t *src = incache.data(), *dst = outcache.data();\n+\t\tfor (unsigned int i = 0; i < dst_width; i++, src += 4, dst += 2) {\n+\t\t\tdst[0] = ((int)src[0] + (int)src[2] + 1) >> 1;\n+\t\t\tdst[1] = ((int)src[1] + (int)src[3] + 1) >> 1;\n+\t\t}\n+\n+\t\tif (j < height - 1)\n+\t\t\tmemcpy(incache.data(), ptr + stride, 2 * src_width);\n+\t\tmemcpy(ptr, outcache.data(), 2 * dst_width);\n+\t}\n+}\n+\n+void downscaleSemiPlanar420(void *memY, void *memUV, unsigned int height,\n+\t\t\t    unsigned int src_width, unsigned int stride)\n+{\n+\tstd::vector<uint8_t> incache(src_width);\n+\tstd::vector<uint8_t> outcache(src_width / 2);\n+\n+\tdownscalePlaneInternal(memY, height, src_width, stride, incache, outcache);\n+\tdownscaleInterleaved2Internal(memUV, height / 2, src_width / 2, stride,\n+\t\t\t\t      incache, outcache);\n+}\n+\n+void downscaleStreamBuffer(RPi::Stream *stream, int index)\n+{\n+\tunsigned int downscale = stream->swDownscale();\n+\t/* Must be a power of 2. */\n+\tASSERT((downscale & (downscale - 1)) == 0);\n+\n+\tunsigned int stride = stream->configuration().stride;\n+\tunsigned int dst_width = stream->configuration().size.width;\n+\tunsigned int height = stream->configuration().size.height;\n+\tconst PixelFormat &pixFormat = stream->configuration().pixelFormat;\n+\tconst RPi::BufferObject &b = stream->getBuffer(index);\n+\tvoid *mem = b.mapped->planes()[0].data();\n+\tASSERT(b.mapped);\n+\n+\t/* Do repeated downscale-by-2 in place until we're done. */\n+\tfor (; downscale > 1; downscale >>= 1) {\n+\t\tunsigned int src_width = downscale * dst_width;\n+\n+\t\tif (pixFormat == formats::RGB888 || pixFormat == formats::BGR888) {\n+\t\t\tdownscaleInterleaved3(mem, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::XRGB8888 || pixFormat == formats::XBGR8888) {\n+\t\t\t/* On some devices these may actually be 24bpp at this point. */\n+\t\t\tif (stream->getFlags() & StreamFlag::Needs32bitConv)\n+\t\t\t\tdownscaleInterleaved3(mem, height, src_width, stride);\n+\t\t\telse\n+\t\t\t\tdownscaleInterleaved4(mem, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::YUV420 || pixFormat == formats::YVU420) {\n+\t\t\t/* These may look like either single or multi-planar buffers. */\n+\t\t\tvoid *mem1;\n+\t\t\tvoid *mem2;\n+\t\t\tif (b.mapped->planes().size() == 3) {\n+\t\t\t\tmem1 = b.mapped->planes()[1].data();\n+\t\t\t\tmem2 = b.mapped->planes()[2].data();\n+\t\t\t} else {\n+\t\t\t\tunsigned int ySize = height * stride;\n+\t\t\t\tmem1 = static_cast<uint8_t *>(mem) + ySize;\n+\t\t\t\tmem2 = static_cast<uint8_t *>(mem1) + ySize / 4;\n+\t\t\t}\n+\t\t\tdownscalePlanar420(mem, mem1, mem2, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::YUV422 || pixFormat == formats::YVU422) {\n+\t\t\t/* These may look like either single or multi-planar buffers. */\n+\t\t\tvoid *mem1;\n+\t\t\tvoid *mem2;\n+\t\t\tif (b.mapped->planes().size() == 3) {\n+\t\t\t\tmem1 = b.mapped->planes()[1].data();\n+\t\t\t\tmem2 = b.mapped->planes()[2].data();\n+\t\t\t} else {\n+\t\t\t\tunsigned int ySize = height * stride;\n+\t\t\t\tmem1 = static_cast<uint8_t *>(mem) + ySize;\n+\t\t\t\tmem2 = static_cast<uint8_t *>(mem1) + ySize / 2;\n+\t\t\t}\n+\t\t\tdownscalePlanar422(mem, mem1, mem2, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::YUYV || pixFormat == formats::YVYU) {\n+\t\t\tdownscaleInterleavedYuyv(mem, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::UYVY || pixFormat == formats::VYUY) {\n+\t\t\tdownscaleInterleavedUyvy(mem, height, src_width, stride);\n+\t\t} else if (pixFormat == formats::NV12 || pixFormat == formats::NV21) {\n+\t\t\t/* These may look like either single or multi-planar buffers. */\n+\t\t\tvoid *mem1;\n+\t\t\tif (b.mapped->planes().size() == 2)\n+\t\t\t\tmem1 = b.mapped->planes()[1].data();\n+\t\t\telse\n+\t\t\t\tmem1 = static_cast<uint8_t *>(mem) + height * stride;\n+\t\t\tdownscaleSemiPlanar420(mem, mem1, height, src_width, stride);\n+\t\t} else {\n+\t\t\tLOG(RPI, Error) << \"Sw downscale unsupported for \" << pixFormat;\n+\t\t\tASSERT(0);\n+\t\t}\n+\t}\n+}\n+\n+/* Return largest width of any of these streams (or of the camera input). */\n+unsigned int getLargestWidth(const V4L2SubdeviceFormat &sensorFormat,\n+\t\t\t     const std::vector<StreamParams> &outStreams)\n+{\n+\tunsigned int largestWidth = sensorFormat.size.width;\n+\n+\tfor (const auto &stream : outStreams)\n+\t\tlargestWidth = std::max(largestWidth, stream.cfg->size.width);\n+\n+\treturn largestWidth;\n+}\n+\n+/* Return the minimum number of pixels required to write out multiples of 16 bytes. */\n+unsigned int getFormatAlignment(const V4L2PixelFormat &fourcc)\n+{\n+\tconst PixelFormatInfo &info = PixelFormatInfo::info(fourcc);\n+\tunsigned int formatAlignment = 0;\n+\tfor (const auto &plane : info.planes) {\n+\t\tif (plane.bytesPerGroup) {\n+\t\t\t/* How many pixels we need in this plane for a multiple of 16 bytes (??). */\n+\t\t\tunsigned int align = 16 * info.pixelsPerGroup /\n+\t\t\t\t\t\tstd::gcd(16u, plane.bytesPerGroup);\n+\t\t\tformatAlignment = std::max(formatAlignment, align);\n+\t\t}\n+\t}\n+\n+\treturn formatAlignment;\n+}\n+\n+/* Calculate the amount of software downscale required (which is a power of 2). */\n+unsigned int calculateSwDownscale(const V4L2DeviceFormat &format, unsigned int largestWidth,\n+\t\t\t\t  unsigned int platformMaxDownscale)\n+{\n+\tunsigned int formatAlignment = getFormatAlignment(format.fourcc);\n+\tunsigned int maxDownscale = platformMaxDownscale * 16 / formatAlignment;\n+\tunsigned int limitWidth = largestWidth / maxDownscale;\n+\n+\tunsigned int hwWidth = format.size.width;\n+\tunsigned int swDownscale = 1;\n+\tfor (; hwWidth < limitWidth; hwWidth *= 2, swDownscale *= 2);\n+\n+\treturn swDownscale;\n+}\n+\n+} /* namespace */\n+\n+using ::libpisp::BackEnd;\n+using ::libpisp::FrontEnd;\n+\n+class PiSPCameraData final : public RPi::CameraData\n+{\n+public:\n+\tPiSPCameraData(PipelineHandler *pipe, const libpisp::PiSPVariant &variant)\n+\t\t: RPi::CameraData(pipe), pispVariant_(variant)\n+\t{\n+\t\t/* Initialise internal libpisp logging. */\n+\t\t::libpisp::logging_init();\n+\t\tLOG(RPI, Info) << \"libpisp version \" << ::libpisp::version();\n+\t}\n+\n+\t~PiSPCameraData()\n+\t{\n+\t\tfreeBuffers();\n+\t}\n+\n+\tV4L2VideoDevice::Formats ispFormats() const override\n+\t{\n+\t\treturn isp_[Isp::Output0].dev()->formats();\n+\t}\n+\n+\tV4L2VideoDevice::Formats rawFormats() const override\n+\t{\n+\t\treturn cfe_[Cfe::Output0].dev()->formats();\n+\t}\n+\n+\tV4L2VideoDevice *frontendDevice() override\n+\t{\n+\t\treturn cfe_[Cfe::Output0].dev();\n+\t}\n+\n+\tCameraConfiguration::Status\n+\tplatformValidate(RPi::RPiCameraConfiguration *rpiConfig) const override;\n+\n+\tint platformPipelineConfigure(const std::unique_ptr<YamlObject> &root) override;\n+\n+\tvoid platformStart() override;\n+\tvoid platformStop() override;\n+\tvoid platformFreeBuffers() override;\n+\n+\tvoid cfeBufferDequeue(FrameBuffer *buffer);\n+\tvoid beInputDequeue(FrameBuffer *buffer);\n+\tvoid beOutputDequeue(FrameBuffer *buffer);\n+\n+\tvoid processStatsComplete(const ipa::RPi::BufferIds &buffers);\n+\tvoid prepareIspComplete(const ipa::RPi::BufferIds &buffers, bool stitchSwapBuffers);\n+\tvoid setCameraTimeout(uint32_t maxFrameLengthMs);\n+\n+\t/* Array of CFE and ISP device streams and associated buffers/streams. */\n+\tRPi::Device<Cfe, 4> cfe_;\n+\tRPi::Device<Isp, 8> isp_;\n+\n+\tconst libpisp::PiSPVariant &pispVariant_;\n+\n+\t/* Frontend/Backend objects shared with the IPA. */\n+\tSharedMemObject<FrontEnd> fe_;\n+\tSharedMemObject<BackEnd> be_;\n+\tbool beEnabled_;\n+\n+\tstd::unique_ptr<V4L2Subdevice> csi2Subdev_;\n+\tstd::unique_ptr<V4L2Subdevice> feSubdev_;\n+\n+\tstd::vector<FrameBuffer *> tdnBuffers_;\n+\tstd::vector<FrameBuffer *> stitchBuffers_;\n+\tunsigned int tdnInputIndex_;\n+\tunsigned int stitchInputIndex_;\n+\n+\tstruct Config {\n+\t\t/*\n+\t\t * Number of CFE config and stats buffers to allocate and use. A\n+\t\t * larger number minimises the possibility of dropping frames,\n+\t\t * but increases the latency for updating the HW configuration.\n+\t\t */\n+\t\tunsigned int numCfeConfigStatsBuffers;\n+\t\t/*\n+\t\t * Number of jobs to queue ahead to the CFE on startup.\n+\t\t * A larger number will increase latency for 3A changes.\n+\t\t */\n+\t\tunsigned int numCfeConfigQueue;\n+\t\t/* Don't use BE temporal denoise and free some memory resources. */\n+\t\tbool disableTdn;\n+\t\t/* Don't use BE HDR and free some memory resources. */\n+\t\tbool disableHdr;\n+\t};\n+\n+\tConfig config_;\n+\n+\tbool adjustDeviceFormat(V4L2DeviceFormat &format) const;\n+\n+private:\n+\tint platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig) override;\n+\n+\tint platformConfigureIpa([[maybe_unused]] ipa::RPi::ConfigParams &params) override\n+\t{\n+\t\treturn 0;\n+\t}\n+\n+\tint platformInitIpa(ipa::RPi::InitParams &params) override;\n+\n+\tint configureEntities(V4L2SubdeviceFormat sensorFormat,\n+\t\t\t      V4L2SubdeviceFormat &embeddedFormat);\n+\tint configureCfe();\n+\tbool calculateCscConfiguration(const V4L2DeviceFormat &v4l2Format, pisp_be_ccm_config &csc);\n+\tint configureBe(const std::optional<ColorSpace> &yuvColorSpace);\n+\n+\tvoid platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) override;\n+\n+\tvoid prepareCfe();\n+\tvoid prepareBe(uint32_t bufferId, bool stitchSwapBuffers);\n+\n+\tvoid tryRunPipeline() override;\n+\n+\tstruct CfeJob {\n+\t\tControlList sensorControls;\n+\t\tunsigned int delayContext;\n+\t\tstd::unordered_map<const RPi::Stream *, FrameBuffer *> buffers;\n+\t};\n+\n+\tstd::queue<CfeJob> cfeJobQueue_;\n+\n+\tbool cfeJobComplete() const\n+\t{\n+\t\tif (cfeJobQueue_.empty())\n+\t\t\treturn false;\n+\n+\t\tconst CfeJob &job = cfeJobQueue_.back();\n+\t\treturn job.buffers.count(&cfe_[Cfe::Output0]) &&\n+\t\t       job.buffers.count(&cfe_[Cfe::Stats]) &&\n+\t\t       (!sensorMetadata_ ||\n+\t\t\tjob.buffers.count(&cfe_[Cfe::Embedded]));\n+\t}\n+\n+\tstd::string last_dump_file_;\n+};\n+\n+class PipelineHandlerPiSP : public RPi::PipelineHandlerBase\n+{\n+public:\n+\tPipelineHandlerPiSP(CameraManager *manager)\n+\t\t: RPi::PipelineHandlerBase(manager)\n+\t{\n+\t}\n+\n+\t~PipelineHandlerPiSP()\n+\t{\n+\t}\n+\n+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+private:\n+\tPiSPCameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<PiSPCameraData *>(camera->_d());\n+\t}\n+\n+\tint prepareBuffers(Camera *camera) override;\n+\tint platformRegister(std::unique_ptr<RPi::CameraData> &cameraData,\n+\t\t\t     MediaDevice *cfe, MediaDevice *isp) override;\n+};\n+\n+bool PipelineHandlerPiSP::match(DeviceEnumerator *enumerator)\n+{\n+\tconstexpr unsigned int numCfeDevices = 2;\n+\n+\t/*\n+\t * Loop over all CFE instances, but return out once a match is found.\n+\t * This is to ensure we correctly enumerate the camera when an instance\n+\t * of the CFE has registered with media controller, but has not registered\n+\t * device nodes due to a sensor subdevice failure.\n+\t */\n+\tfor (unsigned int i = 0; i < numCfeDevices; i++) {\n+\t\tDeviceMatch cfe(\"rp1-cfe\");\n+\t\tcfe.add(\"rp1-cfe-fe-image0\");\n+\t\tcfe.add(\"rp1-cfe-fe-stats\");\n+\t\tcfe.add(\"rp1-cfe-fe-config\");\n+\t\tMediaDevice *cfeDevice = acquireMediaDevice(enumerator, cfe);\n+\n+\t\tif (!cfeDevice) {\n+\t\t\tLOG(RPI, Debug) << \"Unable to acquire a CFE instance\";\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tDeviceMatch isp(\"pispbe\");\n+\t\tisp.add(\"pispbe-input\");\n+\t\tisp.add(\"pispbe-config\");\n+\t\tisp.add(\"pispbe-output0\");\n+\t\tisp.add(\"pispbe-output1\");\n+\t\tisp.add(\"pispbe-tdn_output\");\n+\t\tisp.add(\"pispbe-tdn_input\");\n+\t\tisp.add(\"pispbe-stitch_output\");\n+\t\tisp.add(\"pispbe-stitch_input\");\n+\t\tMediaDevice *ispDevice = acquireMediaDevice(enumerator, isp);\n+\n+\t\tif (!ispDevice) {\n+\t\t\tLOG(RPI, Debug) << \"Unable to acquire ISP instance\";\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The loop below is used to register multiple cameras behind\n+\t\t * one or more video mux devices that are attached to a\n+\t\t * particular CFE instance. Obviously these cameras cannot be\n+\t\t * used simultaneously.\n+\t\t */\n+\t\tunsigned int numCameras = 0;\n+\t\tfor (MediaEntity *entity : cfeDevice->entities()) {\n+\t\t\tif (entity->function() != MEDIA_ENT_F_CAM_SENSOR)\n+\t\t\t\tcontinue;\n+\n+\t\t\tconst libpisp::PiSPVariant &variant =\n+\t\t\t\tlibpisp::get_variant(cfeDevice->hwRevision(),\n+\t\t\t\t\t\t     ispDevice->hwRevision());\n+\t\t\tif (!variant.NumFrontEnds() || !variant.NumBackEnds()) {\n+\t\t\t\tLOG(RPI, Error) << \"Unsupported PiSP variant\";\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tstd::unique_ptr<RPi::CameraData> cameraData =\n+\t\t\t\tstd::make_unique<PiSPCameraData>(this, variant);\n+\t\t\tPiSPCameraData *pisp =\n+\t\t\t\tstatic_cast<PiSPCameraData *>(cameraData.get());\n+\n+\t\t\tpisp->fe_ = SharedMemObject<FrontEnd>\n+\t\t\t\t\t(\"pisp_frontend\", true, pisp->pispVariant_);\n+\t\t\tpisp->be_ = SharedMemObject<BackEnd>\n+\t\t\t\t\t(\"pisp_backend\", BackEnd::Config({}), pisp->pispVariant_);\n+\n+\t\t\tif (!pisp->fe_.fd().isValid() || !pisp->be_.fd().isValid()) {\n+\t\t\t\tLOG(RPI, Error) << \"Failed to create ISP shared objects\";\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tint ret = registerCamera(cameraData, cfeDevice, \"csi2\",\n+\t\t\t\t\t\t ispDevice, entity);\n+\t\t\tif (ret)\n+\t\t\t\tLOG(RPI, Error) << \"Failed to register camera \"\n+\t\t\t\t\t\t<< entity->name() << \": \" << ret;\n+\t\t\telse\n+\t\t\t\tnumCameras++;\n+\t\t}\n+\n+\t\tif (numCameras)\n+\t\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+int PipelineHandlerPiSP::prepareBuffers(Camera *camera)\n+{\n+\tPiSPCameraData *data = cameraData(camera);\n+\tunsigned int numRawBuffers = 0;\n+\tint ret;\n+\n+\tfor (Stream *s : camera->streams()) {\n+\t\tif (PipelineHandlerBase::isRaw(s->configuration().pixelFormat)) {\n+\t\t\tnumRawBuffers = s->configuration().bufferCount;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* Decide how many internal buffers to allocate. */\n+\tfor (auto const stream : data->streams_) {\n+\t\tunsigned int numBuffers;\n+\t\t/*\n+\t\t * For CFE, allocate a minimum of 4 buffers as we want\n+\t\t * to avoid any frame drops.\n+\t\t */\n+\t\tconstexpr unsigned int minBuffers = 4;\n+\t\tif (stream == &data->cfe_[Cfe::Output0]) {\n+\t\t\t/*\n+\t\t\t * If an application has configured a RAW stream, allocate\n+\t\t\t * additional buffers to make up the minimum, but ensure\n+\t\t\t * we have at least 2 sets of internal buffers to use to\n+\t\t\t * minimise frame drops.\n+\t\t\t */\n+\t\t\tnumBuffers = std::max<int>(2, minBuffers - numRawBuffers);\n+\t\t} else if (stream == &data->isp_[Isp::Input]) {\n+\t\t\t/*\n+\t\t\t * ISP input buffers are imported from the CFE, so follow\n+\t\t\t * similar logic as above to count all the RAW buffers\n+\t\t\t * available.\n+\t\t\t */\n+\t\t\tnumBuffers = numRawBuffers +\n+\t\t\t\t\tstd::max<int>(2, minBuffers - numRawBuffers);\n+\t\t} else if (stream == &data->cfe_[Cfe::Embedded]) {\n+\t\t\t/*\n+\t\t\t * Embedded data buffers are (currently) for internal use,\n+\t\t\t * so allocate a reasonably large amount.\n+\t\t\t */\n+\t\t\tnumBuffers = 12;\n+\t\t} else if (stream == &data->cfe_[Cfe::Stats] ||\n+\t\t\t   stream == &data->cfe_[Cfe::Config]) {\n+\t\t\tnumBuffers = data->config_.numCfeConfigStatsBuffers;\n+\t\t} else if (!data->beEnabled_) {\n+\t\t\t/* Backend not enabled, we don't need to allocate buffers. */\n+\t\t\tnumBuffers = 0;\n+\t\t} else if (stream == &data->isp_[Isp::TdnOutput] && data->config_.disableTdn) {\n+\t\t\t/* TDN is explicitly disabled. */\n+\t\t\tcontinue;\n+\t\t} else if (stream == &data->isp_[Isp::StitchOutput] && data->config_.disableHdr) {\n+\t\t\t/* Stitch/HDR is explicitly disabled. */\n+\t\t\tcontinue;\n+\t\t} else {\n+\t\t\t/* Allocate 2 sets of all other Backend buffers */\n+\t\t\tnumBuffers = 2;\n+\t\t}\n+\n+\t\tLOG(RPI, Debug) << \"Preparing \" << numBuffers\n+\t\t\t\t<< \" buffers for stream \" << stream->name();\n+\n+\t\tret = stream->prepareBuffers(numBuffers);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Store the Framebuffer pointers for convenience as we will ping-pong\n+\t * these buffers between the input and output nodes for TDN and Stitch.\n+\t *\n+\t * The buffer size needs to be setup here as well. Conveniently this is\n+\t * the same for both TDN and stitch.\n+\t */\n+\tpisp_image_format_config tdn;\n+\tdata->be_->GetTdnOutputFormat(tdn);\n+\tunsigned int size = tdn.stride * tdn.height;\n+\tfor (auto const &buffer : data->isp_[Isp::TdnOutput].getBuffers()) {\n+\t\tFrameBuffer *b = buffer.second.buffer;\n+\t\tb->_d()->metadata().planes()[0].bytesused = size;\n+\t\tdata->tdnBuffers_.push_back(b);\n+\t}\n+\tfor (auto const &buffer : data->isp_[Isp::StitchOutput].getBuffers()) {\n+\t\tFrameBuffer *b = buffer.second.buffer;\n+\t\tb->_d()->metadata().planes()[0].bytesused = size;\n+\t\tdata->stitchBuffers_.push_back(b);\n+\t}\n+\n+\t/* Size up the config buffers as well. */\n+\tfor (auto &b : data->isp_[Isp::Config].getBuffers()) {\n+\t\tFrameMetadata::Plane &plane = b.second.buffer->_d()->metadata().planes()[0];\n+\t\tplane.bytesused = sizeof(pisp_be_tiles_config);\n+\t}\n+\n+\t/*\n+\t * Pass the stats and embedded data buffers to the IPA. No other\n+\t * buffers need to be passed.\n+\t */\n+\tmapBuffers(camera, data->cfe_[Cfe::Stats].getBuffers(), RPi::MaskStats);\n+\tif (data->sensorMetadata_)\n+\t\tmapBuffers(camera, data->cfe_[Cfe::Embedded].getBuffers(),\n+\t\t\t   RPi::MaskEmbeddedData);\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerPiSP::platformRegister(std::unique_ptr<RPi::CameraData> &cameraData,\n+\t\t\t\t\t  MediaDevice *cfe, MediaDevice *isp)\n+{\n+\tPiSPCameraData *data = static_cast<PiSPCameraData *>(cameraData.get());\n+\tint ret;\n+\n+\tMediaEntity *cfeImage = cfe->getEntityByName(\"rp1-cfe-fe-image0\");\n+\tMediaEntity *cfeEmbedded = cfe->getEntityByName(\"rp1-cfe-csi2-ch1\");\n+\tMediaEntity *cfeStats = cfe->getEntityByName(\"rp1-cfe-fe-stats\");\n+\tMediaEntity *cfeConfig = cfe->getEntityByName(\"rp1-cfe-fe-config\");\n+\tMediaEntity *ispInput = isp->getEntityByName(\"pispbe-input\");\n+\tMediaEntity *IpaPrepare = isp->getEntityByName(\"pispbe-config\");\n+\tMediaEntity *ispOutput0 = isp->getEntityByName(\"pispbe-output0\");\n+\tMediaEntity *ispOutput1 = isp->getEntityByName(\"pispbe-output1\");\n+\tMediaEntity *ispTdnOutput = isp->getEntityByName(\"pispbe-tdn_output\");\n+\tMediaEntity *ispTdnInput = isp->getEntityByName(\"pispbe-tdn_input\");\n+\tMediaEntity *ispStitchOutput = isp->getEntityByName(\"pispbe-stitch_output\");\n+\tMediaEntity *ispStitchInput = isp->getEntityByName(\"pispbe-stitch_input\");\n+\n+\t/* Locate and open the cfe video streams. */\n+\tdata->cfe_[Cfe::Output0] = RPi::Stream(\"CFE Image\", cfeImage, StreamFlag::RequiresMmap);\n+\tdata->cfe_[Cfe::Embedded] = RPi::Stream(\"CFE Embedded\", cfeEmbedded);\n+\tdata->cfe_[Cfe::Stats] = RPi::Stream(\"CFE Stats\", cfeStats);\n+\tdata->cfe_[Cfe::Config] = RPi::Stream(\"CFE Config\", cfeConfig,\n+\t\t\t\t\t      StreamFlag::Recurrent | StreamFlag::RequiresMmap);\n+\n+\t/* Tag the ISP input stream as an import stream. */\n+\tdata->isp_[Isp::Input] =\n+\t\tRPi::Stream(\"ISP Input\", ispInput, StreamFlag::ImportOnly);\n+\tdata->isp_[Isp::Config] =\n+\t\tRPi::Stream(\"ISP Config\", IpaPrepare, StreamFlag::Recurrent |\n+\t\t\t\t\t\t      StreamFlag::RequiresMmap);\n+\tdata->isp_[Isp::Output0] =\n+\t\tRPi::Stream(\"ISP Output0\", ispOutput0, StreamFlag::RequiresMmap);\n+\tdata->isp_[Isp::Output1] =\n+\t\tRPi::Stream(\"ISP Output1\", ispOutput1, StreamFlag::RequiresMmap);\n+\tdata->isp_[Isp::TdnOutput] =\n+\t\tRPi::Stream(\"ISP TDN Output\", ispTdnOutput, StreamFlag::Recurrent);\n+\tdata->isp_[Isp::TdnInput] =\n+\t\tRPi::Stream(\"ISP TDN Input\", ispTdnInput, StreamFlag::ImportOnly |\n+\t\t\t\t\t\t\t  StreamFlag::Recurrent);\n+\tdata->isp_[Isp::StitchOutput] =\n+\t\tRPi::Stream(\"ISP Stitch Output\", ispStitchOutput, StreamFlag::Recurrent);\n+\tdata->isp_[Isp::StitchInput] =\n+\t\tRPi::Stream(\"ISP Stitch Input\", ispStitchInput, StreamFlag::ImportOnly |\n+\t\t\t\t\t\t\t\tStreamFlag::Recurrent);\n+\n+\t/* Wire up all the buffer connections. */\n+\tdata->cfe_[Cfe::Output0].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue);\n+\tdata->cfe_[Cfe::Stats].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue);\n+\tdata->cfe_[Cfe::Config].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue);\n+\tdata->isp_[Isp::Input].dev()->bufferReady.connect(data, &PiSPCameraData::beInputDequeue);\n+\tdata->isp_[Isp::Config].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue);\n+\tdata->isp_[Isp::Output0].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue);\n+\tdata->isp_[Isp::Output1].dev()->bufferReady.connect(data, &PiSPCameraData::beOutputDequeue);\n+\tdata->cfe_[Cfe::Embedded].dev()->bufferReady.connect(data, &PiSPCameraData::cfeBufferDequeue);\n+\n+\tdata->csi2Subdev_ = std::make_unique<V4L2Subdevice>(cfe->getEntityByName(\"csi2\"));\n+\tdata->feSubdev_ = std::make_unique<V4L2Subdevice>(cfe->getEntityByName(\"pisp-fe\"));\n+\tdata->csi2Subdev_->open();\n+\tdata->feSubdev_->open();\n+\n+\t/*\n+\t * Open all CFE and ISP streams. The exception is the embedded data\n+\t * stream, which only gets opened below if the IPA reports that the sensor\n+\t * supports embedded data.\n+\t *\n+\t * The below grouping is just for convenience so that we can easily\n+\t * iterate over all streams in one go.\n+\t */\n+\tdata->streams_.push_back(&data->cfe_[Cfe::Output0]);\n+\tdata->streams_.push_back(&data->cfe_[Cfe::Config]);\n+\tdata->streams_.push_back(&data->cfe_[Cfe::Stats]);\n+\tif (data->sensorMetadata_)\n+\t\tdata->streams_.push_back(&data->cfe_[Cfe::Embedded]);\n+\n+\tdata->streams_.push_back(&data->isp_[Isp::Input]);\n+\tdata->streams_.push_back(&data->isp_[Isp::Output0]);\n+\tdata->streams_.push_back(&data->isp_[Isp::Output1]);\n+\tdata->streams_.push_back(&data->isp_[Isp::Config]);\n+\tdata->streams_.push_back(&data->isp_[Isp::TdnInput]);\n+\tdata->streams_.push_back(&data->isp_[Isp::TdnOutput]);\n+\tdata->streams_.push_back(&data->isp_[Isp::StitchInput]);\n+\tdata->streams_.push_back(&data->isp_[Isp::StitchOutput]);\n+\n+\tfor (auto stream : data->streams_) {\n+\t\tret = stream->dev()->open();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/* Write up all the IPA connections. */\n+\tdata->ipa_->prepareIspComplete.connect(data, &PiSPCameraData::prepareIspComplete);\n+\tdata->ipa_->processStatsComplete.connect(data, &PiSPCameraData::processStatsComplete);\n+\tdata->ipa_->setCameraTimeout.connect(data, &PiSPCameraData::setCameraTimeout);\n+\n+\t/*\n+\t * List the available streams an application may request. At present, we\n+\t * do not advertise CFE Embedded and ISP Statistics streams, as there\n+\t * is no mechanism for the application to request non-image buffer formats.\n+\t */\n+\tstd::set<Stream *> streams;\n+\tstreams.insert(&data->cfe_[Cfe::Output0]);\n+\tstreams.insert(&data->isp_[Isp::Output0]);\n+\tstreams.insert(&data->isp_[Isp::Output1]);\n+\n+\t/* Create and register the camera. */\n+\tconst std::string &id = data->sensor_->id();\n+\tstd::shared_ptr<Camera> camera =\n+\t\tCamera::create(std::move(cameraData), id, streams);\n+\tPipelineHandler::registerCamera(std::move(camera));\n+\n+\tLOG(RPI, Info) << \"Registered camera \" << id\n+\t\t       << \" to CFE device \" << cfe->deviceNode()\n+\t\t       << \" and ISP device \" << isp->deviceNode()\n+\t\t       << \" using PiSP variant \" << data->pispVariant_.Name();\n+\n+\treturn 0;\n+}\n+\n+CameraConfiguration::Status\n+PiSPCameraData::platformValidate(RPi::RPiCameraConfiguration *rpiConfig) const\n+{\n+\tstd::vector<StreamParams> &rawStreams = rpiConfig->rawStreams_;\n+\tstd::vector<StreamParams> &outStreams = rpiConfig->outStreams_;\n+\n+\tCameraConfiguration::Status status = CameraConfiguration::Status::Valid;\n+\n+\t/* Can only output 1 RAW stream and/or 2 YUV/RGB streams for now. */\n+\tif (rawStreams.size() > 1 || outStreams.size() > 2) {\n+\t\tLOG(RPI, Error) << \"Invalid number of streams requested\";\n+\t\treturn CameraConfiguration::Status::Invalid;\n+\t}\n+\n+\tif (!rawStreams.empty()) {\n+\t\trawStreams[0].dev = cfe_[Cfe::Output0].dev();\n+\n+\t\tStreamConfiguration *rawStream = rawStreams[0].cfg;\n+\t\tBayerFormat bayer = BayerFormat::fromPixelFormat(rawStream->pixelFormat);\n+\t\t/*\n+\t\t * We cannot output CSI2 packed or non 16-bit output from the frontend,\n+\t\t * so signal the output as unpacked 16-bits in these cases.\n+\t\t */\n+\t\tif (bayer.packing == BayerFormat::Packing::CSI2 || bayer.bitDepth != 16) {\n+\t\t\tbayer.packing = (bayer.packing == BayerFormat::Packing::CSI2) ?\n+\t\t\t\tBayerFormat::Packing::PISP1 : BayerFormat::Packing::None;\n+\t\t\tbayer.bitDepth = 16;\n+\t\t}\n+\n+\t\t/* The RAW stream size cannot exceed the sensor frame output - for now. */\n+\t\tif (rawStream->size != rpiConfig->sensorFormat_.size ||\n+\t\t    rawStream->pixelFormat != bayer.toPixelFormat()) {\n+\t\t\trawStream->size = rpiConfig->sensorFormat_.size;\n+\t\t\trawStream->pixelFormat = bayer.toPixelFormat();\n+\t\t\tstatus = CameraConfiguration::Adjusted;\n+\t\t}\n+\n+\t\trawStreams[0].format =\n+\t\t\tRPi::PipelineHandlerBase::toV4L2DeviceFormat(cfe_[Cfe::Output0].dev(), rawStream);\n+\n+\t\tcomputeOptimalStride(rawStreams[0].format);\n+\t}\n+\n+\t/*\n+\t * For the two ISP outputs, the lower resolution must be routed from\n+\t * Output 1\n+\t *\n+\t * Index 0 contains the largest requested resolution.\n+\t */\n+\tunsigned int largestWidth = getLargestWidth(rpiConfig->sensorFormat_,\n+\t\t\t\t\t\t    rpiConfig->outStreams_);\n+\n+\tfor (unsigned int i = 0; i < outStreams.size(); i++) {\n+\t\tStreamConfiguration *cfg = outStreams[i].cfg;\n+\n+\t\t/*\n+\t\t * Output 1 must be for the smallest resolution. We will\n+\t\t * have that fixed up in the code above.\n+\t\t */\n+\t\tauto ispOutput = i == 1 || outStreams.size() == 1 ? Isp::Output1\n+\t\t\t\t\t\t\t\t  : Isp::Output0;\n+\t\toutStreams[i].dev = isp_[ispOutput].dev();\n+\n+\t\t/*\n+\t\t * Don't let The output streams downscale by more than 64x when\n+\t\t * a downscaler block is available, or 16x when there's only the\n+\t\t * resampler.\n+\t\t */\n+\t\tSize rawSize = rpiConfig->sensorFormat_.size.boundedToAspectRatio(cfg->size);\n+\t\tunsigned int outputIndex = ispOutput == Isp::Output0 ? 0 : 1;\n+\t\tSize minSize;\n+\t\tif (pispVariant_.BackEndDownscalerAvailable(0, outputIndex)) {\n+\t\t\t/*\n+\t\t\t * Downscaler available. Allow up to 64x downscale. If not a multiple of\n+\t\t\t * 64, round up to the next integer, but also ensure the result is even.\n+\t\t\t */\n+\t\t\tconst unsigned int downscale = 64;\n+\t\t\tminSize.width = (rawSize.width + downscale - 1) / downscale;\n+\t\t\tminSize.width = (minSize.width + 1) & ~1; /* ensure even */\n+\t\t\tminSize.height = (rawSize.height + downscale - 1) / downscale;\n+\t\t\tminSize.height = (minSize.height + 1) & ~1; /* ensure even */\n+\t\t} else {\n+\t\t\t/* No downscale. Resampler requires: (output_dim - 1) * 16 <= input_dim - 1 */\n+\t\t\tconst unsigned int downscale = 16;\n+\t\t\tminSize.width = (rawSize.width - 1 + downscale - 1) / downscale + 1;\n+\t\t\tminSize.width = (minSize.width + 1) & ~1; /* ensure even */\n+\t\t\tminSize.height = (rawSize.height - 1 + downscale - 1) / downscale + 1;\n+\t\t\tminSize.height = (minSize.height + 1) & ~1; /* ensure even */\n+\t\t}\n+\t\tLOG(RPI, Debug) << \"minSize: width \" << minSize.width << \" height \" << minSize.height;\n+\n+\t\t/* Bound the output size to minSize, preserve aspect ratio, and ensure even numbers. */\n+\t\tif (cfg->size.width < minSize.width) {\n+\t\t\tcfg->size.height = (cfg->size.height * minSize.width / cfg->size.width + 1) & ~1;\n+\t\t\tcfg->size.width = minSize.width;\n+\t\t\tstatus = CameraConfiguration::Status::Adjusted;\n+\t\t}\n+\n+\t\tif (cfg->size.height < minSize.height) {\n+\t\t\tcfg->size.width = (cfg->size.width * minSize.height / cfg->size.height + 1) & ~1;\n+\t\t\tcfg->size.height = minSize.height;\n+\t\t\tstatus = CameraConfiguration::Status::Adjusted;\n+\t\t}\n+\n+\t\t/* Make sure output1 is no larger than output 0. */\n+\t\tSize size = cfg->size.boundedTo(outStreams[0].cfg->size);\n+\n+\t\t/* \\todo Warn if upscaling: reduces image quality. */\n+\n+\t\tif (cfg->size != size) {\n+\t\t\tcfg->size = size;\n+\t\t\tstatus = CameraConfiguration::Status::Adjusted;\n+\t\t}\n+\n+\t\toutStreams[i].format =\n+\t\t\tRPi::PipelineHandlerBase::toV4L2DeviceFormat(outStreams[i].dev, outStreams[i].cfg);\n+\n+\t\t/* Compute the optimal stride for the BE output buffers. */\n+\t\tcomputeOptimalStride(outStreams[i].format);\n+\n+\t\t/*\n+\t\t * We need to check for software downscaling. This must happen\n+\t\t * after adjusting the device format so that we can choose the\n+\t\t * largest stride - which might have been the original\n+\t\t * unadjusted format, or the adjusted one (if software\n+\t\t * downscaling means it's larger).\n+\t\t */\n+\t\tV4L2DeviceFormat adjustedFormat = outStreams[i].format;\n+\t\tadjustDeviceFormat(adjustedFormat);\n+\n+\t\tunsigned int swDownscale =\n+\t\t\tcalculateSwDownscale(adjustedFormat, largestWidth,\n+\t\t\t\t\t     be_->GetMaxDownscale());\n+\t\tLOG(RPI, Debug) << \"For stream \" << adjustedFormat\n+\t\t\t\t<< \" swDownscale is \" << swDownscale;\n+\t\tif (swDownscale > 1) {\n+\t\t\tadjustedFormat.size.width *= swDownscale;\n+\t\t\tcomputeOptimalStride(adjustedFormat);\n+\t\t\tfor (unsigned int p = 0; p < outStreams[i].format.planesCount; p++)\n+\t\t\t\toutStreams[i].format.planes[p].bpl =\n+\t\t\t\t\tstd::max(outStreams[i].format.planes[p].bpl, adjustedFormat.planes[p].bpl);\n+\t\t}\n+\t}\n+\n+\treturn status;\n+}\n+\n+int PiSPCameraData::platformPipelineConfigure(const std::unique_ptr<YamlObject> &root)\n+{\n+\tconfig_ = {\n+\t\t.numCfeConfigStatsBuffers = 12,\n+\t\t.numCfeConfigQueue = 2,\n+\t\t.disableTdn = false,\n+\t\t.disableHdr = false,\n+\t};\n+\n+\tif (!root)\n+\t\treturn 0;\n+\n+\tstd::optional<double> ver = (*root)[\"version\"].get<double>();\n+\tif (!ver || *ver != 1.0) {\n+\t\tLOG(RPI, Error) << \"Unexpected configuration file version reported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::optional<std::string> target = (*root)[\"target\"].get<std::string>();\n+\tif (!target || *target != \"pisp\") {\n+\t\tLOG(RPI, Error) << \"Unexpected target reported: expected \\\"pisp\\\", got \"\n+\t\t\t\t<< *target;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tconst YamlObject &phConfig = (*root)[\"pipeline_handler\"];\n+\tconfig_.numCfeConfigStatsBuffers =\n+\t\tphConfig[\"num_cfe_config_stats_buffers\"].get<unsigned int>(config_.numCfeConfigStatsBuffers);\n+\tconfig_.numCfeConfigQueue =\n+\t\tphConfig[\"num_cfe_config_queue\"].get<unsigned int>(config_.numCfeConfigQueue);\n+\tconfig_.disableTdn = phConfig[\"disable_tdn\"].get<bool>(config_.disableTdn);\n+\tconfig_.disableHdr = phConfig[\"disable_hdr\"].get<bool>(config_.disableHdr);\n+\n+\tif (config_.disableTdn) {\n+\t\tLOG(RPI, Info) << \"TDN disabled by user config\";\n+\t\tstreams_.erase(std::remove_if(streams_.begin(), streams_.end(),\n+\t\t\t       [this] (const RPi::Stream *s) { return s == &isp_[Isp::TdnInput] ||\n+\t\t\t\t\t\t\t\t      s == &isp_[Isp::TdnInput]; }),\n+\t\t\t       streams_.end());\n+\t}\n+\n+\tif (config_.disableHdr) {\n+\t\tLOG(RPI, Info) << \"HDR disabled by user config\";\n+\t\tstreams_.erase(std::remove_if(streams_.begin(), streams_.end(),\n+\t\t\t       [this] (const RPi::Stream *s) { return s == &isp_[Isp::StitchInput] ||\n+\t\t\t\t\t\t\t\t      s == &isp_[Isp::StitchOutput]; }),\n+\t\t\t       streams_.end());\n+\t}\n+\n+\tif (config_.numCfeConfigStatsBuffers < 1) {\n+\t\tLOG(RPI, Error)\n+\t\t\t<< \"Invalid configuration: num_cfe_config_stats_buffers must be >= 1\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (config_.numCfeConfigQueue < 1) {\n+\t\tLOG(RPI, Error)\n+\t\t\t<< \"Invalid configuration: numCfeConfigQueue must be >= 1\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+std::unordered_map<uint32_t, uint32_t> deviceAdjustTable = {\n+\t{ V4L2_PIX_FMT_RGBX32, V4L2_PIX_FMT_RGB24 },\n+\t{ V4L2_PIX_FMT_XBGR32, V4L2_PIX_FMT_BGR24 }\n+};\n+\n+bool PiSPCameraData::adjustDeviceFormat(V4L2DeviceFormat &format) const\n+{\n+\tauto it = deviceAdjustTable.find(format.fourcc.fourcc());\n+\n+\tif (pispVariant_.BackendRGB32Supported(0))\n+\t\treturn false;\n+\n+\tif (it != deviceAdjustTable.end()) {\n+\t\tLOG(RPI, Debug) << \"Swapping 32-bit for 24-bit format\";\n+\t\tformat.fourcc = V4L2PixelFormat(it->second);\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+int PiSPCameraData::platformConfigure(const RPi::RPiCameraConfiguration *rpiConfig)\n+{\n+\tconst std::vector<RPi::RPiCameraConfiguration::StreamParams> &rawStreams = rpiConfig->rawStreams_;\n+\tconst std::vector<RPi::RPiCameraConfiguration::StreamParams> &outStreams = rpiConfig->outStreams_;\n+\tint ret;\n+\tV4L2VideoDevice *cfe = cfe_[Cfe::Output0].dev();\n+\tV4L2DeviceFormat cfeFormat;\n+\n+\t/*\n+\t * See which streams are requested, and route the user\n+\t * StreamConfiguration appropriately.\n+\t */\n+\tif (rawStreams.empty()) {\n+\t\t/*\n+\t\t * The CFE Frontend output will always be 16-bits unpacked, so adjust the\n+\t\t * mbus code right at the start.\n+\t\t */\n+\t\tV4L2SubdeviceFormat sensorFormatMod = rpiConfig->sensorFormat_;\n+\t\tsensorFormatMod.code = mbusCodeUnpacked16(sensorFormatMod.code);\n+\t\tcfeFormat = RPi::PipelineHandlerBase::toV4L2DeviceFormat(cfe,\n+\t\t\t\t\t\t\t\t\t sensorFormatMod,\n+\t\t\t\t\t\t\t\t\t BayerFormat::Packing::PISP1);\n+\t\tcomputeOptimalStride(cfeFormat);\n+\t} else {\n+\t\trawStreams[0].cfg->setStream(&cfe_[Cfe::Output0]);\n+\t\tcfe_[Cfe::Output0].setFlags(StreamFlag::External);\n+\t\tcfeFormat = rawStreams[0].format;\n+\t}\n+\n+\t/*\n+\t * If the sensor output is 16-bits, we must endian swap the buffer\n+\t * contents to account for the HW missing this feature.\n+\t */\n+\tcfe_[Cfe::Output0].clearFlags(StreamFlag::Needs16bitEndianSwap);\n+\tif (MediaBusFormatInfo::info(rpiConfig->sensorFormat_.code).bitsPerPixel == 16) {\n+\t\tcfe_[Cfe::Output0].setFlags(StreamFlag::Needs16bitEndianSwap);\n+\t\tLOG(RPI, Warning)\n+\t\t\t<< \"The sensor is configured for a 16-bit output, statistics\"\n+\t\t\t<< \"  will not be correct. You must use manual camera settings.\";\n+\t}\n+\n+\t/* Ditto for the 14-bit unpacking. */\n+\tcfe_[Cfe::Output0].clearFlags(StreamFlag::Needs14bitUnpack);\n+\tif (MediaBusFormatInfo::info(rpiConfig->sensorFormat_.code).bitsPerPixel == 14) {\n+\t\tcfe_[Cfe::Output0].setFlags(StreamFlag::Needs14bitUnpack);\n+\t\tLOG(RPI, Warning)\n+\t\t\t<< \"The sensor is configured for a 14-bit output, statistics\"\n+\t\t\t<< \"  will not be correct. You must use manual camera settings.\";\n+\t}\n+\n+\tret = cfe->setFormat(&cfeFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Set the TDN and Stitch node formats in case they are turned on. */\n+\tisp_[Isp::TdnOutput].dev()->setFormat(&cfeFormat);\n+\tisp_[Isp::TdnInput].dev()->setFormat(&cfeFormat);\n+\tisp_[Isp::StitchOutput].dev()->setFormat(&cfeFormat);\n+\tisp_[Isp::StitchInput].dev()->setFormat(&cfeFormat);\n+\n+\tret = isp_[Isp::Input].dev()->setFormat(&cfeFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tLOG(RPI, Info) << \"Sensor: \" << sensor_->id()\n+\t\t       << \" - Selected sensor format: \" << rpiConfig->sensorFormat_\n+\t\t       << \" - Selected CFE format: \" << cfeFormat;\n+\n+\t/*\n+\t * Find the largest width of any stream; we'll use it later to check for\n+\t * excessive downscaling.\n+\t */\n+\tunsigned int largestWidth = getLargestWidth(rpiConfig->sensorFormat_, outStreams);\n+\n+\tunsigned int beEnables = 0;\n+\tV4L2DeviceFormat format;\n+\n+\t/*\n+\t * First thing is to remove Isp::Output0 and Isp::Output1 from streams_\n+\t * as they may be unused depending on the configuration. Add them back\n+\t * only if needed.\n+\t */\n+\tstreams_.erase(std::remove_if(streams_.begin(), streams_.end(),\n+\t\t       [this] (const RPi::Stream *s) { return s == &isp_[Isp::Output0] ||\n+\t\t\t\t\t\t\t      s == &isp_[Isp::Output1]; }),\n+\t\t       streams_.end());\n+\n+\tcropParams_.clear();\n+\tfor (unsigned int i = 0; i < outStreams.size(); i++) {\n+\t\tStreamConfiguration *cfg = outStreams[i].cfg;\n+\t\tunsigned int ispIndex;\n+\n+\t\t/*\n+\t\t * Output 1 must be for the smallest resolution. We will\n+\t\t * have that fixed up in the code above.\n+\t\t */\n+\t\tRPi::Stream *stream;\n+\t\tif (i == 1 || outStreams.size() == 1) {\n+\t\t\tstream = &isp_[Isp::Output1];\n+\t\t\tbeEnables |= PISP_BE_RGB_ENABLE_OUTPUT1;\n+\t\t\tispIndex = 1;\n+\t\t} else {\n+\t\t\tstream = &isp_[Isp::Output0];\n+\t\t\tbeEnables |= PISP_BE_RGB_ENABLE_OUTPUT0;\n+\t\t\tispIndex = 0;\n+\t\t}\n+\n+\t\tformat = outStreams[i].format;\n+\t\tbool needs32BitConversion = adjustDeviceFormat(format);\n+\n+\t\t/*\n+\t\t * This pixel format may not be the same as the configured\n+\t\t * pixel format if adjustDeviceFormat() above has reqused a change.\n+\t\t */\n+\t\tPixelFormat pixFmt = format.fourcc.toPixelFormat();\n+\n+\t\t/* If there's excessive downscaling we'll do some of it in software. */\n+\t\tunsigned int swDownscale = calculateSwDownscale(format, largestWidth,\n+\t\t\t\t\t\t\t\tbe_->GetMaxDownscale());\n+\t\tunsigned int hwWidth = format.size.width * swDownscale;\n+\t\tformat.size.width = hwWidth;\n+\n+\t\tLOG(RPI, Debug) << \"Setting \" << stream->name() << \" to \"\n+\t\t\t\t<< format << \" (sw downscale \" << swDownscale << \")\";\n+\n+\t\tret = stream->dev()->setFormat(&format);\n+\t\tif (ret)\n+\t\t\treturn -EINVAL;\n+\t\tLOG(RPI, Debug) << \"After setFormat, stride \" << format.planes[0].bpl;\n+\n+\t\tif (format.size.height != cfg->size.height ||\n+\t\t    format.size.width != hwWidth || format.fourcc.toPixelFormat() != pixFmt) {\n+\t\t\tLOG(RPI, Error)\n+\t\t\t\t<< \"Failed to set requested format on \" << stream->name()\n+\t\t\t\t<< \", returned \" << format;\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tLOG(RPI, Debug)\n+\t\t\t<< \"Stream \" << stream->name() << \" has color space \"\n+\t\t\t<< ColorSpace::toString(cfg->colorSpace);\n+\n+\t\tlibcamera::RPi::Stream::StreamFlags flags = StreamFlag::External;\n+\n+\t\tstream->clearFlags(StreamFlag::Needs32bitConv);\n+\t\tif (needs32BitConversion)\n+\t\t\tflags |= StreamFlag::Needs32bitConv;\n+\n+\t\t/* Set smallest selection the ISP will allow. */\n+\t\tSize minCrop{ 32, 32 };\n+\n+\t\t/* Adjust aspect ratio by providing crops on the input image. */\n+\t\tSize size = cfeFormat.size.boundedToAspectRatio(outStreams[i].cfg->size);\n+\t\tRectangle ispCrop = size.centeredTo(Rectangle(cfeFormat.size).center());\n+\n+\t\t/*\n+\t\t * Calculate the minimum crop. The rule is that (output_dim - 1) / (input_dim - 1)\n+\t\t * must be strictly < 16. We add 2 after dividing because +1\n+\t\t * comes from the division that rounds down, and +1 because we\n+\t\t * had (input_dim - 1).\n+\t\t */\n+\t\tSize scalingMinSize = outStreams[i].cfg->size.shrunkBy({ 1, 1 }) / 16;\n+\t\tscalingMinSize.growBy({ 2, 2 });\n+\t\tminCrop.expandTo(scalingMinSize);\n+\n+\t\tplatformSetIspCrop(ispIndex, ispCrop);\n+\t\t/*\n+\t\t * Set the scaler crop to the value we are using (scaled to native sensor\n+\t\t * coordinates).\n+\t\t */\n+\t\tcropParams_.emplace(std::piecewise_construct,\n+\t\t\t\t    std::forward_as_tuple(outStreams[i].index),\n+\t\t\t\t    std::forward_as_tuple(ispCrop, minCrop, ispIndex));\n+\n+\t\tcfg->setStream(stream);\n+\t\tstream->setFlags(flags);\n+\t\tstream->setSwDownscale(swDownscale);\n+\t\tstreams_.push_back(stream);\n+\t}\n+\n+\tpisp_be_global_config global;\n+\tbe_->GetGlobal(global);\n+\tglobal.rgb_enables &= ~(PISP_BE_RGB_ENABLE_OUTPUT0 + PISP_BE_RGB_ENABLE_OUTPUT1);\n+\tglobal.rgb_enables |= beEnables;\n+\tbe_->SetGlobal(global);\n+\n+\tbeEnabled_ = beEnables & (PISP_BE_RGB_ENABLE_OUTPUT0 | PISP_BE_RGB_ENABLE_OUTPUT1);\n+\n+\t/* CFE statistics output format. */\n+\tformat = {};\n+\tformat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPI_FE_STATS);\n+\tret = cfe_[Cfe::Stats].dev()->setFormat(&format);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to set format on CFE stats stream: \"\n+\t\t\t\t<< format;\n+\t\treturn ret;\n+\t}\n+\n+\t/* CFE config format. */\n+\tformat = {};\n+\tformat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RPI_FE_CFG);\n+\tret = cfe_[Cfe::Config].dev()->setFormat(&format);\n+\tif (ret) {\n+\t\tLOG(RPI, Error) << \"Failed to set format on CFE config stream: \"\n+\t\t\t\t<< format;\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Configure the CFE embedded data output format only if the sensor\n+\t * supports it.\n+\t */\n+\tV4L2SubdeviceFormat embeddedFormat = sensor_->embeddedDataFormat();\n+\tif (sensorMetadata_) {\n+\t\tstatic const std::map<uint32_t, V4L2PixelFormat> metaFormats{\n+\t\t\t{ MEDIA_BUS_FMT_META_8, V4L2PixelFormat(V4L2_META_FMT_GENERIC_8) },\n+\t\t\t{ MEDIA_BUS_FMT_META_10, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_10) },\n+\t\t\t{ MEDIA_BUS_FMT_META_12, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_12) },\n+\t\t\t{ MEDIA_BUS_FMT_META_14, V4L2PixelFormat(V4L2_META_FMT_GENERIC_CSI2_14) },\n+\t\t};\n+\n+\t\tconst auto metaFormat = metaFormats.find(embeddedFormat.code);\n+\t\tif (metaFormat == metaFormats.end()) {\n+\t\t\tLOG(RPI, Error)\n+\t\t\t\t<< \"Unsupported metadata format \"\n+\t\t\t\t<< utils::hex(embeddedFormat.code, 4);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tformat = {};\n+\t\tformat.fourcc = metaFormat->second;\n+\t\tformat.size = embeddedFormat.size;\n+\n+\t\tLOG(RPI, Debug) << \"Setting embedded data format \" << format;\n+\t\tret = cfe_[Cfe::Embedded].dev()->setFormat(&format);\n+\t\tif (ret) {\n+\t\t\tLOG(RPI, Error) << \"Failed to set format on CFE embedded: \"\n+\t\t\t\t\t<< format;\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tconfigureEntities(rpiConfig->sensorFormat_, embeddedFormat);\n+\tconfigureCfe();\n+\n+\tif (beEnabled_)\n+\t\tconfigureBe(rpiConfig->yuvColorSpace_);\n+\n+\treturn 0;\n+}\n+\n+void PiSPCameraData::platformStart()\n+{\n+\t/*\n+\t * We don't need to worry about dequeue events for the TDN and Stitch\n+\t * nodes as the buffers are simply ping-ponged every frame. But we do\n+\t * want to track the currently used input index.\n+\t */\n+\ttdnInputIndex_ = 0;\n+\tstitchInputIndex_ = 0;\n+\n+\tcfeJobQueue_ = {};\n+\n+\tfor (unsigned int i = 0; i < config_.numCfeConfigQueue; i++)\n+\t\tprepareCfe();\n+\n+\t/* Clear the debug dump file history. */\n+\tlast_dump_file_.clear();\n+}\n+\n+void PiSPCameraData::platformStop()\n+{\n+\tcfeJobQueue_ = {};\n+}\n+\n+void PiSPCameraData::platformFreeBuffers()\n+{\n+\ttdnBuffers_.clear();\n+\tstitchBuffers_.clear();\n+}\n+\n+void PiSPCameraData::cfeBufferDequeue(FrameBuffer *buffer)\n+{\n+\tRPi::Stream *stream = nullptr;\n+\tint index;\n+\n+\tif (!isRunning())\n+\t\treturn;\n+\n+\tfor (RPi::Stream &s : cfe_) {\n+\t\tindex = s.getBufferId(buffer);\n+\t\tif (index) {\n+\t\t\tstream = &s;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* If the last CFE job has completed, we need a new job entry in the queue. */\n+\tif (cfeJobQueue_.empty() || cfeJobComplete())\n+\t\tcfeJobQueue_.push({});\n+\n+\tCfeJob &job = cfeJobQueue_.back();\n+\n+\t/* The buffer must belong to one of our streams. */\n+\tASSERT(stream);\n+\n+\tLOG(RPI, Debug) << \"Stream \" << stream->name() << \" buffer dequeue\"\n+\t\t\t<< \", buffer id \" << index\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\tjob.buffers[stream] = buffer;\n+\n+\tif (stream == &cfe_[Cfe::Output0]) {\n+\t\t/* Do an endian swap or 14-bit unpacking if needed. */\n+\t\tif (stream->getFlags() & StreamFlag::Needs16bitEndianSwap ||\n+\t\t    stream->getFlags() & StreamFlag::Needs14bitUnpack) {\n+\t\t\tconst unsigned int stride = stream->configuration().stride;\n+\t\t\tconst unsigned int width = stream->configuration().size.width;\n+\t\t\tconst unsigned int height = stream->configuration().size.height;\n+\t\t\tconst RPi::BufferObject &b = stream->getBuffer(index);\n+\n+\t\t\tASSERT(b.mapped);\n+\t\t\tvoid *mem = b.mapped->planes()[0].data();\n+\n+\t\t\tdmabufSyncStart(buffer->planes()[0].fd);\n+\t\t\tif (stream->getFlags() & StreamFlag::Needs16bitEndianSwap)\n+\t\t\t\tdo16BitEndianSwap(mem, width, height, stride);\n+\t\t\telse\n+\t\t\t\tdo14bitUnpack(mem, width, height, stride);\n+\t\t\tdmabufSyncEnd(buffer->planes()[0].fd);\n+\t\t}\n+\n+\t\t/*\n+\t\t * Lookup the sensor controls used for this frame sequence from\n+\t\t * DelayedControl and queue them along with the frame buffer.\n+\t\t */\n+\t\tauto [ctrl, delayContext] = delayedCtrls_->get(buffer->metadata().sequence);\n+\t\t/*\n+\t\t * Add the frame timestamp to the ControlList for the IPA to use\n+\t\t * as it does not receive the FrameBuffer object.\n+\t\t */\n+\t\tctrl.set(controls::SensorTimestamp, buffer->metadata().timestamp);\n+\t\tjob.sensorControls = std::move(ctrl);\n+\t\tjob.delayContext = delayContext;\n+\t} else if (stream == &cfe_[Cfe::Config]) {\n+\t\t/* The config buffer can be re-queued back straight away. */\n+\t\thandleStreamBuffer(buffer, &cfe_[Cfe::Config]);\n+\t\tprepareCfe();\n+\t}\n+\n+\thandleState();\n+}\n+\n+void PiSPCameraData::beInputDequeue(FrameBuffer *buffer)\n+{\n+\tif (!isRunning())\n+\t\treturn;\n+\n+\tLOG(RPI, Debug) << \"Stream ISP Input buffer complete\"\n+\t\t\t<< \", buffer id \" << cfe_[Cfe::Output0].getBufferId(buffer)\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\t/* The ISP input buffer gets re-queued into CFE. */\n+\thandleStreamBuffer(buffer, &cfe_[Cfe::Output0]);\n+\thandleState();\n+}\n+\n+void PiSPCameraData::beOutputDequeue(FrameBuffer *buffer)\n+{\n+\tRPi::Stream *stream = nullptr;\n+\tint index;\n+\n+\tif (!isRunning())\n+\t\treturn;\n+\n+\tfor (RPi::Stream &s : isp_) {\n+\t\tindex = s.getBufferId(buffer);\n+\t\tif (index) {\n+\t\t\tstream = &s;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* The buffer must belong to one of our ISP output streams. */\n+\tASSERT(stream);\n+\n+\tLOG(RPI, Debug) << \"Stream \" << stream->name() << \" buffer complete\"\n+\t\t\t<< \", buffer id \" << index\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\tbool downscale = stream->swDownscale() > 1;\n+\tbool needs32bitConv = !!(stream->getFlags() & StreamFlag::Needs32bitConv);\n+\n+\tif (downscale || needs32bitConv)\n+\t\tdmabufSyncStart(buffer->planes()[0].fd);\n+\n+\tif (downscale) {\n+\t\t/* Further software downscaling must be applied. */\n+\t\tdownscaleStreamBuffer(stream, index);\n+\t}\n+\n+\t/* Convert 24bpp outputs to 32bpp outputs where necessary. */\n+\tif (needs32bitConv) {\n+\t\tunsigned int stride = stream->configuration().stride;\n+\t\tunsigned int width = stream->configuration().size.width;\n+\t\tunsigned int height = stream->configuration().size.height;\n+\n+\t\tconst RPi::BufferObject &b = stream->getBuffer(index);\n+\n+\t\tASSERT(b.mapped);\n+\t\tvoid *mem = b.mapped->planes()[0].data();\n+\t\tdo32BitConversion(mem, width, height, stride);\n+\t}\n+\n+\tif (downscale || needs32bitConv)\n+\t\tdmabufSyncEnd(buffer->planes()[0].fd);\n+\n+\thandleStreamBuffer(buffer, stream);\n+\n+\t/*\n+\t * Increment the number of ISP outputs generated.\n+\t * This is needed to track dropped frames.\n+\t */\n+\tispOutputCount_++;\n+\thandleState();\n+}\n+\n+void PiSPCameraData::processStatsComplete(const ipa::RPi::BufferIds &buffers)\n+{\n+\tif (!isRunning())\n+\t\treturn;\n+\n+\thandleStreamBuffer(cfe_[Cfe::Stats].getBuffers().at(buffers.stats & RPi::MaskID).buffer,\n+\t\t\t   &cfe_[Cfe::Stats]);\n+}\n+\n+void PiSPCameraData::setCameraTimeout(uint32_t maxFrameLengthMs)\n+{\n+\t/*\n+\t * Set the dequeue timeout to the larger of 5x the maximum reported\n+\t * frame length advertised by the IPA over a number of frames. Allow\n+\t * a minimum timeout value of 1s.\n+\t */\n+\tutils::Duration timeout =\n+\t\tstd::max<utils::Duration>(1s, 5 * maxFrameLengthMs * 1ms);\n+\n+\tLOG(RPI, Debug) << \"Setting CFE timeout to \" << timeout;\n+\tcfe_[Cfe::Output0].dev()->setDequeueTimeout(timeout);\n+}\n+\n+void PiSPCameraData::prepareIspComplete(const ipa::RPi::BufferIds &buffers, bool stitchSwapBuffers)\n+{\n+\tunsigned int embeddedId = buffers.embedded & RPi::MaskID;\n+\tunsigned int bayerId = buffers.bayer & RPi::MaskID;\n+\tFrameBuffer *buffer;\n+\n+\tif (!isRunning())\n+\t\treturn;\n+\n+\tif (sensorMetadata_ && embeddedId) {\n+\t\tbuffer = cfe_[Cfe::Embedded].getBuffers().at(embeddedId).buffer;\n+\t\thandleStreamBuffer(buffer, &cfe_[Cfe::Embedded]);\n+\t}\n+\n+\tif (!beEnabled_) {\n+\t\t/*\n+\t\t * If there is no need to run the Backend, just signal that the\n+\t\t * input buffer is completed and all Backend outputs are ready.\n+\t\t */\n+\t\tispOutputCount_ = ispOutputTotal_;\n+\t\tbuffer = cfe_[Cfe::Output0].getBuffers().at(bayerId).buffer;\n+\t\thandleStreamBuffer(buffer, &cfe_[Cfe::Output0]);\n+\t} else\n+\t\tprepareBe(bayerId, stitchSwapBuffers);\n+\n+\tstate_ = State::IpaComplete;\n+\thandleState();\n+}\n+\n+int PiSPCameraData::configureCfe()\n+{\n+\tV4L2DeviceFormat cfeFormat;\n+\tcfe_[Cfe::Output0].dev()->getFormat(&cfeFormat);\n+\n+\tstd::scoped_lock<FrontEnd> l(*fe_);\n+\n+\tpisp_fe_global_config global;\n+\tfe_->GetGlobal(global);\n+\tglobal.enables &= ~PISP_FE_ENABLE_COMPRESS0;\n+\n+\tglobal.enables |= PISP_FE_ENABLE_OUTPUT0;\n+\tglobal.bayer_order = toPiSPBayerOrder(cfeFormat.fourcc);\n+\n+\tpisp_image_format_config image = toPiSPImageFormat(cfeFormat);\n+\tpisp_fe_input_config input = {};\n+\n+\tinput.streaming = 1;\n+\tinput.format = image;\n+\tinput.format.format = PISP_IMAGE_FORMAT_BPS_16;\n+\n+\tif (PISP_IMAGE_FORMAT_COMPRESSED(image.format)) {\n+\t\tpisp_compress_config compress;\n+\t\tcompress.offset = DefaultCompressionOffset;\n+\t\tcompress.mode =\t(image.format & PISP_IMAGE_FORMAT_COMPRESSION_MASK) /\n+\t\t\t\t\tPISP_IMAGE_FORMAT_COMPRESSION_MODE_1;\n+\t\tglobal.enables |= PISP_FE_ENABLE_COMPRESS0;\n+\t\tfe_->SetCompress(0, compress);\n+\t}\n+\n+\tif (input.format.width > pispVariant_.FrontEndDownscalerMaxWidth(0, 0))\n+\t\tglobal.enables |= PISP_FE_ENABLE_DECIMATE;\n+\n+\tfe_->SetGlobal(global);\n+\tfe_->SetInput(input);\n+\tfe_->SetOutputFormat(0, image);\n+\n+\treturn 0;\n+}\n+\n+bool PiSPCameraData::calculateCscConfiguration(const V4L2DeviceFormat &v4l2Format, pisp_be_ccm_config &csc)\n+{\n+\tconst PixelFormat &pixFormat = v4l2Format.fourcc.toPixelFormat();\n+\tconst PixelFormatInfo &info = PixelFormatInfo::info(pixFormat);\n+\tmemset(&csc, 0, sizeof(csc));\n+\n+\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingYUV) {\n+\t\t/* Look up the correct YCbCr conversion matrix for this colour space. */\n+\t\tif (v4l2Format.colorSpace == ColorSpace::Sycc)\n+\t\t\tbe_->InitialiseYcbcr(csc, \"jpeg\");\n+\t\telse if (v4l2Format.colorSpace == ColorSpace::Smpte170m)\n+\t\t\tbe_->InitialiseYcbcr(csc, \"smpte170m\");\n+\t\telse if (v4l2Format.colorSpace == ColorSpace::Rec709)\n+\t\t\tbe_->InitialiseYcbcr(csc, \"rec709\");\n+\t\telse {\n+\t\t\tLOG(RPI, Warning)\n+\t\t\t\t<< \"Unrecognised colour space \"\n+\t\t\t\t<< ColorSpace::toString(v4l2Format.colorSpace)\n+\t\t\t\t<< \", defaulting to sYCC\";\n+\t\t\tbe_->InitialiseYcbcr(csc, \"jpeg\");\n+\t\t}\n+\t\treturn true;\n+\t}\n+\t/* There will be more formats to check for in due course. */\n+\telse if (pixFormat == formats::RGB888 || pixFormat == formats::RGBX8888 ||\n+\t\t pixFormat == formats::XRGB8888 || pixFormat == formats::RGB161616) {\n+\t\t/* Identity matrix but with RB colour swap. */\n+\t\tcsc.coeffs[2] = csc.coeffs[4] = csc.coeffs[6] = 1 << 10;\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+\n+int PiSPCameraData::configureBe(const std::optional<ColorSpace> &yuvColorSpace)\n+{\n+\tpisp_image_format_config inputFormat;\n+\tV4L2DeviceFormat cfeFormat;\n+\n+\tisp_[Isp::Input].dev()->getFormat(&cfeFormat);\n+\tinputFormat = toPiSPImageFormat(cfeFormat);\n+\n+\tpisp_be_global_config global;\n+\tbe_->GetGlobal(global);\n+\tglobal.bayer_enables &= ~(PISP_BE_BAYER_ENABLE_DECOMPRESS +\n+\t\t\t\t  PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS +\n+\t\t\t\t  PISP_BE_BAYER_ENABLE_TDN_COMPRESS +\n+\t\t\t\t  PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS +\n+\t\t\t\t  PISP_BE_BAYER_ENABLE_STITCH_COMPRESS);\n+\tglobal.rgb_enables &= ~(PISP_BE_RGB_ENABLE_RESAMPLE0 +\n+\t\t\t\tPISP_BE_RGB_ENABLE_RESAMPLE1 +\n+\t\t\t\tPISP_BE_RGB_ENABLE_DOWNSCALE0 +\n+\t\t\t\tPISP_BE_RGB_ENABLE_DOWNSCALE1 +\n+\t\t\t\tPISP_BE_RGB_ENABLE_CSC0 +\n+\t\t\t\tPISP_BE_RGB_ENABLE_CSC1);\n+\n+\tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_INPUT;\n+\tglobal.bayer_order = toPiSPBayerOrder(cfeFormat.fourcc);\n+\n+\tispOutputTotal_ = 1; /* Config buffer */\n+\tif (PISP_IMAGE_FORMAT_COMPRESSED(inputFormat.format)) {\n+\t\tpisp_decompress_config decompress;\n+\t\tdecompress.offset = DefaultCompressionOffset;\n+\t\tdecompress.mode = (inputFormat.format & PISP_IMAGE_FORMAT_COMPRESSION_MASK)\n+\t\t\t\t\t/ PISP_IMAGE_FORMAT_COMPRESSION_MODE_1;\n+\t\tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_DECOMPRESS;\n+\t\tbe_->SetDecompress(decompress);\n+\t}\n+\n+\tif (global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT0) {\n+\t\tpisp_be_output_format_config outputFormat0 = {};\n+\t\tV4L2DeviceFormat ispFormat0 = {};\n+\n+\t\tisp_[Isp::Output0].dev()->getFormat(&ispFormat0);\n+\t\toutputFormat0.image = toPiSPImageFormat(ispFormat0);\n+\n+\t\tpisp_be_ccm_config csc;\n+\t\tif (calculateCscConfiguration(ispFormat0, csc)) {\n+\t\t\tglobal.rgb_enables |= PISP_BE_RGB_ENABLE_CSC0;\n+\t\t\tbe_->SetCsc(0, csc);\n+\t\t}\n+\n+\t\tBackEnd::SmartResize resize = {};\n+\t\tresize.width = ispFormat0.size.width;\n+\t\tresize.height = ispFormat0.size.height;\n+\t\tbe_->SetSmartResize(0, resize);\n+\n+\t\tsetupOutputClipping(ispFormat0, outputFormat0);\n+\n+\t\tbe_->SetOutputFormat(0, outputFormat0);\n+\t\tispOutputTotal_++;\n+\t}\n+\n+\tif (global.rgb_enables & PISP_BE_RGB_ENABLE_OUTPUT1) {\n+\t\tpisp_be_output_format_config outputFormat1 = {};\n+\t\tV4L2DeviceFormat ispFormat1 = {};\n+\n+\t\tisp_[Isp::Output1].dev()->getFormat(&ispFormat1);\n+\t\toutputFormat1.image = toPiSPImageFormat(ispFormat1);\n+\n+\t\tpisp_be_ccm_config csc;\n+\t\tif (calculateCscConfiguration(ispFormat1, csc)) {\n+\t\t\tglobal.rgb_enables |= PISP_BE_RGB_ENABLE_CSC1;\n+\t\t\tbe_->SetCsc(1, csc);\n+\t\t}\n+\n+\t\tBackEnd::SmartResize resize = {};\n+\t\tresize.width = ispFormat1.size.width;\n+\t\tresize.height = ispFormat1.size.height;\n+\t\tbe_->SetSmartResize(1, resize);\n+\n+\t\tsetupOutputClipping(ispFormat1, outputFormat1);\n+\n+\t\tbe_->SetOutputFormat(1, outputFormat1);\n+\t\tispOutputTotal_++;\n+\t}\n+\n+\t/* Setup the TDN I/O blocks in case TDN gets turned on later. */\n+\tV4L2DeviceFormat tdnV4L2Format;\n+\tisp_[Isp::TdnOutput].dev()->getFormat(&tdnV4L2Format);\n+\tpisp_image_format_config tdnFormat = toPiSPImageFormat(tdnV4L2Format);\n+\tbe_->SetTdnOutputFormat(tdnFormat);\n+\tbe_->SetTdnInputFormat(tdnFormat);\n+\n+\tif (PISP_IMAGE_FORMAT_COMPRESSED(tdnFormat.format)) {\n+\t\tpisp_decompress_config tdnDecompress;\n+\t\tpisp_compress_config tdnCompress;\n+\n+\t\ttdnDecompress.offset = tdnCompress.offset = DefaultCompressionOffset;\n+\t\ttdnDecompress.mode = tdnCompress.mode = DefaultCompressionMode;\n+\t\tbe_->SetTdnDecompress(tdnDecompress);\n+\t\tbe_->SetTdnCompress(tdnCompress);\n+\t\tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_TDN_DECOMPRESS +\n+\t\t\t\t\tPISP_BE_BAYER_ENABLE_TDN_COMPRESS;\n+\t}\n+\n+\t/* Likewise for the Stitch block. */\n+\tV4L2DeviceFormat stitchV4L2Format;\n+\tisp_[Isp::StitchOutput].dev()->getFormat(&stitchV4L2Format);\n+\tpisp_image_format_config stitchFormat = toPiSPImageFormat(stitchV4L2Format);\n+\tbe_->SetStitchOutputFormat(stitchFormat);\n+\tbe_->SetStitchInputFormat(stitchFormat);\n+\n+\tif (PISP_IMAGE_FORMAT_COMPRESSED(stitchFormat.format)) {\n+\t\tpisp_decompress_config stitchDecompress;\n+\t\tpisp_compress_config stitchCompress;\n+\n+\t\t/* Stitch block is after BLC, so compression offset should be 0. */\n+\t\tstitchDecompress.offset = stitchCompress.offset = 0;\n+\t\tstitchDecompress.mode = stitchCompress.mode = DefaultCompressionMode;\n+\t\tbe_->SetStitchDecompress(stitchDecompress);\n+\t\tbe_->SetStitchCompress(stitchCompress);\n+\t\tglobal.bayer_enables |= PISP_BE_BAYER_ENABLE_STITCH_DECOMPRESS +\n+\t\t\t\t\tPISP_BE_BAYER_ENABLE_STITCH_COMPRESS;\n+\t}\n+\n+\t/*\n+\t * For the bit of the pipeline where we go temporarily into YCbCr, we'll use the\n+\t * same flavour of YCbCr as dictated by the headline colour space. But there's\n+\t * no benefit from compressing and shifting the range, so we'll stick with the\n+\t * full range version of whatever that colour space is.\n+\t */\n+\tif (yuvColorSpace) {\n+\t\tpisp_be_ccm_config ccm;\n+\t\tif (yuvColorSpace == ColorSpace::Sycc) {\n+\t\t\tbe_->InitialiseYcbcr(ccm, \"jpeg\");\n+\t\t\tbe_->SetYcbcr(ccm);\n+\t\t\tbe_->InitialiseYcbcrInverse(ccm, \"jpeg\");\n+\t\t\tbe_->SetYcbcrInverse(ccm);\n+\t\t} else if (yuvColorSpace == ColorSpace::Smpte170m) {\n+\t\t\t/* We want the full range version of smpte170m, aka. jpeg */\n+\t\t\tbe_->InitialiseYcbcr(ccm, \"jpeg\");\n+\t\t\tbe_->SetYcbcr(ccm);\n+\t\t\tbe_->InitialiseYcbcrInverse(ccm, \"jpeg\");\n+\t\t\tbe_->SetYcbcrInverse(ccm);\n+\t\t} else if (yuvColorSpace == ColorSpace::Rec709) {\n+\t\t\tbe_->InitialiseYcbcr(ccm, \"rec709_full\");\n+\t\t\tbe_->SetYcbcr(ccm);\n+\t\t\tbe_->InitialiseYcbcrInverse(ccm, \"rec709_full\");\n+\t\t\tbe_->SetYcbcrInverse(ccm);\n+\t\t} else {\n+\t\t\t/* Validation should have ensured this can't happen. */\n+\t\t\tLOG(RPI, Error)\n+\t\t\t\t<< \"Invalid colour space \"\n+\t\t\t\t<< ColorSpace::toString(yuvColorSpace);\n+\t\t\tASSERT(0);\n+\t\t}\n+\t} else {\n+\t\t/* Again, validation should have prevented this. */\n+\t\tLOG(RPI, Error) << \"No YUV colour space\";\n+\t\tASSERT(0);\n+\t}\n+\n+\tbe_->SetGlobal(global);\n+\tbe_->SetInputFormat(inputFormat);\n+\n+\treturn 0;\n+}\n+\n+void PiSPCameraData::platformSetIspCrop(unsigned int index, const Rectangle &ispCrop)\n+{\n+\tpisp_be_crop_config beCrop = {\n+\t\tstatic_cast<uint16_t>(ispCrop.x),\n+\t\tstatic_cast<uint16_t>(ispCrop.y),\n+\t\tstatic_cast<uint16_t>(ispCrop.width),\n+\t\tstatic_cast<uint16_t>(ispCrop.height)\n+\t};\n+\n+\tLOG(RPI, Debug) << \"Output \" << index << \" \" << ispCrop.toString();\n+\tbe_->SetCrop(index, beCrop);\n+}\n+\n+int PiSPCameraData::platformInitIpa(ipa::RPi::InitParams &params)\n+{\n+\tparams.fe = fe_.fd();\n+\tparams.be = be_.fd();\n+\treturn 0;\n+}\n+\n+int PiSPCameraData::configureEntities(V4L2SubdeviceFormat sensorFormat,\n+\t\t\t\t      V4L2SubdeviceFormat &embeddedFormat)\n+{\n+\tint ret = 0;\n+\n+\tconstexpr unsigned int csiVideoSinkPad = 0;\n+\tconstexpr unsigned int csiVideoSourcePad = 1;\n+\tconstexpr unsigned int csiMetaSourcePad = 2;\n+\n+\tconstexpr unsigned int feVideoSinkPad = 0;\n+\tconstexpr unsigned int feConfigSinkPad = 1;\n+\tconstexpr unsigned int feVideo0SourcePad = 2;\n+\tconstexpr unsigned int feVideo1SourcePad = 3;\n+\tconstexpr unsigned int feStatsSourcePad = 4;\n+\n+\tconst MediaEntity *csi2 = csi2Subdev_->entity();\n+\tconst MediaEntity *fe = feSubdev_->entity();\n+\n+\tfor (MediaLink *link : csi2->pads()[csiVideoSourcePad]->links()) {\n+\t\tif (link->sink()->entity()->name() == \"rp1-cfe-csi2-ch0\")\n+\t\t\tlink->setEnabled(false);\n+\t\telse if (link->sink()->entity()->name() == \"pisp-fe\")\n+\t\t\tlink->setEnabled(true);\n+\t}\n+\n+\tcsi2->pads()[csiMetaSourcePad]->links()[0]->setEnabled(sensorMetadata_);\n+\n+\tfe->pads()[feConfigSinkPad]->links()[0]->setEnabled(true);\n+\tfe->pads()[feVideo0SourcePad]->links()[0]->setEnabled(true);\n+\tfe->pads()[feVideo1SourcePad]->links()[0]->setEnabled(false);\n+\tfe->pads()[feStatsSourcePad]->links()[0]->setEnabled(true);\n+\n+\tconst V4L2Subdevice::Stream imageStream{\n+\t\tcsiVideoSinkPad,\n+\t\tsensor_->imageStream().stream\n+\t};\n+\tconst V4L2Subdevice::Stream embeddedDataStream{\n+\t\tcsiVideoSinkPad,\n+\t\tsensor_->embeddedDataStream().value_or(V4L2Subdevice::Stream{}).stream\n+\t};\n+\n+\tV4L2Subdevice::Routing routing;\n+\trouting.emplace_back(imageStream, V4L2Subdevice::Stream{ csiVideoSourcePad, 0 },\n+\t\t\t     V4L2_SUBDEV_ROUTE_FL_ACTIVE);\n+\n+\tif (sensorMetadata_)\n+\t\trouting.emplace_back(embeddedDataStream,\n+\t\t\t\t     V4L2Subdevice::Stream{ csiMetaSourcePad, 0 },\n+\t\t\t\t     V4L2_SUBDEV_ROUTE_FL_ACTIVE);\n+\n+\tret = csi2Subdev_->setRouting(&routing);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = csi2Subdev_->setFormat(imageStream, &sensorFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (sensorMetadata_) {\n+\t\tret = csi2Subdev_->setFormat(embeddedDataStream, &embeddedFormat);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tV4L2SubdeviceFormat feFormat = sensorFormat;\n+\tfeFormat.code = mbusCodeUnpacked16(sensorFormat.code);\n+\tret = feSubdev_->setFormat(feVideoSinkPad, &feFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = csi2Subdev_->setFormat(csiVideoSourcePad, &feFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tV4L2DeviceFormat feOutputFormat;\n+\tcfe_[Cfe::Output0].dev()->getFormat(&feOutputFormat);\n+\tBayerFormat feOutputBayer = BayerFormat::fromV4L2PixelFormat(feOutputFormat.fourcc);\n+\n+\tfeFormat.code = bayerToMbusCode(feOutputBayer);\n+\tret = feSubdev_->setFormat(feVideo0SourcePad, &feFormat);\n+\n+\treturn ret;\n+}\n+\n+void PiSPCameraData::prepareCfe()\n+{\n+\t/* Fetch an unused config buffer from the stream .*/\n+\tconst RPi::BufferObject &config = cfe_[Cfe::Config].acquireBuffer();\n+\tASSERT(config.mapped);\n+\n+\t{\n+\t\tstd::scoped_lock<FrontEnd> l(*fe_);\n+\t\tSpan<uint8_t> configBuffer = config.mapped->planes()[0];\n+\t\tfe_->Prepare(reinterpret_cast<pisp_fe_config *>(configBuffer.data()));\n+\t}\n+\n+\tconfig.buffer->_d()->metadata().planes()[0].bytesused = sizeof(pisp_fe_config);\n+\tcfe_[Cfe::Config].queueBuffer(config.buffer);\n+}\n+\n+void PiSPCameraData::prepareBe(uint32_t bufferId, bool stitchSwapBuffers)\n+{\n+\tispOutputCount_ = 0;\n+\n+\tFrameBuffer *buffer = cfe_[Cfe::Output0].getBuffers().at(bufferId).buffer;\n+\n+\tLOG(RPI, Debug) << \"Input re-queue to ISP, buffer id \" << bufferId\n+\t\t\t<< \", timestamp: \" << buffer->metadata().timestamp;\n+\n+\tisp_[Isp::Input].queueBuffer(buffer);\n+\n+\t/* Ping-pong between input/output buffers for the TDN and Stitch nodes. */\n+\tif (!config_.disableTdn) {\n+\t\tisp_[Isp::TdnInput].queueBuffer(tdnBuffers_[tdnInputIndex_]);\n+\t\tisp_[Isp::TdnOutput].queueBuffer(tdnBuffers_[tdnInputIndex_ ^ 1]);\n+\t\ttdnInputIndex_ ^= 1;\n+\t}\n+\n+\tif (!config_.disableHdr) {\n+\t\tif (stitchSwapBuffers)\n+\t\t\tstitchInputIndex_ ^= 1;\n+\t\tisp_[Isp::StitchInput].queueBuffer(stitchBuffers_[stitchInputIndex_]);\n+\t\tisp_[Isp::StitchOutput].queueBuffer(stitchBuffers_[stitchInputIndex_ ^ 1]);\n+\t}\n+\n+\t/* Fetch an unused config buffer from the stream .*/\n+\tconst RPi::BufferObject &config = isp_[Isp::Config].acquireBuffer();\n+\tASSERT(config.mapped);\n+\n+\tSpan<uint8_t> configBufferSpan = config.mapped->planes()[0];\n+\tpisp_be_tiles_config *configBuffer = reinterpret_cast<pisp_be_tiles_config *>(configBufferSpan.data());\n+\tbe_->Prepare(configBuffer);\n+\n+\t/*\n+\t * If the LIBCAMERA_RPI_PISP_CONFIG_DUMP environment variable is set,\n+\t * dump the Backend config to the given file. This is a one-shot\n+\t * operation, so log the filename that was provided and allow the\n+\t * application to change the filename for multiple dumps in a single\n+\t * run.\n+\t *\n+\t * \\todo Using an environment variable is only a temporary solution\n+\t * until we have support for vendor specific controls in libcamera.\n+\t */\n+\tconst char *config_dump = utils::secure_getenv(\"LIBCAMERA_RPI_PISP_CONFIG_DUMP\");\n+\tif (config_dump && last_dump_file_ != config_dump) {\n+\t\tstd::ofstream of(config_dump);\n+\t\tif (of.is_open()) {\n+\t\t\tof << be_->GetJsonConfig(configBuffer);\n+\t\t\tlast_dump_file_ = config_dump;\n+\t\t}\n+\t}\n+\n+\tisp_[Isp::Config].queueBuffer(config.buffer);\n+}\n+\n+void PiSPCameraData::tryRunPipeline()\n+{\n+\t/* If any of our request or buffer queues are empty, we cannot proceed. */\n+\tif (state_ != State::Idle || requestQueue_.empty() || !cfeJobComplete())\n+\t\treturn;\n+\n+\tCfeJob &job = cfeJobQueue_.front();\n+\n+\t/* Take the first request from the queue and action the IPA. */\n+\tRequest *request = requestQueue_.front();\n+\n+\t/* See if a new ScalerCrop value needs to be applied. */\n+\tapplyScalerCrop(request->controls());\n+\n+\t/*\n+\t * Clear the request metadata and fill it with some initial non-IPA\n+\t * related controls. We clear it first because the request metadata\n+\t * may have been populated if we have dropped the previous frame.\n+\t */\n+\trequest->metadata().clear();\n+\tfillRequestMetadata(job.sensorControls, request);\n+\n+\t/* Set our state to say the pipeline is active. */\n+\tstate_ = State::Busy;\n+\n+\tunsigned int bayerId = cfe_[Cfe::Output0].getBufferId(job.buffers[&cfe_[Cfe::Output0]]);\n+\tunsigned int statsId = cfe_[Cfe::Stats].getBufferId(job.buffers[&cfe_[Cfe::Stats]]);\n+\tASSERT(bayerId && statsId);\n+\n+\tstd::stringstream ss;\n+\tss << \"Signalling IPA processStats and prepareIsp:\"\n+\t   << \" Bayer buffer id: \" << bayerId\n+\t   << \" Stats buffer id: \" << statsId;\n+\n+\tipa::RPi::PrepareParams params;\n+\tparams.buffers.bayer = RPi::MaskBayerData | bayerId;\n+\tparams.buffers.stats = RPi::MaskStats | statsId;\n+\tparams.buffers.embedded = 0;\n+\tparams.ipaContext = requestQueue_.front()->sequence();\n+\tparams.delayContext = job.delayContext;\n+\tparams.sensorControls = std::move(job.sensorControls);\n+\tparams.requestControls = request->controls();\n+\n+\tif (sensorMetadata_) {\n+\t\tunsigned int embeddedId =\n+\t\t\tcfe_[Cfe::Embedded].getBufferId(job.buffers[&cfe_[Cfe::Embedded]]);\n+\n+\t\tASSERT(embeddedId);\n+\t\tparams.buffers.embedded = RPi::MaskEmbeddedData | embeddedId;\n+\t\tss << \" Embedded buffer id: \" << embeddedId;\n+\t}\n+\n+\tLOG(RPI, Debug) << ss.str();\n+\n+\tcfeJobQueue_.pop();\n+\tipa_->prepareIsp(params);\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerPiSP, \"rpi/pisp\")\n+\n+} /* namespace libcamera */\n",
    "prefixes": [
        "v1",
        "4/4"
    ]
}