[{"id":28960,"web_url":"https://patchwork.libcamera.org/comment/28960/","msgid":"<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>","date":"2024-03-14T14:12:07","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Ouch\n\nOn Thu, Mar 14, 2024 at 03:07:08PM +0100, Jacopo Mondi wrote:\n> Add a pipeline handler for the Mali-C55 ISP.\n>\n> The pipeline doesn't currently support an IPA and does not run\n> any 3 algorithm but only handles the media graph topology and\n      ^\n      3a of course!\n\n> formats/sizes configuration\n>\n> Co-developed-by: Daniel Scally <dan.scally@ideasonboard.com>\n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Acked-by: Nayden Kanchev <nayden.kanchev@arm.com>\n> ---\n>  meson.build                                  |    1 +\n>  meson_options.txt                            |    1 +\n>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1081 ++++++++++++++++++\n>  src/libcamera/pipeline/mali-c55/meson.build  |    5 +\n>  4 files changed, 1088 insertions(+)\n>  create mode 100644 src/libcamera/pipeline/mali-c55/mali-c55.cpp\n>  create mode 100644 src/libcamera/pipeline/mali-c55/meson.build\n>\n> diff --git a/meson.build b/meson.build\n> index cb6b666a7449..740ead1be85f 100644\n> --- a/meson.build\n> +++ b/meson.build\n> @@ -198,6 +198,7 @@ arch_x86 = ['x86', 'x86_64']\n>  pipelines_support = {\n>      'imx8-isi':     arch_arm,\n>      'ipu3':         arch_x86,\n> +    'mali-c55':     arch_arm,\n>      'rkisp1':       arch_arm,\n>      'rpi/vc4':      arch_arm,\n>      'simple':       arch_arm,\n> diff --git a/meson_options.txt b/meson_options.txt\n> index 99dab96d7b21..7c4f6d3a0af6 100644\n> --- a/meson_options.txt\n> +++ b/meson_options.txt\n> @@ -43,6 +43,7 @@ option('pipelines',\n>              'auto',\n>              'imx8-isi',\n>              'ipu3',\n> +            'mali-c55',\n>              'rkisp1',\n>              'rpi/vc4',\n>              'simple',\n> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> new file mode 100644\n> index 000000000000..4508214b864d\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> @@ -0,0 +1,1081 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas on Board Oy\n> + *\n> + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> + */\n> +\n> +#include <algorithm>\n> +#include <array>\n> +#include <map>\n> +#include <memory>\n> +#include <set>\n> +#include <string>\n> +\n> +#include <linux/media-bus-format.h>\n> +#include <linux/media.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/camera.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> +namespace {\n> +\n> +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> +{\n> +\treturn libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> +\t       libcamera::PixelFormatInfo::ColourEncodingRAW;\n> +}\n> +\n> +}; /* namespace */\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(MaliC55)\n> +\n> +const std::map<libcamera::PixelFormat, unsigned int> maliC55FmtToCode = {\n> +\t/* \\todo Support all formats supported by the driver in libcamera. */\n> +\n> +\t{ formats::RGB565, MEDIA_BUS_FMT_RGB121212_1X36 },\n> +\t{ formats::RGB888, MEDIA_BUS_FMT_RGB121212_1X36 },\n> +\t{ formats::YUYV, MEDIA_BUS_FMT_YUV10_1X30 },\n> +\t{ formats::UYVY, MEDIA_BUS_FMT_YUV10_1X30 },\n> +\t{ formats::R8, MEDIA_BUS_FMT_YUV10_1X30 },\n> +\t{ formats::NV12, MEDIA_BUS_FMT_YUV10_1X30 },\n> +\t{ formats::NV21, MEDIA_BUS_FMT_YUV10_1X30 },\n> +\n> +\t/* RAW formats, FR pipe only. */\n> +\t{ formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },\n> +\t{ formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },\n> +\t{ formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },\n> +\t{ formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },\n> +\t{ formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n> +\t{ formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n> +\t{ formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n> +\t{ formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n> +\t{ formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },\n> +\t{ formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },\n> +\t{ formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },\n> +\t{ formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },\n> +\t{ formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 },\n> +\t{ formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 },\n> +\t{ formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 },\n> +\t{ formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 },\n> +\t{ formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 },\n> +\t{ formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 },\n> +\t{ formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 },\n> +\t{ formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 },\n> +};\n> +\n> +constexpr Size kMaliC55MinSize = { 128, 128 };\n> +constexpr Size kMaliC55MaxSize = { 8192, 8192 };\n> +constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36;\n> +\n> +class MaliC55CameraData : public Camera::Private\n> +{\n> +public:\n> +\tMaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity)\n> +\t\t: Camera::Private(pipe), entity_(entity)\n> +\t{\n> +\t}\n> +\n> +\tint init();\n> +\n> +\t/* Deflect these functionalities to either TPG or CameraSensor. */\n> +\tconst std::vector<unsigned int> mbusCodes() const;\n> +\tconst std::vector<Size> sizes(unsigned int mbusCode) const;\n> +\tconst Size resolution() const;\n> +\n> +\tPixelFormat bestRawFormat() const;\n> +\n> +\tPixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;\n> +\tSize adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;\n> +\n> +\tstd::unique_ptr<CameraSensor> sensor_;\n> +\n> +\tMediaEntity *entity_;\n> +\tstd::unique_ptr<V4L2Subdevice> csi_;\n> +\tstd::unique_ptr<V4L2Subdevice> sd_;\n> +\tStream frStream_;\n> +\tStream dsStream_;\n> +\n> +private:\n> +\tvoid initTPGData();\n> +\n> +\tstd::string id_;\n> +\tstd::vector<unsigned int> tpgCodes_;\n> +\tstd::vector<Size> tpgSizes_;\n> +\tSize tpgResolution_;\n> +};\n> +\n> +int MaliC55CameraData::init()\n> +{\n> +\tint ret;\n> +\n> +\tsd_ = std::make_unique<V4L2Subdevice>(entity_);\n> +\tret = sd_->open();\n> +\tif (ret) {\n> +\t\tLOG(MaliC55, Error) << \"Failed to open sensor subdevice\";\n> +\t\treturn ret;\n> +\t}\n> +\n> +\t/* If this camera is created from TPG, we return here. */\n> +\tif (entity_->name() == \"mali-c55 tpg\") {\n> +\t\tinitTPGData();\n> +\t\treturn 0;\n> +\t}\n> +\n> +\t/*\n> +\t * Register a CameraSensor if we connect to a sensor and create\n> +\t * an entity for the connected CSI-2 receiver.\n> +\t */\n> +\tsensor_ = std::make_unique<CameraSensor>(entity_);\n> +\tret = sensor_->init();\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tconst MediaPad *sourcePad = entity_->getPadByIndex(0);\n> +\tMediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();\n> +\n> +\tcsi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n> +\tif (csi_->open()) {\n> +\t\tLOG(MaliC55, Error) << \"Failed to open CSI-2 subdevice\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void MaliC55CameraData::initTPGData()\n> +{\n> +\t/* Replicate the CameraSensor implementation for TPG. */\n> +\tV4L2Subdevice::Formats formats = sd_->formats(0);\n> +\tif (formats.empty())\n> +\t\treturn;\n> +\n> +\ttpgCodes_ = utils::map_keys(formats);\n> +\tstd::sort(tpgCodes_.begin(), tpgCodes_.end());\n> +\n> +\tfor (const auto &format : formats) {\n> +\t\tconst std::vector<SizeRange> &ranges = format.second;\n> +\t\tstd::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes_),\n> +\t\t\t       [](const SizeRange &range) { return range.max; });\n> +\t}\n> +\n> +\ttpgResolution_ = tpgSizes_.back();\n> +}\n> +\n> +const std::vector<unsigned int> MaliC55CameraData::mbusCodes() const\n> +{\n> +\tif (sensor_)\n> +\t\treturn sensor_->mbusCodes();\n> +\n> +\treturn tpgCodes_;\n> +}\n> +\n> +const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const\n> +{\n> +\tif (sensor_)\n> +\t\treturn sensor_->sizes(mbusCode);\n> +\n> +\tV4L2Subdevice::Formats formats = sd_->formats(0);\n> +\tif (formats.empty())\n> +\t\treturn {};\n> +\n> +\tstd::vector<Size> sizes;\n> +\tconst auto &format = formats.find(mbusCode);\n> +\tif (format == formats.end())\n> +\t\treturn {};\n> +\n> +\tconst std::vector<SizeRange> &ranges = format->second;\n> +\tstd::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),\n> +\t\t       [](const SizeRange &range) { return range.max; });\n> +\n> +\tstd::sort(sizes.begin(), sizes.end());\n> +\n> +\treturn sizes;\n> +}\n> +\n> +const Size MaliC55CameraData::resolution() const\n> +{\n> +\tif (sensor_)\n> +\t\treturn sensor_->resolution();\n> +\n> +\treturn tpgResolution_;\n> +}\n> +\n> +PixelFormat MaliC55CameraData::bestRawFormat() const\n> +{\n> +\tunsigned int bitDepth = 0;\n> +\tPixelFormat rawFormat;\n> +\n> +\t/*\n> +\t * Iterate over all the supported PixelFormat and find the one\n> +\t * supported by the camera with the largest bitdepth.\n> +\t */\n> +\tfor (const auto &maliFormat : maliC55FmtToCode) {\n> +\t\tPixelFormat pixFmt = maliFormat.first;\n> +\t\tif (!isFormatRaw(pixFmt))\n> +\t\t\tcontinue;\n> +\n> +\t\tunsigned int rawCode = maliFormat.second;\n> +\t\tconst auto rawSizes = sizes(rawCode);\n> +\t\tif (rawSizes.empty())\n> +\t\t\tcontinue;\n> +\n> +\t\tBayerFormat bayer = BayerFormat::fromMbusCode(rawCode);\n> +\t\tif (bayer.bitDepth > bitDepth) {\n> +\t\t\tbitDepth = bayer.bitDepth;\n> +\t\t\trawFormat = pixFmt;\n> +\t\t}\n> +\t}\n> +\n> +\treturn rawFormat;\n> +}\n> +\n> +/*\n> + * Make sure the provided raw pixel format is supported and adjust it to\n> + * one of the supported ones if it's not.\n> + */\n> +PixelFormat MaliC55CameraData::adjustRawFormat(const PixelFormat &rawFmt) const\n> +{\n> +\t/* Make sure the provided raw format is supported by the pipeline. */\n> +\tauto it = maliC55FmtToCode.find(rawFmt);\n> +\tif (it == maliC55FmtToCode.end())\n> +\t\treturn bestRawFormat();\n> +\n> +\t/* Now make sure the RAW mbus code is supported by the image source. */\n> +\tunsigned int rawCode = it->second;\n> +\tconst auto rawSizes = sizes(rawCode);\n> +\tif (rawSizes.empty())\n> +\t\treturn bestRawFormat();\n> +\n> +\treturn rawFmt;\n> +}\n> +\n> +Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &rawSize) const\n> +{\n> +\t/* Just make sure the format is supported. */\n> +\tauto it = maliC55FmtToCode.find(rawFmt);\n> +\tif (it == maliC55FmtToCode.end())\n> +\t\treturn {};\n> +\n> +\t/* Check if the size is natively supported. */\n> +\tunsigned int rawCode = it->second;\n> +\tconst auto rawSizes = sizes(rawCode);\n> +\tauto sizeIt = std::find(rawSizes.begin(), rawSizes.end(), rawSize);\n> +\tif (sizeIt != rawSizes.end())\n> +\t\treturn rawSize;\n> +\n> +\t/* Or adjust it to the closest supported size. */\n> +\tuint16_t distance = std::numeric_limits<uint16_t>::max();\n> +\tSize bestSize;\n> +\tfor (const Size &size : rawSizes) {\n> +\t\tuint16_t dist = std::abs(static_cast<int>(rawSize.width) -\n> +\t\t\t\t\t static_cast<int>(size.width)) +\n> +\t\t\t\tstd::abs(static_cast<int>(rawSize.height) -\n> +\t\t\t\t\t static_cast<int>(size.height));\n> +\t\tif (dist < distance) {\n> +\t\t\tdist = distance;\n> +\t\t\tbestSize = size;\n> +\t\t}\n> +\t}\n> +\n> +\treturn bestSize;\n> +}\n> +\n> +class MaliC55CameraConfiguration : public CameraConfiguration\n> +{\n> +public:\n> +\tMaliC55CameraConfiguration(MaliC55CameraData *data)\n> +\t\t: CameraConfiguration(), data_(data)\n> +\t{\n> +\t}\n> +\n> +\tStatus validate() override;\n> +\n> +\tV4L2SubdeviceFormat sensorFormat_;\n> +\n> +private:\n> +\tstatic constexpr unsigned int kMaxStreams = 2;\n> +\n> +\tconst MaliC55CameraData *data_;\n> +};\n> +\n> +CameraConfiguration::Status MaliC55CameraConfiguration::validate()\n> +{\n> +\tStatus status = Valid;\n> +\n> +\tif (config_.empty())\n> +\t\treturn Invalid;\n> +\n> +\t/* Only 2 streams available. */\n> +\tif (config_.size() > kMaxStreams) {\n> +\t\tconfig_.resize(kMaxStreams);\n> +\t\tstatus = Adjusted;\n> +\t}\n> +\n> +\tbool frPipeAvailable = true;\n> +\tStreamConfiguration *rawConfig = nullptr;\n> +\tfor (StreamConfiguration &config : config_) {\n> +\t\tif (!isFormatRaw(config.pixelFormat))\n> +\t\t\tcontinue;\n> +\n> +\t\tif (rawConfig) {\n> +\t\t\tLOG(MaliC55, Error)\n> +\t\t\t\t<< \"Only a single RAW stream is supported\";\n> +\t\t\treturn Invalid;\n> +\t\t}\n> +\n> +\t\trawConfig = &config;\n> +\t}\n> +\n> +\tSize maxSize = kMaliC55MaxSize;\n> +\tif (rawConfig) {\n> +\t\t/*\n> +\t\t * \\todo Take into account the Bayer components ordering once\n> +\t\t * we support rotations.\n> +\t\t */\n> +\t\tPixelFormat rawFormat =\n> +\t\t\tdata_->adjustRawFormat(rawConfig->pixelFormat);\n> +\t\tif (rawFormat != rawConfig->pixelFormat) {\n> +\t\t\tLOG(MaliC55, Debug)\n> +\t\t\t\t<< \"RAW format adjusted to \" << rawFormat;\n> +\t\t\trawConfig->pixelFormat = rawFormat;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tSize rawSize =\n> +\t\t\tdata_->adjustRawSizes(rawFormat, rawConfig->size);\n> +\t\tif (rawSize != rawConfig->size) {\n> +\t\t\tLOG(MaliC55, Debug)\n> +\t\t\t\t<< \"RAW sizes adjusted to \" << rawSize;\n> +\t\t\trawConfig->size = rawSize;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tmaxSize = rawSize;\n> +\n> +\t\trawConfig->setStream(const_cast<Stream *>(&data_->frStream_));\n> +\t\tfrPipeAvailable = false;\n> +\t}\n> +\n> +\t/* Adjust processed streams. */\n> +\tSize maxYuvSize;\n> +\tfor (StreamConfiguration &config : config_) {\n> +\t\tif (isFormatRaw(config.pixelFormat))\n> +\t\t\tcontinue;\n> +\n> +\t\t/* Adjust format and size for processed streams. */\n> +\t\tconst auto it = maliC55FmtToCode.find(config.pixelFormat);\n> +\t\tif (it == maliC55FmtToCode.end()) {\n> +\t\t\tLOG(MaliC55, Debug)\n> +\t\t\t\t<< \"Format adjusted to \" << formats::RGB565;\n> +\t\t\tconfig.pixelFormat = formats::RGB565;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tSize size = std::clamp(config.size, kMaliC55MinSize, maxSize);\n> +\t\tif (size != config.size) {\n> +\t\t\tLOG(MaliC55, Debug)\n> +\t\t\t\t<< \"Size adjusted to \" << size;\n> +\t\t\tconfig.size = size;\n> +\t\t\tstatus = Adjusted;\n> +\t\t}\n> +\n> +\t\tif (maxYuvSize < size)\n> +\t\t\tmaxYuvSize = size;\n> +\n> +\t\tif (frPipeAvailable) {\n> +\t\t\tconfig.setStream(const_cast<Stream *>(&data_->frStream_));\n> +\t\t\tfrPipeAvailable = false;\n> +\t\t} else {\n> +\t\t\tconfig.setStream(const_cast<Stream *>(&data_->dsStream_));\n> +\t\t}\n> +\t}\n> +\n> +\t/* Compute the sensor format. */\n> +\n> +\t/* If there's a RAW config, sensor configuration follows it. */\n> +\tif (rawConfig) {\n> +\t\tconst auto it = maliC55FmtToCode.find(rawConfig->pixelFormat);\n> +\t\tsensorFormat_.mbus_code = it->second;\n> +\t\tsensorFormat_.size = rawConfig->size;\n> +\n> +\t\treturn status;\n> +\t}\n> +\n> +\t/* If there's no RAW config, compute the sensor configuration here. */\n> +\tPixelFormat rawFormat = data_->bestRawFormat();\n> +\tconst auto it = maliC55FmtToCode.find(rawFormat);\n> +\tsensorFormat_.mbus_code = it->second;\n> +\n> +\tuint16_t distance = std::numeric_limits<uint16_t>::max();\n> +\tconst auto sizes = data_->sizes(it->second);\n> +\tSize bestSize;\n> +\tfor (const auto &size : sizes) {\n> +\t\t/* Skip sensor sizes that are smaller than the max YUV size. */\n> +\t\tif (maxYuvSize.width > size.width ||\n> +\t\t    maxYuvSize.height > size.height)\n> +\t\t\tcontinue;\n> +\n> +\t\tuint16_t dist = std::abs(static_cast<int>(maxYuvSize.width) -\n> +\t\t\t\t\t static_cast<int>(size.width)) +\n> +\t\t\t\tstd::abs(static_cast<int>(maxYuvSize.height) -\n> +\t\t\t\t\t static_cast<int>(size.height));\n> +\t\tif (dist < distance) {\n> +\t\t\tdist = distance;\n> +\t\t\tbestSize = size;\n> +\t\t}\n> +\t}\n> +\tsensorFormat_.size = bestSize;\n> +\n> +\tLOG(MaliC55, Debug) << \"Computed sensor configuration \" << sensorFormat_;\n> +\n> +\treturn status;\n> +}\n> +\n> +class PipelineHandlerMaliC55 : public PipelineHandler\n> +{\n> +public:\n> +\tPipelineHandlerMaliC55(CameraManager *manager);\n> +\n> +\tstd::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n> +\t\t\t\t\t\t\t\t   Span<const StreamRole> 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> +\tvoid stopDevice(Camera *camera) override;\n> +\n> +\tint queueRequestDevice(Camera *camera, Request *request) override;\n> +\n> +\tvoid bufferReady(FrameBuffer *buffer);\n> +\n> +\tbool match(DeviceEnumerator *enumerator) override;\n> +\n> +private:\n> +\tstruct MaliC55Pipe {\n> +\t\tstd::unique_ptr<V4L2Subdevice> resizer;\n> +\t\tstd::unique_ptr<V4L2VideoDevice> cap;\n> +\t\tStream *stream;\n> +\t};\n> +\n> +\tenum {\n> +\t\tMaliC55FR,\n> +\t\tMaliC55DS,\n> +\t\tMaliC55NumPipes,\n> +\t};\n> +\n> +\tMaliC55CameraData *cameraData(Camera *camera)\n> +\t{\n> +\t\treturn static_cast<MaliC55CameraData *>(camera->_d());\n> +\t}\n> +\n> +\tMaliC55Pipe *pipeFromStream(MaliC55CameraData *data, Stream *stream)\n> +\t{\n> +\t\tif (stream == &data->frStream_)\n> +\t\t\treturn &pipes_[MaliC55FR];\n> +\t\telse if (stream == &data->dsStream_)\n> +\t\t\treturn &pipes_[MaliC55DS];\n> +\t\telse\n> +\t\t\tLOG(MaliC55, Fatal) << \"Stream \" << stream << \" not valid\";\n> +\t\treturn nullptr;\n> +\t}\n> +\n> +\tMaliC55Pipe *pipeFromStream(MaliC55CameraData *data, const Stream *stream)\n> +\t{\n> +\t\treturn pipeFromStream(data, const_cast<Stream *>(stream));\n> +\t}\n> +\n> +\tvoid resetPipes()\n> +\t{\n> +\t\tfor (MaliC55Pipe &pipe : pipes_)\n> +\t\t\tpipe.stream = nullptr;\n> +\t}\n> +\n> +\tint configureRawStream(MaliC55CameraData *data,\n> +\t\t\t       const StreamConfiguration &config,\n> +\t\t\t       V4L2SubdeviceFormat &subdevFormat);\n> +\tint configureProcessedStream(MaliC55CameraData *data,\n> +\t\t\t\t     const StreamConfiguration &config,\n> +\t\t\t\t     V4L2SubdeviceFormat &subdevFormat);\n> +\n> +\tvoid registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> +\t\t\t\tconst std::string &name);\n> +\tbool registerTPGCamera(MediaLink *link);\n> +\tbool registerSensorCamera(MediaLink *link);\n> +\n> +\tMediaDevice *media_;\n> +\tstd::unique_ptr<V4L2Subdevice> isp_;\n> +\n> +\tstd::array<MaliC55Pipe, MaliC55NumPipes> pipes_;\n> +\n> +\tbool dsFitted_;\n> +};\n> +\n> +PipelineHandlerMaliC55::PipelineHandlerMaliC55(CameraManager *manager)\n> +\t: PipelineHandler(manager), dsFitted_(true)\n> +{\n> +}\n> +\n> +std::unique_ptr<CameraConfiguration>\n> +PipelineHandlerMaliC55::generateConfiguration(Camera *camera,\n> +\t\t\t\t\t      Span<const StreamRole> roles)\n> +{\n> +\tMaliC55CameraData *data = cameraData(camera);\n> +\tstd::unique_ptr<CameraConfiguration> config =\n> +\t\tstd::make_unique<MaliC55CameraConfiguration>(data);\n> +\tbool frPipeAvailable = true;\n> +\n> +\tif (roles.empty())\n> +\t\treturn config;\n> +\n> +\t/* Check if one stream is RAW to reserve the FR pipe for it. */\n> +\tif (std::find_if(roles.begin(), roles.end(),\n> +\t\t\t [](const StreamRole &role) {\n> +\t\t\t\t return role == StreamRole::Raw;\n> +\t\t\t }) != roles.end())\n> +\t\tfrPipeAvailable = false;\n> +\n> +\tfor (const StreamRole &role : roles) {\n> +\t\tstruct MaliC55Pipe *pipe;\n> +\n> +\t\t/* Assign pipe for this role. */\n> +\t\tif (role == StreamRole::Raw) {\n> +\t\t\tpipe = &pipes_[MaliC55FR];\n> +\t\t} else {\n> +\t\t\tif (frPipeAvailable) {\n> +\t\t\t\tpipe = &pipes_[MaliC55FR];\n> +\t\t\t\tfrPipeAvailable = false;\n> +\t\t\t} else {\n> +\t\t\t\tpipe = &pipes_[MaliC55DS];\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tSize size = std::min(Size{ 1920, 1080 }, data->resolution());\n> +\t\tPixelFormat pixelFormat;\n> +\n> +\t\tswitch (role) {\n> +\t\tcase StreamRole::StillCapture:\n> +\t\t\tsize = data->resolution();\n> +\t\t\t/* fall-through */\n> +\t\tcase StreamRole::VideoRecording:\n> +\t\t\tpixelFormat = formats::NV12;\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::Viewfinder:\n> +\t\t\tpixelFormat = formats::RGB565;\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::Raw:\n> +\t\t\tpixelFormat = data->bestRawFormat();\n> +\t\t\tif (!pixelFormat.isValid()) {\n> +\t\t\t\tLOG(MaliC55, Error)\n> +\t\t\t\t\t<< \"Camera does not support RAW formats\";\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\tsize = data->resolution();\n> +\t\t\tbreak;\n> +\n> +\t\tdefault:\n> +\t\t\tLOG(MaliC55, Error)\n> +\t\t\t\t<< \"Requested stream role not supported: \" << role;\n> +\t\t\treturn config;\n> +\t\t}\n> +\n> +\t\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n> +\t\tfor (const auto &maliFormat : maliC55FmtToCode) {\n> +\t\t\tPixelFormat pixFmt = maliFormat.first;\n> +\t\t\tbool isRaw = isFormatRaw(pixFmt);\n> +\n> +\t\t\t/* RAW formats are only supported on the FR pipe. */\n> +\t\t\tif (pipe != &pipes_[MaliC55FR] && isRaw)\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tif (isRaw) {\n> +\t\t\t\t/* Make sure the mbus code is supported. */\n> +\t\t\t\tunsigned int rawCode = maliFormat.second;\n> +\t\t\t\tconst auto sizes = data->sizes(rawCode);\n> +\t\t\t\tif (sizes.empty())\n> +\t\t\t\t\tcontinue;\n> +\n> +\t\t\t\t/* And list all sizes the sensor can produce. */\n> +\t\t\t\tstd::vector<SizeRange> sizeRanges;\n> +\t\t\t\tstd::transform(sizes.begin(), sizes.end(),\n> +\t\t\t\t\t       std::back_inserter(sizeRanges),\n> +\t\t\t\t\t       [](const Size &s) {\n> +\t\t\t\t\t\t       return SizeRange(s);\n> +\t\t\t\t\t       });\n> +\n> +\t\t\t\tformats[pixFmt] = sizeRanges;\n> +\t\t\t} else {\n> +\t\t\t\t/* Processed formats are always available. */\n> +\t\t\t\tSize maxSize = std::min(kMaliC55MaxSize,\n> +\t\t\t\t\t\t\tdata->resolution());\n> +\t\t\t\tformats[pixFmt] = { kMaliC55MinSize, maxSize };\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tStreamFormats streamFormats(formats);\n> +\t\tStreamConfiguration cfg(streamFormats);\n> +\t\tcfg.pixelFormat = pixelFormat;\n> +\t\tcfg.bufferCount = 4;\n> +\t\tcfg.size = size;\n> +\n> +\t\tconfig->addConfiguration(cfg);\n> +\t}\n> +\n> +\tif (config->validate() == CameraConfiguration::Invalid)\n> +\t\treturn {};\n> +\n> +\treturn config;\n> +}\n> +\n> +int PipelineHandlerMaliC55::configureRawStream(MaliC55CameraData *data,\n> +\t\t\t\t\t       const StreamConfiguration &config,\n> +\t\t\t\t\t       V4L2SubdeviceFormat &subdevFormat)\n> +{\n> +\tStream *stream = config.stream();\n> +\tMaliC55Pipe *pipe = pipeFromStream(data, stream);\n> +\n> +\tif (pipe != &pipes_[MaliC55FR]) {\n> +\t\tLOG(MaliC55, Fatal) << \"Only the FR pipe supports RAW capture.\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\t/* Enable the debayer route to set fixed internal format on pad #0. */\n> +\tV4L2Subdevice::Routing routing = {};\n> +\tstruct v4l2_subdev_route route = {\n> +\t\t.sink_pad = 0,\n> +\t\t.sink_stream = 0,\n> +\t\t.source_pad = 1,\n> +\t\t.source_stream = 0,\n> +\t\t.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> +\t\t.reserved = {}\n> +\t};\n> +\trouting.push_back(route);\n> +\n> +\tint ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tunsigned int rawCode = subdevFormat.mbus_code;\n> +\tsubdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> +\tret = pipe->resizer->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/* Enable the bypass route and apply RAW formats there. */\n> +\trouting.clear();\n> +\n> +\troute.sink_pad = 2;\n> +\trouting.push_back(route);\n> +\tret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tsubdevFormat.mbus_code = rawCode;\n> +\tret = pipe->resizer->setFormat(2, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = pipe->resizer->setFormat(1, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerMaliC55::configureProcessedStream(MaliC55CameraData *data,\n> +\t\t\t\t\t\t     const StreamConfiguration &config,\n> +\t\t\t\t\t\t     V4L2SubdeviceFormat &subdevFormat)\n> +{\n> +\tStream *stream = config.stream();\n> +\tMaliC55Pipe *pipe = pipeFromStream(data, stream);\n> +\n> +\t/* Enable the debayer route on the resizer pipe. */\n> +\tV4L2Subdevice::Routing routing = {};\n> +\tstruct v4l2_subdev_route route = {\n> +\t\t.sink_pad = 0,\n> +\t\t.sink_stream = 0,\n> +\t\t.source_pad = 1,\n> +\t\t.source_stream = 0,\n> +\t\t.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> +\t\t.reserved = {}\n> +\t};\n> +\trouting.push_back(route);\n> +\n> +\tint ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tsubdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> +\tret = pipe->resizer->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/* \\todo Configure the resizer crop/compose rectangles. */\n> +\tRectangle ispCrop = { 0, 0, config.size };\n> +\tret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &ispCrop);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tsubdevFormat.mbus_code = maliC55FmtToCode.find(config.pixelFormat)->second;\n> +\treturn pipe->resizer->setFormat(1, &subdevFormat);\n> +}\n> +\n> +int PipelineHandlerMaliC55::configure(Camera *camera,\n> +\t\t\t\t      CameraConfiguration *config)\n> +{\n> +\tresetPipes();\n> +\n> +\tint ret = media_->disableLinks();\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/* Link the graph depending if we are operating the TPG or a sensor. */\n> +\tMaliC55CameraData *data = cameraData(camera);\n> +\tif (data->csi_) {\n> +\t\tconst MediaEntity *csiEntity = data->csi_->entity();\n> +\t\tret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n> +\t} else {\n> +\t\tret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true);\n> +\t}\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tMaliC55CameraConfiguration *maliConfig =\n> +\t\tstatic_cast<MaliC55CameraConfiguration *>(config);\n> +\tV4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_;\n> +\tret = data->sd_->getFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tif (data->csi_) {\n> +\t\tret = data->csi_->setFormat(0, &subdevFormat);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\n> +\t\tret = data->csi_->setFormat(1, &subdevFormat);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t}\n> +\n> +\t/*\n> +\t * Propagate the format to the ISP sink pad and configure the input\n> +\t * crop rectangle (no crop at the moment).\n> +\t *\n> +\t * \\todo Configure the CSI-2 receiver.\n> +\t */\n> +\tret = isp_->setFormat(0, &subdevFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tRectangle ispCrop(0, 0, subdevFormat.size);\n> +\tret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\t/*\n> +\t * Configure the resizer: fixed format the sink pad; use the media\n> +\t * bus code associated with the desired capture format on the source\n> +\t * pad.\n> +\t *\n> +\t * Configure the crop and compose rectangles to match the desired\n> +\t * stream output size\n> +\t *\n> +\t * \\todo Make the crop/scaler configurable\n> +\t */\n> +\tfor (const StreamConfiguration &streamConfig : *config) {\n> +\t\tStream *stream = streamConfig.stream();\n> +\t\tMaliC55Pipe *pipe = pipeFromStream(data, stream);\n> +\n> +\t\tif (isFormatRaw(streamConfig.pixelFormat))\n> +\t\t\tret = configureRawStream(data, streamConfig, subdevFormat);\n> +\t\telse\n> +\t\t\tret = configureProcessedStream(data, streamConfig, subdevFormat);\n> +\t\tif (ret) {\n> +\t\t\tLOG(MaliC55, Error) << \"Failed to configure pipeline\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\t/* Now apply the pixel format and size to the capture device. */\n> +\t\tV4L2DeviceFormat captureFormat;\n> +\t\tcaptureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);\n> +\t\tcaptureFormat.size = streamConfig.size;\n> +\n> +\t\tret = pipe->cap->setFormat(&captureFormat);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\n> +\t\tpipe->stream = stream;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream,\n> +\t\t\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> +{\n> +\tMaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> +\tunsigned int count = stream->configuration().bufferCount;\n> +\n> +\treturn pipe->cap->exportBuffers(count, buffers);\n> +}\n> +\n> +int PipelineHandlerMaliC55::start([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList *controls)\n> +{\n> +\tfor (MaliC55Pipe &pipe : pipes_) {\n> +\t\tif (!pipe.stream)\n> +\t\t\tcontinue;\n> +\n> +\t\tStream *stream = pipe.stream;\n> +\n> +\t\tint ret = pipe.cap->importBuffers(stream->configuration().bufferCount);\n> +\t\tif (ret) {\n> +\t\t\tLOG(MaliC55, Error) << \"Failed to import buffers\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\n> +\t\tret = pipe.cap->streamOn();\n> +\t\tif (ret) {\n> +\t\t\tLOG(MaliC55, Error) << \"Failed to start stream\";\n> +\t\t\treturn ret;\n> +\t\t}\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)\n> +{\n> +\tfor (MaliC55Pipe &pipe : pipes_) {\n> +\t\tif (!pipe.stream)\n> +\t\t\tcontinue;\n> +\n> +\t\tpipe.cap->streamOff();\n> +\t\tpipe.cap->releaseBuffers();\n> +\t}\n> +}\n> +\n> +int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request)\n> +{\n> +\tint ret;\n> +\n> +\tfor (auto &[stream, buffer] : request->buffers()) {\n> +\t\tMaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> +\n> +\t\tret = pipe->cap->queueBuffer(buffer);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +void PipelineHandlerMaliC55::bufferReady(FrameBuffer *buffer)\n> +{\n> +\tRequest *request = buffer->request();\n> +\n> +\tcompleteBuffer(request, buffer);\n> +\n> +\tif (request->hasPendingBuffers())\n> +\t\treturn;\n> +\n> +\tcompleteRequest(request);\n> +}\n> +\n> +void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> +\t\t\t\t\t\tconst std::string &name)\n> +{\n> +\tstd::set<Stream *> streams{ &data->frStream_ };\n> +\tif (dsFitted_)\n> +\t\tstreams.insert(&data->dsStream_);\n> +\n> +\tstd::shared_ptr<Camera> camera = Camera::create(std::move(data),\n> +\t\t\t\t\t\t\tname, streams);\n> +\tregisterCamera(std::move(camera));\n> +}\n> +\n> +/*\n> + * The only camera we support through direct connection to the ISP is the\n> + * Mali-C55 TPG. Check we have that and warn if not.\n> + */\n> +bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)\n> +{\n> +\tconst std::string &name = link->source()->entity()->name();\n> +\tif (name != \"mali-c55 tpg\") {\n> +\t\tLOG(MaliC55, Warning) << \"Unsupported direct connection to \"\n> +\t\t\t\t      << link->source()->entity()->name();\n> +\t\t/*\n> +\t\t * Return true and just skip registering a camera for this\n> +\t\t * entity.\n> +\t\t */\n> +\t\treturn true;\n> +\t}\n> +\n> +\tstd::unique_ptr<MaliC55CameraData> data =\n> +\t\tstd::make_unique<MaliC55CameraData>(this, link->source()->entity());\n> +\n> +\tif (data->init())\n> +\t\treturn false;\n> +\n> +\tregisterMaliCamera(std::move(data), name);\n> +\n> +\treturn true;\n> +}\n> +\n> +/*\n> + * Register a Camera for each sensor connected to the ISP through a CSI-2\n> + * receiver.\n> + *\n> + * \\todo Support more complex topologies, such as video muxes.\n> + */\n> +bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)\n> +{\n> +\tMediaEntity *csi2 = ispLink->source()->entity();\n> +\tconst MediaPad *csi2Sink = csi2->getPadByIndex(0);\n> +\n> +\tfor (MediaLink *link : csi2Sink->links()) {\n> +\t\tMediaEntity *sensor = link->source()->entity();\n> +\t\tunsigned int function = sensor->function();\n> +\n> +\t\tif (function != MEDIA_ENT_F_CAM_SENSOR)\n> +\t\t\tcontinue;\n> +\n> +\t\tstd::unique_ptr<MaliC55CameraData> data =\n> +\t\t\tstd::make_unique<MaliC55CameraData>(this, sensor);\n> +\t\tif (data->init())\n> +\t\t\treturn false;\n> +\n> +\t\t/* \\todo: Init properties and controls. */\n> +\n> +\t\tregisterMaliCamera(std::move(data), sensor->name());\n> +\t}\n> +\n> +\treturn true;\n> +}\n> +\n> +bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)\n> +{\n> +\tconst MediaPad *ispSink;\n> +\n> +\t/*\n> +\t * We search for just the ISP subdevice and the full resolution pipe.\n> +\t * The TPG and the downscale pipe are both optional blocks and may not\n> +\t * be fitted.\n> +\t */\n> +\tDeviceMatch dm(\"mali-c55\");\n> +\tdm.add(\"mali-c55 isp\");\n> +\tdm.add(\"mali-c55 resizer fr\");\n> +\tdm.add(\"mali-c55 fr\");\n> +\n> +\tmedia_ = acquireMediaDevice(enumerator, dm);\n> +\tif (!media_)\n> +\t\treturn false;\n> +\n> +\tisp_ = V4L2Subdevice::fromEntityName(media_, \"mali-c55 isp\");\n> +\tif (isp_->open() < 0)\n> +\t\treturn false;\n> +\n> +\tMaliC55Pipe *frPipe = &pipes_[MaliC55FR];\n> +\tfrPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer fr\");\n> +\tif (frPipe->resizer->open() < 0)\n> +\t\treturn false;\n> +\n> +\tfrPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 fr\");\n> +\tif (frPipe->cap->open() < 0)\n> +\t\treturn false;\n> +\n> +\tfrPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> +\n> +\tdsFitted_ = !!media_->getEntityByName(\"mali-c55 ds\");\n> +\tif (dsFitted_) {\n> +\t\tLOG(MaliC55, Debug) << \"Downscaler pipe is fitted\";\n> +\n> +\t\tMaliC55Pipe *dsPipe = &pipes_[MaliC55DS];\n> +\n> +\t\tdsPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer ds\");\n> +\t\tif (dsPipe->resizer->open() < 0)\n> +\t\t\treturn false;\n> +\n> +\t\tdsPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 ds\");\n> +\t\tif (dsPipe->cap->open() < 0)\n> +\t\t\treturn false;\n> +\n> +\t\tdsPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> +\t}\n> +\n> +\tispSink = isp_->entity()->getPadByIndex(0);\n> +\tif (!ispSink || ispSink->links().empty()) {\n> +\t\tLOG(MaliC55, Error) << \"ISP sink pad error\";\n> +\t\treturn false;\n> +\t}\n> +\n> +\t/*\n> +\t * We could have several links pointing to the ISP's sink pad, which\n> +\t * will be from entities with one of the following functions:\n> +\t *\n> +\t * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator\n> +\t * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver\n> +\t * MEDIA_ENT_F_IO_V4L - An input device\n> +\t *\n> +\t * The last one will be unsupported for now. The TPG is relatively easy,\n> +\t * we just register a Camera for it. If we have a CSI-2 receiver we need\n> +\t * to check its sink pad and register Cameras for anything connected to\n> +\t * it (probably...there are some complex situations in which that might\n> +\t * not be true but let's pretend they don't exist until we come across\n> +\t * them)\n> +\t */\n> +\tbool registered;\n> +\tfor (MediaLink *link : ispSink->links()) {\n> +\t\tunsigned int function = link->source()->entity()->function();\n> +\n> +\t\tswitch (function) {\n> +\t\tcase MEDIA_ENT_F_CAM_SENSOR:\n> +\t\t\tregistered = registerTPGCamera(link);\n> +\t\t\tif (!registered)\n> +\t\t\t\treturn registered;\n> +\n> +\t\t\tbreak;\n> +\t\tcase MEDIA_ENT_F_VID_IF_BRIDGE:\n> +\t\t\tregistered = registerSensorCamera(link);\n> +\t\t\tif (!registered)\n> +\t\t\t\treturn registered;\n> +\n> +\t\t\tbreak;\n> +\t\tcase MEDIA_ENT_F_IO_V4L:\n> +\t\t\tLOG(MaliC55, Warning) << \"Memory input not yet supported\";\n> +\t\t\tbreak;\n> +\t\tdefault:\n> +\t\t\tLOG(MaliC55, Error) << \"Unsupported entity function\";\n> +\t\t\treturn false;\n> +\t\t}\n> +\t}\n> +\n> +\treturn true;\n> +}\n> +\n> +REGISTER_PIPELINE_HANDLER(PipelineHandlerMaliC55)\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build\n> new file mode 100644\n> index 000000000000..30fd29b928d5\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/mali-c55/meson.build\n> @@ -0,0 +1,5 @@\n> +# SPDX-License-Identifier: CC0-1.0\n> +\n> +libcamera_sources += files([\n> +    'mali-c55.cpp'\n> +])\n> --\n> 2.44.0\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 C1159BD808\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 14 Mar 2024 14:12:14 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A8DC462CA2;\n\tThu, 14 Mar 2024 15:12:13 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 4184862C9C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 14 Mar 2024 15:12:11 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A18636BE;\n\tThu, 14 Mar 2024 15:11:47 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"AitWdgEX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710425507;\n\tbh=JyDYMNAHXJwTgePnthKISToUn8UvkAKs+KbU6VL6JzA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=AitWdgEXFCUl8/LTVU+uzwFj+gASnyZTZYMPSaiin4DjMUWXXSNsRLb2y9VdqZ+6v\n\tpCmQyhrSNPshp8IlVFUulzruP1md8uGXUbAOwDLytJkNg8mx3PZzJHhG7PxZCNynBj\n\tNMECef9PvhCxdJusY5e41QVrAo64dzxQaEqOcn5k=","Date":"Thu, 14 Mar 2024 15:12:07 +0100","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","Message-ID":"<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>","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>","Cc":"Nayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28961,"web_url":"https://patchwork.libcamera.org/comment/28961/","msgid":"<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","date":"2024-03-14T15:03:41","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Jacopo Mondi (2024-03-14 14:12:07)\n> Ouch\n> \n> On Thu, Mar 14, 2024 at 03:07:08PM +0100, Jacopo Mondi wrote:\n> > Add a pipeline handler for the Mali-C55 ISP.\n> >\n> > The pipeline doesn't currently support an IPA and does not run\n> > any 3 algorithm but only handles the media graph topology and\n>       ^\n>       3a of course!\n> \n> > formats/sizes configuration\n> >\n> > Co-developed-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > Acked-by: Nayden Kanchev <nayden.kanchev@arm.com>\n> > ---\n> >  meson.build                                  |    1 +\n> >  meson_options.txt                            |    1 +\n> >  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1081 ++++++++++++++++++\n> >  src/libcamera/pipeline/mali-c55/meson.build  |    5 +\n> >  4 files changed, 1088 insertions(+)\n> >  create mode 100644 src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> >  create mode 100644 src/libcamera/pipeline/mali-c55/meson.build\n> >\n> > diff --git a/meson.build b/meson.build\n> > index cb6b666a7449..740ead1be85f 100644\n> > --- a/meson.build\n> > +++ b/meson.build\n> > @@ -198,6 +198,7 @@ arch_x86 = ['x86', 'x86_64']\n> >  pipelines_support = {\n> >      'imx8-isi':     arch_arm,\n> >      'ipu3':         arch_x86,\n> > +    'mali-c55':     arch_arm,\n> >      'rkisp1':       arch_arm,\n> >      'rpi/vc4':      arch_arm,\n> >      'simple':       arch_arm,\n> > diff --git a/meson_options.txt b/meson_options.txt\n> > index 99dab96d7b21..7c4f6d3a0af6 100644\n> > --- a/meson_options.txt\n> > +++ b/meson_options.txt\n> > @@ -43,6 +43,7 @@ option('pipelines',\n> >              'auto',\n> >              'imx8-isi',\n> >              'ipu3',\n> > +            'mali-c55',\n> >              'rkisp1',\n> >              'rpi/vc4',\n> >              'simple',\n> > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > new file mode 100644\n> > index 000000000000..4508214b864d\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > @@ -0,0 +1,1081 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy\n> > + *\n> > + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <array>\n> > +#include <map>\n> > +#include <memory>\n> > +#include <set>\n> > +#include <string>\n> > +\n> > +#include <linux/media-bus-format.h>\n> > +#include <linux/media.h>\n> > +\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include <libcamera/camera.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> > +namespace {\n> > +\n> > +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> > +{\n> > +     return libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> > +            libcamera::PixelFormatInfo::ColourEncodingRAW;\n> > +}\n> > +\n> > +}; /* namespace */\n\nI think the /only/ complaint from CI/build-matrix is this tiny little\nadditional ; which should be removed.\n\nWith that, the build stays clean - and as this matches the publicly\nposted driver, I think this could already be merged.\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nOne minor comment below - but I think that can be done on top if\npreferred anyway.\n\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DEFINE_CATEGORY(MaliC55)\n> > +\n> > +const std::map<libcamera::PixelFormat, unsigned int> maliC55FmtToCode = {\n> > +     /* \\todo Support all formats supported by the driver in libcamera. */\n> > +\n> > +     { formats::RGB565, MEDIA_BUS_FMT_RGB121212_1X36 },\n> > +     { formats::RGB888, MEDIA_BUS_FMT_RGB121212_1X36 },\n> > +     { formats::YUYV, MEDIA_BUS_FMT_YUV10_1X30 },\n> > +     { formats::UYVY, MEDIA_BUS_FMT_YUV10_1X30 },\n> > +     { formats::R8, MEDIA_BUS_FMT_YUV10_1X30 },\n> > +     { formats::NV12, MEDIA_BUS_FMT_YUV10_1X30 },\n> > +     { formats::NV21, MEDIA_BUS_FMT_YUV10_1X30 },\n> > +\n> > +     /* RAW formats, FR pipe only. */\n> > +     { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },\n> > +     { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > +     { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },\n> > +     { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },\n> > +     { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n> > +     { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > +     { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n> > +     { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n> > +     { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },\n> > +     { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },\n> > +     { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },\n> > +     { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },\n> > +     { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 },\n> > +     { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 },\n> > +     { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 },\n> > +     { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 },\n> > +     { formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 },\n> > +     { formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 },\n> > +     { formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 },\n> > +     { formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 },\n> > +};\n> > +\n> > +constexpr Size kMaliC55MinSize = { 128, 128 };\n> > +constexpr Size kMaliC55MaxSize = { 8192, 8192 };\n> > +constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36;\n> > +\n> > +class MaliC55CameraData : public Camera::Private\n> > +{\n> > +public:\n> > +     MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity)\n> > +             : Camera::Private(pipe), entity_(entity)\n> > +     {\n> > +     }\n> > +\n> > +     int init();\n> > +\n> > +     /* Deflect these functionalities to either TPG or CameraSensor. */\n> > +     const std::vector<unsigned int> mbusCodes() const;\n> > +     const std::vector<Size> sizes(unsigned int mbusCode) const;\n> > +     const Size resolution() const;\n> > +\n> > +     PixelFormat bestRawFormat() const;\n> > +\n> > +     PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;\n> > +     Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;\n> > +\n> > +     std::unique_ptr<CameraSensor> sensor_;\n> > +\n> > +     MediaEntity *entity_;\n> > +     std::unique_ptr<V4L2Subdevice> csi_;\n> > +     std::unique_ptr<V4L2Subdevice> sd_;\n> > +     Stream frStream_;\n> > +     Stream dsStream_;\n> > +\n> > +private:\n> > +     void initTPGData();\n> > +\n> > +     std::string id_;\n> > +     std::vector<unsigned int> tpgCodes_;\n> > +     std::vector<Size> tpgSizes_;\n> > +     Size tpgResolution_;\n> > +};\n> > +\n> > +int MaliC55CameraData::init()\n> > +{\n> > +     int ret;\n> > +\n> > +     sd_ = std::make_unique<V4L2Subdevice>(entity_);\n> > +     ret = sd_->open();\n> > +     if (ret) {\n> > +             LOG(MaliC55, Error) << \"Failed to open sensor subdevice\";\n> > +             return ret;\n> > +     }\n> > +\n> > +     /* If this camera is created from TPG, we return here. */\n> > +     if (entity_->name() == \"mali-c55 tpg\") {\n> > +             initTPGData();\n> > +             return 0;\n> > +     }\n> > +\n> > +     /*\n> > +      * Register a CameraSensor if we connect to a sensor and create\n> > +      * an entity for the connected CSI-2 receiver.\n> > +      */\n> > +     sensor_ = std::make_unique<CameraSensor>(entity_);\n> > +     ret = sensor_->init();\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     const MediaPad *sourcePad = entity_->getPadByIndex(0);\n> > +     MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();\n> > +\n> > +     csi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n> > +     if (csi_->open()) {\n> > +             LOG(MaliC55, Error) << \"Failed to open CSI-2 subdevice\";\n> > +             return false;\n> > +     }\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +void MaliC55CameraData::initTPGData()\n> > +{\n> > +     /* Replicate the CameraSensor implementation for TPG. */\n> > +     V4L2Subdevice::Formats formats = sd_->formats(0);\n> > +     if (formats.empty())\n> > +             return;\n> > +\n> > +     tpgCodes_ = utils::map_keys(formats);\n> > +     std::sort(tpgCodes_.begin(), tpgCodes_.end());\n> > +\n> > +     for (const auto &format : formats) {\n> > +             const std::vector<SizeRange> &ranges = format.second;\n> > +             std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes_),\n> > +                            [](const SizeRange &range) { return range.max; });\n> > +     }\n> > +\n> > +     tpgResolution_ = tpgSizes_.back();\n> > +}\n> > +\n> > +const std::vector<unsigned int> MaliC55CameraData::mbusCodes() const\n> > +{\n> > +     if (sensor_)\n> > +             return sensor_->mbusCodes();\n> > +\n> > +     return tpgCodes_;\n> > +}\n> > +\n> > +const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const\n> > +{\n> > +     if (sensor_)\n> > +             return sensor_->sizes(mbusCode);\n> > +\n> > +     V4L2Subdevice::Formats formats = sd_->formats(0);\n> > +     if (formats.empty())\n> > +             return {};\n> > +\n> > +     std::vector<Size> sizes;\n> > +     const auto &format = formats.find(mbusCode);\n> > +     if (format == formats.end())\n> > +             return {};\n> > +\n> > +     const std::vector<SizeRange> &ranges = format->second;\n> > +     std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),\n> > +                    [](const SizeRange &range) { return range.max; });\n> > +\n> > +     std::sort(sizes.begin(), sizes.end());\n> > +\n> > +     return sizes;\n> > +}\n> > +\n> > +const Size MaliC55CameraData::resolution() const\n> > +{\n> > +     if (sensor_)\n> > +             return sensor_->resolution();\n> > +\n> > +     return tpgResolution_;\n> > +}\n> > +\n> > +PixelFormat MaliC55CameraData::bestRawFormat() const\n> > +{\n> > +     unsigned int bitDepth = 0;\n> > +     PixelFormat rawFormat;\n> > +\n> > +     /*\n> > +      * Iterate over all the supported PixelFormat and find the one\n> > +      * supported by the camera with the largest bitdepth.\n> > +      */\n> > +     for (const auto &maliFormat : maliC55FmtToCode) {\n> > +             PixelFormat pixFmt = maliFormat.first;\n> > +             if (!isFormatRaw(pixFmt))\n> > +                     continue;\n> > +\n> > +             unsigned int rawCode = maliFormat.second;\n> > +             const auto rawSizes = sizes(rawCode);\n> > +             if (rawSizes.empty())\n> > +                     continue;\n> > +\n> > +             BayerFormat bayer = BayerFormat::fromMbusCode(rawCode);\n> > +             if (bayer.bitDepth > bitDepth) {\n> > +                     bitDepth = bayer.bitDepth;\n> > +                     rawFormat = pixFmt;\n> > +             }\n> > +     }\n> > +\n> > +     return rawFormat;\n> > +}\n> > +\n> > +/*\n> > + * Make sure the provided raw pixel format is supported and adjust it to\n> > + * one of the supported ones if it's not.\n> > + */\n> > +PixelFormat MaliC55CameraData::adjustRawFormat(const PixelFormat &rawFmt) const\n> > +{\n> > +     /* Make sure the provided raw format is supported by the pipeline. */\n> > +     auto it = maliC55FmtToCode.find(rawFmt);\n> > +     if (it == maliC55FmtToCode.end())\n> > +             return bestRawFormat();\n> > +\n> > +     /* Now make sure the RAW mbus code is supported by the image source. */\n> > +     unsigned int rawCode = it->second;\n> > +     const auto rawSizes = sizes(rawCode);\n> > +     if (rawSizes.empty())\n> > +             return bestRawFormat();\n> > +\n> > +     return rawFmt;\n> > +}\n> > +\n> > +Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &rawSize) const\n> > +{\n> > +     /* Just make sure the format is supported. */\n> > +     auto it = maliC55FmtToCode.find(rawFmt);\n> > +     if (it == maliC55FmtToCode.end())\n> > +             return {};\n> > +\n> > +     /* Check if the size is natively supported. */\n> > +     unsigned int rawCode = it->second;\n> > +     const auto rawSizes = sizes(rawCode);\n> > +     auto sizeIt = std::find(rawSizes.begin(), rawSizes.end(), rawSize);\n> > +     if (sizeIt != rawSizes.end())\n> > +             return rawSize;\n> > +\n> > +     /* Or adjust it to the closest supported size. */\n> > +     uint16_t distance = std::numeric_limits<uint16_t>::max();\n> > +     Size bestSize;\n> > +     for (const Size &size : rawSizes) {\n> > +             uint16_t dist = std::abs(static_cast<int>(rawSize.width) -\n> > +                                      static_cast<int>(size.width)) +\n> > +                             std::abs(static_cast<int>(rawSize.height) -\n> > +                                      static_cast<int>(size.height));\n> > +             if (dist < distance) {\n> > +                     dist = distance;\n> > +                     bestSize = size;\n> > +             }\n> > +     }\n> > +\n> > +     return bestSize;\n> > +}\n> > +\n> > +class MaliC55CameraConfiguration : public CameraConfiguration\n> > +{\n> > +public:\n> > +     MaliC55CameraConfiguration(MaliC55CameraData *data)\n> > +             : CameraConfiguration(), data_(data)\n> > +     {\n> > +     }\n> > +\n> > +     Status validate() override;\n> > +\n> > +     V4L2SubdeviceFormat sensorFormat_;\n> > +\n> > +private:\n> > +     static constexpr unsigned int kMaxStreams = 2;\n> > +\n> > +     const MaliC55CameraData *data_;\n> > +};\n> > +\n> > +CameraConfiguration::Status MaliC55CameraConfiguration::validate()\n> > +{\n> > +     Status status = Valid;\n> > +\n> > +     if (config_.empty())\n> > +             return Invalid;\n> > +\n> > +     /* Only 2 streams available. */\n> > +     if (config_.size() > kMaxStreams) {\n> > +             config_.resize(kMaxStreams);\n> > +             status = Adjusted;\n> > +     }\n> > +\n> > +     bool frPipeAvailable = true;\n> > +     StreamConfiguration *rawConfig = nullptr;\n> > +     for (StreamConfiguration &config : config_) {\n> > +             if (!isFormatRaw(config.pixelFormat))\n> > +                     continue;\n> > +\n> > +             if (rawConfig) {\n> > +                     LOG(MaliC55, Error)\n> > +                             << \"Only a single RAW stream is supported\";\n> > +                     return Invalid;\n> > +             }\n> > +\n> > +             rawConfig = &config;\n> > +     }\n> > +\n> > +     Size maxSize = kMaliC55MaxSize;\n> > +     if (rawConfig) {\n> > +             /*\n> > +              * \\todo Take into account the Bayer components ordering once\n> > +              * we support rotations.\n> > +              */\n> > +             PixelFormat rawFormat =\n> > +                     data_->adjustRawFormat(rawConfig->pixelFormat);\n> > +             if (rawFormat != rawConfig->pixelFormat) {\n> > +                     LOG(MaliC55, Debug)\n> > +                             << \"RAW format adjusted to \" << rawFormat;\n> > +                     rawConfig->pixelFormat = rawFormat;\n> > +                     status = Adjusted;\n> > +             }\n> > +\n> > +             Size rawSize =\n> > +                     data_->adjustRawSizes(rawFormat, rawConfig->size);\n> > +             if (rawSize != rawConfig->size) {\n> > +                     LOG(MaliC55, Debug)\n> > +                             << \"RAW sizes adjusted to \" << rawSize;\n> > +                     rawConfig->size = rawSize;\n> > +                     status = Adjusted;\n> > +             }\n> > +\n> > +             maxSize = rawSize;\n> > +\n> > +             rawConfig->setStream(const_cast<Stream *>(&data_->frStream_));\n> > +             frPipeAvailable = false;\n> > +     }\n> > +\n> > +     /* Adjust processed streams. */\n> > +     Size maxYuvSize;\n> > +     for (StreamConfiguration &config : config_) {\n> > +             if (isFormatRaw(config.pixelFormat))\n> > +                     continue;\n> > +\n> > +             /* Adjust format and size for processed streams. */\n> > +             const auto it = maliC55FmtToCode.find(config.pixelFormat);\n> > +             if (it == maliC55FmtToCode.end()) {\n> > +                     LOG(MaliC55, Debug)\n> > +                             << \"Format adjusted to \" << formats::RGB565;\n> > +                     config.pixelFormat = formats::RGB565;\n> > +                     status = Adjusted;\n> > +             }\n> > +\n> > +             Size size = std::clamp(config.size, kMaliC55MinSize, maxSize);\n> > +             if (size != config.size) {\n> > +                     LOG(MaliC55, Debug)\n> > +                             << \"Size adjusted to \" << size;\n> > +                     config.size = size;\n> > +                     status = Adjusted;\n> > +             }\n> > +\n> > +             if (maxYuvSize < size)\n> > +                     maxYuvSize = size;\n> > +\n> > +             if (frPipeAvailable) {\n> > +                     config.setStream(const_cast<Stream *>(&data_->frStream_));\n> > +                     frPipeAvailable = false;\n> > +             } else {\n> > +                     config.setStream(const_cast<Stream *>(&data_->dsStream_));\n> > +             }\n> > +     }\n> > +\n> > +     /* Compute the sensor format. */\n> > +\n> > +     /* If there's a RAW config, sensor configuration follows it. */\n> > +     if (rawConfig) {\n> > +             const auto it = maliC55FmtToCode.find(rawConfig->pixelFormat);\n> > +             sensorFormat_.mbus_code = it->second;\n> > +             sensorFormat_.size = rawConfig->size;\n> > +\n> > +             return status;\n> > +     }\n> > +\n> > +     /* If there's no RAW config, compute the sensor configuration here. */\n> > +     PixelFormat rawFormat = data_->bestRawFormat();\n> > +     const auto it = maliC55FmtToCode.find(rawFormat);\n> > +     sensorFormat_.mbus_code = it->second;\n> > +\n> > +     uint16_t distance = std::numeric_limits<uint16_t>::max();\n> > +     const auto sizes = data_->sizes(it->second);\n> > +     Size bestSize;\n> > +     for (const auto &size : sizes) {\n> > +             /* Skip sensor sizes that are smaller than the max YUV size. */\n> > +             if (maxYuvSize.width > size.width ||\n> > +                 maxYuvSize.height > size.height)\n> > +                     continue;\n> > +\n> > +             uint16_t dist = std::abs(static_cast<int>(maxYuvSize.width) -\n> > +                                      static_cast<int>(size.width)) +\n> > +                             std::abs(static_cast<int>(maxYuvSize.height) -\n> > +                                      static_cast<int>(size.height));\n> > +             if (dist < distance) {\n> > +                     dist = distance;\n> > +                     bestSize = size;\n> > +             }\n> > +     }\n> > +     sensorFormat_.size = bestSize;\n> > +\n> > +     LOG(MaliC55, Debug) << \"Computed sensor configuration \" << sensorFormat_;\n> > +\n> > +     return status;\n> > +}\n> > +\n> > +class PipelineHandlerMaliC55 : public PipelineHandler\n> > +{\n> > +public:\n> > +     PipelineHandlerMaliC55(CameraManager *manager);\n> > +\n> > +     std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n> > +                                                                Span<const StreamRole> roles) override;\n> > +     int configure(Camera *camera, CameraConfiguration *config) override;\n> > +\n> > +     int exportFrameBuffers(Camera *camera, Stream *stream,\n> > +                            std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n> > +\n> > +     int start(Camera *camera, const ControlList *controls) override;\n> > +     void stopDevice(Camera *camera) override;\n> > +\n> > +     int queueRequestDevice(Camera *camera, Request *request) override;\n> > +\n> > +     void bufferReady(FrameBuffer *buffer);\n> > +\n> > +     bool match(DeviceEnumerator *enumerator) override;\n> > +\n> > +private:\n> > +     struct MaliC55Pipe {\n> > +             std::unique_ptr<V4L2Subdevice> resizer;\n> > +             std::unique_ptr<V4L2VideoDevice> cap;\n> > +             Stream *stream;\n> > +     };\n> > +\n> > +     enum {\n> > +             MaliC55FR,\n> > +             MaliC55DS,\n> > +             MaliC55NumPipes,\n> > +     };\n> > +\n> > +     MaliC55CameraData *cameraData(Camera *camera)\n> > +     {\n> > +             return static_cast<MaliC55CameraData *>(camera->_d());\n> > +     }\n> > +\n> > +     MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, Stream *stream)\n> > +     {\n> > +             if (stream == &data->frStream_)\n> > +                     return &pipes_[MaliC55FR];\n> > +             else if (stream == &data->dsStream_)\n> > +                     return &pipes_[MaliC55DS];\n> > +             else\n> > +                     LOG(MaliC55, Fatal) << \"Stream \" << stream << \" not valid\";\n> > +             return nullptr;\n> > +     }\n> > +\n> > +     MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, const Stream *stream)\n> > +     {\n> > +             return pipeFromStream(data, const_cast<Stream *>(stream));\n> > +     }\n> > +\n> > +     void resetPipes()\n> > +     {\n> > +             for (MaliC55Pipe &pipe : pipes_)\n> > +                     pipe.stream = nullptr;\n> > +     }\n> > +\n> > +     int configureRawStream(MaliC55CameraData *data,\n> > +                            const StreamConfiguration &config,\n> > +                            V4L2SubdeviceFormat &subdevFormat);\n> > +     int configureProcessedStream(MaliC55CameraData *data,\n> > +                                  const StreamConfiguration &config,\n> > +                                  V4L2SubdeviceFormat &subdevFormat);\n> > +\n> > +     void registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> > +                             const std::string &name);\n> > +     bool registerTPGCamera(MediaLink *link);\n> > +     bool registerSensorCamera(MediaLink *link);\n> > +\n> > +     MediaDevice *media_;\n> > +     std::unique_ptr<V4L2Subdevice> isp_;\n> > +\n> > +     std::array<MaliC55Pipe, MaliC55NumPipes> pipes_;\n> > +\n> > +     bool dsFitted_;\n> > +};\n> > +\n> > +PipelineHandlerMaliC55::PipelineHandlerMaliC55(CameraManager *manager)\n> > +     : PipelineHandler(manager), dsFitted_(true)\n> > +{\n> > +}\n> > +\n> > +std::unique_ptr<CameraConfiguration>\n> > +PipelineHandlerMaliC55::generateConfiguration(Camera *camera,\n> > +                                           Span<const StreamRole> roles)\n> > +{\n> > +     MaliC55CameraData *data = cameraData(camera);\n> > +     std::unique_ptr<CameraConfiguration> config =\n> > +             std::make_unique<MaliC55CameraConfiguration>(data);\n> > +     bool frPipeAvailable = true;\n> > +\n> > +     if (roles.empty())\n> > +             return config;\n> > +\n> > +     /* Check if one stream is RAW to reserve the FR pipe for it. */\n> > +     if (std::find_if(roles.begin(), roles.end(),\n> > +                      [](const StreamRole &role) {\n> > +                              return role == StreamRole::Raw;\n> > +                      }) != roles.end())\n> > +             frPipeAvailable = false;\n> > +\n> > +     for (const StreamRole &role : roles) {\n> > +             struct MaliC55Pipe *pipe;\n> > +\n> > +             /* Assign pipe for this role. */\n> > +             if (role == StreamRole::Raw) {\n> > +                     pipe = &pipes_[MaliC55FR];\n> > +             } else {\n> > +                     if (frPipeAvailable) {\n> > +                             pipe = &pipes_[MaliC55FR];\n> > +                             frPipeAvailable = false;\n> > +                     } else {\n> > +                             pipe = &pipes_[MaliC55DS];\n> > +                     }\n> > +             }\n> > +\n> > +             Size size = std::min(Size{ 1920, 1080 }, data->resolution());\n> > +             PixelFormat pixelFormat;\n> > +\n> > +             switch (role) {\n> > +             case StreamRole::StillCapture:\n> > +                     size = data->resolution();\n> > +                     /* fall-through */\n> > +             case StreamRole::VideoRecording:\n> > +                     pixelFormat = formats::NV12;\n> > +                     break;\n> > +\n> > +             case StreamRole::Viewfinder:\n> > +                     pixelFormat = formats::RGB565;\n> > +                     break;\n> > +\n> > +             case StreamRole::Raw:\n> > +                     pixelFormat = data->bestRawFormat();\n> > +                     if (!pixelFormat.isValid()) {\n> > +                             LOG(MaliC55, Error)\n> > +                                     << \"Camera does not support RAW formats\";\n> > +                             continue;\n> > +                     }\n> > +\n> > +                     size = data->resolution();\n> > +                     break;\n> > +\n> > +             default:\n> > +                     LOG(MaliC55, Error)\n> > +                             << \"Requested stream role not supported: \" << role;\n> > +                     return config;\n\nI wonder if here the alternative is to simply call 'continue' and ignore\nthat role. The other supported roles would then still generate a\nStreamConfig, and what gets returned would be a best effort match which\nis what I believe the function is intended to achieve.\n\nBut I think that can already be done on top/later - and to me - when\nthis patch passes CI could be merged.\n\n--\nKieran\n\n\n> > +             }\n> > +\n> > +             std::map<PixelFormat, std::vector<SizeRange>> formats;\n> > +             for (const auto &maliFormat : maliC55FmtToCode) {\n> > +                     PixelFormat pixFmt = maliFormat.first;\n> > +                     bool isRaw = isFormatRaw(pixFmt);\n> > +\n> > +                     /* RAW formats are only supported on the FR pipe. */\n> > +                     if (pipe != &pipes_[MaliC55FR] && isRaw)\n> > +                             continue;\n> > +\n> > +                     if (isRaw) {\n> > +                             /* Make sure the mbus code is supported. */\n> > +                             unsigned int rawCode = maliFormat.second;\n> > +                             const auto sizes = data->sizes(rawCode);\n> > +                             if (sizes.empty())\n> > +                                     continue;\n> > +\n> > +                             /* And list all sizes the sensor can produce. */\n> > +                             std::vector<SizeRange> sizeRanges;\n> > +                             std::transform(sizes.begin(), sizes.end(),\n> > +                                            std::back_inserter(sizeRanges),\n> > +                                            [](const Size &s) {\n> > +                                                    return SizeRange(s);\n> > +                                            });\n> > +\n> > +                             formats[pixFmt] = sizeRanges;\n> > +                     } else {\n> > +                             /* Processed formats are always available. */\n> > +                             Size maxSize = std::min(kMaliC55MaxSize,\n> > +                                                     data->resolution());\n> > +                             formats[pixFmt] = { kMaliC55MinSize, maxSize };\n> > +                     }\n> > +             }\n> > +\n> > +             StreamFormats streamFormats(formats);\n> > +             StreamConfiguration cfg(streamFormats);\n> > +             cfg.pixelFormat = pixelFormat;\n> > +             cfg.bufferCount = 4;\n> > +             cfg.size = size;\n> > +\n> > +             config->addConfiguration(cfg);\n> > +     }\n> > +\n> > +     if (config->validate() == CameraConfiguration::Invalid)\n> > +             return {};\n> > +\n> > +     return config;\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::configureRawStream(MaliC55CameraData *data,\n> > +                                            const StreamConfiguration &config,\n> > +                                            V4L2SubdeviceFormat &subdevFormat)\n> > +{\n> > +     Stream *stream = config.stream();\n> > +     MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > +\n> > +     if (pipe != &pipes_[MaliC55FR]) {\n> > +             LOG(MaliC55, Fatal) << \"Only the FR pipe supports RAW capture.\";\n> > +             return -EINVAL;\n> > +     }\n> > +\n> > +     /* Enable the debayer route to set fixed internal format on pad #0. */\n> > +     V4L2Subdevice::Routing routing = {};\n> > +     struct v4l2_subdev_route route = {\n> > +             .sink_pad = 0,\n> > +             .sink_stream = 0,\n> > +             .source_pad = 1,\n> > +             .source_stream = 0,\n> > +             .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> > +             .reserved = {}\n> > +     };\n> > +     routing.push_back(route);\n> > +\n> > +     int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     unsigned int rawCode = subdevFormat.mbus_code;\n> > +     subdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> > +     ret = pipe->resizer->setFormat(0, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     /* Enable the bypass route and apply RAW formats there. */\n> > +     routing.clear();\n> > +\n> > +     route.sink_pad = 2;\n> > +     routing.push_back(route);\n> > +     ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     subdevFormat.mbus_code = rawCode;\n> > +     ret = pipe->resizer->setFormat(2, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     ret = pipe->resizer->setFormat(1, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::configureProcessedStream(MaliC55CameraData *data,\n> > +                                                  const StreamConfiguration &config,\n> > +                                                  V4L2SubdeviceFormat &subdevFormat)\n> > +{\n> > +     Stream *stream = config.stream();\n> > +     MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > +\n> > +     /* Enable the debayer route on the resizer pipe. */\n> > +     V4L2Subdevice::Routing routing = {};\n> > +     struct v4l2_subdev_route route = {\n> > +             .sink_pad = 0,\n> > +             .sink_stream = 0,\n> > +             .source_pad = 1,\n> > +             .source_stream = 0,\n> > +             .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> > +             .reserved = {}\n> > +     };\n> > +     routing.push_back(route);\n> > +\n> > +     int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     subdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> > +     ret = pipe->resizer->setFormat(0, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     /* \\todo Configure the resizer crop/compose rectangles. */\n> > +     Rectangle ispCrop = { 0, 0, config.size };\n> > +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &ispCrop);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     subdevFormat.mbus_code = maliC55FmtToCode.find(config.pixelFormat)->second;\n> > +     return pipe->resizer->setFormat(1, &subdevFormat);\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::configure(Camera *camera,\n> > +                                   CameraConfiguration *config)\n> > +{\n> > +     resetPipes();\n> > +\n> > +     int ret = media_->disableLinks();\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     /* Link the graph depending if we are operating the TPG or a sensor. */\n> > +     MaliC55CameraData *data = cameraData(camera);\n> > +     if (data->csi_) {\n> > +             const MediaEntity *csiEntity = data->csi_->entity();\n> > +             ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n> > +     } else {\n> > +             ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true);\n> > +     }\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     MaliC55CameraConfiguration *maliConfig =\n> > +             static_cast<MaliC55CameraConfiguration *>(config);\n> > +     V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_;\n> > +     ret = data->sd_->getFormat(0, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     if (data->csi_) {\n> > +             ret = data->csi_->setFormat(0, &subdevFormat);\n> > +             if (ret)\n> > +                     return ret;\n> > +\n> > +             ret = data->csi_->setFormat(1, &subdevFormat);\n> > +             if (ret)\n> > +                     return ret;\n> > +     }\n> > +\n> > +     /*\n> > +      * Propagate the format to the ISP sink pad and configure the input\n> > +      * crop rectangle (no crop at the moment).\n> > +      *\n> > +      * \\todo Configure the CSI-2 receiver.\n> > +      */\n> > +     ret = isp_->setFormat(0, &subdevFormat);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     Rectangle ispCrop(0, 0, subdevFormat.size);\n> > +     ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> > +     if (ret)\n> > +             return ret;\n> > +\n> > +     /*\n> > +      * Configure the resizer: fixed format the sink pad; use the media\n> > +      * bus code associated with the desired capture format on the source\n> > +      * pad.\n> > +      *\n> > +      * Configure the crop and compose rectangles to match the desired\n> > +      * stream output size\n> > +      *\n> > +      * \\todo Make the crop/scaler configurable\n> > +      */\n> > +     for (const StreamConfiguration &streamConfig : *config) {\n> > +             Stream *stream = streamConfig.stream();\n> > +             MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > +\n> > +             if (isFormatRaw(streamConfig.pixelFormat))\n> > +                     ret = configureRawStream(data, streamConfig, subdevFormat);\n> > +             else\n> > +                     ret = configureProcessedStream(data, streamConfig, subdevFormat);\n> > +             if (ret) {\n> > +                     LOG(MaliC55, Error) << \"Failed to configure pipeline\";\n> > +                     return ret;\n> > +             }\n> > +\n> > +             /* Now apply the pixel format and size to the capture device. */\n> > +             V4L2DeviceFormat captureFormat;\n> > +             captureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);\n> > +             captureFormat.size = streamConfig.size;\n> > +\n> > +             ret = pipe->cap->setFormat(&captureFormat);\n> > +             if (ret)\n> > +                     return ret;\n> > +\n> > +             pipe->stream = stream;\n> > +     }\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream,\n> > +                                            std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> > +{\n> > +     MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> > +     unsigned int count = stream->configuration().bufferCount;\n> > +\n> > +     return pipe->cap->exportBuffers(count, buffers);\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::start([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList *controls)\n> > +{\n> > +     for (MaliC55Pipe &pipe : pipes_) {\n> > +             if (!pipe.stream)\n> > +                     continue;\n> > +\n> > +             Stream *stream = pipe.stream;\n> > +\n> > +             int ret = pipe.cap->importBuffers(stream->configuration().bufferCount);\n> > +             if (ret) {\n> > +                     LOG(MaliC55, Error) << \"Failed to import buffers\";\n> > +                     return ret;\n> > +             }\n> > +\n> > +             ret = pipe.cap->streamOn();\n> > +             if (ret) {\n> > +                     LOG(MaliC55, Error) << \"Failed to start stream\";\n> > +                     return ret;\n> > +             }\n> > +     }\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)\n> > +{\n> > +     for (MaliC55Pipe &pipe : pipes_) {\n> > +             if (!pipe.stream)\n> > +                     continue;\n> > +\n> > +             pipe.cap->streamOff();\n> > +             pipe.cap->releaseBuffers();\n> > +     }\n> > +}\n> > +\n> > +int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request)\n> > +{\n> > +     int ret;\n> > +\n> > +     for (auto &[stream, buffer] : request->buffers()) {\n> > +             MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> > +\n> > +             ret = pipe->cap->queueBuffer(buffer);\n> > +             if (ret)\n> > +                     return ret;\n> > +     }\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +void PipelineHandlerMaliC55::bufferReady(FrameBuffer *buffer)\n> > +{\n> > +     Request *request = buffer->request();\n> > +\n> > +     completeBuffer(request, buffer);\n> > +\n> > +     if (request->hasPendingBuffers())\n> > +             return;\n> > +\n> > +     completeRequest(request);\n> > +}\n> > +\n> > +void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> > +                                             const std::string &name)\n> > +{\n> > +     std::set<Stream *> streams{ &data->frStream_ };\n> > +     if (dsFitted_)\n> > +             streams.insert(&data->dsStream_);\n> > +\n> > +     std::shared_ptr<Camera> camera = Camera::create(std::move(data),\n> > +                                                     name, streams);\n> > +     registerCamera(std::move(camera));\n> > +}\n> > +\n> > +/*\n> > + * The only camera we support through direct connection to the ISP is the\n> > + * Mali-C55 TPG. Check we have that and warn if not.\n> > + */\n> > +bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)\n> > +{\n> > +     const std::string &name = link->source()->entity()->name();\n> > +     if (name != \"mali-c55 tpg\") {\n> > +             LOG(MaliC55, Warning) << \"Unsupported direct connection to \"\n> > +                                   << link->source()->entity()->name();\n> > +             /*\n> > +              * Return true and just skip registering a camera for this\n> > +              * entity.\n> > +              */\n> > +             return true;\n> > +     }\n> > +\n> > +     std::unique_ptr<MaliC55CameraData> data =\n> > +             std::make_unique<MaliC55CameraData>(this, link->source()->entity());\n> > +\n> > +     if (data->init())\n> > +             return false;\n> > +\n> > +     registerMaliCamera(std::move(data), name);\n> > +\n> > +     return true;\n> > +}\n> > +\n> > +/*\n> > + * Register a Camera for each sensor connected to the ISP through a CSI-2\n> > + * receiver.\n> > + *\n> > + * \\todo Support more complex topologies, such as video muxes.\n> > + */\n> > +bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)\n> > +{\n> > +     MediaEntity *csi2 = ispLink->source()->entity();\n> > +     const MediaPad *csi2Sink = csi2->getPadByIndex(0);\n> > +\n> > +     for (MediaLink *link : csi2Sink->links()) {\n> > +             MediaEntity *sensor = link->source()->entity();\n> > +             unsigned int function = sensor->function();\n> > +\n> > +             if (function != MEDIA_ENT_F_CAM_SENSOR)\n> > +                     continue;\n> > +\n> > +             std::unique_ptr<MaliC55CameraData> data =\n> > +                     std::make_unique<MaliC55CameraData>(this, sensor);\n> > +             if (data->init())\n> > +                     return false;\n> > +\n> > +             /* \\todo: Init properties and controls. */\n> > +\n> > +             registerMaliCamera(std::move(data), sensor->name());\n> > +     }\n> > +\n> > +     return true;\n> > +}\n> > +\n> > +bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)\n> > +{\n> > +     const MediaPad *ispSink;\n> > +\n> > +     /*\n> > +      * We search for just the ISP subdevice and the full resolution pipe.\n> > +      * The TPG and the downscale pipe are both optional blocks and may not\n> > +      * be fitted.\n> > +      */\n> > +     DeviceMatch dm(\"mali-c55\");\n> > +     dm.add(\"mali-c55 isp\");\n> > +     dm.add(\"mali-c55 resizer fr\");\n> > +     dm.add(\"mali-c55 fr\");\n> > +\n> > +     media_ = acquireMediaDevice(enumerator, dm);\n> > +     if (!media_)\n> > +             return false;\n> > +\n> > +     isp_ = V4L2Subdevice::fromEntityName(media_, \"mali-c55 isp\");\n> > +     if (isp_->open() < 0)\n> > +             return false;\n> > +\n> > +     MaliC55Pipe *frPipe = &pipes_[MaliC55FR];\n> > +     frPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer fr\");\n> > +     if (frPipe->resizer->open() < 0)\n> > +             return false;\n> > +\n> > +     frPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 fr\");\n> > +     if (frPipe->cap->open() < 0)\n> > +             return false;\n> > +\n> > +     frPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> > +\n> > +     dsFitted_ = !!media_->getEntityByName(\"mali-c55 ds\");\n> > +     if (dsFitted_) {\n> > +             LOG(MaliC55, Debug) << \"Downscaler pipe is fitted\";\n> > +\n> > +             MaliC55Pipe *dsPipe = &pipes_[MaliC55DS];\n> > +\n> > +             dsPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer ds\");\n> > +             if (dsPipe->resizer->open() < 0)\n> > +                     return false;\n> > +\n> > +             dsPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 ds\");\n> > +             if (dsPipe->cap->open() < 0)\n> > +                     return false;\n> > +\n> > +             dsPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> > +     }\n> > +\n> > +     ispSink = isp_->entity()->getPadByIndex(0);\n> > +     if (!ispSink || ispSink->links().empty()) {\n> > +             LOG(MaliC55, Error) << \"ISP sink pad error\";\n> > +             return false;\n> > +     }\n> > +\n> > +     /*\n> > +      * We could have several links pointing to the ISP's sink pad, which\n> > +      * will be from entities with one of the following functions:\n> > +      *\n> > +      * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator\n> > +      * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver\n> > +      * MEDIA_ENT_F_IO_V4L - An input device\n> > +      *\n> > +      * The last one will be unsupported for now. The TPG is relatively easy,\n> > +      * we just register a Camera for it. If we have a CSI-2 receiver we need\n> > +      * to check its sink pad and register Cameras for anything connected to\n> > +      * it (probably...there are some complex situations in which that might\n> > +      * not be true but let's pretend they don't exist until we come across\n> > +      * them)\n> > +      */\n> > +     bool registered;\n> > +     for (MediaLink *link : ispSink->links()) {\n> > +             unsigned int function = link->source()->entity()->function();\n> > +\n> > +             switch (function) {\n> > +             case MEDIA_ENT_F_CAM_SENSOR:\n> > +                     registered = registerTPGCamera(link);\n> > +                     if (!registered)\n> > +                             return registered;\n> > +\n> > +                     break;\n> > +             case MEDIA_ENT_F_VID_IF_BRIDGE:\n> > +                     registered = registerSensorCamera(link);\n> > +                     if (!registered)\n> > +                             return registered;\n> > +\n> > +                     break;\n> > +             case MEDIA_ENT_F_IO_V4L:\n> > +                     LOG(MaliC55, Warning) << \"Memory input not yet supported\";\n> > +                     break;\n> > +             default:\n> > +                     LOG(MaliC55, Error) << \"Unsupported entity function\";\n> > +                     return false;\n> > +             }\n> > +     }\n> > +\n> > +     return true;\n> > +}\n> > +\n> > +REGISTER_PIPELINE_HANDLER(PipelineHandlerMaliC55)\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build\n> > new file mode 100644\n> > index 000000000000..30fd29b928d5\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/mali-c55/meson.build\n> > @@ -0,0 +1,5 @@\n> > +# SPDX-License-Identifier: CC0-1.0\n> > +\n> > +libcamera_sources += files([\n> > +    'mali-c55.cpp'\n> > +])\n> > --\n> > 2.44.0\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 CB677BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 14 Mar 2024 15:03:47 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C305362C85;\n\tThu, 14 Mar 2024 16:03:46 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CC78B61C65\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 14 Mar 2024 16:03:45 +0100 (CET)","from pendragon.ideasonboard.com\n\t(aztw-30-b2-v4wan-166917-cust845.vm26.cable.virginm.net\n\t[82.37.23.78])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 8225FE4;\n\tThu, 14 Mar 2024 16:03:21 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"OyBxQsFP\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710428601;\n\tbh=t9LGX8qTSKqenKPpOlmEOjYXnW5g2slvHM8QD6ceR6k=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=OyBxQsFPK0NotCYXEcgxua5qdtULNrPr7uEe+WaAVZ9f9vs1x7tI96AinQehU+f8T\n\t1LLd3ugCtm6oWaoTLwigvea3jFM8DVhPWU7/wHvzTGdYj2FbQIQDfl5rpZreYoZ3jC\n\tvjxms0KAiUtdK0JqJLy+bZ6VSHAO6gpP3r15Rphg=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>\n\t<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Date":"Thu, 14 Mar 2024 15:03:41 +0000","Message-ID":"<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","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>","Cc":"Nayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28962,"web_url":"https://patchwork.libcamera.org/comment/28962/","msgid":"<171042871332.252503.18143989750356559422@ping.linuxembedded.co.uk>","date":"2024-03-14T15:05:13","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Kieran Bingham (2024-03-14 15:03:41)\n> Quoting Jacopo Mondi (2024-03-14 14:12:07)\n> > Ouch\n> > \n> > On Thu, Mar 14, 2024 at 03:07:08PM +0100, Jacopo Mondi wrote:\n> > > Add a pipeline handler for the Mali-C55 ISP.\n> > >\n> > > The pipeline doesn't currently support an IPA and does not run\n> > > any 3 algorithm but only handles the media graph topology and\n> >       ^\n> >       3a of course!\n> > \n> > > formats/sizes configuration\n> > >\n> > > Co-developed-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > > Acked-by: Nayden Kanchev <nayden.kanchev@arm.com>\n> > > ---\n> > >  meson.build                                  |    1 +\n> > >  meson_options.txt                            |    1 +\n> > >  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1081 ++++++++++++++++++\n> > >  src/libcamera/pipeline/mali-c55/meson.build  |    5 +\n> > >  4 files changed, 1088 insertions(+)\n> > >  create mode 100644 src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > >  create mode 100644 src/libcamera/pipeline/mali-c55/meson.build\n> > >\n> > > diff --git a/meson.build b/meson.build\n> > > index cb6b666a7449..740ead1be85f 100644\n> > > --- a/meson.build\n> > > +++ b/meson.build\n> > > @@ -198,6 +198,7 @@ arch_x86 = ['x86', 'x86_64']\n> > >  pipelines_support = {\n> > >      'imx8-isi':     arch_arm,\n> > >      'ipu3':         arch_x86,\n> > > +    'mali-c55':     arch_arm,\n> > >      'rkisp1':       arch_arm,\n> > >      'rpi/vc4':      arch_arm,\n> > >      'simple':       arch_arm,\n> > > diff --git a/meson_options.txt b/meson_options.txt\n> > > index 99dab96d7b21..7c4f6d3a0af6 100644\n> > > --- a/meson_options.txt\n> > > +++ b/meson_options.txt\n> > > @@ -43,6 +43,7 @@ option('pipelines',\n> > >              'auto',\n> > >              'imx8-isi',\n> > >              'ipu3',\n> > > +            'mali-c55',\n> > >              'rkisp1',\n> > >              'rpi/vc4',\n> > >              'simple',\n> > > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > new file mode 100644\n> > > index 000000000000..4508214b864d\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > @@ -0,0 +1,1081 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2024, Ideas on Board Oy\n> > > + *\n> > > + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> > > + */\n> > > +\n> > > +#include <algorithm>\n> > > +#include <array>\n> > > +#include <map>\n> > > +#include <memory>\n> > > +#include <set>\n> > > +#include <string>\n> > > +\n> > > +#include <linux/media-bus-format.h>\n> > > +#include <linux/media.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/camera.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> > > +namespace {\n> > > +\n> > > +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> > > +{\n> > > +     return libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> > > +            libcamera::PixelFormatInfo::ColourEncodingRAW;\n> > > +}\n> > > +\n> > > +}; /* namespace */\n> \n> I think the /only/ complaint from CI/build-matrix is this tiny little\n> additional ; which should be removed.\n\n\nhttps://gitlab.freedesktop.org/camera/libcamera/-/jobs/56300677#L493 for\nreference here.\n\n> \n> With that, the build stays clean - and as this matches the publicly\n> posted driver, I think this could already be merged.\n> \n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\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 20E77BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 14 Mar 2024 15:05:18 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A744662C85;\n\tThu, 14 Mar 2024 16:05:17 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E249661C65\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 14 Mar 2024 16:05:15 +0100 (CET)","from pendragon.ideasonboard.com\n\t(aztw-30-b2-v4wan-166917-cust845.vm26.cable.virginm.net\n\t[82.37.23.78])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9DD08E4;\n\tThu, 14 Mar 2024 16:04:52 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"aVX3VmPP\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710428692;\n\tbh=sdpCszZQCEvo/NdUvazcU7eIxt8X9+S1ozvvhqnjg2E=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=aVX3VmPPcPOwXsxfR+V2Gwssc/lCDBM3HRi+8SgyxHF3LgSXacIV1wHRFjegPQXPh\n\tbs2qmlbBjKxfoYYrAFKJwaLxkXnTQCCLsM69oUat2xTRjqJvfXIzu7Gf/InxVy1PJQ\n\tcYO0y31PcqRe06ayS10/mwZ0Gk29J1Tzm6r4FwUI=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>\n\t<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>\n\t<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Date":"Thu, 14 Mar 2024 15:05:13 +0000","Message-ID":"<171042871332.252503.18143989750356559422@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","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>","Cc":"Nayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28974,"web_url":"https://patchwork.libcamera.org/comment/28974/","msgid":"<U0OmhExn8VBWYMqq1wkczOoOKJ4s3CBAHI9jHv1X5ZbKMOmxPcDq-RvJpkI1N_XXSTDBk6imxd7Gdnq-0CUhKKE04YTapCSkr0SwGnJxvJQ=@protonmail.com>","date":"2024-03-15T02:28:26","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":133,"url":"https://patchwork.libcamera.org/api/people/133/","name":"Pőcze Barnabás","email":"pobrn@protonmail.com"},"content":"Hi\n\n\n2024. március 14., csütörtök 15:07 keltezéssel, Jacopo Mondi <jacopo.mondi@ideasonboard.com> írta:\n\n> [...]\n> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> new file mode 100644\n> index 000000000000..4508214b864d\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> @@ -0,0 +1,1081 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas on Board Oy\n> + *\n> + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> + */\n> +\n> +#include <algorithm>\n> +#include <array>\n> +#include <map>\n> +#include <memory>\n> +#include <set>\n> +#include <string>\n> +\n> +#include <linux/media-bus-format.h>\n> +#include <linux/media.h>\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +#include <libcamera/camera.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> +namespace {\n> +\n> +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> +{\n> +\treturn libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> +\t       libcamera::PixelFormatInfo::ColourEncodingRAW;\n> +}\n> +\n> +}; /* namespace */\n> +\n> +namespace libcamera {\n\nDoes this need to be in the libcamera namespace? This will export a lot of\nsymbols that don't really need to be exported as far as I can see.\n(Maybe in the future?) (Although to be fair, this affects most [all?] pipeline handlers.)\n\n\n> [...]\n> +std::unique_ptr<CameraConfiguration>\n> +PipelineHandlerMaliC55::generateConfiguration(Camera *camera,\n> +\t\t\t\t\t      Span<const StreamRole> roles)\n> +{\n> +\tMaliC55CameraData *data = cameraData(camera);\n> +\tstd::unique_ptr<CameraConfiguration> config =\n> +\t\tstd::make_unique<MaliC55CameraConfiguration>(data);\n> +\tbool frPipeAvailable = true;\n> +\n> +\tif (roles.empty())\n> +\t\treturn config;\n> +\n> +\t/* Check if one stream is RAW to reserve the FR pipe for it. */\n> +\tif (std::find_if(roles.begin(), roles.end(),\n> +\t\t\t [](const StreamRole &role) {\n> +\t\t\t\t return role == StreamRole::Raw;\n> +\t\t\t }) != roles.end())\n\nDoesn't\n\n  if (std::find(roles.begin(), roles.end(), StreamRole::Raw) != roles.end())\n\nwork?\n\n\n> +\t\tfrPipeAvailable = false;\n> +\n> +\tfor (const StreamRole &role : roles) {\n> +\t\tstruct MaliC55Pipe *pipe;\n> +\n> +\t\t/* Assign pipe for this role. */\n> +\t\tif (role == StreamRole::Raw) {\n> +\t\t\tpipe = &pipes_[MaliC55FR];\n> +\t\t} else {\n> +\t\t\tif (frPipeAvailable) {\n> +\t\t\t\tpipe = &pipes_[MaliC55FR];\n> +\t\t\t\tfrPipeAvailable = false;\n> +\t\t\t} else {\n> +\t\t\t\tpipe = &pipes_[MaliC55DS];\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tSize size = std::min(Size{ 1920, 1080 }, data->resolution());\n> +\t\tPixelFormat pixelFormat;\n> +\n> +\t\tswitch (role) {\n> +\t\tcase StreamRole::StillCapture:\n> +\t\t\tsize = data->resolution();\n> +\t\t\t/* fall-through */\n\n  [[fallthrough]];\n\n?\n\n> +\t\tcase StreamRole::VideoRecording:\n> +\t\t\tpixelFormat = formats::NV12;\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::Viewfinder:\n> +\t\t\tpixelFormat = formats::RGB565;\n> +\t\t\tbreak;\n> +\n> +\t\tcase StreamRole::Raw:\n> +\t\t\tpixelFormat = data->bestRawFormat();\n> +\t\t\tif (!pixelFormat.isValid()) {\n> +\t\t\t\tLOG(MaliC55, Error)\n> +\t\t\t\t\t<< \"Camera does not support RAW formats\";\n> +\t\t\t\tcontinue;\n> +\t\t\t}\n> +\n> +\t\t\tsize = data->resolution();\n> +\t\t\tbreak;\n> +\n> +\t\tdefault:\n> +\t\t\tLOG(MaliC55, Error)\n> +\t\t\t\t<< \"Requested stream role not supported: \" << role;\n> +\t\t\treturn config;\n> +\t\t}\n> [...]\n\n\nRegards,\nBarnabás Pőcze","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 8E32ABD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 15 Mar 2024 02:28:43 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 25BB562C93;\n\tFri, 15 Mar 2024 03:28:42 +0100 (CET)","from mail-40133.protonmail.ch (mail-40133.protonmail.ch\n\t[185.70.40.133])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0163262973\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 15 Mar 2024 03:28:40 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=protonmail.com header.i=@protonmail.com\n\theader.b=\"gbXWP09k\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com;\n\ts=protonmail3; t=1710469720; x=1710728920;\n\tbh=y86rs0P2qz75ypl4B+l1yHXL4wOOT7F9TgLTXJdFqf0=;\n\th=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References:\n\tFeedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID:\n\tMessage-ID:BIMI-Selector;\n\tb=gbXWP09kGwDOa7K/xnvNsjdcDiormKMVB97HHIDtVRV5tfABHFy42CusWG2tzw0OV\n\tTX4BFYUporFnOYZKeiiCXX+sP2GUYmm3QipnKPoewFuIzfJcElu9rD22wauRnhkF66\n\tGfwSPgj7b8jtOBoOceq7bER9kN71VEJuL1ek/SIame+hzgwkj/D2l86/XPMPhD955w\n\tmtPyU/iqxHTNn5SvgyPv6vXmYkq0iA+w9Pc+E9O3+y7/pf31ETQOyV2xKVDP2/J8IZ\n\tFqGvb3G0qBjoMBT/8GWJRrBKq/EUsrVEsRoJCyVJkgaBHA1S3XnmY6D05zveVoNhzS\n\tHpm3a/+Y9IFbQ==","Date":"Fri, 15 Mar 2024 02:28:26 +0000","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","Message-ID":"<U0OmhExn8VBWYMqq1wkczOoOKJ4s3CBAHI9jHv1X5ZbKMOmxPcDq-RvJpkI1N_XXSTDBk6imxd7Gdnq-0CUhKKE04YTapCSkr0SwGnJxvJQ=@protonmail.com>","In-Reply-To":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>","Feedback-ID":"20568564:user:proton","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"quoted-printable","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>","Cc":"Nayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29002,"web_url":"https://patchwork.libcamera.org/comment/29002/","msgid":"<nb6phoxc6i2mmdk4wgne5agrcu6atc4w6rwwicnmd3hbgxc3is@eln6pu4btjvn>","date":"2024-03-19T13:39:27","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Kieran\n\nOn Thu, Mar 14, 2024 at 03:03:41PM +0000, Kieran Bingham wrote:\n> Quoting Jacopo Mondi (2024-03-14 14:12:07)\n> > Ouch\n> >\n> > On Thu, Mar 14, 2024 at 03:07:08PM +0100, Jacopo Mondi wrote:\n> > > Add a pipeline handler for the Mali-C55 ISP.\n> > >\n> > > The pipeline doesn't currently support an IPA and does not run\n> > > any 3 algorithm but only handles the media graph topology and\n> >       ^\n> >       3a of course!\n> >\n> > > formats/sizes configuration\n> > >\n> > > Co-developed-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > > Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > > Acked-by: Nayden Kanchev <nayden.kanchev@arm.com>\n> > > ---\n> > >  meson.build                                  |    1 +\n> > >  meson_options.txt                            |    1 +\n> > >  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1081 ++++++++++++++++++\n> > >  src/libcamera/pipeline/mali-c55/meson.build  |    5 +\n> > >  4 files changed, 1088 insertions(+)\n> > >  create mode 100644 src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > >  create mode 100644 src/libcamera/pipeline/mali-c55/meson.build\n> > >\n> > > diff --git a/meson.build b/meson.build\n> > > index cb6b666a7449..740ead1be85f 100644\n> > > --- a/meson.build\n> > > +++ b/meson.build\n> > > @@ -198,6 +198,7 @@ arch_x86 = ['x86', 'x86_64']\n> > >  pipelines_support = {\n> > >      'imx8-isi':     arch_arm,\n> > >      'ipu3':         arch_x86,\n> > > +    'mali-c55':     arch_arm,\n> > >      'rkisp1':       arch_arm,\n> > >      'rpi/vc4':      arch_arm,\n> > >      'simple':       arch_arm,\n> > > diff --git a/meson_options.txt b/meson_options.txt\n> > > index 99dab96d7b21..7c4f6d3a0af6 100644\n> > > --- a/meson_options.txt\n> > > +++ b/meson_options.txt\n> > > @@ -43,6 +43,7 @@ option('pipelines',\n> > >              'auto',\n> > >              'imx8-isi',\n> > >              'ipu3',\n> > > +            'mali-c55',\n> > >              'rkisp1',\n> > >              'rpi/vc4',\n> > >              'simple',\n> > > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > new file mode 100644\n> > > index 000000000000..4508214b864d\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > @@ -0,0 +1,1081 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2024, Ideas on Board Oy\n> > > + *\n> > > + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> > > + */\n> > > +\n> > > +#include <algorithm>\n> > > +#include <array>\n> > > +#include <map>\n> > > +#include <memory>\n> > > +#include <set>\n> > > +#include <string>\n> > > +\n> > > +#include <linux/media-bus-format.h>\n> > > +#include <linux/media.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/camera.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> > > +namespace {\n> > > +\n> > > +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> > > +{\n> > > +     return libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> > > +            libcamera::PixelFormatInfo::ColourEncodingRAW;\n> > > +}\n> > > +\n> > > +}; /* namespace */\n>\n> I think the /only/ complaint from CI/build-matrix is this tiny little\n> additional ; which should be removed.\n>\n> With that, the build stays clean - and as this matches the publicly\n> posted driver, I think this could already be merged.\n>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>\n> One minor comment below - but I think that can be done on top if\n> preferred anyway.\n>\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +LOG_DEFINE_CATEGORY(MaliC55)\n> > > +\n> > > +const std::map<libcamera::PixelFormat, unsigned int> maliC55FmtToCode = {\n> > > +     /* \\todo Support all formats supported by the driver in libcamera. */\n> > > +\n> > > +     { formats::RGB565, MEDIA_BUS_FMT_RGB121212_1X36 },\n> > > +     { formats::RGB888, MEDIA_BUS_FMT_RGB121212_1X36 },\n> > > +     { formats::YUYV, MEDIA_BUS_FMT_YUV10_1X30 },\n> > > +     { formats::UYVY, MEDIA_BUS_FMT_YUV10_1X30 },\n> > > +     { formats::R8, MEDIA_BUS_FMT_YUV10_1X30 },\n> > > +     { formats::NV12, MEDIA_BUS_FMT_YUV10_1X30 },\n> > > +     { formats::NV21, MEDIA_BUS_FMT_YUV10_1X30 },\n> > > +\n> > > +     /* RAW formats, FR pipe only. */\n> > > +     { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 },\n> > > +     { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > > +     { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 },\n> > > +     { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 },\n> > > +     { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 },\n> > > +     { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 },\n> > > +     { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 },\n> > > +     { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 },\n> > > +     { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 },\n> > > +     { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 },\n> > > +     { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 },\n> > > +     { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 },\n> > > +     { formats::SGBRG14, MEDIA_BUS_FMT_SGBRG14_1X14 },\n> > > +     { formats::SRGGB14, MEDIA_BUS_FMT_SRGGB14_1X14 },\n> > > +     { formats::SBGGR14, MEDIA_BUS_FMT_SBGGR14_1X14 },\n> > > +     { formats::SGRBG14, MEDIA_BUS_FMT_SGRBG14_1X14 },\n> > > +     { formats::SGBRG16, MEDIA_BUS_FMT_SGBRG16_1X16 },\n> > > +     { formats::SRGGB16, MEDIA_BUS_FMT_SRGGB16_1X16 },\n> > > +     { formats::SBGGR16, MEDIA_BUS_FMT_SBGGR16_1X16 },\n> > > +     { formats::SGRBG16, MEDIA_BUS_FMT_SGRBG16_1X16 },\n> > > +};\n> > > +\n> > > +constexpr Size kMaliC55MinSize = { 128, 128 };\n> > > +constexpr Size kMaliC55MaxSize = { 8192, 8192 };\n> > > +constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36;\n> > > +\n> > > +class MaliC55CameraData : public Camera::Private\n> > > +{\n> > > +public:\n> > > +     MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity)\n> > > +             : Camera::Private(pipe), entity_(entity)\n> > > +     {\n> > > +     }\n> > > +\n> > > +     int init();\n> > > +\n> > > +     /* Deflect these functionalities to either TPG or CameraSensor. */\n> > > +     const std::vector<unsigned int> mbusCodes() const;\n> > > +     const std::vector<Size> sizes(unsigned int mbusCode) const;\n> > > +     const Size resolution() const;\n> > > +\n> > > +     PixelFormat bestRawFormat() const;\n> > > +\n> > > +     PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;\n> > > +     Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;\n> > > +\n> > > +     std::unique_ptr<CameraSensor> sensor_;\n> > > +\n> > > +     MediaEntity *entity_;\n> > > +     std::unique_ptr<V4L2Subdevice> csi_;\n> > > +     std::unique_ptr<V4L2Subdevice> sd_;\n> > > +     Stream frStream_;\n> > > +     Stream dsStream_;\n> > > +\n> > > +private:\n> > > +     void initTPGData();\n> > > +\n> > > +     std::string id_;\n> > > +     std::vector<unsigned int> tpgCodes_;\n> > > +     std::vector<Size> tpgSizes_;\n> > > +     Size tpgResolution_;\n> > > +};\n> > > +\n> > > +int MaliC55CameraData::init()\n> > > +{\n> > > +     int ret;\n> > > +\n> > > +     sd_ = std::make_unique<V4L2Subdevice>(entity_);\n> > > +     ret = sd_->open();\n> > > +     if (ret) {\n> > > +             LOG(MaliC55, Error) << \"Failed to open sensor subdevice\";\n> > > +             return ret;\n> > > +     }\n> > > +\n> > > +     /* If this camera is created from TPG, we return here. */\n> > > +     if (entity_->name() == \"mali-c55 tpg\") {\n> > > +             initTPGData();\n> > > +             return 0;\n> > > +     }\n> > > +\n> > > +     /*\n> > > +      * Register a CameraSensor if we connect to a sensor and create\n> > > +      * an entity for the connected CSI-2 receiver.\n> > > +      */\n> > > +     sensor_ = std::make_unique<CameraSensor>(entity_);\n> > > +     ret = sensor_->init();\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     const MediaPad *sourcePad = entity_->getPadByIndex(0);\n> > > +     MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity();\n> > > +\n> > > +     csi_ = std::make_unique<V4L2Subdevice>(csiEntity);\n> > > +     if (csi_->open()) {\n> > > +             LOG(MaliC55, Error) << \"Failed to open CSI-2 subdevice\";\n> > > +             return false;\n> > > +     }\n> > > +\n> > > +     return 0;\n> > > +}\n> > > +\n> > > +void MaliC55CameraData::initTPGData()\n> > > +{\n> > > +     /* Replicate the CameraSensor implementation for TPG. */\n> > > +     V4L2Subdevice::Formats formats = sd_->formats(0);\n> > > +     if (formats.empty())\n> > > +             return;\n> > > +\n> > > +     tpgCodes_ = utils::map_keys(formats);\n> > > +     std::sort(tpgCodes_.begin(), tpgCodes_.end());\n> > > +\n> > > +     for (const auto &format : formats) {\n> > > +             const std::vector<SizeRange> &ranges = format.second;\n> > > +             std::transform(ranges.begin(), ranges.end(), std::back_inserter(tpgSizes_),\n> > > +                            [](const SizeRange &range) { return range.max; });\n> > > +     }\n> > > +\n> > > +     tpgResolution_ = tpgSizes_.back();\n> > > +}\n> > > +\n> > > +const std::vector<unsigned int> MaliC55CameraData::mbusCodes() const\n> > > +{\n> > > +     if (sensor_)\n> > > +             return sensor_->mbusCodes();\n> > > +\n> > > +     return tpgCodes_;\n> > > +}\n> > > +\n> > > +const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const\n> > > +{\n> > > +     if (sensor_)\n> > > +             return sensor_->sizes(mbusCode);\n> > > +\n> > > +     V4L2Subdevice::Formats formats = sd_->formats(0);\n> > > +     if (formats.empty())\n> > > +             return {};\n> > > +\n> > > +     std::vector<Size> sizes;\n> > > +     const auto &format = formats.find(mbusCode);\n> > > +     if (format == formats.end())\n> > > +             return {};\n> > > +\n> > > +     const std::vector<SizeRange> &ranges = format->second;\n> > > +     std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),\n> > > +                    [](const SizeRange &range) { return range.max; });\n> > > +\n> > > +     std::sort(sizes.begin(), sizes.end());\n> > > +\n> > > +     return sizes;\n> > > +}\n> > > +\n> > > +const Size MaliC55CameraData::resolution() const\n> > > +{\n> > > +     if (sensor_)\n> > > +             return sensor_->resolution();\n> > > +\n> > > +     return tpgResolution_;\n> > > +}\n> > > +\n> > > +PixelFormat MaliC55CameraData::bestRawFormat() const\n> > > +{\n> > > +     unsigned int bitDepth = 0;\n> > > +     PixelFormat rawFormat;\n> > > +\n> > > +     /*\n> > > +      * Iterate over all the supported PixelFormat and find the one\n> > > +      * supported by the camera with the largest bitdepth.\n> > > +      */\n> > > +     for (const auto &maliFormat : maliC55FmtToCode) {\n> > > +             PixelFormat pixFmt = maliFormat.first;\n> > > +             if (!isFormatRaw(pixFmt))\n> > > +                     continue;\n> > > +\n> > > +             unsigned int rawCode = maliFormat.second;\n> > > +             const auto rawSizes = sizes(rawCode);\n> > > +             if (rawSizes.empty())\n> > > +                     continue;\n> > > +\n> > > +             BayerFormat bayer = BayerFormat::fromMbusCode(rawCode);\n> > > +             if (bayer.bitDepth > bitDepth) {\n> > > +                     bitDepth = bayer.bitDepth;\n> > > +                     rawFormat = pixFmt;\n> > > +             }\n> > > +     }\n> > > +\n> > > +     return rawFormat;\n> > > +}\n> > > +\n> > > +/*\n> > > + * Make sure the provided raw pixel format is supported and adjust it to\n> > > + * one of the supported ones if it's not.\n> > > + */\n> > > +PixelFormat MaliC55CameraData::adjustRawFormat(const PixelFormat &rawFmt) const\n> > > +{\n> > > +     /* Make sure the provided raw format is supported by the pipeline. */\n> > > +     auto it = maliC55FmtToCode.find(rawFmt);\n> > > +     if (it == maliC55FmtToCode.end())\n> > > +             return bestRawFormat();\n> > > +\n> > > +     /* Now make sure the RAW mbus code is supported by the image source. */\n> > > +     unsigned int rawCode = it->second;\n> > > +     const auto rawSizes = sizes(rawCode);\n> > > +     if (rawSizes.empty())\n> > > +             return bestRawFormat();\n> > > +\n> > > +     return rawFmt;\n> > > +}\n> > > +\n> > > +Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &rawSize) const\n> > > +{\n> > > +     /* Just make sure the format is supported. */\n> > > +     auto it = maliC55FmtToCode.find(rawFmt);\n> > > +     if (it == maliC55FmtToCode.end())\n> > > +             return {};\n> > > +\n> > > +     /* Check if the size is natively supported. */\n> > > +     unsigned int rawCode = it->second;\n> > > +     const auto rawSizes = sizes(rawCode);\n> > > +     auto sizeIt = std::find(rawSizes.begin(), rawSizes.end(), rawSize);\n> > > +     if (sizeIt != rawSizes.end())\n> > > +             return rawSize;\n> > > +\n> > > +     /* Or adjust it to the closest supported size. */\n> > > +     uint16_t distance = std::numeric_limits<uint16_t>::max();\n> > > +     Size bestSize;\n> > > +     for (const Size &size : rawSizes) {\n> > > +             uint16_t dist = std::abs(static_cast<int>(rawSize.width) -\n> > > +                                      static_cast<int>(size.width)) +\n> > > +                             std::abs(static_cast<int>(rawSize.height) -\n> > > +                                      static_cast<int>(size.height));\n> > > +             if (dist < distance) {\n> > > +                     dist = distance;\n> > > +                     bestSize = size;\n> > > +             }\n> > > +     }\n> > > +\n> > > +     return bestSize;\n> > > +}\n> > > +\n> > > +class MaliC55CameraConfiguration : public CameraConfiguration\n> > > +{\n> > > +public:\n> > > +     MaliC55CameraConfiguration(MaliC55CameraData *data)\n> > > +             : CameraConfiguration(), data_(data)\n> > > +     {\n> > > +     }\n> > > +\n> > > +     Status validate() override;\n> > > +\n> > > +     V4L2SubdeviceFormat sensorFormat_;\n> > > +\n> > > +private:\n> > > +     static constexpr unsigned int kMaxStreams = 2;\n> > > +\n> > > +     const MaliC55CameraData *data_;\n> > > +};\n> > > +\n> > > +CameraConfiguration::Status MaliC55CameraConfiguration::validate()\n> > > +{\n> > > +     Status status = Valid;\n> > > +\n> > > +     if (config_.empty())\n> > > +             return Invalid;\n> > > +\n> > > +     /* Only 2 streams available. */\n> > > +     if (config_.size() > kMaxStreams) {\n> > > +             config_.resize(kMaxStreams);\n> > > +             status = Adjusted;\n> > > +     }\n> > > +\n> > > +     bool frPipeAvailable = true;\n> > > +     StreamConfiguration *rawConfig = nullptr;\n> > > +     for (StreamConfiguration &config : config_) {\n> > > +             if (!isFormatRaw(config.pixelFormat))\n> > > +                     continue;\n> > > +\n> > > +             if (rawConfig) {\n> > > +                     LOG(MaliC55, Error)\n> > > +                             << \"Only a single RAW stream is supported\";\n> > > +                     return Invalid;\n> > > +             }\n> > > +\n> > > +             rawConfig = &config;\n> > > +     }\n> > > +\n> > > +     Size maxSize = kMaliC55MaxSize;\n> > > +     if (rawConfig) {\n> > > +             /*\n> > > +              * \\todo Take into account the Bayer components ordering once\n> > > +              * we support rotations.\n> > > +              */\n> > > +             PixelFormat rawFormat =\n> > > +                     data_->adjustRawFormat(rawConfig->pixelFormat);\n> > > +             if (rawFormat != rawConfig->pixelFormat) {\n> > > +                     LOG(MaliC55, Debug)\n> > > +                             << \"RAW format adjusted to \" << rawFormat;\n> > > +                     rawConfig->pixelFormat = rawFormat;\n> > > +                     status = Adjusted;\n> > > +             }\n> > > +\n> > > +             Size rawSize =\n> > > +                     data_->adjustRawSizes(rawFormat, rawConfig->size);\n> > > +             if (rawSize != rawConfig->size) {\n> > > +                     LOG(MaliC55, Debug)\n> > > +                             << \"RAW sizes adjusted to \" << rawSize;\n> > > +                     rawConfig->size = rawSize;\n> > > +                     status = Adjusted;\n> > > +             }\n> > > +\n> > > +             maxSize = rawSize;\n> > > +\n> > > +             rawConfig->setStream(const_cast<Stream *>(&data_->frStream_));\n> > > +             frPipeAvailable = false;\n> > > +     }\n> > > +\n> > > +     /* Adjust processed streams. */\n> > > +     Size maxYuvSize;\n> > > +     for (StreamConfiguration &config : config_) {\n> > > +             if (isFormatRaw(config.pixelFormat))\n> > > +                     continue;\n> > > +\n> > > +             /* Adjust format and size for processed streams. */\n> > > +             const auto it = maliC55FmtToCode.find(config.pixelFormat);\n> > > +             if (it == maliC55FmtToCode.end()) {\n> > > +                     LOG(MaliC55, Debug)\n> > > +                             << \"Format adjusted to \" << formats::RGB565;\n> > > +                     config.pixelFormat = formats::RGB565;\n> > > +                     status = Adjusted;\n> > > +             }\n> > > +\n> > > +             Size size = std::clamp(config.size, kMaliC55MinSize, maxSize);\n> > > +             if (size != config.size) {\n> > > +                     LOG(MaliC55, Debug)\n> > > +                             << \"Size adjusted to \" << size;\n> > > +                     config.size = size;\n> > > +                     status = Adjusted;\n> > > +             }\n> > > +\n> > > +             if (maxYuvSize < size)\n> > > +                     maxYuvSize = size;\n> > > +\n> > > +             if (frPipeAvailable) {\n> > > +                     config.setStream(const_cast<Stream *>(&data_->frStream_));\n> > > +                     frPipeAvailable = false;\n> > > +             } else {\n> > > +                     config.setStream(const_cast<Stream *>(&data_->dsStream_));\n> > > +             }\n> > > +     }\n> > > +\n> > > +     /* Compute the sensor format. */\n> > > +\n> > > +     /* If there's a RAW config, sensor configuration follows it. */\n> > > +     if (rawConfig) {\n> > > +             const auto it = maliC55FmtToCode.find(rawConfig->pixelFormat);\n> > > +             sensorFormat_.mbus_code = it->second;\n> > > +             sensorFormat_.size = rawConfig->size;\n> > > +\n> > > +             return status;\n> > > +     }\n> > > +\n> > > +     /* If there's no RAW config, compute the sensor configuration here. */\n> > > +     PixelFormat rawFormat = data_->bestRawFormat();\n> > > +     const auto it = maliC55FmtToCode.find(rawFormat);\n> > > +     sensorFormat_.mbus_code = it->second;\n> > > +\n> > > +     uint16_t distance = std::numeric_limits<uint16_t>::max();\n> > > +     const auto sizes = data_->sizes(it->second);\n> > > +     Size bestSize;\n> > > +     for (const auto &size : sizes) {\n> > > +             /* Skip sensor sizes that are smaller than the max YUV size. */\n> > > +             if (maxYuvSize.width > size.width ||\n> > > +                 maxYuvSize.height > size.height)\n> > > +                     continue;\n> > > +\n> > > +             uint16_t dist = std::abs(static_cast<int>(maxYuvSize.width) -\n> > > +                                      static_cast<int>(size.width)) +\n> > > +                             std::abs(static_cast<int>(maxYuvSize.height) -\n> > > +                                      static_cast<int>(size.height));\n> > > +             if (dist < distance) {\n> > > +                     dist = distance;\n> > > +                     bestSize = size;\n> > > +             }\n> > > +     }\n> > > +     sensorFormat_.size = bestSize;\n> > > +\n> > > +     LOG(MaliC55, Debug) << \"Computed sensor configuration \" << sensorFormat_;\n> > > +\n> > > +     return status;\n> > > +}\n> > > +\n> > > +class PipelineHandlerMaliC55 : public PipelineHandler\n> > > +{\n> > > +public:\n> > > +     PipelineHandlerMaliC55(CameraManager *manager);\n> > > +\n> > > +     std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n> > > +                                                                Span<const StreamRole> roles) override;\n> > > +     int configure(Camera *camera, CameraConfiguration *config) override;\n> > > +\n> > > +     int exportFrameBuffers(Camera *camera, Stream *stream,\n> > > +                            std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n> > > +\n> > > +     int start(Camera *camera, const ControlList *controls) override;\n> > > +     void stopDevice(Camera *camera) override;\n> > > +\n> > > +     int queueRequestDevice(Camera *camera, Request *request) override;\n> > > +\n> > > +     void bufferReady(FrameBuffer *buffer);\n> > > +\n> > > +     bool match(DeviceEnumerator *enumerator) override;\n> > > +\n> > > +private:\n> > > +     struct MaliC55Pipe {\n> > > +             std::unique_ptr<V4L2Subdevice> resizer;\n> > > +             std::unique_ptr<V4L2VideoDevice> cap;\n> > > +             Stream *stream;\n> > > +     };\n> > > +\n> > > +     enum {\n> > > +             MaliC55FR,\n> > > +             MaliC55DS,\n> > > +             MaliC55NumPipes,\n> > > +     };\n> > > +\n> > > +     MaliC55CameraData *cameraData(Camera *camera)\n> > > +     {\n> > > +             return static_cast<MaliC55CameraData *>(camera->_d());\n> > > +     }\n> > > +\n> > > +     MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, Stream *stream)\n> > > +     {\n> > > +             if (stream == &data->frStream_)\n> > > +                     return &pipes_[MaliC55FR];\n> > > +             else if (stream == &data->dsStream_)\n> > > +                     return &pipes_[MaliC55DS];\n> > > +             else\n> > > +                     LOG(MaliC55, Fatal) << \"Stream \" << stream << \" not valid\";\n> > > +             return nullptr;\n> > > +     }\n> > > +\n> > > +     MaliC55Pipe *pipeFromStream(MaliC55CameraData *data, const Stream *stream)\n> > > +     {\n> > > +             return pipeFromStream(data, const_cast<Stream *>(stream));\n> > > +     }\n> > > +\n> > > +     void resetPipes()\n> > > +     {\n> > > +             for (MaliC55Pipe &pipe : pipes_)\n> > > +                     pipe.stream = nullptr;\n> > > +     }\n> > > +\n> > > +     int configureRawStream(MaliC55CameraData *data,\n> > > +                            const StreamConfiguration &config,\n> > > +                            V4L2SubdeviceFormat &subdevFormat);\n> > > +     int configureProcessedStream(MaliC55CameraData *data,\n> > > +                                  const StreamConfiguration &config,\n> > > +                                  V4L2SubdeviceFormat &subdevFormat);\n> > > +\n> > > +     void registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> > > +                             const std::string &name);\n> > > +     bool registerTPGCamera(MediaLink *link);\n> > > +     bool registerSensorCamera(MediaLink *link);\n> > > +\n> > > +     MediaDevice *media_;\n> > > +     std::unique_ptr<V4L2Subdevice> isp_;\n> > > +\n> > > +     std::array<MaliC55Pipe, MaliC55NumPipes> pipes_;\n> > > +\n> > > +     bool dsFitted_;\n> > > +};\n> > > +\n> > > +PipelineHandlerMaliC55::PipelineHandlerMaliC55(CameraManager *manager)\n> > > +     : PipelineHandler(manager), dsFitted_(true)\n> > > +{\n> > > +}\n> > > +\n> > > +std::unique_ptr<CameraConfiguration>\n> > > +PipelineHandlerMaliC55::generateConfiguration(Camera *camera,\n> > > +                                           Span<const StreamRole> roles)\n> > > +{\n> > > +     MaliC55CameraData *data = cameraData(camera);\n> > > +     std::unique_ptr<CameraConfiguration> config =\n> > > +             std::make_unique<MaliC55CameraConfiguration>(data);\n> > > +     bool frPipeAvailable = true;\n> > > +\n> > > +     if (roles.empty())\n> > > +             return config;\n> > > +\n> > > +     /* Check if one stream is RAW to reserve the FR pipe for it. */\n> > > +     if (std::find_if(roles.begin(), roles.end(),\n> > > +                      [](const StreamRole &role) {\n> > > +                              return role == StreamRole::Raw;\n> > > +                      }) != roles.end())\n> > > +             frPipeAvailable = false;\n> > > +\n> > > +     for (const StreamRole &role : roles) {\n> > > +             struct MaliC55Pipe *pipe;\n> > > +\n> > > +             /* Assign pipe for this role. */\n> > > +             if (role == StreamRole::Raw) {\n> > > +                     pipe = &pipes_[MaliC55FR];\n> > > +             } else {\n> > > +                     if (frPipeAvailable) {\n> > > +                             pipe = &pipes_[MaliC55FR];\n> > > +                             frPipeAvailable = false;\n> > > +                     } else {\n> > > +                             pipe = &pipes_[MaliC55DS];\n> > > +                     }\n> > > +             }\n> > > +\n> > > +             Size size = std::min(Size{ 1920, 1080 }, data->resolution());\n> > > +             PixelFormat pixelFormat;\n> > > +\n> > > +             switch (role) {\n> > > +             case StreamRole::StillCapture:\n> > > +                     size = data->resolution();\n> > > +                     /* fall-through */\n> > > +             case StreamRole::VideoRecording:\n> > > +                     pixelFormat = formats::NV12;\n> > > +                     break;\n> > > +\n> > > +             case StreamRole::Viewfinder:\n> > > +                     pixelFormat = formats::RGB565;\n> > > +                     break;\n> > > +\n> > > +             case StreamRole::Raw:\n> > > +                     pixelFormat = data->bestRawFormat();\n> > > +                     if (!pixelFormat.isValid()) {\n> > > +                             LOG(MaliC55, Error)\n> > > +                                     << \"Camera does not support RAW formats\";\n> > > +                             continue;\n> > > +                     }\n> > > +\n> > > +                     size = data->resolution();\n> > > +                     break;\n> > > +\n> > > +             default:\n> > > +                     LOG(MaliC55, Error)\n> > > +                             << \"Requested stream role not supported: \" << role;\n> > > +                     return config;\n>\n> I wonder if here the alternative is to simply call 'continue' and ignore\n> that role. The other supported roles would then still generate a\n> StreamConfig, and what gets returned would be a best effort match which\n> is what I believe the function is intended to achieve.\n\nThis is not something about this specific pipeline handler, but about\nthe expected Camera::generateConfiguration() behaviour, which,\naccording to its documentation\n\n * \\return A CameraConfiguration if the requested roles can be satisfied, or a\n * null pointer otherwise.\n\nRequires the requested roles to be supported (also, it says a nullptr\nshould returned instead of an empty config as done a few lines above).\n\nAlso note that the above Raw case should return an error instead of\ncontinuing.\n\n>\n> But I think that can already be done on top/later - and to me - when\n> this patch passes CI could be merged.\n>\n> --\n> Kieran\n>\n>\n> > > +             }\n> > > +\n> > > +             std::map<PixelFormat, std::vector<SizeRange>> formats;\n> > > +             for (const auto &maliFormat : maliC55FmtToCode) {\n> > > +                     PixelFormat pixFmt = maliFormat.first;\n> > > +                     bool isRaw = isFormatRaw(pixFmt);\n> > > +\n> > > +                     /* RAW formats are only supported on the FR pipe. */\n> > > +                     if (pipe != &pipes_[MaliC55FR] && isRaw)\n> > > +                             continue;\n> > > +\n> > > +                     if (isRaw) {\n> > > +                             /* Make sure the mbus code is supported. */\n> > > +                             unsigned int rawCode = maliFormat.second;\n> > > +                             const auto sizes = data->sizes(rawCode);\n> > > +                             if (sizes.empty())\n> > > +                                     continue;\n> > > +\n> > > +                             /* And list all sizes the sensor can produce. */\n> > > +                             std::vector<SizeRange> sizeRanges;\n> > > +                             std::transform(sizes.begin(), sizes.end(),\n> > > +                                            std::back_inserter(sizeRanges),\n> > > +                                            [](const Size &s) {\n> > > +                                                    return SizeRange(s);\n> > > +                                            });\n> > > +\n> > > +                             formats[pixFmt] = sizeRanges;\n> > > +                     } else {\n> > > +                             /* Processed formats are always available. */\n> > > +                             Size maxSize = std::min(kMaliC55MaxSize,\n> > > +                                                     data->resolution());\n> > > +                             formats[pixFmt] = { kMaliC55MinSize, maxSize };\n> > > +                     }\n> > > +             }\n> > > +\n> > > +             StreamFormats streamFormats(formats);\n> > > +             StreamConfiguration cfg(streamFormats);\n> > > +             cfg.pixelFormat = pixelFormat;\n> > > +             cfg.bufferCount = 4;\n> > > +             cfg.size = size;\n> > > +\n> > > +             config->addConfiguration(cfg);\n> > > +     }\n> > > +\n> > > +     if (config->validate() == CameraConfiguration::Invalid)\n> > > +             return {};\n> > > +\n> > > +     return config;\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::configureRawStream(MaliC55CameraData *data,\n> > > +                                            const StreamConfiguration &config,\n> > > +                                            V4L2SubdeviceFormat &subdevFormat)\n> > > +{\n> > > +     Stream *stream = config.stream();\n> > > +     MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > > +\n> > > +     if (pipe != &pipes_[MaliC55FR]) {\n> > > +             LOG(MaliC55, Fatal) << \"Only the FR pipe supports RAW capture.\";\n> > > +             return -EINVAL;\n> > > +     }\n> > > +\n> > > +     /* Enable the debayer route to set fixed internal format on pad #0. */\n> > > +     V4L2Subdevice::Routing routing = {};\n> > > +     struct v4l2_subdev_route route = {\n> > > +             .sink_pad = 0,\n> > > +             .sink_stream = 0,\n> > > +             .source_pad = 1,\n> > > +             .source_stream = 0,\n> > > +             .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> > > +             .reserved = {}\n> > > +     };\n> > > +     routing.push_back(route);\n> > > +\n> > > +     int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     unsigned int rawCode = subdevFormat.mbus_code;\n> > > +     subdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> > > +     ret = pipe->resizer->setFormat(0, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     /* Enable the bypass route and apply RAW formats there. */\n> > > +     routing.clear();\n> > > +\n> > > +     route.sink_pad = 2;\n> > > +     routing.push_back(route);\n> > > +     ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     subdevFormat.mbus_code = rawCode;\n> > > +     ret = pipe->resizer->setFormat(2, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     ret = pipe->resizer->setFormat(1, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     return 0;\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::configureProcessedStream(MaliC55CameraData *data,\n> > > +                                                  const StreamConfiguration &config,\n> > > +                                                  V4L2SubdeviceFormat &subdevFormat)\n> > > +{\n> > > +     Stream *stream = config.stream();\n> > > +     MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > > +\n> > > +     /* Enable the debayer route on the resizer pipe. */\n> > > +     V4L2Subdevice::Routing routing = {};\n> > > +     struct v4l2_subdev_route route = {\n> > > +             .sink_pad = 0,\n> > > +             .sink_stream = 0,\n> > > +             .source_pad = 1,\n> > > +             .source_stream = 0,\n> > > +             .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,\n> > > +             .reserved = {}\n> > > +     };\n> > > +     routing.push_back(route);\n> > > +\n> > > +     int ret = pipe->resizer->setRouting(&routing, V4L2Subdevice::ActiveFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     subdevFormat.mbus_code = kMaliC55ISPInternalFormat;\n> > > +     ret = pipe->resizer->setFormat(0, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     /* \\todo Configure the resizer crop/compose rectangles. */\n> > > +     Rectangle ispCrop = { 0, 0, config.size };\n> > > +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     ret = pipe->resizer->setSelection(0, V4L2_SEL_TGT_COMPOSE, &ispCrop);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     subdevFormat.mbus_code = maliC55FmtToCode.find(config.pixelFormat)->second;\n> > > +     return pipe->resizer->setFormat(1, &subdevFormat);\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::configure(Camera *camera,\n> > > +                                   CameraConfiguration *config)\n> > > +{\n> > > +     resetPipes();\n> > > +\n> > > +     int ret = media_->disableLinks();\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     /* Link the graph depending if we are operating the TPG or a sensor. */\n> > > +     MaliC55CameraData *data = cameraData(camera);\n> > > +     if (data->csi_) {\n> > > +             const MediaEntity *csiEntity = data->csi_->entity();\n> > > +             ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true);\n> > > +     } else {\n> > > +             ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true);\n> > > +     }\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     MaliC55CameraConfiguration *maliConfig =\n> > > +             static_cast<MaliC55CameraConfiguration *>(config);\n> > > +     V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_;\n> > > +     ret = data->sd_->getFormat(0, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     if (data->csi_) {\n> > > +             ret = data->csi_->setFormat(0, &subdevFormat);\n> > > +             if (ret)\n> > > +                     return ret;\n> > > +\n> > > +             ret = data->csi_->setFormat(1, &subdevFormat);\n> > > +             if (ret)\n> > > +                     return ret;\n> > > +     }\n> > > +\n> > > +     /*\n> > > +      * Propagate the format to the ISP sink pad and configure the input\n> > > +      * crop rectangle (no crop at the moment).\n> > > +      *\n> > > +      * \\todo Configure the CSI-2 receiver.\n> > > +      */\n> > > +     ret = isp_->setFormat(0, &subdevFormat);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     Rectangle ispCrop(0, 0, subdevFormat.size);\n> > > +     ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &ispCrop);\n> > > +     if (ret)\n> > > +             return ret;\n> > > +\n> > > +     /*\n> > > +      * Configure the resizer: fixed format the sink pad; use the media\n> > > +      * bus code associated with the desired capture format on the source\n> > > +      * pad.\n> > > +      *\n> > > +      * Configure the crop and compose rectangles to match the desired\n> > > +      * stream output size\n> > > +      *\n> > > +      * \\todo Make the crop/scaler configurable\n> > > +      */\n> > > +     for (const StreamConfiguration &streamConfig : *config) {\n> > > +             Stream *stream = streamConfig.stream();\n> > > +             MaliC55Pipe *pipe = pipeFromStream(data, stream);\n> > > +\n> > > +             if (isFormatRaw(streamConfig.pixelFormat))\n> > > +                     ret = configureRawStream(data, streamConfig, subdevFormat);\n> > > +             else\n> > > +                     ret = configureProcessedStream(data, streamConfig, subdevFormat);\n> > > +             if (ret) {\n> > > +                     LOG(MaliC55, Error) << \"Failed to configure pipeline\";\n> > > +                     return ret;\n> > > +             }\n> > > +\n> > > +             /* Now apply the pixel format and size to the capture device. */\n> > > +             V4L2DeviceFormat captureFormat;\n> > > +             captureFormat.fourcc = pipe->cap->toV4L2PixelFormat(streamConfig.pixelFormat);\n> > > +             captureFormat.size = streamConfig.size;\n> > > +\n> > > +             ret = pipe->cap->setFormat(&captureFormat);\n> > > +             if (ret)\n> > > +                     return ret;\n> > > +\n> > > +             pipe->stream = stream;\n> > > +     }\n> > > +\n> > > +     return 0;\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream,\n> > > +                                            std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n> > > +{\n> > > +     MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> > > +     unsigned int count = stream->configuration().bufferCount;\n> > > +\n> > > +     return pipe->cap->exportBuffers(count, buffers);\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::start([[maybe_unused]] Camera *camera, [[maybe_unused]] const ControlList *controls)\n> > > +{\n> > > +     for (MaliC55Pipe &pipe : pipes_) {\n> > > +             if (!pipe.stream)\n> > > +                     continue;\n> > > +\n> > > +             Stream *stream = pipe.stream;\n> > > +\n> > > +             int ret = pipe.cap->importBuffers(stream->configuration().bufferCount);\n> > > +             if (ret) {\n> > > +                     LOG(MaliC55, Error) << \"Failed to import buffers\";\n> > > +                     return ret;\n> > > +             }\n> > > +\n> > > +             ret = pipe.cap->streamOn();\n> > > +             if (ret) {\n> > > +                     LOG(MaliC55, Error) << \"Failed to start stream\";\n> > > +                     return ret;\n> > > +             }\n> > > +     }\n> > > +\n> > > +     return 0;\n> > > +}\n> > > +\n> > > +void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)\n> > > +{\n> > > +     for (MaliC55Pipe &pipe : pipes_) {\n> > > +             if (!pipe.stream)\n> > > +                     continue;\n> > > +\n> > > +             pipe.cap->streamOff();\n> > > +             pipe.cap->releaseBuffers();\n> > > +     }\n> > > +}\n> > > +\n> > > +int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request)\n> > > +{\n> > > +     int ret;\n> > > +\n> > > +     for (auto &[stream, buffer] : request->buffers()) {\n> > > +             MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);\n> > > +\n> > > +             ret = pipe->cap->queueBuffer(buffer);\n> > > +             if (ret)\n> > > +                     return ret;\n> > > +     }\n> > > +\n> > > +     return 0;\n> > > +}\n> > > +\n> > > +void PipelineHandlerMaliC55::bufferReady(FrameBuffer *buffer)\n> > > +{\n> > > +     Request *request = buffer->request();\n> > > +\n> > > +     completeBuffer(request, buffer);\n> > > +\n> > > +     if (request->hasPendingBuffers())\n> > > +             return;\n> > > +\n> > > +     completeRequest(request);\n> > > +}\n> > > +\n> > > +void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,\n> > > +                                             const std::string &name)\n> > > +{\n> > > +     std::set<Stream *> streams{ &data->frStream_ };\n> > > +     if (dsFitted_)\n> > > +             streams.insert(&data->dsStream_);\n> > > +\n> > > +     std::shared_ptr<Camera> camera = Camera::create(std::move(data),\n> > > +                                                     name, streams);\n> > > +     registerCamera(std::move(camera));\n> > > +}\n> > > +\n> > > +/*\n> > > + * The only camera we support through direct connection to the ISP is the\n> > > + * Mali-C55 TPG. Check we have that and warn if not.\n> > > + */\n> > > +bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)\n> > > +{\n> > > +     const std::string &name = link->source()->entity()->name();\n> > > +     if (name != \"mali-c55 tpg\") {\n> > > +             LOG(MaliC55, Warning) << \"Unsupported direct connection to \"\n> > > +                                   << link->source()->entity()->name();\n> > > +             /*\n> > > +              * Return true and just skip registering a camera for this\n> > > +              * entity.\n> > > +              */\n> > > +             return true;\n> > > +     }\n> > > +\n> > > +     std::unique_ptr<MaliC55CameraData> data =\n> > > +             std::make_unique<MaliC55CameraData>(this, link->source()->entity());\n> > > +\n> > > +     if (data->init())\n> > > +             return false;\n> > > +\n> > > +     registerMaliCamera(std::move(data), name);\n> > > +\n> > > +     return true;\n> > > +}\n> > > +\n> > > +/*\n> > > + * Register a Camera for each sensor connected to the ISP through a CSI-2\n> > > + * receiver.\n> > > + *\n> > > + * \\todo Support more complex topologies, such as video muxes.\n> > > + */\n> > > +bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)\n> > > +{\n> > > +     MediaEntity *csi2 = ispLink->source()->entity();\n> > > +     const MediaPad *csi2Sink = csi2->getPadByIndex(0);\n> > > +\n> > > +     for (MediaLink *link : csi2Sink->links()) {\n> > > +             MediaEntity *sensor = link->source()->entity();\n> > > +             unsigned int function = sensor->function();\n> > > +\n> > > +             if (function != MEDIA_ENT_F_CAM_SENSOR)\n> > > +                     continue;\n> > > +\n> > > +             std::unique_ptr<MaliC55CameraData> data =\n> > > +                     std::make_unique<MaliC55CameraData>(this, sensor);\n> > > +             if (data->init())\n> > > +                     return false;\n> > > +\n> > > +             /* \\todo: Init properties and controls. */\n> > > +\n> > > +             registerMaliCamera(std::move(data), sensor->name());\n> > > +     }\n> > > +\n> > > +     return true;\n> > > +}\n> > > +\n> > > +bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)\n> > > +{\n> > > +     const MediaPad *ispSink;\n> > > +\n> > > +     /*\n> > > +      * We search for just the ISP subdevice and the full resolution pipe.\n> > > +      * The TPG and the downscale pipe are both optional blocks and may not\n> > > +      * be fitted.\n> > > +      */\n> > > +     DeviceMatch dm(\"mali-c55\");\n> > > +     dm.add(\"mali-c55 isp\");\n> > > +     dm.add(\"mali-c55 resizer fr\");\n> > > +     dm.add(\"mali-c55 fr\");\n> > > +\n> > > +     media_ = acquireMediaDevice(enumerator, dm);\n> > > +     if (!media_)\n> > > +             return false;\n> > > +\n> > > +     isp_ = V4L2Subdevice::fromEntityName(media_, \"mali-c55 isp\");\n> > > +     if (isp_->open() < 0)\n> > > +             return false;\n> > > +\n> > > +     MaliC55Pipe *frPipe = &pipes_[MaliC55FR];\n> > > +     frPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer fr\");\n> > > +     if (frPipe->resizer->open() < 0)\n> > > +             return false;\n> > > +\n> > > +     frPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 fr\");\n> > > +     if (frPipe->cap->open() < 0)\n> > > +             return false;\n> > > +\n> > > +     frPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> > > +\n> > > +     dsFitted_ = !!media_->getEntityByName(\"mali-c55 ds\");\n> > > +     if (dsFitted_) {\n> > > +             LOG(MaliC55, Debug) << \"Downscaler pipe is fitted\";\n> > > +\n> > > +             MaliC55Pipe *dsPipe = &pipes_[MaliC55DS];\n> > > +\n> > > +             dsPipe->resizer = V4L2Subdevice::fromEntityName(media_, \"mali-c55 resizer ds\");\n> > > +             if (dsPipe->resizer->open() < 0)\n> > > +                     return false;\n> > > +\n> > > +             dsPipe->cap = V4L2VideoDevice::fromEntityName(media_, \"mali-c55 ds\");\n> > > +             if (dsPipe->cap->open() < 0)\n> > > +                     return false;\n> > > +\n> > > +             dsPipe->cap->bufferReady.connect(this, &PipelineHandlerMaliC55::bufferReady);\n> > > +     }\n> > > +\n> > > +     ispSink = isp_->entity()->getPadByIndex(0);\n> > > +     if (!ispSink || ispSink->links().empty()) {\n> > > +             LOG(MaliC55, Error) << \"ISP sink pad error\";\n> > > +             return false;\n> > > +     }\n> > > +\n> > > +     /*\n> > > +      * We could have several links pointing to the ISP's sink pad, which\n> > > +      * will be from entities with one of the following functions:\n> > > +      *\n> > > +      * MEDIA_ENT_F_CAM_SENSOR - The test pattern generator\n> > > +      * MEDIA_ENT_F_VID_IF_BRIDGE - A CSI-2 receiver\n> > > +      * MEDIA_ENT_F_IO_V4L - An input device\n> > > +      *\n> > > +      * The last one will be unsupported for now. The TPG is relatively easy,\n> > > +      * we just register a Camera for it. If we have a CSI-2 receiver we need\n> > > +      * to check its sink pad and register Cameras for anything connected to\n> > > +      * it (probably...there are some complex situations in which that might\n> > > +      * not be true but let's pretend they don't exist until we come across\n> > > +      * them)\n> > > +      */\n> > > +     bool registered;\n> > > +     for (MediaLink *link : ispSink->links()) {\n> > > +             unsigned int function = link->source()->entity()->function();\n> > > +\n> > > +             switch (function) {\n> > > +             case MEDIA_ENT_F_CAM_SENSOR:\n> > > +                     registered = registerTPGCamera(link);\n> > > +                     if (!registered)\n> > > +                             return registered;\n> > > +\n> > > +                     break;\n> > > +             case MEDIA_ENT_F_VID_IF_BRIDGE:\n> > > +                     registered = registerSensorCamera(link);\n> > > +                     if (!registered)\n> > > +                             return registered;\n> > > +\n> > > +                     break;\n> > > +             case MEDIA_ENT_F_IO_V4L:\n> > > +                     LOG(MaliC55, Warning) << \"Memory input not yet supported\";\n> > > +                     break;\n> > > +             default:\n> > > +                     LOG(MaliC55, Error) << \"Unsupported entity function\";\n> > > +                     return false;\n> > > +             }\n> > > +     }\n> > > +\n> > > +     return true;\n> > > +}\n> > > +\n> > > +REGISTER_PIPELINE_HANDLER(PipelineHandlerMaliC55)\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build\n> > > new file mode 100644\n> > > index 000000000000..30fd29b928d5\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/mali-c55/meson.build\n> > > @@ -0,0 +1,5 @@\n> > > +# SPDX-License-Identifier: CC0-1.0\n> > > +\n> > > +libcamera_sources += files([\n> > > +    'mali-c55.cpp'\n> > > +])\n> > > --\n> > > 2.44.0\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 859BDBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 19 Mar 2024 13:39:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BE43962CAA;\n\tTue, 19 Mar 2024 14:39:33 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BE67462CAA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 Mar 2024 14:39:31 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 8A922C80;\n\tTue, 19 Mar 2024 14:39:04 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"orYMAHqX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710855544;\n\tbh=RkzmwF5XVP8Kkog/nmaW9EBT86ftOqeCgPPfHLDV6a4=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=orYMAHqXE/BOnSoFsSdAL6poT+283RajwxtykZwB05yAwr2gRYjRL641ldhPq6rOj\n\t5hFUo1vQVQKm12Js43GiT+sjxc+rssu2GlYYAAN8xggIL9FJiV2kDgPahww2VjtWy+\n\tZYBebhUGl7L30wWGDk2q6pdFYsgrX3OCtpdmfs7s=","Date":"Tue, 19 Mar 2024 14:39:27 +0100","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tNayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","Message-ID":"<nb6phoxc6i2mmdk4wgne5agrcu6atc4w6rwwicnmd3hbgxc3is@eln6pu4btjvn>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>\n\t<4p5vpb76rrsrkhisi2i7xeqrqvm5rkb5z7mvbrvnoaysye7wnm@furtzqlj2cba>\n\t<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<171042862198.252503.5337494888683535756@ping.linuxembedded.co.uk>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29003,"web_url":"https://patchwork.libcamera.org/comment/29003/","msgid":"<5rlhxo577xm42kmopglqk6nlrim65zeikbe5czax34kxlfr77j@jdiilceqtuhz>","date":"2024-03-19T14:43:14","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Fri, Mar 15, 2024 at 02:28:26AM +0000, Barnabás Pőcze wrote:\n> Hi\n>\n>\n> 2024. március 14., csütörtök 15:07 keltezéssel, Jacopo Mondi <jacopo.mondi@ideasonboard.com> írta:\n>\n> > [...]\n> > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > new file mode 100644\n> > index 000000000000..4508214b864d\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > @@ -0,0 +1,1081 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy\n> > + *\n> > + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <array>\n> > +#include <map>\n> > +#include <memory>\n> > +#include <set>\n> > +#include <string>\n> > +\n> > +#include <linux/media-bus-format.h>\n> > +#include <linux/media.h>\n> > +\n> > +#include <libcamera/base/log.h>\n> > +\n> > +#include <libcamera/camera.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> > +namespace {\n> > +\n> > +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> > +{\n> > +\treturn libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> > +\t       libcamera::PixelFormatInfo::ColourEncodingRAW;\n> > +}\n> > +\n> > +}; /* namespace */\n> > +\n> > +namespace libcamera {\n>\n> Does this need to be in the libcamera namespace? This will export a lot of\n\nDo you mean the pipeline handler and the associated classes ? Maybe we\nshould partition these even further in a per-pipeline namespaces, yes\n\n> symbols that don't really need to be exported as far as I can see.\n\nWhat are the implications in the generated .so file of exposing these\nsymbols in the libcamera namespace ? I don't think further namespace\nnesting has any implication on the symbols' external visibility, but\nit's only meant to restrict access to symbols within the code\n\n\n> (Maybe in the future?) (Although to be fair, this affects most [all?] pipeline handlers.)\n\nyeah, all pipelines (but rpi which has nested namespaces) are like\nthis if I'm not mistaken..\n\n>\n>\n> > [...]\n> > +std::unique_ptr<CameraConfiguration>\n> > +PipelineHandlerMaliC55::generateConfiguration(Camera *camera,\n> > +\t\t\t\t\t      Span<const StreamRole> roles)\n> > +{\n> > +\tMaliC55CameraData *data = cameraData(camera);\n> > +\tstd::unique_ptr<CameraConfiguration> config =\n> > +\t\tstd::make_unique<MaliC55CameraConfiguration>(data);\n> > +\tbool frPipeAvailable = true;\n> > +\n> > +\tif (roles.empty())\n> > +\t\treturn config;\n> > +\n> > +\t/* Check if one stream is RAW to reserve the FR pipe for it. */\n> > +\tif (std::find_if(roles.begin(), roles.end(),\n> > +\t\t\t [](const StreamRole &role) {\n> > +\t\t\t\t return role == StreamRole::Raw;\n> > +\t\t\t }) != roles.end())\n>\n> Doesn't\n>\n>   if (std::find(roles.begin(), roles.end(), StreamRole::Raw) != roles.end())\n>\n> work?\n>\n\nI presume it does, thanks!\n\n>\n> > +\t\tfrPipeAvailable = false;\n> > +\n> > +\tfor (const StreamRole &role : roles) {\n> > +\t\tstruct MaliC55Pipe *pipe;\n> > +\n> > +\t\t/* Assign pipe for this role. */\n> > +\t\tif (role == StreamRole::Raw) {\n> > +\t\t\tpipe = &pipes_[MaliC55FR];\n> > +\t\t} else {\n> > +\t\t\tif (frPipeAvailable) {\n> > +\t\t\t\tpipe = &pipes_[MaliC55FR];\n> > +\t\t\t\tfrPipeAvailable = false;\n> > +\t\t\t} else {\n> > +\t\t\t\tpipe = &pipes_[MaliC55DS];\n> > +\t\t\t}\n> > +\t\t}\n> > +\n> > +\t\tSize size = std::min(Size{ 1920, 1080 }, data->resolution());\n> > +\t\tPixelFormat pixelFormat;\n> > +\n> > +\t\tswitch (role) {\n> > +\t\tcase StreamRole::StillCapture:\n> > +\t\t\tsize = data->resolution();\n> > +\t\t\t/* fall-through */\n>\n>   [[fallthrough]];\n\nAh C++..\n\n>\n> ?\n>\n> > +\t\tcase StreamRole::VideoRecording:\n> > +\t\t\tpixelFormat = formats::NV12;\n> > +\t\t\tbreak;\n> > +\n> > +\t\tcase StreamRole::Viewfinder:\n> > +\t\t\tpixelFormat = formats::RGB565;\n> > +\t\t\tbreak;\n> > +\n> > +\t\tcase StreamRole::Raw:\n> > +\t\t\tpixelFormat = data->bestRawFormat();\n> > +\t\t\tif (!pixelFormat.isValid()) {\n> > +\t\t\t\tLOG(MaliC55, Error)\n> > +\t\t\t\t\t<< \"Camera does not support RAW formats\";\n> > +\t\t\t\tcontinue;\n> > +\t\t\t}\n> > +\n> > +\t\t\tsize = data->resolution();\n> > +\t\t\tbreak;\n> > +\n> > +\t\tdefault:\n> > +\t\t\tLOG(MaliC55, Error)\n> > +\t\t\t\t<< \"Requested stream role not supported: \" << role;\n> > +\t\t\treturn config;\n> > +\t\t}\n> > [...]\n>\n>\n> Regards,\n> Barnabás Pőcze","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 AE5ECC3272\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 19 Mar 2024 14:43:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EC8EA62CAC;\n\tTue, 19 Mar 2024 15:43:18 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B225962CA7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 19 Mar 2024 15:43:17 +0100 (CET)","from ideasonboard.com (unknown\n\t[IPv6:2001:b07:5d2e:52c9:cc1e:e404:491f:e6ea])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A7206480;\n\tTue, 19 Mar 2024 15:42:50 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"HMUUPhWW\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710859370;\n\tbh=j2vVCpDtLGIivH2y7CJYsqzAidh2YMxGOXe7tDAjXcY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=HMUUPhWWztlDhU7nfPcMvd036XkoN7ws3h76gm2Z01XiUnTyDer1Vs2+TXzdPDowx\n\t3tzO1CIlOUe8NQZeGQ62Xhh0akKKnXxiapC+7tnHu8YxcW93v4xD+s1d5yJp/JxHvG\n\tU+eOD8emhNRmYhWYDH4BrP6DuQgbubeGwul4G/60=","Date":"Tue, 19 Mar 2024 15:43:14 +0100","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tNayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","Message-ID":"<5rlhxo577xm42kmopglqk6nlrim65zeikbe5czax34kxlfr77j@jdiilceqtuhz>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>\n\t<U0OmhExn8VBWYMqq1wkczOoOKJ4s3CBAHI9jHv1X5ZbKMOmxPcDq-RvJpkI1N_XXSTDBk6imxd7Gdnq-0CUhKKE04YTapCSkr0SwGnJxvJQ=@protonmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<U0OmhExn8VBWYMqq1wkczOoOKJ4s3CBAHI9jHv1X5ZbKMOmxPcDq-RvJpkI1N_XXSTDBk6imxd7Gdnq-0CUhKKE04YTapCSkr0SwGnJxvJQ=@protonmail.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":29008,"web_url":"https://patchwork.libcamera.org/comment/29008/","msgid":"<sfEqdQg-lWP2I2OV2od2PY_Jxgs2MMX-JRQe2IRXlQ3Yk2FyFIM8HJEB3LZAC64DF3bW9RlKQ5b_IR6e0LJCvgRhU_k9lqnoJFHj2FTf_CA=@protonmail.com>","date":"2024-03-20T00:13:36","subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","submitter":{"id":133,"url":"https://patchwork.libcamera.org/api/people/133/","name":"Pőcze Barnabás","email":"pobrn@protonmail.com"},"content":"Hi\n\n\n2024. március 19., kedd 15:43 keltezéssel, Jacopo Mondi <jacopo.mondi@ideasonboard.com> írta:\n\n> Hi Barnabás\n> \n> On Fri, Mar 15, 2024 at 02:28:26AM +0000, Barnabás Pőcze wrote:\n> > Hi\n> >\n> >\n> > 2024. március 14., csütörtök 15:07 keltezéssel, Jacopo Mondi <jacopo.mondi@ideasonboard.com> írta:\n> >\n> > > [...]\n> > > diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > new file mode 100644\n> > > index 000000000000..4508214b864d\n> > > --- /dev/null\n> > > +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp\n> > > @@ -0,0 +1,1081 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2024, Ideas on Board Oy\n> > > + *\n> > > + * mali-c55.cpp - Pipeline Handler for ARM's Mali-C55 ISP\n> > > + */\n> > > +\n> > > +#include <algorithm>\n> > > +#include <array>\n> > > +#include <map>\n> > > +#include <memory>\n> > > +#include <set>\n> > > +#include <string>\n> > > +\n> > > +#include <linux/media-bus-format.h>\n> > > +#include <linux/media.h>\n> > > +\n> > > +#include <libcamera/base/log.h>\n> > > +\n> > > +#include <libcamera/camera.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> > > +namespace {\n> > > +\n> > > +bool isFormatRaw(const libcamera::PixelFormat &pixFmt)\n> > > +{\n> > > +\treturn libcamera::PixelFormatInfo::info(pixFmt).colourEncoding ==\n> > > +\t       libcamera::PixelFormatInfo::ColourEncodingRAW;\n> > > +}\n> > > +\n> > > +}; /* namespace */\n> > > +\n> > > +namespace libcamera {\n> >\n> > Does this need to be in the libcamera namespace? This will export a lot of\n> \n> Do you mean the pipeline handler and the associated classes ? Maybe we\n> should partition these even further in a per-pipeline namespaces, yes\n> \n> > symbols that don't really need to be exported as far as I can see.\n> \n> What are the implications in the generated .so file of exposing these\n> symbols in the libcamera namespace ? I don't think further namespace\n> nesting has any implication on the symbols' external visibility, but\n> it's only meant to restrict access to symbols within the code\n\nI was actually concerned about the external visibility; you're right that changing\nwhich named namespace the symbol is in does not change its visibility.\n\nWhat I had in mind is just using an anonymous namespace instead of \"libcamera\"\nin this file, because as far as I can tell, these symbols don't need to be exported.\nAn anonymous namespace would have multiple advantages:\n\n * avoids name conflicts (unlikely but still),\n * fewer public symbols usually coincide with less work for the dynamic loader,\n * enables certain optimizations.\n\nIn any case, this is not critical at all. I only remarked on it because\nit is such a low-hanging fruit in this case.\n\n\n> \n> \n> > (Maybe in the future?) (Although to be fair, this affects most [all?] pipeline handlers.)\n> \n> yeah, all pipelines (but rpi which has nested namespaces) are like\n> this if I'm not mistaken..\n> [...]\n\n\nRegards,\nBarnabás Pőcze","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 9D98EBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 20 Mar 2024 00:13:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 378CF62CA8;\n\tWed, 20 Mar 2024 01:13:45 +0100 (CET)","from mail-40133.protonmail.ch (mail-40133.protonmail.ch\n\t[185.70.40.133])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D664F61C6D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 20 Mar 2024 01:13:43 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=protonmail.com header.i=@protonmail.com\n\theader.b=\"SYcrN+9C\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com;\n\ts=protonmail3; t=1710893623; x=1711152823;\n\tbh=4KfgQ3ENNXzRgs26zZgW5mEYf/yW3zQKisKHq2eLJng=;\n\th=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References:\n\tFeedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID:\n\tMessage-ID:BIMI-Selector;\n\tb=SYcrN+9Cov0BqM/pjOA2/a9hs5HIpFU3F9cR/DVCLrIlVn10fR/meqfgnYlBuRijI\n\tn/z5bZZxLs+avCJu5AeBk0F8G0oSoCCSM5laG1T2VnkA55KcCd89mhbvbKeYrxeYf0\n\t+GgaG02YnPFKYXwDK7C3B8I3jpkJJMUeTgXurnrlcnx2GWa8pS/zx06UJ5bjYdiTUS\n\txSp/7D3CnoQp3k+4uWxpIh4+qouC4cJybzTS8ZXjr+0rd23yifK4JmeXs/fwnKsuRl\n\ttJdYkDA4UDoNtVSqgUfZc9gklQ77Zrp1j08fZ2N9y/P3AX8DPy7GNu2U9ASW4OZc8G\n\t+I1aqMlnUjbRQ==","Date":"Wed, 20 Mar 2024 00:13:36 +0000","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <pobrn@protonmail.com>","Cc":"Nayden Kanchev <nayden.kanchev@arm.com>,\n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH] libcamera: pipeline: Add Mali-C55 ISP pipeline","Message-ID":"<sfEqdQg-lWP2I2OV2od2PY_Jxgs2MMX-JRQe2IRXlQ3Yk2FyFIM8HJEB3LZAC64DF3bW9RlKQ5b_IR6e0LJCvgRhU_k9lqnoJFHj2FTf_CA=@protonmail.com>","In-Reply-To":"<5rlhxo577xm42kmopglqk6nlrim65zeikbe5czax34kxlfr77j@jdiilceqtuhz>","References":"<20240314140720.41728-1-jacopo.mondi@ideasonboard.com>\n\t<U0OmhExn8VBWYMqq1wkczOoOKJ4s3CBAHI9jHv1X5ZbKMOmxPcDq-RvJpkI1N_XXSTDBk6imxd7Gdnq-0CUhKKE04YTapCSkr0SwGnJxvJQ=@protonmail.com>\n\t<5rlhxo577xm42kmopglqk6nlrim65zeikbe5czax34kxlfr77j@jdiilceqtuhz>","Feedback-ID":"20568564:user:proton","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"quoted-printable","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]