Patch Detail
Show a patch.
GET /api/patches/17791/?format=api
{ "id": 17791, "url": "https://patchwork.libcamera.org/api/patches/17791/?format=api", "web_url": "https://patchwork.libcamera.org/patch/17791/", "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": "<20221114212555.6936-2-jacopo@jmondi.org>", "date": "2022-11-14T21:25:54", "name": "[libcamera-devel,v3,1/1] libcamera: pipeline: Add IMX8 ISI pipeline", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "3a38d37d734c37e50979c0e0fb017d44665c3a48", "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/17791/mbox/", "series": [ { "id": 3608, "url": "https://patchwork.libcamera.org/api/series/3608/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3608", "date": "2022-11-14T21:25:53", "name": "libcamera: pipeline: Add IMX8 ISI pipeline", "version": 3, "mbox": "https://patchwork.libcamera.org/series/3608/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/17791/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/17791/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 73A2CBE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 14 Nov 2022 21:26:05 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 306916307F;\n\tMon, 14 Nov 2022 22:26:05 +0100 (CET)", "from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\n\t[217.70.183.195])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 34CD663087\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 14 Nov 2022 22:26:03 +0100 (CET)", "(Authenticated sender: jacopo@jmondi.org)\n\tby mail.gandi.net (Postfix) with ESMTPSA id BBAE060002;\n\tMon, 14 Nov 2022 21:26:01 +0000 (UTC)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1668461165;\n\tbh=odLa2pXffWVXZaKcX/niGm76og2c3CNqQuGrrZSvNZs=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=ItOCm5c1ljZZFFxf1k1CKstt/5623bWtgBEDh0n0/5oIv06YMli269Q0zGNNDQxR0\n\tfDJAgQmPmAjb9mGTPmf96YoRlcwLyMRHdspbIKKt1PSStTdQkucYB1DmE4iXtHO3zu\n\tP0cKo5gXsffvD3T++ZNnQpqafQqTdndxsJ1tqaZmBuW7rrUWRjr0JqNYMi4jOA3eKC\n\teKn6KVigCxYzf9mqqysnwu8lrQFXUNsZEPhHA+hDRdyYXAISVYjSv0Wk+t7i9jHglG\n\t3TsGD/Eb4WHRJSCeBG4DWactFep/FNHp4UUNWgKY5cX5bfD+kthbZ9dg71QMeucCn9\n\txu2OZ8FVYCdfg==", "To": "libcamera-devel@lists.libcamera.org", "Date": "Mon, 14 Nov 2022 22:25:54 +0100", "Message-Id": "<20221114212555.6936-2-jacopo@jmondi.org>", "X-Mailer": "git-send-email 2.38.1", "In-Reply-To": "<20221114212555.6936-1-jacopo@jmondi.org>", "References": "<20221114212555.6936-1-jacopo@jmondi.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v3 1/1] libcamera: pipeline: Add IMX8 ISI\n\tpipeline", "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 multiple streams from the same\ncamera in YUV, RGB or RAW formats. The number of streams is limited by\nthe number of ISI pipelines, and is currently hardcoded to 2 as the code\nhas been tested on the i.MX8MP only. Further development will make this\ndynamic to support other SoCs.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n---\n meson_options.txt | 2 +-\n src/libcamera/pipeline/imx8-isi/imx8-isi.cpp | 962 +++++++++++++++++++\n src/libcamera/pipeline/imx8-isi/meson.build | 5 +\n 3 files changed, 968 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", "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..4b4f27bc60dd\n--- /dev/null\n+++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\n@@ -0,0 +1,962 @@\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 <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\t/*\n+\t\t * \\todo Assume 2 channels only for now, as that's the number of\n+\t\t * available channels on i.MX8MP.\n+\t\t */\n+\t\tstreams_.resize(2);\n+\t}\n+\n+\tPipelineHandlerISI *pipe();\n+\n+\tint init();\n+\n+\tunsigned int pipeIndex(const Stream *stream)\n+\t{\n+\t\treturn stream - &*streams_.begin();\n+\t}\n+\n+\tstd::unique_ptr<CameraSensor> sensor_;\n+\tstd::unique_ptr<V4L2Subdevice> csis_;\n+\n+\tstd::vector<Stream> streams_;\n+\n+\tstd::vector<Stream *> enabledStreams_;\n+\n+\tunsigned int xbarSink_;\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\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+\tCameraConfiguration::Status\n+\tvalidateRaw(std::set<Stream *> &availableStreams, const Size &maxResolution);\n+\tCameraConfiguration::Status\n+\tvalidateYuv(std::set<Stream *> &availableStreams, const Size &maxResolution);\n+\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+\tstd::unique_ptr<CameraConfiguration>\n+\tgenerateConfiguration(Camera *camera, 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 kPreviewSize = { 1920, 1080 };\n+\tstatic constexpr Size kMinISISize = { 1, 1 };\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(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+/**\n+ * \\todo Do not associate the sensor format to non-RAW pixelformats, as\n+ * the ISI can do colorspace conversion.\n+ */\n+const ISICameraConfiguration::FormatMap ISICameraConfiguration::formatsMap_ = {\n+\t{\n+\t\tformats::YUYV,\n+\t\t{ MEDIA_BUS_FMT_YUV8_1X24,\n+\t\t MEDIA_BUS_FMT_UYVY8_1X16 },\n+\t},\n+\t{\n+\t\tformats::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{ MEDIA_BUS_FMT_RGB888_1X24,\n+\t\t MEDIA_BUS_FMT_RGB565_1X16 },\n+\t},\n+\t{\n+\t\tformats::SBGGR8,\n+\t\t{ MEDIA_BUS_FMT_SBGGR8_1X8,\n+\t\t MEDIA_BUS_FMT_SBGGR8_1X8 },\n+\t},\n+\t{\n+\t\tformats::SGBRG8,\n+\t\t{ MEDIA_BUS_FMT_SGBRG8_1X8,\n+\t\t MEDIA_BUS_FMT_SGBRG8_1X8 },\n+\t},\n+\t{\n+\t\tformats::SGRBG8,\n+\t\t{ MEDIA_BUS_FMT_SGRBG8_1X8,\n+\t\t MEDIA_BUS_FMT_SGRBG8_1X8 },\n+\t},\n+\t{\n+\t\tformats::SRGGB8,\n+\t\t{ MEDIA_BUS_FMT_SRGGB8_1X8,\n+\t\t MEDIA_BUS_FMT_SRGGB8_1X8 },\n+\t},\n+\t{\n+\t\tformats::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{ MEDIA_BUS_FMT_SGBRG10_1X10,\n+\t\t MEDIA_BUS_FMT_SGBRG10_1X10 },\n+\t},\n+\t{\n+\t\tformats::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{ MEDIA_BUS_FMT_SRGGB10_1X10,\n+\t\t MEDIA_BUS_FMT_SRGGB10_1X10 },\n+\t},\n+\t{\n+\t\tformats::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{ MEDIA_BUS_FMT_SGBRG12_1X12,\n+\t\t MEDIA_BUS_FMT_SGBRG12_1X12 },\n+\t},\n+\t{\n+\t\tformats::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{ MEDIA_BUS_FMT_SRGGB12_1X12,\n+\t\t MEDIA_BUS_FMT_SRGGB12_1X12 },\n+\t},\n+};\n+\n+/*\n+ * Adjust stream configuration when the first requested stream is RAW: all the\n+ * streams will have the same RAW pixelformat and size.\n+ */\n+CameraConfiguration::Status\n+ISICameraConfiguration::validateRaw(std::set<Stream *> &availableStreams,\n+\t\t\t\t const Size &maxResolution)\n+{\n+\tCameraConfiguration::Status status = Valid;\n+\n+\t/*\n+\t * Make sure the requested RAW format is supported by the\n+\t * pipeline, otherwise adjust it.\n+\t */\n+\tstd::vector<unsigned int> mbusCodes = data_->sensor_->mbusCodes();\n+\tStreamConfiguration &rawConfig = config_[0];\n+\n+\tbool supported = false;\n+\tauto it = formatsMap_.find(rawConfig.pixelFormat);\n+\tif (it != formatsMap_.end()) {\n+\t\tunsigned int mbusCode = it->second.sensorCode;\n+\n+\t\tif (std::count(mbusCodes.begin(), mbusCodes.end(), mbusCode))\n+\t\t\tsupported = true;\n+\t}\n+\n+\tif (!supported) {\n+\t\t/*\n+\t\t * Adjust to the first mbus code supported by both the\n+\t\t * sensor and the pipeline.\n+\t\t */\n+\t\tconst FormatMap::value_type *pipeConfig = nullptr;\n+\t\tfor (unsigned int code : mbusCodes) {\n+\t\t\tconst BayerFormat &bayerFormat = BayerFormat::fromMbusCode(code);\n+\t\t\tif (!bayerFormat.isValid())\n+\t\t\t\tcontinue;\n+\n+\t\t\tauto fmt = std::find_if(ISICameraConfiguration::formatsMap_.begin(),\n+\t\t\t\t\t\tISICameraConfiguration::formatsMap_.end(),\n+\t\t\t\t\t\t[code](const auto &isiFormat) {\n+\t\t\t\t\t\t\tconst auto &pipe = isiFormat.second;\n+\t\t\t\t\t\t\treturn pipe.sensorCode == code;\n+\t\t\t\t\t\t});\n+\n+\t\t\tif (fmt == ISICameraConfiguration::formatsMap_.end())\n+\t\t\t\tcontinue;\n+\n+\t\t\tpipeConfig = &(*fmt);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!pipeConfig) {\n+\t\t\tLOG(ISI, Error) << \"Cannot adjust RAW format \"\n+\t\t\t\t\t<< rawConfig.pixelFormat;\n+\t\t\treturn Invalid;\n+\t\t}\n+\n+\t\trawConfig.pixelFormat = pipeConfig->first;\n+\t\tLOG(ISI, Debug) << \"RAW pixelformat adjusted to \"\n+\t\t\t\t<< pipeConfig->first;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/* Cap the RAW stream size to the maximum resolution. */\n+\tconst Size configSize = rawConfig.size;\n+\trawConfig.size.boundTo(maxResolution);\n+\tif (rawConfig.size != configSize) {\n+\t\tLOG(ISI, Debug) << \"RAW size adjusted to \"\n+\t\t\t\t<< rawConfig.size;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/* Adjust all other streams to RAW. */\n+\tunsigned int i = 0;\n+\tfor (StreamConfiguration &cfg : config_) {\n+\n+\t\tLOG(ISI, Debug) << \"Stream \" << i << \": \" << cfg.toString();\n+\t\tconst PixelFormat pixFmt = cfg.pixelFormat;\n+\t\tconst Size size = cfg.size;\n+\n+\t\tcfg.pixelFormat = rawConfig.pixelFormat;\n+\t\tcfg.size = rawConfig.size;\n+\n+\t\tif (cfg.pixelFormat != pixFmt || cfg.size != size) {\n+\t\t\tLOG(ISI, Debug) << \"Stream \" << i << \" adjusted to \"\n+\t\t\t\t\t<< cfg.toString();\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\tcfg.stride = info.stride(cfg.size.width, 0);\n+\t\tcfg.frameSize = info.frameSize(cfg.size, info.bitsPerPixel);\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+\n+\t\ti++;\n+\t}\n+\n+\treturn status;\n+}\n+\n+/*\n+ * Adjust stream configuration when the first requested stream is not RAW: all\n+ * the streams will be either YUV or RGB processed formats.\n+ */\n+CameraConfiguration::Status\n+ISICameraConfiguration::validateYuv(std::set<Stream *> &availableStreams,\n+\t\t\t\t const Size &maxResolution)\n+{\n+\tCameraConfiguration::Status status = Valid;\n+\n+\tunsigned int i = 0;\n+\tfor (StreamConfiguration &cfg : config_) {\n+\n+\t\tLOG(ISI, Debug) << \"Stream \" << i << \": \" << cfg.toString();\n+\n+\t\t/* If the stream is RAW or not supported default it to YUYV. */\n+\t\tconst PixelFormatInfo &cfgInfo = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\tif (cfgInfo.colourEncoding == PixelFormatInfo::ColourEncodingRAW ||\n+\t\t !formatsMap_.count(cfg.pixelFormat)) {\n+\n+\t\t\tLOG(ISI, Debug) << \"Stream \" << i << \" format: \"\n+\t\t\t\t\t<< cfg.pixelFormat << \" adjusted to YUYV\";\n+\n+\t\t\tcfg.pixelFormat = formats::YUYV;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\t/* Cap the streams 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, Debug)\n+\t\t\t\t<< \"Stream \" << i << \" adjusted to \" << cfg.size;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\t/* Re-fetch the pixel format info in case it has been adjusted. */\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);\n+\n+\t\t/* \\todo Multiplane ? */\n+\t\tcfg.stride = info.stride(cfg.size.width, 0);\n+\t\tcfg.frameSize = info.frameSize(cfg.size, info.bitsPerPixel);\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+\n+\t\ti++;\n+\t}\n+\n+\treturn status;\n+}\n+\n+CameraConfiguration::Status ISICameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tstd::set<Stream *> availableStreams;\n+\tstd::transform(data_->streams_.begin(), data_->streams_.end(),\n+\t\t std::inserter(availableStreams, availableStreams.end()),\n+\t\t [](const Stream &s) { return const_cast<Stream *>(&s); });\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\t/* Cap the number of streams to the number of available ISI pipes. */\n+\tif (config_.size() > availableStreams.size()) {\n+\t\tconfig_.resize(availableStreams.size());\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/*\n+\t * If more than a single stream is requested, the maximum allowed input\n+\t * image width is 2048. Cap the maximum image size accordingly.\n+\t *\n+\t * \\todo The (size > 1) check only applies to i.MX8MP which has 2 ISI\n+\t * channels. SoCs with more channels than the i.MX8MP are capable of\n+\t * supporting more streams with input width > 2048 by chaining\n+\t * successive channels together. Define a policy for channels allocation\n+\t * to fully support other SoCs.\n+\t */\n+\tCameraSensor *sensor = data_->sensor_.get();\n+\tSize maxResolution = sensor->resolution();\n+\tif (config_.size() > 1)\n+\t\tmaxResolution.width = std::min(2048U, maxResolution.width);\n+\n+\t/* Validate streams according to the format of the first one. */\n+\tconst PixelFormatInfo info = PixelFormatInfo::info(config_[0].pixelFormat);\n+\n+\tStatus validationStatus;\n+\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW)\n+\t\tvalidationStatus = validateRaw(availableStreams, maxResolution);\n+\telse\n+\t\tvalidationStatus = validateYuv(availableStreams, maxResolution);\n+\n+\tif (validationStatus == Invalid)\n+\t\treturn Invalid;\n+\n+\tif (validationStatus == Adjusted)\n+\t\tstatus = Adjusted;\n+\n+\t/*\n+\t * Sensor format selection policy: the first stream selects the media\n+\t * bus code to use, the largest stream selects the size.\n+\t *\n+\t * \\todo The sensor format selection policy could be changed to\n+\t * prefer operating the sensor at full resolution to prioritize\n+\t * image quality in exchange of a usually slower frame rate.\n+\t * Usage of the STILL_CAPTURE role could be consider for this.\n+\t */\n+\tconst PipeFormat &pipeFmt = formatsMap_.at(config_[0].pixelFormat);\n+\n+\tSize maxSize;\n+\tfor (const auto &cfg : config_) {\n+\t\tif (cfg.size > maxSize)\n+\t\t\tmaxSize = cfg.size;\n+\t}\n+\n+\tV4L2SubdeviceFormat sensorFormat{};\n+\tsensorFormat.mbus_code = pipeFmt.sensorCode;\n+\tsensorFormat.size = maxSize;\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 aspect 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 aspect 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 that\n+\t * exceed the maximum allowed input width.\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+\n+PipelineHandlerISI::PipelineHandlerISI(CameraManager *manager)\n+\t: PipelineHandler(manager)\n+{\n+}\n+\n+std::unique_ptr<CameraConfiguration>\n+PipelineHandlerISI::generateConfiguration(Camera *camera,\n+\t\t\t\t\t const StreamRoles &roles)\n+{\n+\tISICameraData *data = cameraData(camera);\n+\tstd::unique_ptr<ISICameraConfiguration> config =\n+\t\tstd::make_unique<ISICameraConfiguration>(data);\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tif (roles.size() > data->streams_.size()) {\n+\t\tLOG(ISI, Error) << \"Only up to \" << data->streams_.size()\n+\t\t\t\t<< \" streams are supported\";\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 YUYV\n+\t\t * - ViewFinder/VideoRecording: 1080p YUYV\n+\t\t * - RAW: sensor's native format and resolution\n+\t\t */\n+\t\tPixelFormat pixelFormat;\n+\t\tSize size;\n+\n+\t\tswitch (role) {\n+\t\tcase StillCapture:\n+\t\t\t/*\n+\t\t\t * \\todo Make sure the sensor can produce non-RAW formats\n+\t\t\t * compatible with the ones supported by the pipeline.\n+\t\t\t */\n+\t\t\tsize = data->sensor_->resolution();\n+\t\t\tpixelFormat = formats::YUYV;\n+\t\t\tbreak;\n+\n+\t\tcase Viewfinder:\n+\t\t\t[[fallthrough]];\n+\t\tcase VideoRecording:\n+\t\t\t/*\n+\t\t\t * \\todo Make sure the sensor can produce non-RAW formats\n+\t\t\t * compatible with the ones supported by the pipeline.\n+\t\t\t */\n+\t\t\tsize = PipelineHandlerISI::kPreviewSize;\n+\t\t\tpixelFormat = formats::YUYV;\n+\t\t\tbreak;\n+\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\tconst ISICameraConfiguration::FormatMap::value_type *rawPipeFormat = nullptr;\n+\t\t\tunsigned int maxDepth = 0;\n+\n+\t\t\tfor (unsigned int code : data->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\t/* Make sure the format is supported by the pipeline handler. */\n+\t\t\t\tauto it = std::find_if(ISICameraConfiguration::formatsMap_.begin(),\n+\t\t\t\t\t\t ISICameraConfiguration::formatsMap_.end(),\n+\t\t\t\t\t\t [code](auto &isiFormat) {\n+\t\t\t\t\t\t\t auto &pipe = isiFormat.second;\n+\t\t\t\t\t\t\t return pipe.sensorCode == code;\n+\t\t\t\t\t\t });\n+\t\t\t\tif (it == ISICameraConfiguration::formatsMap_.end())\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\trawPipeFormat = &(*it);\n+\t\t\t\t}\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\treturn nullptr;\n+\t\t\t}\n+\n+\t\t\tsize = data->sensor_->resolution();\n+\t\t\tpixelFormat = rawPipeFormat->first;\n+\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tdefault:\n+\t\t\tLOG(ISI, Error) << \"Requested stream role not supported: \" << role;\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\t/* \\todo Add all supported formats. */\n+\t\tstd::map<PixelFormat, std::vector<SizeRange>> streamFormats;\n+\t\tstreamFormats[pixelFormat] = { { kMinISISize, size } };\n+\t\tStreamFormats formats(streamFormats);\n+\n+\t\tStreamConfiguration cfg(formats);\n+\t\tcfg.pixelFormat = pixelFormat;\n+\t\tcfg.size = size;\n+\t\tcfg.bufferCount = 4;\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+\tISICameraData *data = cameraData(camera);\n+\n+\t/* All links are immutable except the sensor -> csis link. */\n+\tconst MediaPad *sensorSrc = data->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+\tunsigned int xbarFirstSource = crossbar_->entity()->pads().size() / 2 + 1;\n+\n+\tfor (const auto &[idx, config] : utils::enumerate(*c)) {\n+\t\tstruct v4l2_subdev_route route = {\n+\t\t\t.sink_pad = data->xbarSink_,\n+\t\t\t.sink_stream = 0,\n+\t\t\t.source_pad = static_cast<uint32_t>(xbarFirstSource + 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+\tint ret = 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 format = camConfig->sensorFormat_;\n+\tret = data->sensor_->setFormat(&format);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = data->csis_->setFormat(0, &format);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = crossbar_->setFormat(data->xbarSink_, &format);\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, &format);\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 reduce the output image size.\n+\t\t */\n+\t\tRectangle isiScale(config.size);\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\tconst ISICameraConfiguration::PipeFormat &pipeFormat =\n+\t\t\tISICameraConfiguration::formatsMap_.at(config.pixelFormat);\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 = pipe->capture->toV4L2PixelFormat(config.pixelFormat);\n+\t\tcaptureFmt.size = config.size;\n+\n+\t\t/* \\todo Set stride and format. */\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\tconst StreamConfiguration &config = stream->configuration();\n+\n+\t\tint ret = pipe->capture->importBuffers(config.bufferCount);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = pipe->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+\n+\t\tpipe->capture->streamOff();\n+\t\tpipe->capture->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+\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 false;\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\treturn false;\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\tLOG(ISI, Error) << \"Unable to enumerate pipes\";\n+\t\treturn false;\n+\t}\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+\tunsigned int numSinks = 0;\n+\tfor (MediaPad *pad : crossbar_->entity()->pads()) {\n+\t\tunsigned int sink = numSinks;\n+\n+\t\tif (!(pad->flags() & MEDIA_PAD_FL_SINK) || pad->links().empty())\n+\t\t\tcontinue;\n+\n+\t\t/*\n+\t\t * Count each crossbar sink pad to correctly configure\n+\t\t * routing and format for this camera.\n+\t\t */\n+\t\tnumSinks++;\n+\n+\t\tMediaEntity *csi = pad->links()[0]->source()->entity();\n+\t\tif (csi->pads().size() != 2) {\n+\t\t\tLOG(ISI, Debug) << \"Skip unsupported CSI-2 receiver \"\n+\t\t\t\t\t<< csi->name();\n+\t\t\tcontinue;\n+\t\t}\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\tLOG(ISI, Debug) << \"Skip unsupported subdevice \"\n+\t\t\t\t\t<< sensor->name();\n+\t\t\tcontinue;\n+\t\t}\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->xbarSink_ = sink;\n+\n+\t\tret = data->init();\n+\t\tif (ret) {\n+\t\t\tLOG(ISI, Error) << \"Failed to initialize camera data\";\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\t/* Register the camera. */\n+\t\tconst std::string &id = data->sensor_->id();\n+\t\tstd::set<Stream *> streams;\n+\t\tstd::transform(data->streams_.begin(), data->streams_.end(),\n+\t\t\t std::inserter(streams, streams.end()),\n+\t\t\t [](Stream &s) { return &s; });\n+\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+PipelineHandlerISI::Pipe *PipelineHandlerISI::pipeFromStream(Camera *camera,\n+\t\t\t\t\t\t\t const Stream *stream)\n+{\n+\tISICameraData *data = cameraData(camera);\n+\tunsigned int pipeIndex = data->pipeIndex(stream);\n+\n+\tASSERT(pipeIndex < pipes_.size());\n+\n+\treturn &pipes_[pipeIndex];\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", "v3", "1/1" ] }