[{"id":25804,"web_url":"https://patchwork.libcamera.org/comment/25804/","msgid":"<Y3Pxrt1XRZp+28Td@pyrite.rasen.tech>","date":"2022-11-15T20:08:14","subject":"Re: [libcamera-devel] [PATCH v3 1/1] libcamera: pipeline: Add IMX8\n\tISI pipeline","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"On Mon, Nov 14, 2022 at 10:25:54PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> Add a pipeline handler for the ISI capture interface found on\n> several versions of the i.MX8 SoC generation.\n> \n> The pipeline handler supports capturing multiple streams from the same\n> camera in YUV, RGB or RAW formats. The number of streams is limited by\n> the number of ISI pipelines, and is currently hardcoded to 2 as the code\n> has been tested on the i.MX8MP only. Further development will make this\n> dynamic to support other SoCs.\n> \n> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\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\n> \n> diff --git a/meson_options.txt b/meson_options.txt\n> index 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',\n> diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\n> new file mode 100644\n> index 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 */\n> diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build\n> new file mode 100644\n> index 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> -- \n> 2.38.1\n>","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 EF466BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 15 Nov 2022 20:08:25 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1A00E63088;\n\tTue, 15 Nov 2022 21:08:25 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7BF6D63079\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Nov 2022 21:08:23 +0100 (CET)","from pyrite.rasen.tech (h175-177-042-159.catv02.itscom.jp\n\t[175.177.42.159])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D0D80CCE;\n\tTue, 15 Nov 2022 21:08:21 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1668542905;\n\tbh=DEy/HqafFe1RRqTu+E8eFTB/r8wWgb+p+j1ruPw3ZHo=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=ge7zrSQLUadf+Gp1YyAY5bAEVuB5F6gAxY/8wvNwldNW5qyn7fw7S6m/ZtcXVRWDM\n\t5vig4LnaU41BrZgPPP0b/r1NkOD2EgnO8SDswd7On6afWbdj9tdLMPV6EjD7T2iI9V\n\txaAjrubUMv0TXQVyRnv7GJycQ6F8xjW7Wq3iNdej4kw6HuvQp+fIaof2VjDnczzbJw\n\tNxqx7vP2HQR7KLKmVkLmKrPtPaLI0c+ewiJWaLygFt3I0Xrmps8NbZUWP77aTltW1M\n\tWQYeM6L+gvWSLACKnQD3fWWF7Osai8QYdFYDiohZgFSh18tP7RZ9PzlJcLRMGjt2/t\n\tHEiRvKmOsbB6g==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1668542903;\n\tbh=DEy/HqafFe1RRqTu+E8eFTB/r8wWgb+p+j1ruPw3ZHo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=eBmWYlq8i04b6TT+hJaxQgr/VOxfah7xCAaxKyjNZdr61senxiAC9QFDi6Jtu7dHv\n\tgtdCw/HvUdn2uO/JIXPAHNryr+tAlaXF71969bKbbosKgcPG6sedkJR2bSypbnfn6k\n\tyJAb57edvFUJ1Ch/IQIEOaLWAnbvi6dbh53Ct+Ao="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"eBmWYlq8\"; dkim-atps=neutral","Date":"Wed, 16 Nov 2022 05:08:14 +0900","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<Y3Pxrt1XRZp+28Td@pyrite.rasen.tech>","References":"<20221114212555.6936-1-jacopo@jmondi.org>\n\t<20221114212555.6936-2-jacopo@jmondi.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<20221114212555.6936-2-jacopo@jmondi.org>","Subject":"Re: [libcamera-devel] [PATCH v3 1/1] libcamera: pipeline: Add IMX8\n\tISI 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":"Paul Elder via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":25810,"web_url":"https://patchwork.libcamera.org/comment/25810/","msgid":"<Y3YAHGsLUDOhK39g@pendragon.ideasonboard.com>","date":"2022-11-17T09:34:20","subject":"Re: [libcamera-devel] [PATCH v3 1/1] libcamera: pipeline: Add IMX8\n\tISI pipeline","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch.\n\nOn Mon, Nov 14, 2022 at 10:25:54PM +0100, Jacopo Mondi via libcamera-devel wrote:\n> Add a pipeline handler for the ISI capture interface found on\n> several versions of the i.MX8 SoC generation.\n> \n> The pipeline handler supports capturing multiple streams from the same\n> camera in YUV, RGB or RAW formats. The number of streams is limited by\n> the number of ISI pipelines, and is currently hardcoded to 2 as the code\n> has been tested on the i.MX8MP only. Further development will make this\n> dynamic to support other SoCs.\n> \n> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>\n> Reviewed-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\n> \n> diff --git a/meson_options.txt b/meson_options.txt\n> index 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',\n> diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp\n> new file mode 100644\n> index 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\nThe first and second entries are identical. v1 has formats::YUV422 for\nthe first entry.\n\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\nIf two cases are immediately consecutive, I think [[fallthrough]] is not\nneeded.\n\nNo need for a v3, those minor issues (plus utils::enumerate) can be\nfixed when pushing.\n\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 */\n> diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build\n> new file mode 100644\n> index 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> +])","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 E7884BD16B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 17 Nov 2022 09:34:37 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2FE2C632E3;\n\tThu, 17 Nov 2022 10:34:37 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id EF69363095\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 17 Nov 2022 10:34:35 +0100 (CET)","from pendragon.ideasonboard.com\n\t(host-62-245-140-144.customer.m-online.net [62.245.140.144])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 5593C6E0;\n\tThu, 17 Nov 2022 10:34:35 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1668677677;\n\tbh=P+E2nIHL0wXpkjWqHwDRWdpG13jSoZJBbXwItPJkkfI=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=GkHKaOqeTAIxB+TTdyanB+xF2I6tg+okc14rUzwg7BK9Pp8hqwlCaQ8NGp51XdbZ8\n\tFcr/YEjZA0RVqO2GqPnUMHdUSg0VLC8LVPWPbgSXKqenJhKImM2YgqkmLdrtK6psB6\n\t9PJb1mqa66FDo6Hnar8/NtBi7/4jpDI34mO+DWSXmwQkIN7fEsqXGkTlFgXjddWhtd\n\tqj3sbA73BmIYKChRNiwy6IzytG/so+EhwU+T9FRa7XTArLXAQLUnNoAR++3CGZ3ZTg\n\t7hCxqVrjDRfJm+JhDHGTOmnclGE0w3aeqWQNvoKKr5sb7zsPJXCJn0qwC4B4aHnWzm\n\t9DquP3EwdLi7w==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1668677675;\n\tbh=P+E2nIHL0wXpkjWqHwDRWdpG13jSoZJBbXwItPJkkfI=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=bh2tObJvGtMKE739BY/VfReXIc9sr3hq8/GUj98cfxVh4g/15NyOg/uIkyVYFS4Dk\n\tpzUsr5kXnUbs4WfOxQO4pNihU8k9/xwb7sH56L4nUW8lnsBiwlEyal5DAmMmRK3xWU\n\t0yh9Y/pPYodn8wAW26rggCVL4a3kSwoROA++6CZo="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"bh2tObJv\"; dkim-atps=neutral","Date":"Thu, 17 Nov 2022 11:34:20 +0200","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<Y3YAHGsLUDOhK39g@pendragon.ideasonboard.com>","References":"<20221114212555.6936-1-jacopo@jmondi.org>\n\t<20221114212555.6936-2-jacopo@jmondi.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20221114212555.6936-2-jacopo@jmondi.org>","Subject":"Re: [libcamera-devel] [PATCH v3 1/1] libcamera: pipeline: Add IMX8\n\tISI 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":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]