Show a patch.

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

{
    "id": 17623,
    "url": "https://patchwork.libcamera.org/api/patches/17623/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/17623/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20221018164852.173916-1-jacopo@jmondi.org>",
    "date": "2022-10-18T16:48:52",
    "name": "[libcamera-devel] libcamera: pipeline: Add IMX8 ISI pipeline",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "6c5c57a7640bf97dbbb815d033e520a24ed1a8fc",
    "submitter": {
        "id": 3,
        "url": "https://patchwork.libcamera.org/api/people/3/?format=api",
        "name": "Jacopo Mondi",
        "email": "jacopo@jmondi.org"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/17623/mbox/",
    "series": [
        {
            "id": 3564,
            "url": "https://patchwork.libcamera.org/api/series/3564/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3564",
            "date": "2022-10-18T16:48:52",
            "name": "[libcamera-devel] libcamera: pipeline: Add IMX8 ISI pipeline",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/3564/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/17623/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/17623/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 10885BD16B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 18 Oct 2022 16:49:02 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2A839604E7;\n\tTue, 18 Oct 2022 18:49:01 +0200 (CEST)",
            "from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net\n\t[IPv6:2001:4b98:dc4:8::226])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9E07C604DA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 18 Oct 2022 18:48:59 +0200 (CEST)",
            "(Authenticated sender: jacopo@jmondi.org)\n\tby mail.gandi.net (Postfix) with ESMTPSA id BAAC0C0006;\n\tTue, 18 Oct 2022 16:48:58 +0000 (UTC)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1666111741;\n\tbh=r7m+pMQv5QJxpYRzE9tNfWTR3ITP3Rz3c4CDDBzjf6w=;\n\th=To:Date:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post:\n\tList-Help:List-Subscribe:From:Reply-To:From;\n\tb=BuHQBtSSbAN4bnSq9zpEA5bwfp0tJ5D4spK+DMR0tGchtt9tL661lyQxa+vW6fV/W\n\tz4Y5He8uHd+hL6ykfR7xJ/WQjDaQrH1gxp4KG4yDAnK3ocx/7uhnstHir6bak+in7D\n\tPeoVxpwA5NxmoaFcFoMEUdrKox+G9SLT4zm+3cdyq98Ziyx+sRq0uAnkzW9EfnBQqB\n\tCaF/IzrBmISlXzdHmAY33nuu8nHhw6tPSyI6OmYymkPAGzB/QaUJepraACdT2PGdBl\n\trKSdrTUt2WQRnmnwWU0zU6W4YeDG+jCTSOk4bbBwgU6Gpu6elNkeH0VWjNrWj6Yw75\n\tJv+/d1aBqUiRQ==",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Tue, 18 Oct 2022 18:48:52 +0200",
        "Message-Id": "<20221018164852.173916-1-jacopo@jmondi.org>",
        "X-Mailer": "git-send-email 2.37.3",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH] libcamera: pipeline: Add IMX8 ISI pipeline",
        "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>",
        "From": "Jacopo Mondi via libcamera-devel <libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Jacopo Mondi <jacopo@jmondi.org>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Add a pipeline handler for the ISI capture interface found on\nseveral versions of the i.MX8 SoC generation.\n\nThe pipeline handler supports capturing up to two streams in YUV, RGB or\nRAW formats.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\n---\n meson_options.txt                            |   2 +-\n src/libcamera/pipeline/imx8-isi/imx8-isi.cpp | 856 +++++++++++++++++++\n src/libcamera/pipeline/imx8-isi/meson.build  |   5 +\n 3 files changed, 862 insertions(+), 1 deletion(-)\n create mode 100644 src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\n create mode 100644 src/libcamera/pipeline/imx8-isi/meson.build\n\n--\n2.37.3",
    "diff": "diff --git a/meson_options.txt b/meson_options.txt\nindex f1d678089452..1ba6778ce257 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -37,7 +37,7 @@ option('lc-compliance',\n\n option('pipelines',\n         type : 'array',\n-        choices : ['ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],\n+        choices : ['imx8-isi', 'ipu3', 'raspberrypi', 'rkisp1', 'simple', 'uvcvideo', 'vimc'],\n         description : 'Select which pipeline handlers to include')\n\n option('qcam',\ndiff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\nnew file mode 100644\nindex 000000000000..d404d00353c4\n--- /dev/null\n+++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\n@@ -0,0 +1,856 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2022 - Jacopo Mondi <jacopo@jmondi.org>\n+ *\n+ * imx8-isi.cpp - Pipeline handler for ISI interface found on NXP i.MX8 SoC\n+ */\n+\n+#include <algorithm>\n+#include <map>\n+#include <memory>\n+#include <set>\n+#include <sstream>\n+#include <string>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/camera_manager.h>\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/device_enumerator.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+#include \"linux/media-bus-format.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(ISI)\n+\n+class PipelineHandlerISI;\n+\n+class ISICameraData : public Camera::Private\n+{\n+public:\n+\tISICameraData(PipelineHandler *ph)\n+\t\t: Camera::Private(ph)\n+\t{\n+\t}\n+\n+\tPipelineHandlerISI *pipe();\n+\n+\tint init();\n+\n+\t/*\n+\t * stream1_ maps on the first ISI channel, stream2_ on the second one.\n+\t *\n+\t * \\todo Assume 2 channels only for now, as that's the number of\n+\t * available channels on i.MX8MP.\n+\t */\n+\tunsigned int pipeIndex(const Stream *stream)\n+\t{\n+\t\treturn stream == &stream1_ ? 0 : 1;\n+\t}\n+\n+\tstd::unique_ptr<CameraSensor> sensor;\n+\tstd::unique_ptr<V4L2Subdevice> csis;\n+\n+\tStream stream1_;\n+\tStream stream2_;\n+\n+\tstd::vector<Stream *> enabledStreams_;\n+\n+\tunsigned int id_;\n+};\n+\n+class ISICameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\t/*\n+\t * formatsMap records the association between an output pixel format\n+\t * and the combination of V4L2 pixel format and media bus codes that have\n+\t * to be applied to the pipeline.\n+\t */\n+\tstruct PipeFormat {\n+\t\tV4L2PixelFormat fourcc;\n+\t\tunsigned int isiCode;\n+\t\tunsigned int sensorCode;\n+\t};\n+\n+\tusing FormatMap = std::map<PixelFormat, PipeFormat>;\n+\n+\tISICameraConfiguration(ISICameraData *data)\n+\t\t: data_(data)\n+\t{\n+\t}\n+\n+\tStatus validate() override;\n+\n+\tstatic const FormatMap formatsMap;\n+\n+\tV4L2SubdeviceFormat sensorFormat_;\n+\n+private:\n+\tconst ISICameraData *data_;\n+};\n+\n+class PipelineHandlerISI : public PipelineHandler\n+{\n+public:\n+\tPipelineHandlerISI(CameraManager *manager);\n+\n+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+\tCameraConfiguration *generateConfiguration(Camera *camera,\n+\t\t\t\t\t\t   const StreamRoles &roles) override;\n+\tint configure(Camera *camera, CameraConfiguration *config) override;\n+\n+\tint exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\n+\tint start(Camera *camera, const ControlList *controls) override;\n+\n+protected:\n+\tvoid stopDevice(Camera *camera) override;\n+\n+\tint queueRequestDevice(Camera *camera, Request *request) override;\n+\n+private:\n+\tstatic constexpr Size previewSize = { 1920, 1080 };\n+\n+\tstruct Pipe {\n+\t\tstd::unique_ptr<V4L2Subdevice> isi;\n+\t\tstd::unique_ptr<V4L2VideoDevice> capture;\n+\t};\n+\n+\tISICameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<ISICameraData *>(camera->_d());\n+\t}\n+\n+\tPipe *pipeFromStream(const Camera *camera, const Stream *stream);\n+\n+\tvoid bufferReady(FrameBuffer *buffer);\n+\n+\tMediaDevice *isiDev_;\n+\n+\tstd::unique_ptr<V4L2Subdevice> crossbar_;\n+\tstd::vector<Pipe> pipes_;\n+};\n+\n+/* -----------------------------------------------------------------------------\n+ * Camera Data\n+ */\n+\n+PipelineHandlerISI *ISICameraData::pipe()\n+{\n+\treturn static_cast<PipelineHandlerISI *>(Camera::Private::pipe());\n+}\n+\n+/* Open and initialize pipe components. */\n+int ISICameraData::init()\n+{\n+\tint ret = sensor->init();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = csis->open();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tproperties_ = sensor->properties();\n+\n+\treturn 0;\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Camera Configuration\n+ */\n+\n+const ISICameraConfiguration::FormatMap ISICameraConfiguration::formatsMap = {\n+\t{\n+\t\tformats::YUYV,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_YUYV),\n+\t\t  MEDIA_BUS_FMT_YUV8_1X24,\n+\t\t  MEDIA_BUS_FMT_UYVY8_1X16 },\n+\t},\n+\t{\n+\t\tformats::RGB565,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_RGB565),\n+\t\t  MEDIA_BUS_FMT_RGB888_1X24,\n+\t\t  MEDIA_BUS_FMT_RGB565_1X16 },\n+\t},\n+\t{\n+\t\tformats::SBGGR8,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SBGGR8),\n+\t\t  MEDIA_BUS_FMT_SBGGR8_1X8,\n+\t\t  MEDIA_BUS_FMT_SBGGR8_1X8 },\n+\t},\n+\t{\n+\t\tformats::SBGGR10,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10),\n+\t\t  MEDIA_BUS_FMT_SBGGR10_1X10,\n+\t\t  MEDIA_BUS_FMT_SBGGR10_1X10 },\n+\t},\n+\t{\n+\t\tformats::SGBRG10,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10),\n+\t\t  MEDIA_BUS_FMT_SGBRG10_1X10,\n+\t\t  MEDIA_BUS_FMT_SGBRG10_1X10 },\n+\t},\n+\t{\n+\t\tformats::SGRBG10,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10),\n+\t\t  MEDIA_BUS_FMT_SGRBG10_1X10,\n+\t\t  MEDIA_BUS_FMT_SGRBG10_1X10 },\n+\t},\n+\t{\n+\t\tformats::SRGGB10,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10),\n+\t\t  MEDIA_BUS_FMT_SRGGB10_1X10,\n+\t\t  MEDIA_BUS_FMT_SRGGB10_1X10 },\n+\t},\n+\t{\n+\t\tformats::SBGGR12,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12),\n+\t\t  MEDIA_BUS_FMT_SBGGR12_1X12,\n+\t\t  MEDIA_BUS_FMT_SBGGR12_1X12 },\n+\t},\n+\t{\n+\t\tformats::SGBRG12,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12),\n+\t\t  MEDIA_BUS_FMT_SGBRG12_1X12,\n+\t\t  MEDIA_BUS_FMT_SGBRG12_1X12 },\n+\t},\n+\t{\n+\t\tformats::SGRBG12,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SGRBG12),\n+\t\t  MEDIA_BUS_FMT_SGRBG12_1X12,\n+\t\t  MEDIA_BUS_FMT_SGRBG12_1X12 },\n+\t},\n+\t{\n+\t\tformats::SRGGB12,\n+\t\t{ V4L2PixelFormat(V4L2_PIX_FMT_SRGGB12),\n+\t\t  MEDIA_BUS_FMT_SRGGB12_1X12,\n+\t\t  MEDIA_BUS_FMT_SRGGB12_1X12 },\n+\t},\n+};\n+\n+CameraConfiguration::Status ISICameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tstd::set<Stream *> availableStreams = {\n+\t\tconst_cast<Stream *>(&data_->stream1_),\n+\t\tconst_cast<Stream *>(&data_->stream2_)\n+\t};\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\t/* Assume at most two streams available. */\n+\tif (config_.size() > 2) {\n+\t\tconfig_.resize(2);\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/*\n+\t * If more than a single stream is requested, the maximum allowed image\n+\t * width is 2048. Cap the maximum image size accordingly.\n+\t */\n+\tCameraSensor *sensor = data_->sensor.get();\n+\tSize maxResolution = sensor->resolution();\n+\tif (config_.size() == 2)\n+\t\tmaxResolution.boundTo({ std::min(2048U, maxResolution.width),\n+\t\t\t\t\tmaxResolution.height });\n+\n+\t/* Indentify the largest RAW stream, if any. */\n+\tconst ISICameraConfiguration::PipeFormat *pipeConfig = nullptr;\n+\tStreamConfiguration *rawConfig = nullptr;\n+\tSize maxRawSize;\n+\n+\tfor (StreamConfiguration &cfg : config_) {\n+\t\t/* Make sure format is supported and default to YUV if it's not. */\n+\t\tauto it = formatsMap.find(cfg.pixelFormat);\n+\t\tif (it == formatsMap.end()) {\n+\t\t\tLOG(ISI, Warning) << \"Unsupported format: \" << cfg.pixelFormat\n+\t\t\t\t\t  << \" - adjusted to YUV\";\n+\t\t\tit = formatsMap.find(formats::YUYV);\n+\t\t\tASSERT(it != formatsMap.end());\n+\n+\t\t\tcfg.pixelFormat = it->first;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\tconst PixelFormatInfo info = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\tif (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n+\t\t\tcontinue;\n+\n+\t\t/* Cap the RAW stream size to the maximum resolution. */\n+\t\tSize configSize = cfg.size;\n+\t\tcfg.size.boundTo(maxResolution);\n+\t\tif (cfg.size != configSize) {\n+\t\t\tLOG(ISI, Warning)\n+\t\t\t\t<< \"RAW Stream configuration adjusted to \"\n+\t\t\t\t<< cfg.size;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\tif (cfg.size > maxRawSize) {\n+\t\t\t/* Store the stream and the pipe configurations. */\n+\t\t\tpipeConfig = &it->second;\n+\t\t\tmaxRawSize = cfg.size;\n+\t\t\trawConfig = &cfg;\n+\t\t}\n+\n+\t\t/* All the RAW streams must have the same format. */\n+\t\tif (rawConfig && rawConfig->pixelFormat != cfg.pixelFormat) {\n+\t\t\tLOG(ISI, Error)\n+\t\t\t\t<< \"All the RAW streams must have the same format.\";\n+\t\t\treturn Invalid;\n+\t\t}\n+\n+\t\t/* Assign streams in the order they are presented, with RAW first. */\n+\t\tauto stream = availableStreams.extract(availableStreams.begin());\n+\t\tcfg.setStream(stream.value());\n+\t}\n+\n+\t/* Now re-iterate the YUV streams to adjust formats and sizes. */\n+\tSize maxYuvSize;\n+\tfor (StreamConfiguration &cfg : config_) {\n+\t\tconst PixelFormatInfo info = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)\n+\t\t\tcontinue;\n+\n+\t\tif (rawConfig) {\n+\t\t\t/*\n+\t\t\t * The ISI can perform color conversion (RGB<->YUV) but\n+\t\t\t * not debayering. If a RAW stream is requested all\n+\t\t\t * other streams must have the same RAW format.\n+\t\t\t */\n+\t\t\tcfg.pixelFormat = rawConfig->pixelFormat;\n+\t\t\tstatus = Adjusted;\n+\n+\t\t\t/* The RAW stream size caps the YUV stream sizes. */\n+\t\t\tcfg.size.boundTo(rawConfig->size);\n+\n+\t\t\tLOG(ISI, Debug) << \"Stream configuration adjusted to: \"\n+\t\t\t\t\t<< cfg.size << \"/\" << rawConfig->pixelFormat;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* Cap the YUV stream size to the maximum accepted resolution. */\n+\t\tSize configSize = cfg.size;\n+\t\tcfg.size.boundTo(maxResolution);\n+\t\tif (cfg.size != configSize) {\n+\t\t\tLOG(ISI, Warning)\n+\t\t\t\t<< \"Stream configuration adjusted to \" << cfg.size;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\t/* The largest YUV stream picks the pipeConfig. */\n+\t\tif (cfg.size > maxYuvSize) {\n+\t\t\tpipeConfig = &formatsMap.find(cfg.pixelFormat)->second;\n+\t\t\tmaxYuvSize = cfg.size;\n+\t\t}\n+\n+\t\t/* Assign streams in the order they are presented. */\n+\t\tauto stream = availableStreams.extract(availableStreams.begin());\n+\t\tcfg.setStream(stream.value());\n+\t}\n+\n+\t/*\n+\t * Sensor format configuration: if a RAW stream is requested, use its\n+\t * configuration, otherwise use the largerst YUV one.\n+\t *\n+\t * \\todo The sensor format selection policies could be changed to\n+\t * prefer operating the sensor at full resolution to prioritize\n+\t * image quality and FOV in exchange of a usually slower frame rate.\n+\t * Usage of the STILL_CAPTURE role could be consider for this.\n+\t */\n+\tV4L2SubdeviceFormat sensorFormat{};\n+\tsensorFormat.mbus_code = pipeConfig->sensorCode;\n+\tsensorFormat.size = rawConfig ? rawConfig->size : maxYuvSize;\n+\n+\tLOG(ISI, Debug) << \"Computed sensor configuration: \" << sensorFormat;\n+\n+\t/*\n+\t * We can't use CameraSensor::getFormat() as it might return a\n+\t * format larger than our strict width limit, as that function\n+\t * prioritizes formats with the same FOV ratio over formats with less\n+\t * difference in size.\n+\t *\n+\t * Manually walk all the sensor supported sizes searching for\n+\t * the smallest larger format without considering the FOV ratio\n+\t * as the ISI can freely scale.\n+\t */\n+\tauto sizes = sensor->sizes(sensorFormat.mbus_code);\n+\tSize bestSize;\n+\n+\tfor (const Size &s : sizes) {\n+\t\t/* Ignore smaller sizes. */\n+\t\tif (s.width < sensorFormat.size.width ||\n+\t\t    s.height < sensorFormat.size.height)\n+\t\t\tcontinue;\n+\n+\t\t/* Make sure the width stays in the limits. */\n+\t\tif (s.width > maxResolution.width)\n+\t\t\tcontinue;\n+\n+\t\tbestSize = s;\n+\t\tbreak;\n+\t}\n+\n+\t/*\n+\t * This should happen only if the sensor can only produce formats big\n+\t * enough to accommodate all streams but that exceeds the maximum\n+\t * allowed input size.\n+\t */\n+\tif (bestSize.isNull()) {\n+\t\tLOG(ISI, Error) << \"Unable to find a suitable sensor format\";\n+\t\treturn Invalid;\n+\t}\n+\n+\tsensorFormat_.mbus_code = sensorFormat.mbus_code;\n+\tsensorFormat_.size = bestSize;\n+\n+\tLOG(ISI, Debug) << \"Selected sensor format: \" << sensorFormat_;\n+\n+\treturn status;\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Pipeline Handler\n+ */\n+PipelineHandlerISI::Pipe *PipelineHandlerISI::pipeFromStream(const Camera *camera,\n+\t\t\t\t\t\t\t     const Stream *stream)\n+{\n+\tISICameraData *data = cameraData(const_cast<Camera *>(camera));\n+\tunsigned int pipeIndex = data->pipeIndex(stream);\n+\n+\tASSERT(pipeIndex < pipes_.size());\n+\n+\treturn &pipes_[pipeIndex];\n+}\n+\n+PipelineHandlerISI::PipelineHandlerISI(CameraManager *manager)\n+\t: PipelineHandler(manager)\n+{\n+}\n+\n+CameraConfiguration *\n+PipelineHandlerISI::generateConfiguration(Camera *camera,\n+\t\t\t\t\t  const StreamRoles &roles)\n+{\n+\tISICameraData *data = cameraData(camera);\n+\tCameraSensor *sensor = data->sensor.get();\n+\tCameraConfiguration *config = new ISICameraConfiguration(data);\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tif (roles.size() > 2) {\n+\t\tLOG(ISI, Error) << \"Only two streams are supported\";\n+\t\tdelete config;\n+\t\treturn nullptr;\n+\t}\n+\n+\tfor (const auto &role : roles) {\n+\t\t/*\n+\t\t * Prefer the following formats\n+\t\t * - Still Capture: Full resolution RGB565\n+\t\t * - Preview/VideoRecording: 1080p YUYV\n+\t\t * - RAW: sensor's native format and resolution\n+\t\t */\n+\t\tStreamConfiguration cfg;\n+\n+\t\tswitch (role) {\n+\t\tcase StillCapture:\n+\t\t\tcfg.size = data->sensor->resolution();\n+\t\t\tcfg.pixelFormat = formats::RGB565;\n+\t\t\tcfg.bufferCount = 4;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tLOG(ISI, Warning) << \"Unsupported role: \" << role;\n+\t\t\t[[fallthrough]];\n+\t\tcase Viewfinder:\n+\t\tcase VideoRecording:\n+\t\t\tcfg.size = PipelineHandlerISI::previewSize;\n+\t\t\tcfg.pixelFormat = formats::YUYV;\n+\t\t\tcfg.bufferCount = 4;\n+\t\t\tbreak;\n+\t\tcase Raw:\n+\t\t\t/*\n+\t\t\t * Make sure the sensor can generate a RAW format and\n+\t\t\t * prefer the ones with a larger bitdepth.\n+\t\t\t */\n+\t\t\tunsigned int maxDepth = 0;\n+\t\t\tunsigned int rawCode = 0;\n+\n+\t\t\tfor (unsigned int code : sensor->mbusCodes()) {\n+\t\t\t\tconst BayerFormat &bayerFormat = BayerFormat::fromMbusCode(code);\n+\t\t\t\tif (!bayerFormat.isValid())\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\tif (bayerFormat.bitDepth > maxDepth) {\n+\t\t\t\t\tmaxDepth = bayerFormat.bitDepth;\n+\t\t\t\t\trawCode = code;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (!rawCode) {\n+\t\t\t\tLOG(ISI, Error)\n+\t\t\t\t\t<< \"Cannot generate a configuration for RAW stream\";\n+\t\t\t\tLOG(ISI, Error)\n+\t\t\t\t\t<< \"The sensor does not support RAW\";\n+\t\t\t\tdelete config;\n+\t\t\t\treturn nullptr;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * Search the PixelFormat that corresponds to the\n+\t\t\t * selected sensor's mbus code.\n+\t\t\t */\n+\t\t\tconst ISICameraConfiguration::PipeFormat *rawPipeFormat = nullptr;\n+\t\t\tPixelFormat rawFormat;\n+\n+\t\t\tfor (const auto &format : ISICameraConfiguration::formatsMap) {\n+\t\t\t\tconst ISICameraConfiguration::PipeFormat &pipeFormat = format.second;\n+\n+\t\t\t\tif (pipeFormat.sensorCode != rawCode)\n+\t\t\t\t\tcontinue;\n+\n+\t\t\t\trawPipeFormat = &pipeFormat;\n+\t\t\t\trawFormat = format.first;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tif (!rawPipeFormat) {\n+\t\t\t\tLOG(ISI, Error)\n+\t\t\t\t\t<< \"Cannot generate a configuration for RAW stream\";\n+\t\t\t\tLOG(ISI, Error)\n+\t\t\t\t\t<< \"Format not supported: \"\n+\t\t\t\t\t<< BayerFormat::fromMbusCode(rawCode);\n+\t\t\t\tdelete config;\n+\t\t\t\treturn nullptr;\n+\t\t\t}\n+\n+\t\t\tcfg.size = sensor->resolution();\n+\t\t\tcfg.pixelFormat = rawFormat;\n+\t\t\tcfg.bufferCount = 4;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tconfig->addConfiguration(cfg);\n+\t}\n+\n+\tconfig->validate();\n+\n+\treturn config;\n+}\n+\n+int PipelineHandlerISI::configure(Camera *camera, CameraConfiguration *c)\n+{\n+\tISICameraConfiguration *camConfig = static_cast<ISICameraConfiguration *>(c);\n+\n+\tISICameraData *data = cameraData(camera);\n+\tCameraSensor *sensor = data->sensor.get();\n+\n+\t/* All links are immutable except the sensor -> csis link. */\n+\tconst MediaPad *sensorSrc = sensor->entity()->getPadByIndex(0);\n+\tsensorSrc->links()[0]->setEnabled(true);\n+\n+\t/*\n+\t * Reset the crossbar switch routing and enable one route for each\n+\t * requested stream configuration.\n+\t *\n+\t * \\todo Handle concurrent usage of multiple cameras by adjusting the\n+\t * routing table instead of resetting it.\n+\t */\n+\tV4L2Subdevice::Routing routing = {};\n+\n+\tint ret = crossbar_->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\trouting = {};\n+\tfor (const auto &[idx, config] : utils::enumerate(*c)) {\n+\t\tstruct v4l2_subdev_route route = {\n+\t\t\t.sink_pad = data->id_,\n+\t\t\t.sink_stream = 0,\n+\t\t\t.source_pad = static_cast<unsigned int>(3 + idx),\n+\t\t\t.source_stream = 0,\n+\t\t\t.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n+\t\t\t.reserved = {}\n+\t\t};\n+\n+\t\trouting.push_back(route);\n+\t}\n+\n+\tret = crossbar_->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Apply format to the sensor and CSIS receiver. */\n+\tV4L2SubdeviceFormat sensorFmt = camConfig->sensorFormat_;\n+\tret = sensor->setFormat(&sensorFmt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = data->csis->setFormat(0, &sensorFmt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = data->csis->setFormat(1, &sensorFmt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = crossbar_->setFormat(data->id_, &sensorFmt);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Now configure the ISI and video node instances, one per stream. */\n+\tdata->enabledStreams_.clear();\n+\tfor (const auto &config : *c) {\n+\t\tPipe *pipe = pipeFromStream(camera, config.stream());\n+\n+\t\t/*\n+\t\t * Set the format on the ISI sink pad: it must match what is\n+\t\t * received by the CSIS.\n+\t\t */\n+\t\tret = pipe->isi->setFormat(0, &sensorFmt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/*\n+\t\t * Configure the ISI sink compose rectangle to downscale the\n+\t\t * image.\n+\t\t *\n+\t\t * \\todo Additional cropping could be applied on the ISI source\n+\t\t * pad to further downscale the image.\n+\t\t */\n+\t\tRectangle isiScale = {};\n+\t\tisiScale.x = 0;\n+\t\tisiScale.y = 0;\n+\t\tisiScale.width = config.size.width;\n+\t\tisiScale.height = config.size.height;\n+\n+\t\tret = pipe->isi->setSelection(0, V4L2_SEL_TGT_COMPOSE, &isiScale);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/*\n+\t\t * Set the format on ISI source pad: only the media bus code\n+\t\t * is relevant as it configures format conversion, while the\n+\t\t * size is taken from the sink's COMPOSE (or source's CROP,\n+\t\t * if any) rectangles.\n+\t\t */\n+\t\tauto fmtMap = ISICameraConfiguration::formatsMap.find(config.pixelFormat);\n+\t\tISICameraConfiguration::PipeFormat pipeFormat = fmtMap->second;\n+\n+\t\tV4L2SubdeviceFormat isiFormat{};\n+\t\tisiFormat.mbus_code = pipeFormat.isiCode;\n+\t\tisiFormat.size = config.size;\n+\n+\t\tret = pipe->isi->setFormat(1, &isiFormat);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tV4L2DeviceFormat captureFmt{};\n+\t\tcaptureFmt.fourcc = pipeFormat.fourcc;\n+\t\tcaptureFmt.size = config.size;\n+\n+\t\tret = pipe->capture->setFormat(&captureFmt);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\t/* Store the list of enabled streams for later use. */\n+\t\tdata->enabledStreams_.push_back(config.stream());\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerISI::exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t\t\t   std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tunsigned int count = stream->configuration().bufferCount;\n+\tPipe *pipe = pipeFromStream(camera, stream);\n+\n+\treturn pipe->capture->exportBuffers(count, buffers);\n+}\n+\n+int PipelineHandlerISI::start(Camera *camera,\n+\t\t\t      [[maybe_unused]] const ControlList *controls)\n+{\n+\tISICameraData *data = cameraData(camera);\n+\n+\tfor (const auto &stream : data->enabledStreams_) {\n+\t\tPipe *pipe = pipeFromStream(camera, stream);\n+\t\tV4L2VideoDevice *capture = pipe->capture.get();\n+\t\tconst StreamConfiguration &config = stream->configuration();\n+\n+\t\tint ret = capture->importBuffers(config.bufferCount);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = capture->streamOn();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void PipelineHandlerISI::stopDevice(Camera *camera)\n+{\n+\tISICameraData *data = cameraData(camera);\n+\n+\tfor (const auto &stream : data->enabledStreams_) {\n+\t\tPipe *pipe = pipeFromStream(camera, stream);\n+\t\tV4L2VideoDevice *capture = pipe->capture.get();\n+\n+\t\tcapture->streamOff();\n+\t\tcapture->releaseBuffers();\n+\t}\n+}\n+\n+int PipelineHandlerISI::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tfor (auto &[stream, buffer] : request->buffers()) {\n+\t\tPipe *pipe = pipeFromStream(camera, stream);\n+\n+\t\tint ret = pipe->capture->queueBuffer(buffer);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+bool PipelineHandlerISI::match(DeviceEnumerator *enumerator)\n+{\n+\tDeviceMatch dm(\"mxc-isi\");\n+\tdm.add(\"crossbar\");\n+\tdm.add(\"mxc_isi.0\");\n+\tdm.add(\"mxc_isi.0.capture\");\n+\tdm.add(\"mxc_isi.1\");\n+\tdm.add(\"mxc_isi.1.capture\");\n+\n+\tisiDev_ = acquireMediaDevice(enumerator, dm);\n+\tif (!isiDev_)\n+\t\treturn false;\n+\n+\t/*\n+\t * Acquire the subdevs and video nodes for the crossbar switch and the\n+\t * processing pipelines.\n+\t */\n+\tcrossbar_ = V4L2Subdevice::fromEntityName(isiDev_, \"crossbar\");\n+\tif (!crossbar_)\n+\t\treturn false;\n+\n+\tint ret = crossbar_->open();\n+\tif (ret)\n+\t\treturn false;\n+\n+\tfor (unsigned int i = 0;; ++i) {\n+\t\tstd::string entityName = \"mxc_isi.\" + std::to_string(i);\n+\t\tstd::unique_ptr<V4L2Subdevice> isi =\n+\t\t\tV4L2Subdevice::fromEntityName(isiDev_, entityName);\n+\t\tif (!isi)\n+\t\t\tbreak;\n+\n+\t\tret = isi->open();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tentityName += \".capture\";\n+\t\tstd::unique_ptr<V4L2VideoDevice> capture =\n+\t\t\tV4L2VideoDevice::fromEntityName(isiDev_, entityName);\n+\t\tif (!capture)\n+\t\t\tbreak;\n+\n+\t\tcapture->bufferReady.connect(this, &PipelineHandlerISI::bufferReady);\n+\n+\t\tret = capture->open();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tpipes_.push_back({ std::move(isi), std::move(capture) });\n+\t}\n+\n+\tif (pipes_.empty())\n+\t\treturn false;\n+\n+\t/*\n+\t * Loop over all the crossbar switch sink pads to find connected CSI-2\n+\t * receivers and camera sensors.\n+\t */\n+\tunsigned int numCameras = 0;\n+\tfor (MediaPad *pad : crossbar_->entity()->pads()) {\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SINK) || pad->links().empty())\n+\t\t\tcontinue;\n+\n+\t\tMediaEntity *csi = pad->links()[0]->source()->entity();\n+\t\tif (csi->pads().size() != 2)\n+\t\t\tcontinue;\n+\n+\t\tpad = csi->pads()[0];\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SINK) || pad->links().empty())\n+\t\t\tcontinue;\n+\n+\t\tMediaEntity *sensor = pad->links()[0]->source()->entity();\n+\t\tif (sensor->function() != MEDIA_ENT_F_CAM_SENSOR)\n+\t\t\tcontinue;\n+\n+\t\t/* Create the camera data. */\n+\t\tstd::unique_ptr<ISICameraData> data =\n+\t\t\tstd::make_unique<ISICameraData>(this);\n+\n+\t\tdata->sensor = std::make_unique<CameraSensor>(sensor);\n+\t\tdata->csis = std::make_unique<V4L2Subdevice>(csi);\n+\t\tdata->id_ = numCameras;\n+\n+\t\tret = data->init();\n+\t\tif (ret)\n+\t\t\treturn false;\n+\n+\t\t/* Register the camera. */\n+\t\tconst std::string &id = data->sensor->id();\n+\t\tstd::set<Stream *> streams = {\n+\t\t\t&data->stream1_,\n+\t\t\t&data->stream2_\n+\t\t};\n+\t\tstd::shared_ptr<Camera> camera =\n+\t\t\tCamera::create(std::move(data), id, streams);\n+\n+\t\tregisterCamera(std::move(camera));\n+\t\tnumCameras++;\n+\t}\n+\n+\treturn numCameras > 0;\n+}\n+\n+void PipelineHandlerISI::bufferReady(FrameBuffer *buffer)\n+{\n+\tRequest *request = buffer->request();\n+\n+\tcompleteBuffer(request, buffer);\n+\tif (request->hasPendingBuffers())\n+\t\treturn;\n+\n+\tcompleteRequest(request);\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerISI)\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build\nnew file mode 100644\nindex 000000000000..ffd0ce54ce92\n--- /dev/null\n+++ b/src/libcamera/pipeline/imx8-isi/meson.build\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_sources += files([\n+    'imx8-isi.cpp'\n+])\n",
    "prefixes": [
        "libcamera-devel"
    ]
}