[{"id":28940,"web_url":"https://patchwork.libcamera.org/comment/28940/","msgid":"<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","date":"2024-03-13T12:20:36","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"On Fri, 1 Mar 2024 at 21:21, Laurent Pinchart\n<laurent.pinchart@ideasonboard.com> wrote:\n>\n> Add a new CameraSensorRaw implementation of the CameraSensor interface\n> tailored to devices that implement the new V4L2 raw camera sensors API.\n>\n> This new class duplicates code from the CameraSensorLegacy class. The\n> two classes will be refactored to share code.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  Documentation/Doxyfile.in                  |    1 +\n>  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n>  src/libcamera/sensor/meson.build           |    1 +\n>  3 files changed, 1065 insertions(+)\n>  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n>\n> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> index 75326c1964e9..8bc55be60a59 100644\n> --- a/Documentation/Doxyfile.in\n> +++ b/Documentation/Doxyfile.in\n> @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n>                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n>                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n>                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n>                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> new file mode 100644\n> index 000000000000..8c17da5876a4\n> --- /dev/null\n> +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> @@ -0,0 +1,1063 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas on Board Oy.\n> + *\n> + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> + */\n> +\n> +#include <algorithm>\n> +#include <float.h>\n> +#include <iomanip>\n> +#include <limits.h>\n> +#include <map>\n> +#include <math.h>\n> +#include <memory>\n> +#include <optional>\n> +#include <string.h>\n> +#include <string>\n> +#include <vector>\n> +\n> +#include <libcamera/base/class.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/control_ids.h>\n> +#include <libcamera/controls.h>\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/orientation.h>\n> +#include <libcamera/property_ids.h>\n> +#include <libcamera/transform.h>\n> +\n> +#include <libcamera/ipa/core_ipa_interface.h>\n> +\n> +#include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/camera_lens.h\"\n> +#include \"libcamera/internal/camera_sensor.h\"\n> +#include \"libcamera/internal/camera_sensor_properties.h\"\n> +#include \"libcamera/internal/formats.h\"\n> +#include \"libcamera/internal/media_device.h\"\n> +#include \"libcamera/internal/sysfs.h\"\n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +class BayerFormat;\n> +class CameraLens;\n> +class MediaEntity;\n> +class SensorConfiguration;\n> +\n> +struct CameraSensorProperties;\n> +\n> +enum class Orientation;\n> +\n> +LOG_DECLARE_CATEGORY(CameraSensor)\n> +\n> +class CameraSensorRaw : public CameraSensor, protected Loggable\n> +{\n> +public:\n> +       CameraSensorRaw(const MediaEntity *entity);\n> +       ~CameraSensorRaw();\n> +\n> +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> +       match(MediaEntity *entity);\n> +\n> +       const std::string &model() const override { return model_; }\n> +       const std::string &id() const override { return id_; }\n> +\n> +       const MediaEntity *entity() const override { return entity_; }\n> +       V4L2Subdevice *device() override { return subdev_.get(); }\n> +\n> +       CameraLens *focusLens() override { return focusLens_.get(); }\n> +\n> +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> +       Size resolution() const override;\n> +\n> +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                                     const Size &size) const override;\n> +       int setFormat(V4L2SubdeviceFormat *format,\n> +                     Transform transform = Transform::Identity) override;\n> +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> +\n> +       int applyConfiguration(const SensorConfiguration &config,\n> +                              Transform transform = Transform::Identity,\n> +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> +\n> +       const ControlList &properties() const override { return properties_; }\n> +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> +       Transform computeTransform(Orientation *orientation) const override;\n> +       BayerFormat::Order bayerOrder(Transform t) const override;\n> +\n> +       const ControlInfoMap &controls() const override;\n> +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> +       int setControls(ControlList *ctrls) override;\n> +\n> +       const std::vector<controls::draft::TestPatternModeEnum> &\n> +       testPatternModes() const override { return testPatternModes_; }\n> +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> +\n> +protected:\n> +       std::string logPrefix() const override;\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> +\n> +       std::optional<int> init();\n> +       int initProperties();\n> +       void initStaticProperties();\n> +       void initTestPatternModes();\n> +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> +\n> +       const MediaEntity *entity_;\n> +       std::unique_ptr<V4L2Subdevice> subdev_;\n> +\n> +       struct Streams {\n> +               V4L2Subdevice::Stream sink;\n> +               V4L2Subdevice::Stream source;\n> +       };\n> +\n> +       struct {\n> +               Streams image;\n> +               std::optional<Streams> edata;\n> +       } streams_;\n> +\n> +       const CameraSensorProperties *staticProps_;\n> +\n> +       std::string model_;\n> +       std::string id_;\n> +\n> +       V4L2Subdevice::Formats formats_;\n> +       std::vector<unsigned int> mbusCodes_;\n> +       std::vector<Size> sizes_;\n> +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> +       controls::draft::TestPatternModeEnum testPatternMode_;\n> +\n> +       Size pixelArraySize_;\n> +       Rectangle activeArea_;\n> +       BayerFormat::Order cfaPattern_;\n> +       bool supportFlips_;\n> +       bool flipsAlterBayerOrder_;\n> +       Orientation mountingOrientation_;\n> +\n> +       ControlList properties_;\n> +\n> +       std::unique_ptr<CameraLens> focusLens_;\n> +};\n> +\n> +/**\n> + * \\class CameraSensorRaw\n> + * \\brief A camera sensor based on V4L2 subdevices\n> + *\n> + * This class supports single-subdev sensors with a single source pad and one\n> + * or two internal sink pads (for the image and embedded data streams).\n> + */\n> +\n> +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> +{\n> +}\n> +\n> +CameraSensorRaw::~CameraSensorRaw() = default;\n> +\n> +std::variant<std::unique_ptr<CameraSensor>, int>\n> +CameraSensorRaw::match(MediaEntity *entity)\n> +{\n> +       /* Check the entity type. */\n> +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported entity type (\"\n> +                       << utils::to_underlying(entity->type())\n> +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /* Count and check the number of pads. */\n> +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> +                                               | MEDIA_PAD_FL_SOURCE\n> +                                               | MEDIA_PAD_FL_INTERNAL;\n> +       unsigned int numSinks = 0;\n> +       unsigned int numSources = 0;\n> +\n> +       for (const MediaPad *pad : entity->pads()) {\n> +               switch (pad->flags() & kPadFlagsMask) {\n> +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> +                       numSinks++;\n> +                       break;\n> +\n> +               case MEDIA_PAD_FL_SOURCE:\n> +                       numSources++;\n> +                       break;\n> +\n> +               default:\n> +                       libcamera::LOG(CameraSensor, Debug)\n> +                               << entity->name() << \": unsupported pad \" << pad->index()\n> +                               << \" type \" << utils::hex(pad->flags());\n> +                       return { 0 };\n> +               }\n> +       }\n> +\n> +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported number of sinks (\"\n> +                       << numSinks << \") or sources (\" << numSources << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /*\n> +        * The entity matches. Create the camera sensor and initialize it. The\n> +        * init() function will perform further match checks.\n> +        */\n> +       std::unique_ptr<CameraSensorRaw> sensor =\n> +               std::make_unique<CameraSensorRaw>(entity);\n> +\n> +       std::optional<int> err = sensor->init();\n> +       if (err)\n> +               return { *err };\n> +\n> +       return { std::move(sensor) };\n> +}\n> +\n> +std::optional<int> CameraSensorRaw::init()\n> +{\n> +       /* Create and open the subdev. */\n> +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> +       int ret = subdev_->open();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 1. Identify the pads.\n> +        */\n> +\n> +       /*\n> +        * First locate the source pad. The match() function guarantees there\n> +        * is one and only one source pad.\n> +        */\n> +       unsigned int sourcePad = UINT_MAX;\n> +\n> +       for (const MediaPad *pad : entity_->pads()) {\n> +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> +                       sourcePad = pad->index();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * Iterate over the routes to identify the streams on the source pad,\n> +        * and the internal sink pads.\n> +        */\n> +       V4L2Subdevice::Routing routing = {};\n> +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       bool imageStreamFound = false;\n> +\n> +       for (const V4L2Subdevice::Route &route : routing) {\n> +               if (route.source.pad != sourcePad) {\n> +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> +                       return { -EINVAL };\n> +               }\n> +\n> +               /* Identify the stream type based on the supported formats. */\n> +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> +\n> +               std::optional<MediaBusFormatInfo::Type> type;\n> +\n> +               for (const auto &[code, sizes] : formats) {\n> +                       const MediaBusFormatInfo &info =\n> +                               MediaBusFormatInfo::info(code);\n> +                       if (info.isValid()) {\n> +                               type = info.type;\n> +                               break;\n> +                       }\n> +               }\n> +\n> +               if (!type) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"No known format on pad \" << route.source;\n> +                       continue;\n> +               }\n> +\n> +               switch (*type) {\n> +               case MediaBusFormatInfo::Type::Image:\n> +                       if (imageStreamFound) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal image streams (\"\n> +                                       << streams_.image.sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       imageStreamFound = true;\n> +                       streams_.image.sink = route.sink;\n> +                       streams_.image.source = route.source;\n> +                       break;\n> +\n?> +               case MediaBusFormatInfo::Type::Metadata:\n> +                       /*\n> +                        * Skip metadata streams that are not sensor embedded\n> +                        * data. The source stream reports a generic metadata\n> +                        * format, check the sink stream for the exact format.\n> +                        */\n> +                       formats = subdev_->formats(route.sink);\n> +                       if (formats.size() != 1)\n> +                               continue;\n\nShould this test be if (!formats.size()) insead?  It might be possible\nto have multiple metadata types.\n\n> +\n> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> +                               continue;\n\nThe IMX219 driver (from Tomi's kernel tree) advertises\nMEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\ndata stream, which translates to a type of\nMediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\nshould this check include MediaBusFormatInfo::Type::Metadata?\n\n> +\n> +                       if (streams_.edata) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal embedded data streams (\"\n> +                                       << streams_.edata->sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       streams_.edata = { route.sink, route.source };\n> +                       break;\n> +\n> +               default:\n> +                       break;\n> +               }\n> +       }\n> +\n> +       if (!imageStreamFound) {\n> +               LOG(CameraSensor, Error) << \"No image stream found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       LOG(CameraSensor, Debug)\n> +               << \"Found image stream \" << streams_.image.sink\n> +               << \" -> \" << streams_.image.source;\n> +\n> +       if (streams_.edata)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Found embedded data stream \" << streams_.edata->sink\n> +                       << \" -> \" << streams_.edata->source;\n> +\n> +       /*\n> +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> +        * array order for the image stream.\n> +        */\n> +\n> +       /*\n> +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> +        * the internal image sink pad as it is guaranteed to expose a single\n> +        * format, and is not affected by flips.\n> +        */\n> +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> +       if (formats.size() != 1) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Image pad has \" << formats.size()\n> +                       << \" formats, expected 1\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       uint32_t nativeFormat = formats.cbegin()->first;\n> +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> +       if (!bayerFormat.isValid()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Invalid native format \" << nativeFormat;\n> +               return { 0 };\n> +       }\n> +\n> +       cfaPattern_ = bayerFormat.order;\n\ncfaPattern_ does not account for the current transform applied to the\nsensor.  So we could end up with the wrong Bayer order.  Also related,\nflipsAlterBayerOrder_ never gets set correctly.  I do have a patch to\nfix both that can be squashed into this if you want.\n\nRegards,\nNaush\n\n> +\n> +       /*\n> +        * Retrieve and cache the media bus codes and sizes on the source image\n> +        * stream.\n> +        */\n> +       formats_ = subdev_->formats(streams_.image.source);\n> +       if (formats_.empty()) {\n> +               LOG(CameraSensor, Error) << \"No image format found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       /* Populate and sort the media bus codes and the sizes. */\n> +       for (const auto &[code, ranges] : formats_) {\n> +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> +                       continue;\n> +\n> +               mbusCodes_.push_back(code);\n> +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> +                              [](const SizeRange &range) { return range.max; });\n> +       }\n> +\n> +       if (mbusCodes_.empty()) {\n> +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> +               return { 0 };\n> +       }\n> +\n> +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> +       std::sort(sizes_.begin(), sizes_.end());\n> +\n> +       /*\n> +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> +        * they are the keys in the formats map.\n> +        */\n> +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> +       sizes_.erase(last, sizes_.end());\n> +\n> +       /*\n> +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> +        * all the expected selection rectangles are supported.\n> +        */\n> +\n> +       Rectangle rect;\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> +               return { ret };\n> +       }\n> +\n> +       pixelArraySize_ = rect.size();\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> +                                   &activeArea_);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> +               return { ret };\n> +       }\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * 4. Verify that all required controls are present.\n> +        */\n> +\n> +       const ControlIdMap &controls = subdev_->controls().idmap();\n> +\n> +       static constexpr uint32_t mandatoryControls[] = {\n> +               V4L2_CID_ANALOGUE_GAIN,\n> +               V4L2_CID_CAMERA_ORIENTATION,\n> +               V4L2_CID_EXPOSURE,\n> +               V4L2_CID_HBLANK,\n> +               V4L2_CID_PIXEL_RATE,\n> +               V4L2_CID_VBLANK,\n> +       };\n> +\n> +       ret = 0;\n> +\n> +       for (uint32_t ctrl : mandatoryControls) {\n> +               if (!controls.count(ctrl)) {\n> +                       LOG(CameraSensor, Error)\n> +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> +                               << \" not available\";\n> +                       ret = -EINVAL;\n> +               }\n> +       }\n> +\n> +       if (ret) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"The sensor kernel driver needs to be fixed\";\n> +               LOG(CameraSensor, Error)\n> +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * Verify if sensor supports horizontal/vertical flips\n> +        *\n> +        * \\todo Handle horizontal and vertical flips independently.\n> +        */\n> +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> +               supportFlips_ = true;\n> +\n> +       if (!supportFlips_)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> +\n> +       /*\n> +        * 5. Discover ancillary devices.\n> +        *\n> +        * \\todo This code may be shared by different V4L2 sensor classes.\n> +        */\n> +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> +               switch (ancillary->function()) {\n> +               case MEDIA_ENT_F_LENS:\n> +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> +                       ret = focusLens_->init();\n> +                       if (ret) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Lens initialisation failed, lens disabled\";\n> +                               focusLens_.reset();\n> +                       }\n> +                       break;\n> +\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported ancillary entity function \"\n> +                               << ancillary->function();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * 6. Initialize properties.\n> +        */\n> +\n> +       ret = initProperties();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 7. Initialize controls.\n> +        */\n> +\n> +       /*\n> +        * Set HBLANK to the minimum to start with a well-defined line length,\n> +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> +        * minimum line length in their calculations.\n> +        *\n> +        * At present, there is no way of knowing if a control is read-only.\n> +        * As a workaround, assume that if the minimum and maximum values of\n> +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> +        * is read-only.\n> +        *\n> +        * \\todo The control API ought to have a flag to specify if a control\n> +        * is read-only which could be used below.\n> +        */\n> +       const ControlInfoMap &ctrls = subdev_->controls();\n> +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> +\n> +               if (hblankMin != hblankMax) {\n> +                       ControlList ctrl(subdev_->controls());\n> +\n> +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> +                       ret = subdev_->setControls(&ctrl);\n> +                       if (ret)\n> +                               return { ret };\n> +               }\n> +       }\n> +\n> +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       return {};\n> +}\n> +\n> +int CameraSensorRaw::initProperties()\n> +{\n> +       model_ = subdev_->model();\n> +       properties_.set(properties::Model, utils::toAscii(model_));\n> +\n> +       /* Generate a unique ID for the sensor. */\n> +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> +       if (id_.empty()) {\n> +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       /* Initialize the static properties from the sensor database. */\n> +       initStaticProperties();\n> +\n> +       /* Retrieve and register properties from the kernel interface. */\n> +       const ControlInfoMap &controls = subdev_->controls();\n> +\n> +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> +       if (orientation != controls.end()) {\n> +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> +               int32_t propertyValue;\n> +\n> +               switch (v4l2Orientation) {\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported camera location \"\n> +                               << v4l2Orientation << \", setting to External\";\n> +                       [[fallthrough]];\n> +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> +                       propertyValue = properties::CameraLocationExternal;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> +                       propertyValue = properties::CameraLocationFront;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_BACK:\n> +                       propertyValue = properties::CameraLocationBack;\n> +                       break;\n> +               }\n> +               properties_.set(properties::Location, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> +       }\n> +\n> +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> +       if (rotationControl != controls.end()) {\n> +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> +\n> +               /*\n> +                * Cache the Transform associated with the camera mounting\n> +                * rotation for later use in computeTransform().\n> +                */\n> +               bool success;\n> +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> +               if (!success) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Invalid rotation of \" << propertyValue\n> +                               << \" degrees - ignoring\";\n> +                       mountingOrientation_ = Orientation::Rotate0;\n> +               }\n> +\n> +               properties_.set(properties::Rotation, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning)\n> +                       << \"Rotation control not available, default to 0 degrees\";\n> +               properties_.set(properties::Rotation, 0);\n> +               mountingOrientation_ = Orientation::Rotate0;\n> +       }\n> +\n> +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> +\n> +       /* Color filter array pattern. */\n> +       uint32_t cfa;\n\nGCC 12 complains about cfa possibly used without being initialised.\nThe compiler should really know that the switch below covers all enum\ncases, but it does not.\n\n\n> +\n> +       switch (cfaPattern_) {\n> +       case BayerFormat::BGGR:\n> +               cfa = properties::draft::BGGR;\n> +               break;\n> +       case BayerFormat::GBRG:\n> +               cfa = properties::draft::GBRG;\n> +               break;\n> +       case BayerFormat::GRBG:\n> +               cfa = properties::draft::GRBG;\n> +               break;\n> +       case BayerFormat::RGGB:\n> +               cfa = properties::draft::RGGB;\n> +               break;\n> +       case BayerFormat::MONO:\n> +               cfa = properties::draft::MONO;\n> +               break;\n> +       }\n> +\n> +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> +\n> +       return 0;\n> +}\n> +\n> +void CameraSensorRaw::initStaticProperties()\n> +{\n> +       staticProps_ = CameraSensorProperties::get(model_);\n> +       if (!staticProps_)\n> +               return;\n> +\n> +       /* Register the properties retrieved from the sensor database. */\n> +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> +\n> +       initTestPatternModes();\n> +}\n> +\n> +void CameraSensorRaw::initTestPatternModes()\n> +{\n> +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> +       if (v4l2TestPattern == controls().end()) {\n> +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> +               return;\n> +       }\n> +\n> +       const auto &testPatternModes = staticProps_->testPatternModes;\n> +       if (testPatternModes.empty()) {\n> +               /*\n> +                * The camera sensor supports test patterns but we don't know\n> +                * how to map them so this should be fixed.\n> +                */\n> +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> +                                        << model() << \"\\'\";\n> +               return;\n> +       }\n> +\n> +       /*\n> +        * Create a map that associates the V4L2 control index to the test\n> +        * pattern mode by reversing the testPatternModes map provided by the\n> +        * camera sensor properties. This makes it easier to verify if the\n> +        * control index is supported in the below for loop that creates the\n> +        * list of supported test patterns.\n> +        */\n> +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> +       for (const auto &it : testPatternModes)\n> +               indexToTestPatternMode[it.second] = it.first;\n> +\n> +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> +               const int32_t index = value.get<int32_t>();\n> +\n> +               const auto it = indexToTestPatternMode.find(index);\n> +               if (it == indexToTestPatternMode.end()) {\n> +                       LOG(CameraSensor, Debug)\n> +                               << \"Test pattern mode \" << index << \" ignored\";\n> +                       continue;\n> +               }\n> +\n> +               testPatternModes_.push_back(it->second);\n> +       }\n> +}\n> +\n> +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> +{\n> +       std::vector<Size> sizes;\n> +\n> +       const auto &format = formats_.find(mbusCode);\n> +       if (format == formats_.end())\n> +               return sizes;\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> +Size CameraSensorRaw::resolution() const\n> +{\n> +       return std::min(sizes_.back(), activeArea_.size());\n> +}\n> +\n> +V4L2SubdeviceFormat\n> +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                          const Size &size) const\n> +{\n> +       unsigned int desiredArea = size.width * size.height;\n> +       unsigned int bestArea = UINT_MAX;\n> +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> +       float bestRatio = FLT_MAX;\n> +       const Size *bestSize = nullptr;\n> +       uint32_t bestCode = 0;\n> +\n> +       for (unsigned int code : mbusCodes) {\n> +               const auto formats = formats_.find(code);\n> +               if (formats == formats_.end())\n> +                       continue;\n> +\n> +               for (const SizeRange &range : formats->second) {\n> +                       const Size &sz = range.max;\n> +\n> +                       if (sz.width < size.width || sz.height < size.height)\n> +                               continue;\n> +\n> +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> +                       unsigned int area = sz.width * sz.height;\n> +                       unsigned int areaDiff = area - desiredArea;\n> +\n> +                       if (ratioDiff > bestRatio)\n> +                               continue;\n> +\n> +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> +                               bestRatio = ratioDiff;\n> +                               bestArea = areaDiff;\n> +                               bestSize = &sz;\n> +                               bestCode = code;\n> +                       }\n> +               }\n> +       }\n> +\n> +       if (!bestSize) {\n> +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> +               return {};\n> +       }\n> +\n> +       V4L2SubdeviceFormat format{\n> +               .code = bestCode,\n> +               .size = *bestSize,\n> +               .colorSpace = ColorSpace::Raw,\n> +       };\n> +\n> +       return format;\n> +}\n> +\n> +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> +{\n> +       /* Configure flips if the sensor supports that. */\n> +       if (supportFlips_) {\n> +               ControlList flipCtrls(subdev_->controls());\n> +\n> +               flipCtrls.set(V4L2_CID_HFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> +               flipCtrls.set(V4L2_CID_VFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> +\n> +               int ret = subdev_->setControls(&flipCtrls);\n> +               if (ret)\n> +                       return ret;\n> +       }\n> +\n> +       /* Apply format on the subdev. */\n> +       int ret = subdev_->setFormat(streams_.image.source, format);\n> +       if (ret)\n> +               return ret;\n> +\n> +       subdev_->updateControlInfo();\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> +{\n> +       return subdev_->setFormat(streams_.image.source, format,\n> +                                 V4L2Subdevice::Whence::TryFormat);\n> +}\n> +\n> +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> +                                       Transform transform,\n> +                                       V4L2SubdeviceFormat *sensorFormat)\n> +{\n> +       if (!config.isValid()) {\n> +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       std::vector<unsigned int> filteredCodes;\n> +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> +                    std::back_inserter(filteredCodes),\n> +                    [&config](unsigned int mbusCode) {\n> +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> +                            if (bayer.bitDepth == config.bitDepth)\n> +                                    return true;\n> +                            return false;\n> +                    });\n> +       if (filteredCodes.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Cannot find any format with bit depth \"\n> +                       << config.bitDepth;\n> +               return -EINVAL;\n> +       }\n> +\n> +       /*\n> +        * Compute the sensor's data frame size by applying the cropping\n> +        * rectangle, subsampling and output crop to the sensor's pixel array\n> +        * size.\n> +        *\n> +        * \\todo The actual size computation is for now ignored and only the\n> +        * output size is considered. This implies that resolutions obtained\n> +        * with two different cropping/subsampling will look identical and\n> +        * only the first found one will be considered.\n> +        */\n> +       V4L2SubdeviceFormat subdevFormat = {};\n> +       for (unsigned int code : filteredCodes) {\n> +               for (const Size &size : sizes(code)) {\n> +                       if (size.width != config.outputSize.width ||\n> +                           size.height != config.outputSize.height)\n> +                               continue;\n> +\n> +                       subdevFormat.code = code;\n> +                       subdevFormat.size = size;\n> +                       break;\n> +               }\n> +       }\n> +       if (!subdevFormat.code) {\n> +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       int ret = setFormat(&subdevFormat, transform);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * Return to the caller the format actually applied to the sensor.\n> +        * This is relevant if transform has changed the bayer pattern order.\n> +        */\n> +       if (sensorFormat)\n> +               *sensorFormat = subdevFormat;\n> +\n> +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> +       /* \\todo Handle scaling in the digital domain. */\n> +\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> +{\n> +       info->model = model();\n> +\n> +       /*\n> +        * The active area size is a static property, while the crop\n> +        * rectangle needs to be re-read as it depends on the sensor\n> +        * configuration.\n> +        */\n> +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> +\n> +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                       &info->analogCrop);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> +        * are defined relatively to the active pixel area, while V4L2's\n> +        * TGT_CROP target is defined in respect to the full pixel array.\n> +        *\n> +        * Compensate it by subtracting the active area offset.\n> +        */\n> +       info->analogCrop.x -= activeArea_.x;\n> +       info->analogCrop.y -= activeArea_.y;\n> +\n> +       /* The bit depth and image size depend on the currently applied format. */\n> +       V4L2SubdeviceFormat format{};\n> +       ret = subdev_->getFormat(streams_.image.source, &format);\n> +       if (ret)\n> +               return ret;\n> +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> +       info->outputSize = format.size;\n> +\n> +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> +\n> +       /*\n> +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> +        */\n> +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> +                                                  V4L2_CID_HBLANK,\n> +                                                  V4L2_CID_VBLANK });\n> +       if (ctrls.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Failed to retrieve camera info controls\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> +\n> +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> +\n> +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> +\n> +       return 0;\n> +}\n> +\n> +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> +{\n> +       /*\n> +        * If we cannot do any flips we cannot change the native camera mounting\n> +        * orientation.\n> +        */\n> +       if (!supportFlips_) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       /*\n> +        * Now compute the required transform to obtain 'orientation' starting\n> +        * from the mounting rotation.\n> +        *\n> +        * As a note:\n> +        *      orientation / mountingOrientation_ = transform\n> +        *      mountingOrientation_ * transform = orientation\n> +        */\n> +       Transform transform = *orientation / mountingOrientation_;\n> +\n> +       /*\n> +        * If transform contains any Transpose we cannot do it, so adjust\n> +        * 'orientation' to report the image native orientation and return Identity.\n> +        */\n> +       if (!!(transform & Transform::Transpose)) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       return transform;\n> +}\n> +\n> +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> +{\n> +       if (!flipsAlterBayerOrder_)\n> +               return cfaPattern_;\n> +\n> +       /*\n> +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> +        * using the rest of the Bayer format supplied by the caller.\n> +        */\n> +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> +       return format.transform(t).order;\n> +}\n> +\n> +const ControlInfoMap &CameraSensorRaw::controls() const\n> +{\n> +       return subdev_->controls();\n> +}\n> +\n> +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> +{\n> +       return subdev_->getControls(ids);\n> +}\n> +\n> +int CameraSensorRaw::setControls(ControlList *ctrls)\n> +{\n> +       return subdev_->setControls(ctrls);\n> +}\n> +\n> +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternMode_ == mode)\n> +               return 0;\n> +\n> +       if (testPatternModes_.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Camera sensor does not support test pattern modes.\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       return applyTestPatternMode(mode);\n> +}\n> +\n> +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternModes_.empty())\n> +               return 0;\n> +\n> +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> +                           mode);\n> +       if (it == testPatternModes_.end()) {\n> +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> +                                        << mode;\n> +               return -EINVAL;\n> +       }\n> +\n> +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> +\n> +       int32_t index = staticProps_->testPatternModes.at(mode);\n> +       ControlList ctrls{ controls() };\n> +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> +\n> +       int ret = setControls(&ctrls);\n> +       if (ret)\n> +               return ret;\n> +\n> +       testPatternMode_ = mode;\n> +\n> +       return 0;\n> +}\n> +\n> +std::string CameraSensorRaw::logPrefix() const\n> +{\n> +       return \"'\" + entity_->name() + \"'\";\n> +}\n> +\n> +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> index e83020fc22c3..e3c39aaf13b8 100644\n> --- a/src/libcamera/sensor/meson.build\n> +++ b/src/libcamera/sensor/meson.build\n> @@ -4,4 +4,5 @@ libcamera_sources += files([\n>      'camera_sensor.cpp',\n>      'camera_sensor_legacy.cpp',\n>      'camera_sensor_properties.cpp',\n> +    'camera_sensor_raw.cpp',\n>  ])\n> --\n> Regards,\n>\n> Laurent Pinchart\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 501B7BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:21:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7CE7762C93;\n\tWed, 13 Mar 2024 13:21:15 +0100 (CET)","from mail-yw1-x112c.google.com (mail-yw1-x112c.google.com\n\t[IPv6:2607:f8b0:4864:20::112c])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DBCEE62C86\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:21:13 +0100 (CET)","by mail-yw1-x112c.google.com with SMTP id\n\t00721157ae682-609f24f447cso58987087b3.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 05:21:13 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"CXmHxTCe\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1710332472; x=1710937272;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=5Q+6CHK4gJqInUCLngGfOECboPsd+n1J2w8g7qZKvjU=;\n\tb=CXmHxTCek93zbBm0UR0aiI1LTDo5sKwMOiDpDSyp8J0AwBddhlaKV5KT+x7bOdDW6c\n\tcvJqH334cXbl1EcF3fHT8NQ8Uq0gS2X3+u/IuU4LOPX9ZB99HXTSh4/v5yJuPLQP0Qob\n\tN7f7lzfNLo7V4oQASKS4+bWxwFfdgexJUVQghUlKz92RFPZqg7RCpqqAyfy02urgOH1J\n\t9DKfzIfVK12GnhApvxuo14jHvubhDeYRIETH0q14bnmNgoXh5DQb1Sgb3087Dpe+IA7e\n\tD1DegXYZsaObO1prcEdm8qMqplcjf5lhUfXZ52fMbEgfGxy6YURDqnvEpAT2JxdyzlOl\n\tz0gQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1710332472; x=1710937272;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=5Q+6CHK4gJqInUCLngGfOECboPsd+n1J2w8g7qZKvjU=;\n\tb=Jey0plezdURke7X+LdCPiQveZKlt4ezUpX8hn8nipQ5ZPnylIuY/8e8ZD9H4sDNkgz\n\tuJkWzxo72uFNLev1IsbUrfpsQldYZkY+oQZwgXx2E+Nc60iDoRfJ4efCk30EyzqyWwLS\n\tbWhkLnicVmm941ia36E1Emc+9nQWf7qOtZnpH7erYBaFBe0jLbsvxMepJa0R6pZ01dja\n\t2dyJcLwJPqvN2/TzmncFfBuEAiaea3OGaMDNy1mdQgsrq0Ax70sRk0+CoAkYBaKvpzlH\n\tTBnfq+/MdIIhrTE7vdKf2lIl8zdki1L8de2/cV5NiNhHIc/FKVZPW5ctIwHmXoZNXyKh\n\thmiQ==","X-Gm-Message-State":"AOJu0YwyyxaCNYW2tE+pRF1dDMFIEvcmPONxnkbjVUz/FRR0QmyjdB8g\n\tabVe+KR//91b2oHWpaQDUx1A+hQc8PVISEmpaN3MKZ+ybj/0eWgjX2yh4PvAA3SdkX6Sv4cEe3V\n\tlq2IaJM1gq0pQLGPUrrXL4Udo9ULETTN/RX6DOQ==","X-Google-Smtp-Source":"AGHT+IFTkCrVtUzGYwQcRsym4/KEMfRgEXTnvl1bQ2iO5JiRhODtg+8qPxZCltVVgesyieja90UtGWepsuPsmia9SmQ=","X-Received":"by 2002:a81:5288:0:b0:60a:3ec:b078 with SMTP id\n\tg130-20020a815288000000b0060a03ecb078mr2500924ywb.12.1710332472391;\n\tWed, 13 Mar 2024 05:21:12 -0700 (PDT)","MIME-Version":"1.0","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","In-Reply-To":"<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Wed, 13 Mar 2024 12:20:36 +0000","Message-ID":"<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28941,"web_url":"https://patchwork.libcamera.org/comment/28941/","msgid":"<CAEmqJPpN7vLojATfFkSjA02GbSmdgLagz0SqAnNyxqyOjaiRwg@mail.gmail.com>","date":"2024-03-13T12:26:43","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"On Wed, 13 Mar 2024 at 12:20, Naushir Patuck <naush@raspberrypi.com> wrote:\n>\n> On Fri, 1 Mar 2024 at 21:21, Laurent Pinchart\n> <laurent.pinchart@ideasonboard.com> wrote:\n> >\n> > Add a new CameraSensorRaw implementation of the CameraSensor interface\n> > tailored to devices that implement the new V4L2 raw camera sensors API.\n> >\n> > This new class duplicates code from the CameraSensorLegacy class. The\n> > two classes will be refactored to share code.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  Documentation/Doxyfile.in                  |    1 +\n> >  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n> >  src/libcamera/sensor/meson.build           |    1 +\n> >  3 files changed, 1065 insertions(+)\n> >  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n> >\n> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> > index 75326c1964e9..8bc55be60a59 100644\n> > --- a/Documentation/Doxyfile.in\n> > +++ b/Documentation/Doxyfile.in\n> > @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n> >                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n> >                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> > +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n> >                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n> >                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> > diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > new file mode 100644\n> > index 000000000000..8c17da5876a4\n> > --- /dev/null\n> > +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > @@ -0,0 +1,1063 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy.\n> > + *\n> > + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <float.h>\n> > +#include <iomanip>\n> > +#include <limits.h>\n> > +#include <map>\n> > +#include <math.h>\n> > +#include <memory>\n> > +#include <optional>\n> > +#include <string.h>\n> > +#include <string>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/base/class.h>\n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/utils.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/control_ids.h>\n> > +#include <libcamera/controls.h>\n> > +#include <libcamera/geometry.h>\n> > +#include <libcamera/orientation.h>\n> > +#include <libcamera/property_ids.h>\n> > +#include <libcamera/transform.h>\n> > +\n> > +#include <libcamera/ipa/core_ipa_interface.h>\n> > +\n> > +#include \"libcamera/internal/bayer_format.h\"\n> > +#include \"libcamera/internal/camera_lens.h\"\n> > +#include \"libcamera/internal/camera_sensor.h\"\n> > +#include \"libcamera/internal/camera_sensor_properties.h\"\n> > +#include \"libcamera/internal/formats.h\"\n> > +#include \"libcamera/internal/media_device.h\"\n> > +#include \"libcamera/internal/sysfs.h\"\n> > +#include \"libcamera/internal/v4l2_subdevice.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +class BayerFormat;\n> > +class CameraLens;\n> > +class MediaEntity;\n> > +class SensorConfiguration;\n> > +\n> > +struct CameraSensorProperties;\n> > +\n> > +enum class Orientation;\n> > +\n> > +LOG_DECLARE_CATEGORY(CameraSensor)\n> > +\n> > +class CameraSensorRaw : public CameraSensor, protected Loggable\n> > +{\n> > +public:\n> > +       CameraSensorRaw(const MediaEntity *entity);\n> > +       ~CameraSensorRaw();\n> > +\n> > +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> > +       match(MediaEntity *entity);\n> > +\n> > +       const std::string &model() const override { return model_; }\n> > +       const std::string &id() const override { return id_; }\n> > +\n> > +       const MediaEntity *entity() const override { return entity_; }\n> > +       V4L2Subdevice *device() override { return subdev_.get(); }\n> > +\n> > +       CameraLens *focusLens() override { return focusLens_.get(); }\n> > +\n> > +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> > +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> > +       Size resolution() const override;\n> > +\n> > +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                                     const Size &size) const override;\n> > +       int setFormat(V4L2SubdeviceFormat *format,\n> > +                     Transform transform = Transform::Identity) override;\n> > +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> > +\n> > +       int applyConfiguration(const SensorConfiguration &config,\n> > +                              Transform transform = Transform::Identity,\n> > +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> > +\n> > +       const ControlList &properties() const override { return properties_; }\n> > +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> > +       Transform computeTransform(Orientation *orientation) const override;\n> > +       BayerFormat::Order bayerOrder(Transform t) const override;\n> > +\n> > +       const ControlInfoMap &controls() const override;\n> > +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> > +       int setControls(ControlList *ctrls) override;\n> > +\n> > +       const std::vector<controls::draft::TestPatternModeEnum> &\n> > +       testPatternModes() const override { return testPatternModes_; }\n> > +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> > +\n> > +protected:\n> > +       std::string logPrefix() const override;\n> > +\n> > +private:\n> > +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> > +\n> > +       std::optional<int> init();\n> > +       int initProperties();\n> > +       void initStaticProperties();\n> > +       void initTestPatternModes();\n> > +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> > +\n> > +       const MediaEntity *entity_;\n> > +       std::unique_ptr<V4L2Subdevice> subdev_;\n> > +\n> > +       struct Streams {\n> > +               V4L2Subdevice::Stream sink;\n> > +               V4L2Subdevice::Stream source;\n> > +       };\n> > +\n> > +       struct {\n> > +               Streams image;\n> > +               std::optional<Streams> edata;\n> > +       } streams_;\n> > +\n> > +       const CameraSensorProperties *staticProps_;\n> > +\n> > +       std::string model_;\n> > +       std::string id_;\n> > +\n> > +       V4L2Subdevice::Formats formats_;\n> > +       std::vector<unsigned int> mbusCodes_;\n> > +       std::vector<Size> sizes_;\n> > +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> > +       controls::draft::TestPatternModeEnum testPatternMode_;\n> > +\n> > +       Size pixelArraySize_;\n> > +       Rectangle activeArea_;\n> > +       BayerFormat::Order cfaPattern_;\n> > +       bool supportFlips_;\n> > +       bool flipsAlterBayerOrder_;\n> > +       Orientation mountingOrientation_;\n> > +\n> > +       ControlList properties_;\n> > +\n> > +       std::unique_ptr<CameraLens> focusLens_;\n> > +};\n> > +\n> > +/**\n> > + * \\class CameraSensorRaw\n> > + * \\brief A camera sensor based on V4L2 subdevices\n> > + *\n> > + * This class supports single-subdev sensors with a single source pad and one\n> > + * or two internal sink pads (for the image and embedded data streams).\n> > + */\n> > +\n> > +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> > +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> > +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> > +{\n> > +}\n> > +\n> > +CameraSensorRaw::~CameraSensorRaw() = default;\n> > +\n> > +std::variant<std::unique_ptr<CameraSensor>, int>\n> > +CameraSensorRaw::match(MediaEntity *entity)\n> > +{\n> > +       /* Check the entity type. */\n> > +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> > +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported entity type (\"\n> > +                       << utils::to_underlying(entity->type())\n> > +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /* Count and check the number of pads. */\n> > +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> > +                                               | MEDIA_PAD_FL_SOURCE\n> > +                                               | MEDIA_PAD_FL_INTERNAL;\n> > +       unsigned int numSinks = 0;\n> > +       unsigned int numSources = 0;\n> > +\n> > +       for (const MediaPad *pad : entity->pads()) {\n> > +               switch (pad->flags() & kPadFlagsMask) {\n> > +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> > +                       numSinks++;\n> > +                       break;\n> > +\n> > +               case MEDIA_PAD_FL_SOURCE:\n> > +                       numSources++;\n> > +                       break;\n> > +\n> > +               default:\n> > +                       libcamera::LOG(CameraSensor, Debug)\n> > +                               << entity->name() << \": unsupported pad \" << pad->index()\n> > +                               << \" type \" << utils::hex(pad->flags());\n> > +                       return { 0 };\n> > +               }\n> > +       }\n> > +\n> > +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported number of sinks (\"\n> > +                       << numSinks << \") or sources (\" << numSources << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /*\n> > +        * The entity matches. Create the camera sensor and initialize it. The\n> > +        * init() function will perform further match checks.\n> > +        */\n> > +       std::unique_ptr<CameraSensorRaw> sensor =\n> > +               std::make_unique<CameraSensorRaw>(entity);\n> > +\n> > +       std::optional<int> err = sensor->init();\n> > +       if (err)\n> > +               return { *err };\n> > +\n> > +       return { std::move(sensor) };\n> > +}\n> > +\n> > +std::optional<int> CameraSensorRaw::init()\n> > +{\n> > +       /* Create and open the subdev. */\n> > +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> > +       int ret = subdev_->open();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 1. Identify the pads.\n> > +        */\n> > +\n> > +       /*\n> > +        * First locate the source pad. The match() function guarantees there\n> > +        * is one and only one source pad.\n> > +        */\n> > +       unsigned int sourcePad = UINT_MAX;\n> > +\n> > +       for (const MediaPad *pad : entity_->pads()) {\n> > +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> > +                       sourcePad = pad->index();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Iterate over the routes to identify the streams on the source pad,\n> > +        * and the internal sink pads.\n> > +        */\n> > +       V4L2Subdevice::Routing routing = {};\n> > +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       bool imageStreamFound = false;\n> > +\n> > +       for (const V4L2Subdevice::Route &route : routing) {\n> > +               if (route.source.pad != sourcePad) {\n> > +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> > +                       return { -EINVAL };\n> > +               }\n> > +\n> > +               /* Identify the stream type based on the supported formats. */\n> > +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n\nThis fails to pick up the correct formats for the embedded data\npad/stream for the IMX219 drive on Tomi's kernel tree.  I think the\nreason is that enum_mbus_code and enum_frame_size is expecting the\nsink pad instead of the source pad maybe?  The following patch to the\ndriver does fix it:\n\ndiff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c\nindex 16868382a694..586ea18daae1 100644\n--- a/drivers/media/i2c/imx219.c\n+++ b/drivers/media/i2c/imx219.c\n@@ -790,7 +790,7 @@ static int imx219_enum_mbus_code(struct v4l2_subdev *sd,\n {\n        struct imx219 *imx219 = to_imx219(sd);\n\n-       if (code->pad == IMX219_PAD_EDATA) {\n+       if (code->pad == IMX219_PAD_EDATA || code->stream ==\nIMX219_STREAM_EDATA) {\n                struct v4l2_mbus_framefmt *fmt;\n\n@@ -817,7 +821,7 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,\n        struct imx219 *imx219 = to_imx219(sd);\n        u32 code;\n\n-       if (fse->pad == IMX219_PAD_EDATA) {\n+       if (fse->pad == IMX219_PAD_EDATA || fse->stream ==\nIMX219_STREAM_EDATA) {\n                struct v4l2_mbus_framefmt *fmt;\n\nBut I equally think the following might work (not tested) in the libcamera code:\n\nV4L2Subdevice::Formats formats = subdev_->formats(route.sink);\n\nNaush\n\n> > +\n> > +               std::optional<MediaBusFormatInfo::Type> type;\n> > +\n> > +               for (const auto &[code, sizes] : formats) {\n> > +                       const MediaBusFormatInfo &info =\n> > +                               MediaBusFormatInfo::info(code);\n> > +                       if (info.isValid()) {\n> > +                               type = info.type;\n> > +                               break;\n> > +                       }\n> > +               }\n> > +\n> > +               if (!type) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"No known format on pad \" << route.source;\n> > +                       continue;\n> > +               }\n> > +\n> > +               switch (*type) {\n> > +               case MediaBusFormatInfo::Type::Image:\n> > +                       if (imageStreamFound) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal image streams (\"\n> > +                                       << streams_.image.sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       imageStreamFound = true;\n> > +                       streams_.image.sink = route.sink;\n> > +                       streams_.image.source = route.source;\n> > +                       break;\n> > +\n> ?> +               case MediaBusFormatInfo::Type::Metadata:\n> > +                       /*\n> > +                        * Skip metadata streams that are not sensor embedded\n> > +                        * data. The source stream reports a generic metadata\n> > +                        * format, check the sink stream for the exact format.\n> > +                        */\n> > +                       formats = subdev_->formats(route.sink);\n> > +                       if (formats.size() != 1)\n> > +                               continue;\n>\n> Should this test be if (!formats.size()) insead?  It might be possible\n> to have multiple metadata types.\n>\n> > +\n> > +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> > +                           MediaBusFormatInfo::Type::EmbeddedData)\n> > +                               continue;\n>\n> The IMX219 driver (from Tomi's kernel tree) advertises\n> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> data stream, which translates to a type of\n> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> should this check include MediaBusFormatInfo::Type::Metadata?\n>\n> > +\n> > +                       if (streams_.edata) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal embedded data streams (\"\n> > +                                       << streams_.edata->sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       streams_.edata = { route.sink, route.source };\n> > +                       break;\n> > +\n> > +               default:\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       if (!imageStreamFound) {\n> > +               LOG(CameraSensor, Error) << \"No image stream found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug)\n> > +               << \"Found image stream \" << streams_.image.sink\n> > +               << \" -> \" << streams_.image.source;\n> > +\n> > +       if (streams_.edata)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Found embedded data stream \" << streams_.edata->sink\n> > +                       << \" -> \" << streams_.edata->source;\n> > +\n> > +       /*\n> > +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> > +        * array order for the image stream.\n> > +        */\n> > +\n> > +       /*\n> > +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> > +        * the internal image sink pad as it is guaranteed to expose a single\n> > +        * format, and is not affected by flips.\n> > +        */\n> > +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> > +       if (formats.size() != 1) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Image pad has \" << formats.size()\n> > +                       << \" formats, expected 1\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       uint32_t nativeFormat = formats.cbegin()->first;\n> > +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> > +       if (!bayerFormat.isValid()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Invalid native format \" << nativeFormat;\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       cfaPattern_ = bayerFormat.order;\n>\n> cfaPattern_ does not account for the current transform applied to the\n> sensor.  So we could end up with the wrong Bayer order.  Also related,\n> flipsAlterBayerOrder_ never gets set correctly.  I do have a patch to\n> fix both that can be squashed into this if you want.\n>\n> Regards,\n> Naush\n>\n> > +\n> > +       /*\n> > +        * Retrieve and cache the media bus codes and sizes on the source image\n> > +        * stream.\n> > +        */\n> > +       formats_ = subdev_->formats(streams_.image.source);\n> > +       if (formats_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"No image format found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       /* Populate and sort the media bus codes and the sizes. */\n> > +       for (const auto &[code, ranges] : formats_) {\n> > +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> > +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> > +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> > +                       continue;\n> > +\n> > +               mbusCodes_.push_back(code);\n> > +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> > +                              [](const SizeRange &range) { return range.max; });\n> > +       }\n> > +\n> > +       if (mbusCodes_.empty()) {\n> > +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> > +       std::sort(sizes_.begin(), sizes_.end());\n> > +\n> > +       /*\n> > +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> > +        * they are the keys in the formats map.\n> > +        */\n> > +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> > +       sizes_.erase(last, sizes_.end());\n> > +\n> > +       /*\n> > +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> > +        * all the expected selection rectangles are supported.\n> > +        */\n> > +\n> > +       Rectangle rect;\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       pixelArraySize_ = rect.size();\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> > +                                   &activeArea_);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * 4. Verify that all required controls are present.\n> > +        */\n> > +\n> > +       const ControlIdMap &controls = subdev_->controls().idmap();\n> > +\n> > +       static constexpr uint32_t mandatoryControls[] = {\n> > +               V4L2_CID_ANALOGUE_GAIN,\n> > +               V4L2_CID_CAMERA_ORIENTATION,\n> > +               V4L2_CID_EXPOSURE,\n> > +               V4L2_CID_HBLANK,\n> > +               V4L2_CID_PIXEL_RATE,\n> > +               V4L2_CID_VBLANK,\n> > +       };\n> > +\n> > +       ret = 0;\n> > +\n> > +       for (uint32_t ctrl : mandatoryControls) {\n> > +               if (!controls.count(ctrl)) {\n> > +                       LOG(CameraSensor, Error)\n> > +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> > +                               << \" not available\";\n> > +                       ret = -EINVAL;\n> > +               }\n> > +       }\n> > +\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"The sensor kernel driver needs to be fixed\";\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * Verify if sensor supports horizontal/vertical flips\n> > +        *\n> > +        * \\todo Handle horizontal and vertical flips independently.\n> > +        */\n> > +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> > +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> > +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> > +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> > +               supportFlips_ = true;\n> > +\n> > +       if (!supportFlips_)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> > +\n> > +       /*\n> > +        * 5. Discover ancillary devices.\n> > +        *\n> > +        * \\todo This code may be shared by different V4L2 sensor classes.\n> > +        */\n> > +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> > +               switch (ancillary->function()) {\n> > +               case MEDIA_ENT_F_LENS:\n> > +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> > +                       ret = focusLens_->init();\n> > +                       if (ret) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Lens initialisation failed, lens disabled\";\n> > +                               focusLens_.reset();\n> > +                       }\n> > +                       break;\n> > +\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported ancillary entity function \"\n> > +                               << ancillary->function();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * 6. Initialize properties.\n> > +        */\n> > +\n> > +       ret = initProperties();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 7. Initialize controls.\n> > +        */\n> > +\n> > +       /*\n> > +        * Set HBLANK to the minimum to start with a well-defined line length,\n> > +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> > +        * minimum line length in their calculations.\n> > +        *\n> > +        * At present, there is no way of knowing if a control is read-only.\n> > +        * As a workaround, assume that if the minimum and maximum values of\n> > +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> > +        * is read-only.\n> > +        *\n> > +        * \\todo The control API ought to have a flag to specify if a control\n> > +        * is read-only which could be used below.\n> > +        */\n> > +       const ControlInfoMap &ctrls = subdev_->controls();\n> > +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> > +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> > +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> > +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> > +\n> > +               if (hblankMin != hblankMax) {\n> > +                       ControlList ctrl(subdev_->controls());\n> > +\n> > +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> > +                       ret = subdev_->setControls(&ctrl);\n> > +                       if (ret)\n> > +                               return { ret };\n> > +               }\n> > +       }\n> > +\n> > +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       return {};\n> > +}\n> > +\n> > +int CameraSensorRaw::initProperties()\n> > +{\n> > +       model_ = subdev_->model();\n> > +       properties_.set(properties::Model, utils::toAscii(model_));\n> > +\n> > +       /* Generate a unique ID for the sensor. */\n> > +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> > +       if (id_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /* Initialize the static properties from the sensor database. */\n> > +       initStaticProperties();\n> > +\n> > +       /* Retrieve and register properties from the kernel interface. */\n> > +       const ControlInfoMap &controls = subdev_->controls();\n> > +\n> > +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> > +       if (orientation != controls.end()) {\n> > +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> > +               int32_t propertyValue;\n> > +\n> > +               switch (v4l2Orientation) {\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported camera location \"\n> > +                               << v4l2Orientation << \", setting to External\";\n> > +                       [[fallthrough]];\n> > +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> > +                       propertyValue = properties::CameraLocationExternal;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> > +                       propertyValue = properties::CameraLocationFront;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_BACK:\n> > +                       propertyValue = properties::CameraLocationBack;\n> > +                       break;\n> > +               }\n> > +               properties_.set(properties::Location, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> > +       }\n> > +\n> > +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> > +       if (rotationControl != controls.end()) {\n> > +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> > +\n> > +               /*\n> > +                * Cache the Transform associated with the camera mounting\n> > +                * rotation for later use in computeTransform().\n> > +                */\n> > +               bool success;\n> > +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> > +               if (!success) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Invalid rotation of \" << propertyValue\n> > +                               << \" degrees - ignoring\";\n> > +                       mountingOrientation_ = Orientation::Rotate0;\n> > +               }\n> > +\n> > +               properties_.set(properties::Rotation, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning)\n> > +                       << \"Rotation control not available, default to 0 degrees\";\n> > +               properties_.set(properties::Rotation, 0);\n> > +               mountingOrientation_ = Orientation::Rotate0;\n> > +       }\n> > +\n> > +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> > +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> > +\n> > +       /* Color filter array pattern. */\n> > +       uint32_t cfa;\n>\n> GCC 12 complains about cfa possibly used without being initialised.\n> The compiler should really know that the switch below covers all enum\n> cases, but it does not.\n>\n>\n> > +\n> > +       switch (cfaPattern_) {\n> > +       case BayerFormat::BGGR:\n> > +               cfa = properties::draft::BGGR;\n> > +               break;\n> > +       case BayerFormat::GBRG:\n> > +               cfa = properties::draft::GBRG;\n> > +               break;\n> > +       case BayerFormat::GRBG:\n> > +               cfa = properties::draft::GRBG;\n> > +               break;\n> > +       case BayerFormat::RGGB:\n> > +               cfa = properties::draft::RGGB;\n> > +               break;\n> > +       case BayerFormat::MONO:\n> > +               cfa = properties::draft::MONO;\n> > +               break;\n> > +       }\n> > +\n> > +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +void CameraSensorRaw::initStaticProperties()\n> > +{\n> > +       staticProps_ = CameraSensorProperties::get(model_);\n> > +       if (!staticProps_)\n> > +               return;\n> > +\n> > +       /* Register the properties retrieved from the sensor database. */\n> > +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> > +\n> > +       initTestPatternModes();\n> > +}\n> > +\n> > +void CameraSensorRaw::initTestPatternModes()\n> > +{\n> > +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> > +       if (v4l2TestPattern == controls().end()) {\n> > +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> > +               return;\n> > +       }\n> > +\n> > +       const auto &testPatternModes = staticProps_->testPatternModes;\n> > +       if (testPatternModes.empty()) {\n> > +               /*\n> > +                * The camera sensor supports test patterns but we don't know\n> > +                * how to map them so this should be fixed.\n> > +                */\n> > +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> > +                                        << model() << \"\\'\";\n> > +               return;\n> > +       }\n> > +\n> > +       /*\n> > +        * Create a map that associates the V4L2 control index to the test\n> > +        * pattern mode by reversing the testPatternModes map provided by the\n> > +        * camera sensor properties. This makes it easier to verify if the\n> > +        * control index is supported in the below for loop that creates the\n> > +        * list of supported test patterns.\n> > +        */\n> > +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> > +       for (const auto &it : testPatternModes)\n> > +               indexToTestPatternMode[it.second] = it.first;\n> > +\n> > +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> > +               const int32_t index = value.get<int32_t>();\n> > +\n> > +               const auto it = indexToTestPatternMode.find(index);\n> > +               if (it == indexToTestPatternMode.end()) {\n> > +                       LOG(CameraSensor, Debug)\n> > +                               << \"Test pattern mode \" << index << \" ignored\";\n> > +                       continue;\n> > +               }\n> > +\n> > +               testPatternModes_.push_back(it->second);\n> > +       }\n> > +}\n> > +\n> > +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> > +{\n> > +       std::vector<Size> sizes;\n> > +\n> > +       const auto &format = formats_.find(mbusCode);\n> > +       if (format == formats_.end())\n> > +               return sizes;\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> > +Size CameraSensorRaw::resolution() const\n> > +{\n> > +       return std::min(sizes_.back(), activeArea_.size());\n> > +}\n> > +\n> > +V4L2SubdeviceFormat\n> > +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                          const Size &size) const\n> > +{\n> > +       unsigned int desiredArea = size.width * size.height;\n> > +       unsigned int bestArea = UINT_MAX;\n> > +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> > +       float bestRatio = FLT_MAX;\n> > +       const Size *bestSize = nullptr;\n> > +       uint32_t bestCode = 0;\n> > +\n> > +       for (unsigned int code : mbusCodes) {\n> > +               const auto formats = formats_.find(code);\n> > +               if (formats == formats_.end())\n> > +                       continue;\n> > +\n> > +               for (const SizeRange &range : formats->second) {\n> > +                       const Size &sz = range.max;\n> > +\n> > +                       if (sz.width < size.width || sz.height < size.height)\n> > +                               continue;\n> > +\n> > +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> > +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> > +                       unsigned int area = sz.width * sz.height;\n> > +                       unsigned int areaDiff = area - desiredArea;\n> > +\n> > +                       if (ratioDiff > bestRatio)\n> > +                               continue;\n> > +\n> > +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> > +                               bestRatio = ratioDiff;\n> > +                               bestArea = areaDiff;\n> > +                               bestSize = &sz;\n> > +                               bestCode = code;\n> > +                       }\n> > +               }\n> > +       }\n> > +\n> > +       if (!bestSize) {\n> > +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> > +               return {};\n> > +       }\n> > +\n> > +       V4L2SubdeviceFormat format{\n> > +               .code = bestCode,\n> > +               .size = *bestSize,\n> > +               .colorSpace = ColorSpace::Raw,\n> > +       };\n> > +\n> > +       return format;\n> > +}\n> > +\n> > +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> > +{\n> > +       /* Configure flips if the sensor supports that. */\n> > +       if (supportFlips_) {\n> > +               ControlList flipCtrls(subdev_->controls());\n> > +\n> > +               flipCtrls.set(V4L2_CID_HFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> > +               flipCtrls.set(V4L2_CID_VFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> > +\n> > +               int ret = subdev_->setControls(&flipCtrls);\n> > +               if (ret)\n> > +                       return ret;\n> > +       }\n> > +\n> > +       /* Apply format on the subdev. */\n> > +       int ret = subdev_->setFormat(streams_.image.source, format);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       subdev_->updateControlInfo();\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> > +{\n> > +       return subdev_->setFormat(streams_.image.source, format,\n> > +                                 V4L2Subdevice::Whence::TryFormat);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> > +                                       Transform transform,\n> > +                                       V4L2SubdeviceFormat *sensorFormat)\n> > +{\n> > +       if (!config.isValid()) {\n> > +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       std::vector<unsigned int> filteredCodes;\n> > +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> > +                    std::back_inserter(filteredCodes),\n> > +                    [&config](unsigned int mbusCode) {\n> > +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> > +                            if (bayer.bitDepth == config.bitDepth)\n> > +                                    return true;\n> > +                            return false;\n> > +                    });\n> > +       if (filteredCodes.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Cannot find any format with bit depth \"\n> > +                       << config.bitDepth;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /*\n> > +        * Compute the sensor's data frame size by applying the cropping\n> > +        * rectangle, subsampling and output crop to the sensor's pixel array\n> > +        * size.\n> > +        *\n> > +        * \\todo The actual size computation is for now ignored and only the\n> > +        * output size is considered. This implies that resolutions obtained\n> > +        * with two different cropping/subsampling will look identical and\n> > +        * only the first found one will be considered.\n> > +        */\n> > +       V4L2SubdeviceFormat subdevFormat = {};\n> > +       for (unsigned int code : filteredCodes) {\n> > +               for (const Size &size : sizes(code)) {\n> > +                       if (size.width != config.outputSize.width ||\n> > +                           size.height != config.outputSize.height)\n> > +                               continue;\n> > +\n> > +                       subdevFormat.code = code;\n> > +                       subdevFormat.size = size;\n> > +                       break;\n> > +               }\n> > +       }\n> > +       if (!subdevFormat.code) {\n> > +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       int ret = setFormat(&subdevFormat, transform);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * Return to the caller the format actually applied to the sensor.\n> > +        * This is relevant if transform has changed the bayer pattern order.\n> > +        */\n> > +       if (sensorFormat)\n> > +               *sensorFormat = subdevFormat;\n> > +\n> > +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> > +       /* \\todo Handle scaling in the digital domain. */\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> > +{\n> > +       info->model = model();\n> > +\n> > +       /*\n> > +        * The active area size is a static property, while the crop\n> > +        * rectangle needs to be re-read as it depends on the sensor\n> > +        * configuration.\n> > +        */\n> > +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> > +\n> > +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                       &info->analogCrop);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> > +        * are defined relatively to the active pixel area, while V4L2's\n> > +        * TGT_CROP target is defined in respect to the full pixel array.\n> > +        *\n> > +        * Compensate it by subtracting the active area offset.\n> > +        */\n> > +       info->analogCrop.x -= activeArea_.x;\n> > +       info->analogCrop.y -= activeArea_.y;\n> > +\n> > +       /* The bit depth and image size depend on the currently applied format. */\n> > +       V4L2SubdeviceFormat format{};\n> > +       ret = subdev_->getFormat(streams_.image.source, &format);\n> > +       if (ret)\n> > +               return ret;\n> > +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> > +       info->outputSize = format.size;\n> > +\n> > +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> > +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> > +\n> > +       /*\n> > +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> > +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> > +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> > +        */\n> > +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> > +                                                  V4L2_CID_HBLANK,\n> > +                                                  V4L2_CID_VBLANK });\n> > +       if (ctrls.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Failed to retrieve camera info controls\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> > +\n> > +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> > +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> > +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> > +\n> > +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> > +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> > +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> > +{\n> > +       /*\n> > +        * If we cannot do any flips we cannot change the native camera mounting\n> > +        * orientation.\n> > +        */\n> > +       if (!supportFlips_) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       /*\n> > +        * Now compute the required transform to obtain 'orientation' starting\n> > +        * from the mounting rotation.\n> > +        *\n> > +        * As a note:\n> > +        *      orientation / mountingOrientation_ = transform\n> > +        *      mountingOrientation_ * transform = orientation\n> > +        */\n> > +       Transform transform = *orientation / mountingOrientation_;\n> > +\n> > +       /*\n> > +        * If transform contains any Transpose we cannot do it, so adjust\n> > +        * 'orientation' to report the image native orientation and return Identity.\n> > +        */\n> > +       if (!!(transform & Transform::Transpose)) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       return transform;\n> > +}\n> > +\n> > +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> > +{\n> > +       if (!flipsAlterBayerOrder_)\n> > +               return cfaPattern_;\n> > +\n> > +       /*\n> > +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> > +        * using the rest of the Bayer format supplied by the caller.\n> > +        */\n> > +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> > +       return format.transform(t).order;\n> > +}\n> > +\n> > +const ControlInfoMap &CameraSensorRaw::controls() const\n> > +{\n> > +       return subdev_->controls();\n> > +}\n> > +\n> > +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> > +{\n> > +       return subdev_->getControls(ids);\n> > +}\n> > +\n> > +int CameraSensorRaw::setControls(ControlList *ctrls)\n> > +{\n> > +       return subdev_->setControls(ctrls);\n> > +}\n> > +\n> > +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternMode_ == mode)\n> > +               return 0;\n> > +\n> > +       if (testPatternModes_.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Camera sensor does not support test pattern modes.\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       return applyTestPatternMode(mode);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternModes_.empty())\n> > +               return 0;\n> > +\n> > +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> > +                           mode);\n> > +       if (it == testPatternModes_.end()) {\n> > +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> > +                                        << mode;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> > +\n> > +       int32_t index = staticProps_->testPatternModes.at(mode);\n> > +       ControlList ctrls{ controls() };\n> > +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> > +\n> > +       int ret = setControls(&ctrls);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       testPatternMode_ = mode;\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +std::string CameraSensorRaw::logPrefix() const\n> > +{\n> > +       return \"'\" + entity_->name() + \"'\";\n> > +}\n> > +\n> > +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> > index e83020fc22c3..e3c39aaf13b8 100644\n> > --- a/src/libcamera/sensor/meson.build\n> > +++ b/src/libcamera/sensor/meson.build\n> > @@ -4,4 +4,5 @@ libcamera_sources += files([\n> >      'camera_sensor.cpp',\n> >      'camera_sensor_legacy.cpp',\n> >      'camera_sensor_properties.cpp',\n> > +    'camera_sensor_raw.cpp',\n> >  ])\n> > --\n> > Regards,\n> >\n> > Laurent Pinchart\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 2B81DBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:27:25 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BB6AF62C90;\n\tWed, 13 Mar 2024 13:27:23 +0100 (CET)","from mail-yw1-x1133.google.com (mail-yw1-x1133.google.com\n\t[IPv6:2607:f8b0:4864:20::1133])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 31F7762C85\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:27:21 +0100 (CET)","by mail-yw1-x1133.google.com with SMTP id\n\t00721157ae682-609f1b77728so63653147b3.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 05:27:21 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"WUhFgW1j\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1710332840; x=1710937640;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=3N6PWL/kOOCkm3mh4fU42EUq9YbEdTR8p5rptiBV5ME=;\n\tb=WUhFgW1j5QI8GrPVWuSMYNQhMKYBYXPRdFz+DhNV5r3I0b1u7h7gasF9sK0AWU3GSD\n\t8Pivk/SyB1uwZz0ck9tT0qXbuZN1u4jIq3Tuddc9Uq6ebtyQF+hAIrV+f6iudMKZ7CIj\n\t1pgj3+N1VO5FOdyZRFBRJuGOgV5vPg3KzgXrfKPgtuhoVrWp/qZXvHdHvrSSFkgVtBlH\n\tp+V76zBE9VIAJp6droV39QISiAI0zs7d+t7j1WAIq5KQyha+6BEPqeRNWgX20tQLZuQJ\n\twVTHr2APUZpjH5W5zfWLnkCCRWayAnxSocGqNSPta7aZ/UyFa0TNYZlFtgFCrw38S56l\n\tcv/g==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1710332840; x=1710937640;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=3N6PWL/kOOCkm3mh4fU42EUq9YbEdTR8p5rptiBV5ME=;\n\tb=krQ6EYCxq/JC7zncp8eaqafioqD//3I+qCE4N71970vrtRILHidm4p68GUZn5zoF3w\n\t5a7abbE6d1TI8fT6kHr4aPW+dp0bIffdUePZf/skC9noKpC2YqF8OwcXQavOoan8NQlV\n\t2X+0qNZI0U8Lx1QDgll9kdwWCgTCc6A5WnuWybvymm22xgU0sKl+AJjjfGbTryttxPZV\n\tp7j29erB1YHnmdpog7Od01g3u/d2KIihhFIEpqvj4QhLju0VR03GQqTPLuz1zvamcdyD\n\ticoMJgcbnFXA2QmJ9J6UdQFLV05gKRCjNBgShpccNTMtdPXPs5YtLczixn22y8i3Udro\n\t1uRQ==","X-Gm-Message-State":"AOJu0YxfCMzZ+VfFDcGH6UdOIl0XyqyH6fjVI11PGaDGd6cQ5FVwI5lI\n\tqbVHtgidhdlBzfV5tAoSQG1nKOVz3wrYQyeRPIotx2r1Zr8KOrxl95sZbjRDikqB7QtAl4YzP+I\n\t9zgzouRV+yEIVL8WYO9B0k4hm+FLX3snrDGnFOA==","X-Google-Smtp-Source":"AGHT+IFf5hmNf0yAWrLEBtAjgnHmWZrMmW21F5ltnonsHfb03vcV5L99aItyeQmtoVWHstMoaF52oH1bP73Bi9yNbfs=","X-Received":"by 2002:a81:94c2:0:b0:60a:297c:4c4d with SMTP id\n\tl185-20020a8194c2000000b0060a297c4c4dmr2531356ywg.33.1710332839733;\n\tWed, 13 Mar 2024 05:27:19 -0700 (PDT)","MIME-Version":"1.0","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","In-Reply-To":"<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Wed, 13 Mar 2024 12:26:43 +0000","Message-ID":"<CAEmqJPpN7vLojATfFkSjA02GbSmdgLagz0SqAnNyxqyOjaiRwg@mail.gmail.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28942,"web_url":"https://patchwork.libcamera.org/comment/28942/","msgid":"<CAEmqJPqBNBkyig4Si_tJAZG-tcdZzDkoNv_BAHx5ejYrXvn7Pw@mail.gmail.com>","date":"2024-03-13T12:31:22","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Sorry, one more thing in this patch that I forgot (last one I promise).\n\nOn Fri, 1 Mar 2024 at 21:21, Laurent Pinchart\n<laurent.pinchart@ideasonboard.com> wrote:\n>\n> Add a new CameraSensorRaw implementation of the CameraSensor interface\n> tailored to devices that implement the new V4L2 raw camera sensors API.\n>\n> This new class duplicates code from the CameraSensorLegacy class. The\n> two classes will be refactored to share code.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  Documentation/Doxyfile.in                  |    1 +\n>  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n>  src/libcamera/sensor/meson.build           |    1 +\n>  3 files changed, 1065 insertions(+)\n>  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n>\n> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> index 75326c1964e9..8bc55be60a59 100644\n> --- a/Documentation/Doxyfile.in\n> +++ b/Documentation/Doxyfile.in\n> @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n>                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n>                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n>                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n>                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> new file mode 100644\n> index 000000000000..8c17da5876a4\n> --- /dev/null\n> +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> @@ -0,0 +1,1063 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas on Board Oy.\n> + *\n> + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> + */\n> +\n> +#include <algorithm>\n> +#include <float.h>\n> +#include <iomanip>\n> +#include <limits.h>\n> +#include <map>\n> +#include <math.h>\n> +#include <memory>\n> +#include <optional>\n> +#include <string.h>\n> +#include <string>\n> +#include <vector>\n> +\n> +#include <libcamera/base/class.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/control_ids.h>\n> +#include <libcamera/controls.h>\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/orientation.h>\n> +#include <libcamera/property_ids.h>\n> +#include <libcamera/transform.h>\n> +\n> +#include <libcamera/ipa/core_ipa_interface.h>\n> +\n> +#include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/camera_lens.h\"\n> +#include \"libcamera/internal/camera_sensor.h\"\n> +#include \"libcamera/internal/camera_sensor_properties.h\"\n> +#include \"libcamera/internal/formats.h\"\n> +#include \"libcamera/internal/media_device.h\"\n> +#include \"libcamera/internal/sysfs.h\"\n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +class BayerFormat;\n> +class CameraLens;\n> +class MediaEntity;\n> +class SensorConfiguration;\n> +\n> +struct CameraSensorProperties;\n> +\n> +enum class Orientation;\n> +\n> +LOG_DECLARE_CATEGORY(CameraSensor)\n> +\n> +class CameraSensorRaw : public CameraSensor, protected Loggable\n> +{\n> +public:\n> +       CameraSensorRaw(const MediaEntity *entity);\n> +       ~CameraSensorRaw();\n> +\n> +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> +       match(MediaEntity *entity);\n> +\n> +       const std::string &model() const override { return model_; }\n> +       const std::string &id() const override { return id_; }\n> +\n> +       const MediaEntity *entity() const override { return entity_; }\n> +       V4L2Subdevice *device() override { return subdev_.get(); }\n> +\n> +       CameraLens *focusLens() override { return focusLens_.get(); }\n> +\n> +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> +       Size resolution() const override;\n> +\n> +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                                     const Size &size) const override;\n> +       int setFormat(V4L2SubdeviceFormat *format,\n> +                     Transform transform = Transform::Identity) override;\n> +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> +\n> +       int applyConfiguration(const SensorConfiguration &config,\n> +                              Transform transform = Transform::Identity,\n> +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> +\n> +       const ControlList &properties() const override { return properties_; }\n> +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> +       Transform computeTransform(Orientation *orientation) const override;\n> +       BayerFormat::Order bayerOrder(Transform t) const override;\n> +\n> +       const ControlInfoMap &controls() const override;\n> +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> +       int setControls(ControlList *ctrls) override;\n> +\n> +       const std::vector<controls::draft::TestPatternModeEnum> &\n> +       testPatternModes() const override { return testPatternModes_; }\n> +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> +\n> +protected:\n> +       std::string logPrefix() const override;\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> +\n> +       std::optional<int> init();\n> +       int initProperties();\n> +       void initStaticProperties();\n> +       void initTestPatternModes();\n> +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> +\n> +       const MediaEntity *entity_;\n> +       std::unique_ptr<V4L2Subdevice> subdev_;\n> +\n> +       struct Streams {\n> +               V4L2Subdevice::Stream sink;\n> +               V4L2Subdevice::Stream source;\n> +       };\n> +\n> +       struct {\n> +               Streams image;\n> +               std::optional<Streams> edata;\n> +       } streams_;\n> +\n> +       const CameraSensorProperties *staticProps_;\n> +\n> +       std::string model_;\n> +       std::string id_;\n> +\n> +       V4L2Subdevice::Formats formats_;\n> +       std::vector<unsigned int> mbusCodes_;\n> +       std::vector<Size> sizes_;\n> +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> +       controls::draft::TestPatternModeEnum testPatternMode_;\n> +\n> +       Size pixelArraySize_;\n> +       Rectangle activeArea_;\n> +       BayerFormat::Order cfaPattern_;\n> +       bool supportFlips_;\n> +       bool flipsAlterBayerOrder_;\n> +       Orientation mountingOrientation_;\n> +\n> +       ControlList properties_;\n> +\n> +       std::unique_ptr<CameraLens> focusLens_;\n> +};\n> +\n> +/**\n> + * \\class CameraSensorRaw\n> + * \\brief A camera sensor based on V4L2 subdevices\n> + *\n> + * This class supports single-subdev sensors with a single source pad and one\n> + * or two internal sink pads (for the image and embedded data streams).\n> + */\n> +\n> +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> +{\n> +}\n> +\n> +CameraSensorRaw::~CameraSensorRaw() = default;\n> +\n> +std::variant<std::unique_ptr<CameraSensor>, int>\n> +CameraSensorRaw::match(MediaEntity *entity)\n> +{\n> +       /* Check the entity type. */\n> +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported entity type (\"\n> +                       << utils::to_underlying(entity->type())\n> +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /* Count and check the number of pads. */\n> +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> +                                               | MEDIA_PAD_FL_SOURCE\n> +                                               | MEDIA_PAD_FL_INTERNAL;\n> +       unsigned int numSinks = 0;\n> +       unsigned int numSources = 0;\n> +\n> +       for (const MediaPad *pad : entity->pads()) {\n> +               switch (pad->flags() & kPadFlagsMask) {\n> +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> +                       numSinks++;\n> +                       break;\n> +\n> +               case MEDIA_PAD_FL_SOURCE:\n> +                       numSources++;\n> +                       break;\n> +\n> +               default:\n> +                       libcamera::LOG(CameraSensor, Debug)\n> +                               << entity->name() << \": unsupported pad \" << pad->index()\n> +                               << \" type \" << utils::hex(pad->flags());\n> +                       return { 0 };\n> +               }\n> +       }\n> +\n> +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported number of sinks (\"\n> +                       << numSinks << \") or sources (\" << numSources << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /*\n> +        * The entity matches. Create the camera sensor and initialize it. The\n> +        * init() function will perform further match checks.\n> +        */\n> +       std::unique_ptr<CameraSensorRaw> sensor =\n> +               std::make_unique<CameraSensorRaw>(entity);\n> +\n> +       std::optional<int> err = sensor->init();\n> +       if (err)\n> +               return { *err };\n> +\n> +       return { std::move(sensor) };\n> +}\n> +\n> +std::optional<int> CameraSensorRaw::init()\n> +{\n> +       /* Create and open the subdev. */\n> +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> +       int ret = subdev_->open();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 1. Identify the pads.\n> +        */\n> +\n> +       /*\n> +        * First locate the source pad. The match() function guarantees there\n> +        * is one and only one source pad.\n> +        */\n> +       unsigned int sourcePad = UINT_MAX;\n> +\n> +       for (const MediaPad *pad : entity_->pads()) {\n> +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> +                       sourcePad = pad->index();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * Iterate over the routes to identify the streams on the source pad,\n> +        * and the internal sink pads.\n> +        */\n> +       V4L2Subdevice::Routing routing = {};\n> +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       bool imageStreamFound = false;\n> +\n> +       for (const V4L2Subdevice::Route &route : routing) {\n> +               if (route.source.pad != sourcePad) {\n> +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> +                       return { -EINVAL };\n> +               }\n> +\n> +               /* Identify the stream type based on the supported formats. */\n> +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> +\n> +               std::optional<MediaBusFormatInfo::Type> type;\n> +\n> +               for (const auto &[code, sizes] : formats) {\n> +                       const MediaBusFormatInfo &info =\n> +                               MediaBusFormatInfo::info(code);\n> +                       if (info.isValid()) {\n> +                               type = info.type;\n> +                               break;\n> +                       }\n> +               }\n> +\n> +               if (!type) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"No known format on pad \" << route.source;\n> +                       continue;\n> +               }\n> +\n> +               switch (*type) {\n> +               case MediaBusFormatInfo::Type::Image:\n> +                       if (imageStreamFound) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal image streams (\"\n> +                                       << streams_.image.sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       imageStreamFound = true;\n> +                       streams_.image.sink = route.sink;\n> +                       streams_.image.source = route.source;\n> +                       break;\n> +\n> +               case MediaBusFormatInfo::Type::Metadata:\n> +                       /*\n> +                        * Skip metadata streams that are not sensor embedded\n> +                        * data. The source stream reports a generic metadata\n> +                        * format, check the sink stream for the exact format.\n> +                        */\n> +                       formats = subdev_->formats(route.sink);\n> +                       if (formats.size() != 1)\n> +                               continue;\n> +\n> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> +                               continue;\n> +\n> +                       if (streams_.edata) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal embedded data streams (\"\n> +                                       << streams_.edata->sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       streams_.edata = { route.sink, route.source };\n> +                       break;\n> +\n> +               default:\n> +                       break;\n> +               }\n> +       }\n> +\n> +       if (!imageStreamFound) {\n> +               LOG(CameraSensor, Error) << \"No image stream found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       LOG(CameraSensor, Debug)\n> +               << \"Found image stream \" << streams_.image.sink\n> +               << \" -> \" << streams_.image.source;\n> +\n> +       if (streams_.edata)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Found embedded data stream \" << streams_.edata->sink\n> +                       << \" -> \" << streams_.edata->source;\n> +\n> +       /*\n> +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> +        * array order for the image stream.\n> +        */\n> +\n> +       /*\n> +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> +        * the internal image sink pad as it is guaranteed to expose a single\n> +        * format, and is not affected by flips.\n> +        */\n> +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> +       if (formats.size() != 1) {\n\nThis will fail the IMX219 case as formats.size() == 2 (8/10-bits).\nPerhaps we need \"if (!formats.size())\" here?\n\n> +               LOG(CameraSensor, Error)\n> +                       << \"Image pad has \" << formats.size()\n> +                       << \" formats, expected 1\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       uint32_t nativeFormat = formats.cbegin()->first;\n> +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> +       if (!bayerFormat.isValid()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Invalid native format \" << nativeFormat;\n> +               return { 0 };\n> +       }\n> +\n> +       cfaPattern_ = bayerFormat.order;\n> +\n> +       /*\n> +        * Retrieve and cache the media bus codes and sizes on the source image\n> +        * stream.\n> +        */\n> +       formats_ = subdev_->formats(streams_.image.source);\n> +       if (formats_.empty()) {\n> +               LOG(CameraSensor, Error) << \"No image format found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       /* Populate and sort the media bus codes and the sizes. */\n> +       for (const auto &[code, ranges] : formats_) {\n> +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> +                       continue;\n> +\n> +               mbusCodes_.push_back(code);\n> +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> +                              [](const SizeRange &range) { return range.max; });\n> +       }\n> +\n> +       if (mbusCodes_.empty()) {\n> +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> +               return { 0 };\n> +       }\n> +\n> +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> +       std::sort(sizes_.begin(), sizes_.end());\n> +\n> +       /*\n> +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> +        * they are the keys in the formats map.\n> +        */\n> +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> +       sizes_.erase(last, sizes_.end());\n> +\n> +       /*\n> +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> +        * all the expected selection rectangles are supported.\n> +        */\n> +\n> +       Rectangle rect;\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> +               return { ret };\n> +       }\n> +\n> +       pixelArraySize_ = rect.size();\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> +                                   &activeArea_);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> +               return { ret };\n> +       }\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * 4. Verify that all required controls are present.\n> +        */\n> +\n> +       const ControlIdMap &controls = subdev_->controls().idmap();\n> +\n> +       static constexpr uint32_t mandatoryControls[] = {\n> +               V4L2_CID_ANALOGUE_GAIN,\n> +               V4L2_CID_CAMERA_ORIENTATION,\n> +               V4L2_CID_EXPOSURE,\n> +               V4L2_CID_HBLANK,\n> +               V4L2_CID_PIXEL_RATE,\n> +               V4L2_CID_VBLANK,\n> +       };\n> +\n> +       ret = 0;\n> +\n> +       for (uint32_t ctrl : mandatoryControls) {\n> +               if (!controls.count(ctrl)) {\n> +                       LOG(CameraSensor, Error)\n> +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> +                               << \" not available\";\n> +                       ret = -EINVAL;\n> +               }\n> +       }\n> +\n> +       if (ret) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"The sensor kernel driver needs to be fixed\";\n> +               LOG(CameraSensor, Error)\n> +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * Verify if sensor supports horizontal/vertical flips\n> +        *\n> +        * \\todo Handle horizontal and vertical flips independently.\n> +        */\n> +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> +               supportFlips_ = true;\n> +\n> +       if (!supportFlips_)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> +\n> +       /*\n> +        * 5. Discover ancillary devices.\n> +        *\n> +        * \\todo This code may be shared by different V4L2 sensor classes.\n> +        */\n> +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> +               switch (ancillary->function()) {\n> +               case MEDIA_ENT_F_LENS:\n> +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> +                       ret = focusLens_->init();\n> +                       if (ret) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Lens initialisation failed, lens disabled\";\n> +                               focusLens_.reset();\n> +                       }\n> +                       break;\n> +\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported ancillary entity function \"\n> +                               << ancillary->function();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * 6. Initialize properties.\n> +        */\n> +\n> +       ret = initProperties();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 7. Initialize controls.\n> +        */\n> +\n> +       /*\n> +        * Set HBLANK to the minimum to start with a well-defined line length,\n> +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> +        * minimum line length in their calculations.\n> +        *\n> +        * At present, there is no way of knowing if a control is read-only.\n> +        * As a workaround, assume that if the minimum and maximum values of\n> +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> +        * is read-only.\n> +        *\n> +        * \\todo The control API ought to have a flag to specify if a control\n> +        * is read-only which could be used below.\n> +        */\n> +       const ControlInfoMap &ctrls = subdev_->controls();\n> +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> +\n> +               if (hblankMin != hblankMax) {\n> +                       ControlList ctrl(subdev_->controls());\n> +\n> +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> +                       ret = subdev_->setControls(&ctrl);\n> +                       if (ret)\n> +                               return { ret };\n> +               }\n> +       }\n> +\n> +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       return {};\n> +}\n> +\n> +int CameraSensorRaw::initProperties()\n> +{\n> +       model_ = subdev_->model();\n> +       properties_.set(properties::Model, utils::toAscii(model_));\n> +\n> +       /* Generate a unique ID for the sensor. */\n> +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> +       if (id_.empty()) {\n> +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       /* Initialize the static properties from the sensor database. */\n> +       initStaticProperties();\n> +\n> +       /* Retrieve and register properties from the kernel interface. */\n> +       const ControlInfoMap &controls = subdev_->controls();\n> +\n> +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> +       if (orientation != controls.end()) {\n> +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> +               int32_t propertyValue;\n> +\n> +               switch (v4l2Orientation) {\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported camera location \"\n> +                               << v4l2Orientation << \", setting to External\";\n> +                       [[fallthrough]];\n> +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> +                       propertyValue = properties::CameraLocationExternal;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> +                       propertyValue = properties::CameraLocationFront;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_BACK:\n> +                       propertyValue = properties::CameraLocationBack;\n> +                       break;\n> +               }\n> +               properties_.set(properties::Location, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> +       }\n> +\n> +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> +       if (rotationControl != controls.end()) {\n> +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> +\n> +               /*\n> +                * Cache the Transform associated with the camera mounting\n> +                * rotation for later use in computeTransform().\n> +                */\n> +               bool success;\n> +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> +               if (!success) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Invalid rotation of \" << propertyValue\n> +                               << \" degrees - ignoring\";\n> +                       mountingOrientation_ = Orientation::Rotate0;\n> +               }\n> +\n> +               properties_.set(properties::Rotation, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning)\n> +                       << \"Rotation control not available, default to 0 degrees\";\n> +               properties_.set(properties::Rotation, 0);\n> +               mountingOrientation_ = Orientation::Rotate0;\n> +       }\n> +\n> +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> +\n> +       /* Color filter array pattern. */\n> +       uint32_t cfa;\n> +\n> +       switch (cfaPattern_) {\n> +       case BayerFormat::BGGR:\n> +               cfa = properties::draft::BGGR;\n> +               break;\n> +       case BayerFormat::GBRG:\n> +               cfa = properties::draft::GBRG;\n> +               break;\n> +       case BayerFormat::GRBG:\n> +               cfa = properties::draft::GRBG;\n> +               break;\n> +       case BayerFormat::RGGB:\n> +               cfa = properties::draft::RGGB;\n> +               break;\n> +       case BayerFormat::MONO:\n> +               cfa = properties::draft::MONO;\n> +               break;\n> +       }\n> +\n> +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> +\n> +       return 0;\n> +}\n> +\n> +void CameraSensorRaw::initStaticProperties()\n> +{\n> +       staticProps_ = CameraSensorProperties::get(model_);\n> +       if (!staticProps_)\n> +               return;\n> +\n> +       /* Register the properties retrieved from the sensor database. */\n> +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> +\n> +       initTestPatternModes();\n> +}\n> +\n> +void CameraSensorRaw::initTestPatternModes()\n> +{\n> +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> +       if (v4l2TestPattern == controls().end()) {\n> +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> +               return;\n> +       }\n> +\n> +       const auto &testPatternModes = staticProps_->testPatternModes;\n> +       if (testPatternModes.empty()) {\n> +               /*\n> +                * The camera sensor supports test patterns but we don't know\n> +                * how to map them so this should be fixed.\n> +                */\n> +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> +                                        << model() << \"\\'\";\n> +               return;\n> +       }\n> +\n> +       /*\n> +        * Create a map that associates the V4L2 control index to the test\n> +        * pattern mode by reversing the testPatternModes map provided by the\n> +        * camera sensor properties. This makes it easier to verify if the\n> +        * control index is supported in the below for loop that creates the\n> +        * list of supported test patterns.\n> +        */\n> +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> +       for (const auto &it : testPatternModes)\n> +               indexToTestPatternMode[it.second] = it.first;\n> +\n> +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> +               const int32_t index = value.get<int32_t>();\n> +\n> +               const auto it = indexToTestPatternMode.find(index);\n> +               if (it == indexToTestPatternMode.end()) {\n> +                       LOG(CameraSensor, Debug)\n> +                               << \"Test pattern mode \" << index << \" ignored\";\n> +                       continue;\n> +               }\n> +\n> +               testPatternModes_.push_back(it->second);\n> +       }\n> +}\n> +\n> +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> +{\n> +       std::vector<Size> sizes;\n> +\n> +       const auto &format = formats_.find(mbusCode);\n> +       if (format == formats_.end())\n> +               return sizes;\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> +Size CameraSensorRaw::resolution() const\n> +{\n> +       return std::min(sizes_.back(), activeArea_.size());\n> +}\n> +\n> +V4L2SubdeviceFormat\n> +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                          const Size &size) const\n> +{\n> +       unsigned int desiredArea = size.width * size.height;\n> +       unsigned int bestArea = UINT_MAX;\n> +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> +       float bestRatio = FLT_MAX;\n> +       const Size *bestSize = nullptr;\n> +       uint32_t bestCode = 0;\n> +\n> +       for (unsigned int code : mbusCodes) {\n> +               const auto formats = formats_.find(code);\n> +               if (formats == formats_.end())\n> +                       continue;\n> +\n> +               for (const SizeRange &range : formats->second) {\n> +                       const Size &sz = range.max;\n> +\n> +                       if (sz.width < size.width || sz.height < size.height)\n> +                               continue;\n> +\n> +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> +                       unsigned int area = sz.width * sz.height;\n> +                       unsigned int areaDiff = area - desiredArea;\n> +\n> +                       if (ratioDiff > bestRatio)\n> +                               continue;\n> +\n> +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> +                               bestRatio = ratioDiff;\n> +                               bestArea = areaDiff;\n> +                               bestSize = &sz;\n> +                               bestCode = code;\n> +                       }\n> +               }\n> +       }\n> +\n> +       if (!bestSize) {\n> +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> +               return {};\n> +       }\n> +\n> +       V4L2SubdeviceFormat format{\n> +               .code = bestCode,\n> +               .size = *bestSize,\n> +               .colorSpace = ColorSpace::Raw,\n> +       };\n> +\n> +       return format;\n> +}\n> +\n> +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> +{\n> +       /* Configure flips if the sensor supports that. */\n> +       if (supportFlips_) {\n> +               ControlList flipCtrls(subdev_->controls());\n> +\n> +               flipCtrls.set(V4L2_CID_HFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> +               flipCtrls.set(V4L2_CID_VFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> +\n> +               int ret = subdev_->setControls(&flipCtrls);\n> +               if (ret)\n> +                       return ret;\n> +       }\n> +\n> +       /* Apply format on the subdev. */\n> +       int ret = subdev_->setFormat(streams_.image.source, format);\n> +       if (ret)\n> +               return ret;\n> +\n> +       subdev_->updateControlInfo();\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> +{\n> +       return subdev_->setFormat(streams_.image.source, format,\n> +                                 V4L2Subdevice::Whence::TryFormat);\n> +}\n> +\n> +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> +                                       Transform transform,\n> +                                       V4L2SubdeviceFormat *sensorFormat)\n> +{\n> +       if (!config.isValid()) {\n> +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       std::vector<unsigned int> filteredCodes;\n> +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> +                    std::back_inserter(filteredCodes),\n> +                    [&config](unsigned int mbusCode) {\n> +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> +                            if (bayer.bitDepth == config.bitDepth)\n> +                                    return true;\n> +                            return false;\n> +                    });\n> +       if (filteredCodes.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Cannot find any format with bit depth \"\n> +                       << config.bitDepth;\n> +               return -EINVAL;\n> +       }\n> +\n> +       /*\n> +        * Compute the sensor's data frame size by applying the cropping\n> +        * rectangle, subsampling and output crop to the sensor's pixel array\n> +        * size.\n> +        *\n> +        * \\todo The actual size computation is for now ignored and only the\n> +        * output size is considered. This implies that resolutions obtained\n> +        * with two different cropping/subsampling will look identical and\n> +        * only the first found one will be considered.\n> +        */\n> +       V4L2SubdeviceFormat subdevFormat = {};\n> +       for (unsigned int code : filteredCodes) {\n> +               for (const Size &size : sizes(code)) {\n> +                       if (size.width != config.outputSize.width ||\n> +                           size.height != config.outputSize.height)\n> +                               continue;\n> +\n> +                       subdevFormat.code = code;\n> +                       subdevFormat.size = size;\n> +                       break;\n> +               }\n> +       }\n> +       if (!subdevFormat.code) {\n> +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       int ret = setFormat(&subdevFormat, transform);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * Return to the caller the format actually applied to the sensor.\n> +        * This is relevant if transform has changed the bayer pattern order.\n> +        */\n> +       if (sensorFormat)\n> +               *sensorFormat = subdevFormat;\n> +\n> +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> +       /* \\todo Handle scaling in the digital domain. */\n> +\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> +{\n> +       info->model = model();\n> +\n> +       /*\n> +        * The active area size is a static property, while the crop\n> +        * rectangle needs to be re-read as it depends on the sensor\n> +        * configuration.\n> +        */\n> +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> +\n> +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                       &info->analogCrop);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> +        * are defined relatively to the active pixel area, while V4L2's\n> +        * TGT_CROP target is defined in respect to the full pixel array.\n> +        *\n> +        * Compensate it by subtracting the active area offset.\n> +        */\n> +       info->analogCrop.x -= activeArea_.x;\n> +       info->analogCrop.y -= activeArea_.y;\n> +\n> +       /* The bit depth and image size depend on the currently applied format. */\n> +       V4L2SubdeviceFormat format{};\n> +       ret = subdev_->getFormat(streams_.image.source, &format);\n> +       if (ret)\n> +               return ret;\n> +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> +       info->outputSize = format.size;\n> +\n> +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> +\n> +       /*\n> +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> +        */\n> +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> +                                                  V4L2_CID_HBLANK,\n> +                                                  V4L2_CID_VBLANK });\n> +       if (ctrls.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Failed to retrieve camera info controls\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> +\n> +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> +\n> +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> +\n> +       return 0;\n> +}\n> +\n> +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> +{\n> +       /*\n> +        * If we cannot do any flips we cannot change the native camera mounting\n> +        * orientation.\n> +        */\n> +       if (!supportFlips_) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       /*\n> +        * Now compute the required transform to obtain 'orientation' starting\n> +        * from the mounting rotation.\n> +        *\n> +        * As a note:\n> +        *      orientation / mountingOrientation_ = transform\n> +        *      mountingOrientation_ * transform = orientation\n> +        */\n> +       Transform transform = *orientation / mountingOrientation_;\n> +\n> +       /*\n> +        * If transform contains any Transpose we cannot do it, so adjust\n> +        * 'orientation' to report the image native orientation and return Identity.\n> +        */\n> +       if (!!(transform & Transform::Transpose)) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       return transform;\n> +}\n> +\n> +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> +{\n> +       if (!flipsAlterBayerOrder_)\n> +               return cfaPattern_;\n> +\n> +       /*\n> +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> +        * using the rest of the Bayer format supplied by the caller.\n> +        */\n> +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> +       return format.transform(t).order;\n> +}\n> +\n> +const ControlInfoMap &CameraSensorRaw::controls() const\n> +{\n> +       return subdev_->controls();\n> +}\n> +\n> +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> +{\n> +       return subdev_->getControls(ids);\n> +}\n> +\n> +int CameraSensorRaw::setControls(ControlList *ctrls)\n> +{\n> +       return subdev_->setControls(ctrls);\n> +}\n> +\n> +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternMode_ == mode)\n> +               return 0;\n> +\n> +       if (testPatternModes_.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Camera sensor does not support test pattern modes.\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       return applyTestPatternMode(mode);\n> +}\n> +\n> +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternModes_.empty())\n> +               return 0;\n> +\n> +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> +                           mode);\n> +       if (it == testPatternModes_.end()) {\n> +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> +                                        << mode;\n> +               return -EINVAL;\n> +       }\n> +\n> +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> +\n> +       int32_t index = staticProps_->testPatternModes.at(mode);\n> +       ControlList ctrls{ controls() };\n> +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> +\n> +       int ret = setControls(&ctrls);\n> +       if (ret)\n> +               return ret;\n> +\n> +       testPatternMode_ = mode;\n> +\n> +       return 0;\n> +}\n> +\n> +std::string CameraSensorRaw::logPrefix() const\n> +{\n> +       return \"'\" + entity_->name() + \"'\";\n> +}\n> +\n> +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> index e83020fc22c3..e3c39aaf13b8 100644\n> --- a/src/libcamera/sensor/meson.build\n> +++ b/src/libcamera/sensor/meson.build\n> @@ -4,4 +4,5 @@ libcamera_sources += files([\n>      'camera_sensor.cpp',\n>      'camera_sensor_legacy.cpp',\n>      'camera_sensor_properties.cpp',\n> +    'camera_sensor_raw.cpp',\n>  ])\n> --\n> Regards,\n>\n> Laurent Pinchart\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 4A590BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:32:03 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2EC8C62C96;\n\tWed, 13 Mar 2024 13:32:02 +0100 (CET)","from mail-yw1-x1132.google.com (mail-yw1-x1132.google.com\n\t[IPv6:2607:f8b0:4864:20::1132])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B205362C86\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:31:59 +0100 (CET)","by mail-yw1-x1132.google.com with SMTP id\n\t00721157ae682-60a5628ad97so4736827b3.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 05:31:59 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"k9LsdkWT\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1710333118; x=1710937918;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=k5bg260bWftcZiLEZODmJzrz846/ScbGzNt82p+XgtM=;\n\tb=k9LsdkWTnw3fv9kQxHv4vm0s+mhIhphIPXv8z/iiTOaAp6VELhmQafjdK0X/xR9o1v\n\tI+UHIU/BvoJXkLME4bftQ7DddqF4LQ5jrxLvntmLGMZl0MbJaezaqTgVYHehUekRNVMt\n\tVLFGpkxW1hUQ0yUiGhDzB38P1mxKa1D0S69X4eaFV7aTptLwf5FTu9hy9X26og66F5fg\n\twHA7gCPxn3IAt2UjkhrpH9Yu7syMX8dVn3FCdjQWx7cAf/U4rEJXiSlgufFR9CECs3js\n\ttVP2mW0WNA3vKy/OTrk5KdCZinbh10AFf2hma3ag8zFuw2bQwfjHkuweV9wB+y2X2xxs\n\trJiA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1710333118; x=1710937918;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=k5bg260bWftcZiLEZODmJzrz846/ScbGzNt82p+XgtM=;\n\tb=WVxpW+tEgjdn9mm/SS2Oovi/XOtuNYj2ZbFi8256ZXaJ7dE/HVI9mINoHIZpTVWSLn\n\t8wNVodyxF/6QC8oRguyA6poaSVePaDU/eD8dXlXpD7+TLne/36qmdVpqhEmR02DdESze\n\tqHMIKU2e9XBVwjt8mwHcur0fQlT8F5fuf+d9StnokdB0p3jTdbQ/3wBjybM00G8e7JYb\n\tltZV0g7o9ZtyR8D8gilVfNwdFxSBZqedL9pSidv7fV/ppIq384pJ+mJAIoLv0GXQ72bx\n\tuhdhIx68mT7sBfu0NdTP+b85D1J13AgCFhusrot+WWHijfjkYt6LMwbNpLEtWpbPNlB/\n\tdksQ==","X-Gm-Message-State":"AOJu0YwIvj2sU7Sck1qltvpsQVNLTAad8FA55n+fwJChJsdHbf7BxYF1\n\tNzEYWhJXgUdm/Si5+BjrDPZgABRNiilX2ZCOHxTsElhQtzEExp0fyHftZfZYzzQ2O0cbalTvgn1\n\tNEteS2PplnxzqHQ2s+WLyHA4wTrUyGwA7h1F+pmkWALgZ+eMS","X-Google-Smtp-Source":"AGHT+IGThDadt0ohZAKnyAYETGuGoXtFpH3W6Ww02eYnzNRxHWrseUBZnpRfcxCFBoa47LlbCEg/KOzJygjhAdckL+k=","X-Received":"by 2002:a0d:db54:0:b0:609:840c:4d2 with SMTP id\n\td81-20020a0ddb54000000b00609840c04d2mr1390428ywe.14.1710333118330;\n\tWed, 13 Mar 2024 05:31:58 -0700 (PDT)","MIME-Version":"1.0","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","In-Reply-To":"<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Wed, 13 Mar 2024 12:31:22 +0000","Message-ID":"<CAEmqJPqBNBkyig4Si_tJAZG-tcdZzDkoNv_BAHx5ejYrXvn7Pw@mail.gmail.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28943,"web_url":"https://patchwork.libcamera.org/comment/28943/","msgid":"<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","date":"2024-03-13T12:46:30","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"On 13/03/2024 14:20, Naushir Patuck wrote:\n\n> ?> +               case MediaBusFormatInfo::Type::Metadata:\n>> +                       /*\n>> +                        * Skip metadata streams that are not sensor embedded\n>> +                        * data. The source stream reports a generic metadata\n>> +                        * format, check the sink stream for the exact format.\n>> +                        */\n>> +                       formats = subdev_->formats(route.sink);\n>> +                       if (formats.size() != 1)\n>> +                               continue;\n> \n> Should this test be if (!formats.size()) insead?  It might be possible\n> to have multiple metadata types.\n\nThe driver in my branch is old and hacky. I should see what Laurent has \ndone with the imx219 in his branch, and possibly just take that one.\n\nI think advertising only a single format makes sense here, as the \nembedded format is defined by the video format.\n\n>> +\n>> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n>> +                               continue;\n> \n> The IMX219 driver (from Tomi's kernel tree) advertises\n> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> data stream, which translates to a type of\n> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> should this check include MediaBusFormatInfo::Type::Metadata?\n\nLaurent's version should also report those same mbus formats. Hmm, oh, \nbut it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n\n  Tomi","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 7DDECBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:46:36 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7A10862C8C;\n\tWed, 13 Mar 2024 13:46:35 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2DD3F62C85\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:46:34 +0100 (CET)","from [192.168.88.20] (91-154-34-181.elisa-laajakaista.fi\n\t[91.154.34.181])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 39D92899;\n\tWed, 13 Mar 2024 13:46:11 +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=\"vyHPptYj\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710333971;\n\tbh=9Qo9I8coDYLBOYPh1kHpJrNwqsqIKOKSsqDDBEwkQ5I=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=vyHPptYjtKQy3An9CwHFqFSnBKZS3I+Q4crnTnGY0e3MINFOM3EMPq8YiOlqgH2Wm\n\tIvCbj0LFrgE7WzHdKrQkvwigbvywBu0El/FdSSO07I+v3BQIDlvAi6xE11eWMd+VDP\n\tGu5xxLA+lTkEiR0vywXVf7hAlb1H71lQu6x5NA4w=","Message-ID":"<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","Date":"Wed, 13 Mar 2024 14:46:30 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Content-Language":"en-US","To":"Naushir Patuck <naush@raspberrypi.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","From":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Autocrypt":"addr=tomi.valkeinen@ideasonboard.com; keydata=\n\txsFNBE6ms0cBEACyizowecZqXfMZtnBniOieTuFdErHAUyxVgtmr0f5ZfIi9Z4l+uUN4Zdw2\n\twCEZjx3o0Z34diXBaMRJ3rAk9yB90UJAnLtb8A97Oq64DskLF81GCYB2P1i0qrG7UjpASgCA\n\tRu0lVvxsWyIwSfoYoLrazbT1wkWRs8YBkkXQFfL7Mn3ZMoGPcpfwYH9O7bV1NslbmyJzRCMO\n\teYV258gjCcwYlrkyIratlHCek4GrwV8Z9NQcjD5iLzrONjfafrWPwj6yn2RlL0mQEwt1lOvn\n\tLnI7QRtB3zxA3yB+FLsT1hx0va6xCHpX3QO2gBsyHCyVafFMrg3c/7IIWkDLngJxFgz6DLiA\n\tG4ld1QK/jsYqfP2GIMH1mFdjY+iagG4DqOsjip479HCWAptpNxSOCL6z3qxCU8MCz8iNOtZk\n\tDYXQWVscM5qgYSn+fmMM2qN+eoWlnCGVURZZLDjg387S2E1jT/dNTOsM/IqQj+ZROUZuRcF7\n\t0RTtuU5q1HnbRNwy+23xeoSGuwmLQ2UsUk7Q5CnrjYfiPo3wHze8avK95JBoSd+WIRmV3uoO\n\trXCoYOIRlDhg9XJTrbnQ3Ot5zOa0Y9c4IpyAlut6mDtxtKXr4+8OzjSVFww7tIwadTK3wDQv\n\tBus4jxHjS6dz1g2ypT65qnHen6mUUH63lhzewqO9peAHJ0SLrQARAQABzTBUb21pIFZhbGtl\n\taW5lbiA8dG9taS52YWxrZWluZW5AaWRlYXNvbmJvYXJkLmNvbT7CwY4EEwEIADgWIQTEOAw+\n\tll79gQef86f6PaqMvJYe9QUCX/HruAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD6\n\tPaqMvJYe9WmFD/99NGoD5lBJhlFDHMZvO+Op8vCwnIRZdTsyrtGl72rVh9xRfcSgYPZUvBuT\n\tVDxE53mY9HaZyu1eGMccYRBaTLJSfCXl/g317CrMNdY0k40b9YeIX10feiRYEWoDIPQ3tMmA\n\t0nHDygzcnuPiPT68JYZ6tUOvAt7r6OX/litM+m2/E9mtp8xCoWOo/kYO4mOAIoMNvLB8vufi\n\tuBB4e/AvAjtny4ScuNV5c5q8MkfNIiOyag9QCiQ/JfoAqzXRjVb4VZG72AKaElwipiKCWEcU\n\tR4+Bu5Qbaxj7Cd36M/bI54OrbWWETJkVVSV1i0tghCd6HHyquTdFl7wYcz6cL1hn/6byVnD+\n\tsR3BLvSBHYp8WSwv0TCuf6tLiNgHAO1hWiQ1pOoXyMEsxZlgPXT+wb4dbNVunckwqFjGxRbl\n\tRz7apFT/ZRwbazEzEzNyrBOfB55xdipG/2+SmFn0oMFqFOBEszXLQVslh64lI0CMJm2OYYe3\n\tPxHqYaztyeXsx13Bfnq9+bUynAQ4uW1P5DJ3OIRZWKmbQd/Me3Fq6TU57LsvwRgE0Le9PFQs\n\tdcP2071rMTpqTUteEgODJS4VDf4lXJfY91u32BJkiqM7/62Cqatcz5UWWHq5xeF03MIUTqdE\n\tqHWk3RJEoWHWQRzQfcx6Fn2fDAUKhAddvoopfcjAHfpAWJ+ENc7BTQROprNHARAAx0aat8GU\n\thsusCLc4MIxOQwidecCTRc9Dz/7U2goUwhw2O5j9TPqLtp57VITmHILnvZf6q3QAho2QMQyE\n\tDDvHubrdtEoqaaSKxKkFie1uhWNNvXPhwkKLYieyL9m2JdU+b88HaDnpzdyTTR4uH7wk0bBa\n\tKbTSgIFDDe5lXInypewPO30TmYNkFSexnnM3n1PBCqiJXsJahE4ZQ+WnV5FbPUj8T2zXS2xk\n\t0LZ0+DwKmZ0ZDovvdEWRWrz3UzJ8DLHb7blPpGhmqj3ANXQXC7mb9qJ6J/VSl61GbxIO2Dwb\n\txPNkHk8fwnxlUBCOyBti/uD2uSTgKHNdabhVm2dgFNVuS1y3bBHbI/qjC3J7rWE0WiaHWEqy\n\tUVPk8rsph4rqITsj2RiY70vEW0SKePrChvET7D8P1UPqmveBNNtSS7In+DdZ5kUqLV7rJnM9\n\t/4cwy+uZUt8cuCZlcA5u8IsBCNJudxEqBG10GHg1B6h1RZIz9Q9XfiBdaqa5+CjyFs8ua01c\n\t9HmyfkuhXG2OLjfQuK+Ygd56mV3lq0aFdwbaX16DG22c6flkkBSjyWXYepFtHz9KsBS0DaZb\n\t4IkLmZwEXpZcIOQjQ71fqlpiXkXSIaQ6YMEs8WjBbpP81h7QxWIfWtp+VnwNGc6nq5IQDESH\n\tmvQcsFS7d3eGVI6eyjCFdcAO8eMAEQEAAcLBXwQYAQIACQUCTqazRwIbDAAKCRD6PaqMvJYe\n\t9fA7EACS6exUedsBKmt4pT7nqXBcRsqm6YzT6DeCM8PWMTeaVGHiR4TnNFiT3otD5UpYQI7S\n\tsuYxoTdHrrrBzdlKe5rUWpzoZkVK6p0s9OIvGzLT0lrb0HC9iNDWT3JgpYDnk4Z2mFi6tTbq\n\txKMtpVFRA6FjviGDRsfkfoURZI51nf2RSAk/A8BEDDZ7lgJHskYoklSpwyrXhkp9FHGMaYII\n\tm9EKuUTX9JPDG2FTthCBrdsgWYPdJQvM+zscq09vFMQ9Fykbx5N8z/oFEUy3ACyPqW2oyfvU\n\tCH5WDpWBG0s5BALp1gBJPytIAd/pY/5ZdNoi0Cx3+Z7jaBFEyYJdWy1hGddpkgnMjyOfLI7B\n\tCFrdecTZbR5upjNSDvQ7RG85SnpYJTIin+SAUazAeA2nS6gTZzumgtdw8XmVXZwdBfF+ICof\n\t92UkbYcYNbzWO/GHgsNT1WnM4sa9lwCSWH8Fw1o/3bX1VVPEsnESOfxkNdu+gAF5S6+I6n3a\n\tueeIlwJl5CpT5l8RpoZXEOVtXYn8zzOJ7oGZYINRV9Pf8qKGLf3Dft7zKBP832I3PQjeok7F\n\tyjt+9S+KgSFSHP3Pa4E7lsSdWhSlHYNdG/czhoUkSCN09C0rEK93wxACx3vtxPLjXu6RptBw\n\t3dRq7n+mQChEB1am0BueV1JZaBboIL0AGlSJkm23kw==","In-Reply-To":"<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28944,"web_url":"https://patchwork.libcamera.org/comment/28944/","msgid":"<CAEmqJPrzZT+L8uNeuSx1HOXgK1TO-V7GjYdJUgOTvYEnooNnPA@mail.gmail.com>","date":"2024-03-13T12:49:44","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"On Wed, 13 Mar 2024 at 12:46, Tomi Valkeinen\n<tomi.valkeinen@ideasonboard.com> wrote:\n>\n> On 13/03/2024 14:20, Naushir Patuck wrote:\n>\n> > ?> +               case MediaBusFormatInfo::Type::Metadata:\n> >> +                       /*\n> >> +                        * Skip metadata streams that are not sensor embedded\n> >> +                        * data. The source stream reports a generic metadata\n> >> +                        * format, check the sink stream for the exact format.\n> >> +                        */\n> >> +                       formats = subdev_->formats(route.sink);\n> >> +                       if (formats.size() != 1)\n> >> +                               continue;\n> >\n> > Should this test be if (!formats.size()) insead?  It might be possible\n> > to have multiple metadata types.\n>\n> The driver in my branch is old and hacky. I should see what Laurent has\n> done with the imx219 in his branch, and possibly just take that one.\n>\n> I think advertising only a single format makes sense here, as the\n> embedded format is defined by the video format.\n>\n> >> +\n> >> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> >> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> >> +                               continue;\n> >\n> > The IMX219 driver (from Tomi's kernel tree) advertises\n> > MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> > data stream, which translates to a type of\n> > MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> > should this check include MediaBusFormatInfo::Type::Metadata?\n>\n> Laurent's version should also report those same mbus formats. Hmm, oh,\n> but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n\nCan you point to Laurent's tree?  I'll take the driver from there and\nsee if these comments are still valid.\n\n>\n>   Tomi\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 7B540BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:50:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 827FD62C93;\n\tWed, 13 Mar 2024 13:50:23 +0100 (CET)","from mail-yw1-x112e.google.com (mail-yw1-x112e.google.com\n\t[IPv6:2607:f8b0:4864:20::112e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3026462C86\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:50:21 +0100 (CET)","by mail-yw1-x112e.google.com with SMTP id\n\t00721157ae682-609f3ca61e0so65198387b3.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 05:50:21 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"UIQb9xoC\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1710334220; x=1710939020;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=ofzexEW/V9VApbS4fDUuMYzWh8472tNasQ1a+4R7LcA=;\n\tb=UIQb9xoCN24gFomllSgFf8VNaSKpfhkZSObd5dRRyuw5MokDeBfi8AWEoAMTu2jYh5\n\tHvdLPJbvEjyZ3Iit6JcrNKA/UvOeG8OA/X8ejDEAlPi3nldzCC47fukL4LrtJDC7Ut7f\n\t66vKzARI4TeG75zJsizEcv/QLC5l4iGeFcpka6VOmnO8jTppN0nQNFEJfUw8wrNfxmS3\n\tRLcd99mkmgVsijqnTDXOUkNr6RLv1C1yNC1lQ+FS62yU//KPe6/XwAh4dRNkdSYtzmrm\n\tV9M0vkXPoKrGivDNMBl0LEuYEMDfpCFjk9Zno0sMOfdKs1LZoybRQ1ACnm2qJ3VXCVOi\n\tK3nw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1710334220; x=1710939020;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=ofzexEW/V9VApbS4fDUuMYzWh8472tNasQ1a+4R7LcA=;\n\tb=eNo3Mn1YY4pBFE5L+WV/UXyTtlwAmB0bH3nIW3Nx8pBLEU7V94a6Utj/F4MpY49PrL\n\tSEeqIB/tjYQCvy21DVRcJqonXEkA7zrS26HVIkwee6GrqioAgh9WhwxSD4JSZG717wrk\n\twpcOLIA+xM2bg2iHvBZ6uwYv/hA/zRMMXxujd0vIIQpPXKwtNp8QH1boSXZG7DRQ95mo\n\tfyIp16pGAChzqDsvGixa0h2Lh+Y7AsNFFa9pkbii+5E8KLGNv5n6JyuIXDnRwThBs99S\n\t+9twTY9xlBxdnkC2KxWu2RPkTjhNqEUwsBQKW9wYl36gmTDQpwdxoqpDcEGWbggzkKXl\n\tVcCg==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCVYSpJ1MaRAyAjlyqML7shKUewqdg6m5EZn7qFG3xJ3oAsaDkWsROA0Qz/02Zxa4O2cefTA2FRTPkh+RVc9tctr0bKio5Ck1M2yzRq2yNRtfrfYgQ==","X-Gm-Message-State":"AOJu0YzFgCaw2ToOJJ+XTLHyw0g0bnb6vE8wRoGGdYisVApEmU1Sb6po\n\tWVapNXULqA7lOT7tYbNu4UhczRuaYdC7RzXgTt/658GAVETdkmtFtoYMX01e6iR+8SqtlZ/QJs9\n\t88m04GJId2K+xYJ1ItwG8odKH7SvhD9+IXwi5nQ==","X-Google-Smtp-Source":"AGHT+IFoVC8fe0zpj/mH24Q4xn/s5btj638HHZ4zR0OcAIVXSnoKJJhNtir10pEjibTdnaNNs5mMExnx1WUifaQOw7Y=","X-Received":"by 2002:a0d:ebd7:0:b0:60a:1d20:4fb4 with SMTP id\n\tu206-20020a0debd7000000b0060a1d204fb4mr2256062ywe.14.1710334219998;\n\tWed, 13 Mar 2024 05:50:19 -0700 (PDT)","MIME-Version":"1.0","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","In-Reply-To":"<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Wed, 13 Mar 2024 12:49:44 +0000","Message-ID":"<CAEmqJPrzZT+L8uNeuSx1HOXgK1TO-V7GjYdJUgOTvYEnooNnPA@mail.gmail.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28945,"web_url":"https://patchwork.libcamera.org/comment/28945/","msgid":"<a7fcd9b7-9553-48cd-851e-bdaa40cda67e@ideasonboard.com>","date":"2024-03-13T12:55:06","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"On 13/03/2024 14:49, Naushir Patuck wrote:\n> On Wed, 13 Mar 2024 at 12:46, Tomi Valkeinen\n> <tomi.valkeinen@ideasonboard.com> wrote:\n>>\n>> On 13/03/2024 14:20, Naushir Patuck wrote:\n>>\n>>> ?> +               case MediaBusFormatInfo::Type::Metadata:\n>>>> +                       /*\n>>>> +                        * Skip metadata streams that are not sensor embedded\n>>>> +                        * data. The source stream reports a generic metadata\n>>>> +                        * format, check the sink stream for the exact format.\n>>>> +                        */\n>>>> +                       formats = subdev_->formats(route.sink);\n>>>> +                       if (formats.size() != 1)\n>>>> +                               continue;\n>>>\n>>> Should this test be if (!formats.size()) insead?  It might be possible\n>>> to have multiple metadata types.\n>>\n>> The driver in my branch is old and hacky. I should see what Laurent has\n>> done with the imx219 in his branch, and possibly just take that one.\n>>\n>> I think advertising only a single format makes sense here, as the\n>> embedded format is defined by the video format.\n>>\n>>>> +\n>>>> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n>>>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n>>>> +                               continue;\n>>>\n>>> The IMX219 driver (from Tomi's kernel tree) advertises\n>>> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n>>> data stream, which translates to a type of\n>>> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n>>> should this check include MediaBusFormatInfo::Type::Metadata?\n>>\n>> Laurent's version should also report those same mbus formats. Hmm, oh,\n>> but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n> \n> Can you point to Laurent's tree?  I'll take the driver from there and\n> see if these comments are still valid.\n\nhttps://lore.kernel.org/all/20240301213231.10340-1-laurent.pinchart@ideasonboard.com/\n\n  Tomi","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 AAE18BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 12:55:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AB3B662C8C;\n\tWed, 13 Mar 2024 13:55:11 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 52ECF62C85\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 13:55:10 +0100 (CET)","from [192.168.88.20] (91-154-34-181.elisa-laajakaista.fi\n\t[91.154.34.181])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 62C34899;\n\tWed, 13 Mar 2024 13:54: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=\"RlR17mHa\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710334487;\n\tbh=br4BCcuR0Q1vCPrtTiNoSp0HXt1g6JvhRFpDkMRBLVQ=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=RlR17mHaNEQeu6rQefIhyX2wz63VzKOC6E6eimUlASi877IO3X10DyjSpoTLuJFzf\n\t28efidHEus4xgor5V2NXF1x5fFa6TBwp5NRMZCheimA1pwbY8+nh6Fqeedp+TKw01m\n\tJQZb2bDXvi2fy2uBvbkMK2cgY4P1qi8fhnI9Jfus=","Message-ID":"<a7fcd9b7-9553-48cd-851e-bdaa40cda67e@ideasonboard.com>","Date":"Wed, 13 Mar 2024 14:55:06 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Content-Language":"en-US","To":"Naushir Patuck <naush@raspberrypi.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>\n\t<CAEmqJPrzZT+L8uNeuSx1HOXgK1TO-V7GjYdJUgOTvYEnooNnPA@mail.gmail.com>","From":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Autocrypt":"addr=tomi.valkeinen@ideasonboard.com; keydata=\n\txsFNBE6ms0cBEACyizowecZqXfMZtnBniOieTuFdErHAUyxVgtmr0f5ZfIi9Z4l+uUN4Zdw2\n\twCEZjx3o0Z34diXBaMRJ3rAk9yB90UJAnLtb8A97Oq64DskLF81GCYB2P1i0qrG7UjpASgCA\n\tRu0lVvxsWyIwSfoYoLrazbT1wkWRs8YBkkXQFfL7Mn3ZMoGPcpfwYH9O7bV1NslbmyJzRCMO\n\teYV258gjCcwYlrkyIratlHCek4GrwV8Z9NQcjD5iLzrONjfafrWPwj6yn2RlL0mQEwt1lOvn\n\tLnI7QRtB3zxA3yB+FLsT1hx0va6xCHpX3QO2gBsyHCyVafFMrg3c/7IIWkDLngJxFgz6DLiA\n\tG4ld1QK/jsYqfP2GIMH1mFdjY+iagG4DqOsjip479HCWAptpNxSOCL6z3qxCU8MCz8iNOtZk\n\tDYXQWVscM5qgYSn+fmMM2qN+eoWlnCGVURZZLDjg387S2E1jT/dNTOsM/IqQj+ZROUZuRcF7\n\t0RTtuU5q1HnbRNwy+23xeoSGuwmLQ2UsUk7Q5CnrjYfiPo3wHze8avK95JBoSd+WIRmV3uoO\n\trXCoYOIRlDhg9XJTrbnQ3Ot5zOa0Y9c4IpyAlut6mDtxtKXr4+8OzjSVFww7tIwadTK3wDQv\n\tBus4jxHjS6dz1g2ypT65qnHen6mUUH63lhzewqO9peAHJ0SLrQARAQABzTBUb21pIFZhbGtl\n\taW5lbiA8dG9taS52YWxrZWluZW5AaWRlYXNvbmJvYXJkLmNvbT7CwY4EEwEIADgWIQTEOAw+\n\tll79gQef86f6PaqMvJYe9QUCX/HruAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD6\n\tPaqMvJYe9WmFD/99NGoD5lBJhlFDHMZvO+Op8vCwnIRZdTsyrtGl72rVh9xRfcSgYPZUvBuT\n\tVDxE53mY9HaZyu1eGMccYRBaTLJSfCXl/g317CrMNdY0k40b9YeIX10feiRYEWoDIPQ3tMmA\n\t0nHDygzcnuPiPT68JYZ6tUOvAt7r6OX/litM+m2/E9mtp8xCoWOo/kYO4mOAIoMNvLB8vufi\n\tuBB4e/AvAjtny4ScuNV5c5q8MkfNIiOyag9QCiQ/JfoAqzXRjVb4VZG72AKaElwipiKCWEcU\n\tR4+Bu5Qbaxj7Cd36M/bI54OrbWWETJkVVSV1i0tghCd6HHyquTdFl7wYcz6cL1hn/6byVnD+\n\tsR3BLvSBHYp8WSwv0TCuf6tLiNgHAO1hWiQ1pOoXyMEsxZlgPXT+wb4dbNVunckwqFjGxRbl\n\tRz7apFT/ZRwbazEzEzNyrBOfB55xdipG/2+SmFn0oMFqFOBEszXLQVslh64lI0CMJm2OYYe3\n\tPxHqYaztyeXsx13Bfnq9+bUynAQ4uW1P5DJ3OIRZWKmbQd/Me3Fq6TU57LsvwRgE0Le9PFQs\n\tdcP2071rMTpqTUteEgODJS4VDf4lXJfY91u32BJkiqM7/62Cqatcz5UWWHq5xeF03MIUTqdE\n\tqHWk3RJEoWHWQRzQfcx6Fn2fDAUKhAddvoopfcjAHfpAWJ+ENc7BTQROprNHARAAx0aat8GU\n\thsusCLc4MIxOQwidecCTRc9Dz/7U2goUwhw2O5j9TPqLtp57VITmHILnvZf6q3QAho2QMQyE\n\tDDvHubrdtEoqaaSKxKkFie1uhWNNvXPhwkKLYieyL9m2JdU+b88HaDnpzdyTTR4uH7wk0bBa\n\tKbTSgIFDDe5lXInypewPO30TmYNkFSexnnM3n1PBCqiJXsJahE4ZQ+WnV5FbPUj8T2zXS2xk\n\t0LZ0+DwKmZ0ZDovvdEWRWrz3UzJ8DLHb7blPpGhmqj3ANXQXC7mb9qJ6J/VSl61GbxIO2Dwb\n\txPNkHk8fwnxlUBCOyBti/uD2uSTgKHNdabhVm2dgFNVuS1y3bBHbI/qjC3J7rWE0WiaHWEqy\n\tUVPk8rsph4rqITsj2RiY70vEW0SKePrChvET7D8P1UPqmveBNNtSS7In+DdZ5kUqLV7rJnM9\n\t/4cwy+uZUt8cuCZlcA5u8IsBCNJudxEqBG10GHg1B6h1RZIz9Q9XfiBdaqa5+CjyFs8ua01c\n\t9HmyfkuhXG2OLjfQuK+Ygd56mV3lq0aFdwbaX16DG22c6flkkBSjyWXYepFtHz9KsBS0DaZb\n\t4IkLmZwEXpZcIOQjQ71fqlpiXkXSIaQ6YMEs8WjBbpP81h7QxWIfWtp+VnwNGc6nq5IQDESH\n\tmvQcsFS7d3eGVI6eyjCFdcAO8eMAEQEAAcLBXwQYAQIACQUCTqazRwIbDAAKCRD6PaqMvJYe\n\t9fA7EACS6exUedsBKmt4pT7nqXBcRsqm6YzT6DeCM8PWMTeaVGHiR4TnNFiT3otD5UpYQI7S\n\tsuYxoTdHrrrBzdlKe5rUWpzoZkVK6p0s9OIvGzLT0lrb0HC9iNDWT3JgpYDnk4Z2mFi6tTbq\n\txKMtpVFRA6FjviGDRsfkfoURZI51nf2RSAk/A8BEDDZ7lgJHskYoklSpwyrXhkp9FHGMaYII\n\tm9EKuUTX9JPDG2FTthCBrdsgWYPdJQvM+zscq09vFMQ9Fykbx5N8z/oFEUy3ACyPqW2oyfvU\n\tCH5WDpWBG0s5BALp1gBJPytIAd/pY/5ZdNoi0Cx3+Z7jaBFEyYJdWy1hGddpkgnMjyOfLI7B\n\tCFrdecTZbR5upjNSDvQ7RG85SnpYJTIin+SAUazAeA2nS6gTZzumgtdw8XmVXZwdBfF+ICof\n\t92UkbYcYNbzWO/GHgsNT1WnM4sa9lwCSWH8Fw1o/3bX1VVPEsnESOfxkNdu+gAF5S6+I6n3a\n\tueeIlwJl5CpT5l8RpoZXEOVtXYn8zzOJ7oGZYINRV9Pf8qKGLf3Dft7zKBP832I3PQjeok7F\n\tyjt+9S+KgSFSHP3Pa4E7lsSdWhSlHYNdG/czhoUkSCN09C0rEK93wxACx3vtxPLjXu6RptBw\n\t3dRq7n+mQChEB1am0BueV1JZaBboIL0AGlSJkm23kw==","In-Reply-To":"<CAEmqJPrzZT+L8uNeuSx1HOXgK1TO-V7GjYdJUgOTvYEnooNnPA@mail.gmail.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28946,"web_url":"https://patchwork.libcamera.org/comment/28946/","msgid":"<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@ideasonboard.com>","date":"2024-03-13T16:16:15","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"On 13/03/2024 14:46, Tomi Valkeinen wrote:\n> On 13/03/2024 14:20, Naushir Patuck wrote:\n> \n>> ?> +               case MediaBusFormatInfo::Type::Metadata:\n>>> +                       /*\n>>> +                        * Skip metadata streams that are not sensor \n>>> embedded\n>>> +                        * data. The source stream reports a generic \n>>> metadata\n>>> +                        * format, check the sink stream for the \n>>> exact format.\n>>> +                        */\n>>> +                       formats = subdev_->formats(route.sink);\n>>> +                       if (formats.size() != 1)\n>>> +                               continue;\n>>\n>> Should this test be if (!formats.size()) insead?  It might be possible\n>> to have multiple metadata types.\n> \n> The driver in my branch is old and hacky. I should see what Laurent has \n> done with the imx219 in his branch, and possibly just take that one.\n> \n> I think advertising only a single format makes sense here, as the \n> embedded format is defined by the video format.\n> \n>>> +\n>>> +                       if \n>>> (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n>>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n>>> +                               continue;\n>>\n>> The IMX219 driver (from Tomi's kernel tree) advertises\n>> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n>> data stream, which translates to a type of\n>> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n>> should this check include MediaBusFormatInfo::Type::Metadata?\n> \n> Laurent's version should also report those same mbus formats. Hmm, oh, \n> but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n\nThis looks a bit odd. The driver gives MEDIA_BUS_FMT_CCS_EMBEDDED when \nenumerating the mbus codes, but then always sets the format to META_8 or \nMETA_10. I'm guessing the driver is supposed to keep the internal pad's \nformat as MEDIA_BUS_FMT_CCS_EMBEDDED, and only the external pad would \nuse META_8/10...\n\n  Tomi","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 3E020BD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 16:16:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4CE5D62C8B;\n\tWed, 13 Mar 2024 17:16:20 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8255C62C86\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 17:16:18 +0100 (CET)","from [192.168.88.20] (91-154-34-181.elisa-laajakaista.fi\n\t[91.154.34.181])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 79697B1;\n\tWed, 13 Mar 2024 17:15:55 +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=\"Kn2ouMz1\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710346555;\n\tbh=yFQTiDwLxxrWq+fRWRSbPhXIYcyTp5ULx81B7YvPeJw=;\n\th=Date:Subject:From:To:Cc:References:In-Reply-To:From;\n\tb=Kn2ouMz1S1KfD0ZMRLxxN/t1VVoG+JsPMzg7immOc+vlr6H6RFzN8X50+ijJl+Nja\n\tmosJ+lcAZ6F6K+Um1oIdvqUvHZZMmcIqvJAnypgrHs3PzTX/5XW1EsgTfvoHFNCe1F\n\tbhtZEdrv+KtTr04JRBHLLi1o4z+l4hxd9uJwfBh0=","Message-ID":"<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@ideasonboard.com>","Date":"Wed, 13 Mar 2024 18:16:15 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Content-Language":"en-US","From":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","To":"Naushir Patuck <naush@raspberrypi.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","Autocrypt":"addr=tomi.valkeinen@ideasonboard.com; keydata=\n\txsFNBE6ms0cBEACyizowecZqXfMZtnBniOieTuFdErHAUyxVgtmr0f5ZfIi9Z4l+uUN4Zdw2\n\twCEZjx3o0Z34diXBaMRJ3rAk9yB90UJAnLtb8A97Oq64DskLF81GCYB2P1i0qrG7UjpASgCA\n\tRu0lVvxsWyIwSfoYoLrazbT1wkWRs8YBkkXQFfL7Mn3ZMoGPcpfwYH9O7bV1NslbmyJzRCMO\n\teYV258gjCcwYlrkyIratlHCek4GrwV8Z9NQcjD5iLzrONjfafrWPwj6yn2RlL0mQEwt1lOvn\n\tLnI7QRtB3zxA3yB+FLsT1hx0va6xCHpX3QO2gBsyHCyVafFMrg3c/7IIWkDLngJxFgz6DLiA\n\tG4ld1QK/jsYqfP2GIMH1mFdjY+iagG4DqOsjip479HCWAptpNxSOCL6z3qxCU8MCz8iNOtZk\n\tDYXQWVscM5qgYSn+fmMM2qN+eoWlnCGVURZZLDjg387S2E1jT/dNTOsM/IqQj+ZROUZuRcF7\n\t0RTtuU5q1HnbRNwy+23xeoSGuwmLQ2UsUk7Q5CnrjYfiPo3wHze8avK95JBoSd+WIRmV3uoO\n\trXCoYOIRlDhg9XJTrbnQ3Ot5zOa0Y9c4IpyAlut6mDtxtKXr4+8OzjSVFww7tIwadTK3wDQv\n\tBus4jxHjS6dz1g2ypT65qnHen6mUUH63lhzewqO9peAHJ0SLrQARAQABzTBUb21pIFZhbGtl\n\taW5lbiA8dG9taS52YWxrZWluZW5AaWRlYXNvbmJvYXJkLmNvbT7CwY4EEwEIADgWIQTEOAw+\n\tll79gQef86f6PaqMvJYe9QUCX/HruAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD6\n\tPaqMvJYe9WmFD/99NGoD5lBJhlFDHMZvO+Op8vCwnIRZdTsyrtGl72rVh9xRfcSgYPZUvBuT\n\tVDxE53mY9HaZyu1eGMccYRBaTLJSfCXl/g317CrMNdY0k40b9YeIX10feiRYEWoDIPQ3tMmA\n\t0nHDygzcnuPiPT68JYZ6tUOvAt7r6OX/litM+m2/E9mtp8xCoWOo/kYO4mOAIoMNvLB8vufi\n\tuBB4e/AvAjtny4ScuNV5c5q8MkfNIiOyag9QCiQ/JfoAqzXRjVb4VZG72AKaElwipiKCWEcU\n\tR4+Bu5Qbaxj7Cd36M/bI54OrbWWETJkVVSV1i0tghCd6HHyquTdFl7wYcz6cL1hn/6byVnD+\n\tsR3BLvSBHYp8WSwv0TCuf6tLiNgHAO1hWiQ1pOoXyMEsxZlgPXT+wb4dbNVunckwqFjGxRbl\n\tRz7apFT/ZRwbazEzEzNyrBOfB55xdipG/2+SmFn0oMFqFOBEszXLQVslh64lI0CMJm2OYYe3\n\tPxHqYaztyeXsx13Bfnq9+bUynAQ4uW1P5DJ3OIRZWKmbQd/Me3Fq6TU57LsvwRgE0Le9PFQs\n\tdcP2071rMTpqTUteEgODJS4VDf4lXJfY91u32BJkiqM7/62Cqatcz5UWWHq5xeF03MIUTqdE\n\tqHWk3RJEoWHWQRzQfcx6Fn2fDAUKhAddvoopfcjAHfpAWJ+ENc7BTQROprNHARAAx0aat8GU\n\thsusCLc4MIxOQwidecCTRc9Dz/7U2goUwhw2O5j9TPqLtp57VITmHILnvZf6q3QAho2QMQyE\n\tDDvHubrdtEoqaaSKxKkFie1uhWNNvXPhwkKLYieyL9m2JdU+b88HaDnpzdyTTR4uH7wk0bBa\n\tKbTSgIFDDe5lXInypewPO30TmYNkFSexnnM3n1PBCqiJXsJahE4ZQ+WnV5FbPUj8T2zXS2xk\n\t0LZ0+DwKmZ0ZDovvdEWRWrz3UzJ8DLHb7blPpGhmqj3ANXQXC7mb9qJ6J/VSl61GbxIO2Dwb\n\txPNkHk8fwnxlUBCOyBti/uD2uSTgKHNdabhVm2dgFNVuS1y3bBHbI/qjC3J7rWE0WiaHWEqy\n\tUVPk8rsph4rqITsj2RiY70vEW0SKePrChvET7D8P1UPqmveBNNtSS7In+DdZ5kUqLV7rJnM9\n\t/4cwy+uZUt8cuCZlcA5u8IsBCNJudxEqBG10GHg1B6h1RZIz9Q9XfiBdaqa5+CjyFs8ua01c\n\t9HmyfkuhXG2OLjfQuK+Ygd56mV3lq0aFdwbaX16DG22c6flkkBSjyWXYepFtHz9KsBS0DaZb\n\t4IkLmZwEXpZcIOQjQ71fqlpiXkXSIaQ6YMEs8WjBbpP81h7QxWIfWtp+VnwNGc6nq5IQDESH\n\tmvQcsFS7d3eGVI6eyjCFdcAO8eMAEQEAAcLBXwQYAQIACQUCTqazRwIbDAAKCRD6PaqMvJYe\n\t9fA7EACS6exUedsBKmt4pT7nqXBcRsqm6YzT6DeCM8PWMTeaVGHiR4TnNFiT3otD5UpYQI7S\n\tsuYxoTdHrrrBzdlKe5rUWpzoZkVK6p0s9OIvGzLT0lrb0HC9iNDWT3JgpYDnk4Z2mFi6tTbq\n\txKMtpVFRA6FjviGDRsfkfoURZI51nf2RSAk/A8BEDDZ7lgJHskYoklSpwyrXhkp9FHGMaYII\n\tm9EKuUTX9JPDG2FTthCBrdsgWYPdJQvM+zscq09vFMQ9Fykbx5N8z/oFEUy3ACyPqW2oyfvU\n\tCH5WDpWBG0s5BALp1gBJPytIAd/pY/5ZdNoi0Cx3+Z7jaBFEyYJdWy1hGddpkgnMjyOfLI7B\n\tCFrdecTZbR5upjNSDvQ7RG85SnpYJTIin+SAUazAeA2nS6gTZzumgtdw8XmVXZwdBfF+ICof\n\t92UkbYcYNbzWO/GHgsNT1WnM4sa9lwCSWH8Fw1o/3bX1VVPEsnESOfxkNdu+gAF5S6+I6n3a\n\tueeIlwJl5CpT5l8RpoZXEOVtXYn8zzOJ7oGZYINRV9Pf8qKGLf3Dft7zKBP832I3PQjeok7F\n\tyjt+9S+KgSFSHP3Pa4E7lsSdWhSlHYNdG/czhoUkSCN09C0rEK93wxACx3vtxPLjXu6RptBw\n\t3dRq7n+mQChEB1am0BueV1JZaBboIL0AGlSJkm23kw==","In-Reply-To":"<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28948,"web_url":"https://patchwork.libcamera.org/comment/28948/","msgid":"<20240313185615.GA8399@pendragon.ideasonboard.com>","date":"2024-03-13T18:56:15","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Naush,\n\nOn Wed, Mar 13, 2024 at 12:20:36PM +0000, Naushir Patuck wrote:\n> On Fri, 1 Mar 2024 at 21:21, Laurent Pinchart wrote:\n> >\n> > Add a new CameraSensorRaw implementation of the CameraSensor interface\n> > tailored to devices that implement the new V4L2 raw camera sensors API.\n> >\n> > This new class duplicates code from the CameraSensorLegacy class. The\n> > two classes will be refactored to share code.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  Documentation/Doxyfile.in                  |    1 +\n> >  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n> >  src/libcamera/sensor/meson.build           |    1 +\n> >  3 files changed, 1065 insertions(+)\n> >  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n> >\n> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> > index 75326c1964e9..8bc55be60a59 100644\n> > --- a/Documentation/Doxyfile.in\n> > +++ b/Documentation/Doxyfile.in\n> > @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n> >                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n> >                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> > +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n> >                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n> >                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> > diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > new file mode 100644\n> > index 000000000000..8c17da5876a4\n> > --- /dev/null\n> > +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > @@ -0,0 +1,1063 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy.\n> > + *\n> > + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <float.h>\n> > +#include <iomanip>\n> > +#include <limits.h>\n> > +#include <map>\n> > +#include <math.h>\n> > +#include <memory>\n> > +#include <optional>\n> > +#include <string.h>\n> > +#include <string>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/base/class.h>\n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/utils.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/control_ids.h>\n> > +#include <libcamera/controls.h>\n> > +#include <libcamera/geometry.h>\n> > +#include <libcamera/orientation.h>\n> > +#include <libcamera/property_ids.h>\n> > +#include <libcamera/transform.h>\n> > +\n> > +#include <libcamera/ipa/core_ipa_interface.h>\n> > +\n> > +#include \"libcamera/internal/bayer_format.h\"\n> > +#include \"libcamera/internal/camera_lens.h\"\n> > +#include \"libcamera/internal/camera_sensor.h\"\n> > +#include \"libcamera/internal/camera_sensor_properties.h\"\n> > +#include \"libcamera/internal/formats.h\"\n> > +#include \"libcamera/internal/media_device.h\"\n> > +#include \"libcamera/internal/sysfs.h\"\n> > +#include \"libcamera/internal/v4l2_subdevice.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +class BayerFormat;\n> > +class CameraLens;\n> > +class MediaEntity;\n> > +class SensorConfiguration;\n> > +\n> > +struct CameraSensorProperties;\n> > +\n> > +enum class Orientation;\n> > +\n> > +LOG_DECLARE_CATEGORY(CameraSensor)\n> > +\n> > +class CameraSensorRaw : public CameraSensor, protected Loggable\n> > +{\n> > +public:\n> > +       CameraSensorRaw(const MediaEntity *entity);\n> > +       ~CameraSensorRaw();\n> > +\n> > +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> > +       match(MediaEntity *entity);\n> > +\n> > +       const std::string &model() const override { return model_; }\n> > +       const std::string &id() const override { return id_; }\n> > +\n> > +       const MediaEntity *entity() const override { return entity_; }\n> > +       V4L2Subdevice *device() override { return subdev_.get(); }\n> > +\n> > +       CameraLens *focusLens() override { return focusLens_.get(); }\n> > +\n> > +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> > +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> > +       Size resolution() const override;\n> > +\n> > +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                                     const Size &size) const override;\n> > +       int setFormat(V4L2SubdeviceFormat *format,\n> > +                     Transform transform = Transform::Identity) override;\n> > +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> > +\n> > +       int applyConfiguration(const SensorConfiguration &config,\n> > +                              Transform transform = Transform::Identity,\n> > +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> > +\n> > +       const ControlList &properties() const override { return properties_; }\n> > +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> > +       Transform computeTransform(Orientation *orientation) const override;\n> > +       BayerFormat::Order bayerOrder(Transform t) const override;\n> > +\n> > +       const ControlInfoMap &controls() const override;\n> > +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> > +       int setControls(ControlList *ctrls) override;\n> > +\n> > +       const std::vector<controls::draft::TestPatternModeEnum> &\n> > +       testPatternModes() const override { return testPatternModes_; }\n> > +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> > +\n> > +protected:\n> > +       std::string logPrefix() const override;\n> > +\n> > +private:\n> > +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> > +\n> > +       std::optional<int> init();\n> > +       int initProperties();\n> > +       void initStaticProperties();\n> > +       void initTestPatternModes();\n> > +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> > +\n> > +       const MediaEntity *entity_;\n> > +       std::unique_ptr<V4L2Subdevice> subdev_;\n> > +\n> > +       struct Streams {\n> > +               V4L2Subdevice::Stream sink;\n> > +               V4L2Subdevice::Stream source;\n> > +       };\n> > +\n> > +       struct {\n> > +               Streams image;\n> > +               std::optional<Streams> edata;\n> > +       } streams_;\n> > +\n> > +       const CameraSensorProperties *staticProps_;\n> > +\n> > +       std::string model_;\n> > +       std::string id_;\n> > +\n> > +       V4L2Subdevice::Formats formats_;\n> > +       std::vector<unsigned int> mbusCodes_;\n> > +       std::vector<Size> sizes_;\n> > +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> > +       controls::draft::TestPatternModeEnum testPatternMode_;\n> > +\n> > +       Size pixelArraySize_;\n> > +       Rectangle activeArea_;\n> > +       BayerFormat::Order cfaPattern_;\n> > +       bool supportFlips_;\n> > +       bool flipsAlterBayerOrder_;\n> > +       Orientation mountingOrientation_;\n> > +\n> > +       ControlList properties_;\n> > +\n> > +       std::unique_ptr<CameraLens> focusLens_;\n> > +};\n> > +\n> > +/**\n> > + * \\class CameraSensorRaw\n> > + * \\brief A camera sensor based on V4L2 subdevices\n> > + *\n> > + * This class supports single-subdev sensors with a single source pad and one\n> > + * or two internal sink pads (for the image and embedded data streams).\n> > + */\n> > +\n> > +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> > +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> > +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> > +{\n> > +}\n> > +\n> > +CameraSensorRaw::~CameraSensorRaw() = default;\n> > +\n> > +std::variant<std::unique_ptr<CameraSensor>, int>\n> > +CameraSensorRaw::match(MediaEntity *entity)\n> > +{\n> > +       /* Check the entity type. */\n> > +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> > +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported entity type (\"\n> > +                       << utils::to_underlying(entity->type())\n> > +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /* Count and check the number of pads. */\n> > +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> > +                                               | MEDIA_PAD_FL_SOURCE\n> > +                                               | MEDIA_PAD_FL_INTERNAL;\n> > +       unsigned int numSinks = 0;\n> > +       unsigned int numSources = 0;\n> > +\n> > +       for (const MediaPad *pad : entity->pads()) {\n> > +               switch (pad->flags() & kPadFlagsMask) {\n> > +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> > +                       numSinks++;\n> > +                       break;\n> > +\n> > +               case MEDIA_PAD_FL_SOURCE:\n> > +                       numSources++;\n> > +                       break;\n> > +\n> > +               default:\n> > +                       libcamera::LOG(CameraSensor, Debug)\n> > +                               << entity->name() << \": unsupported pad \" << pad->index()\n> > +                               << \" type \" << utils::hex(pad->flags());\n> > +                       return { 0 };\n> > +               }\n> > +       }\n> > +\n> > +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported number of sinks (\"\n> > +                       << numSinks << \") or sources (\" << numSources << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /*\n> > +        * The entity matches. Create the camera sensor and initialize it. The\n> > +        * init() function will perform further match checks.\n> > +        */\n> > +       std::unique_ptr<CameraSensorRaw> sensor =\n> > +               std::make_unique<CameraSensorRaw>(entity);\n> > +\n> > +       std::optional<int> err = sensor->init();\n> > +       if (err)\n> > +               return { *err };\n> > +\n> > +       return { std::move(sensor) };\n> > +}\n> > +\n> > +std::optional<int> CameraSensorRaw::init()\n> > +{\n> > +       /* Create and open the subdev. */\n> > +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> > +       int ret = subdev_->open();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 1. Identify the pads.\n> > +        */\n> > +\n> > +       /*\n> > +        * First locate the source pad. The match() function guarantees there\n> > +        * is one and only one source pad.\n> > +        */\n> > +       unsigned int sourcePad = UINT_MAX;\n> > +\n> > +       for (const MediaPad *pad : entity_->pads()) {\n> > +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> > +                       sourcePad = pad->index();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Iterate over the routes to identify the streams on the source pad,\n> > +        * and the internal sink pads.\n> > +        */\n> > +       V4L2Subdevice::Routing routing = {};\n> > +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       bool imageStreamFound = false;\n> > +\n> > +       for (const V4L2Subdevice::Route &route : routing) {\n> > +               if (route.source.pad != sourcePad) {\n> > +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> > +                       return { -EINVAL };\n> > +               }\n> > +\n> > +               /* Identify the stream type based on the supported formats. */\n> > +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> > +\n> > +               std::optional<MediaBusFormatInfo::Type> type;\n> > +\n> > +               for (const auto &[code, sizes] : formats) {\n> > +                       const MediaBusFormatInfo &info =\n> > +                               MediaBusFormatInfo::info(code);\n> > +                       if (info.isValid()) {\n> > +                               type = info.type;\n> > +                               break;\n> > +                       }\n> > +               }\n> > +\n> > +               if (!type) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"No known format on pad \" << route.source;\n> > +                       continue;\n> > +               }\n> > +\n> > +               switch (*type) {\n> > +               case MediaBusFormatInfo::Type::Image:\n> > +                       if (imageStreamFound) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal image streams (\"\n> > +                                       << streams_.image.sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       imageStreamFound = true;\n> > +                       streams_.image.sink = route.sink;\n> > +                       streams_.image.source = route.source;\n> > +                       break;\n> > +\n> ?> +               case MediaBusFormatInfo::Type::Metadata:\n> > +                       /*\n> > +                        * Skip metadata streams that are not sensor embedded\n> > +                        * data. The source stream reports a generic metadata\n> > +                        * format, check the sink stream for the exact format.\n> > +                        */\n> > +                       formats = subdev_->formats(route.sink);\n> > +                       if (formats.size() != 1)\n> > +                               continue;\n> \n> Should this test be if (!formats.size()) insead?  It might be possible\n> to have multiple metadata types.\n\nOn the internal metadata pads, the sensor should advertise a single\nformat (e.g. MEDIA_BUS_FMT_CCS_EMBEDDED, MEDIA_BUS_FMT_OV2740_EMBEDDED,\nfuture additions will likely include things like statistics, ...) in\norder to clearly indicate what data the sensor generate for the\ncorresponding stream. On the sensor's source pad, this translates to one\nof the MEDIA_BUS_FMT_META_* formats, so there we can have multiple\nformats.\n\n> > +\n> > +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> > +                           MediaBusFormatInfo::Type::EmbeddedData)\n> > +                               continue;\n> \n> The IMX219 driver (from Tomi's kernel tree) advertises\n> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> data stream, which translates to a type of\n> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> should this check include MediaBusFormatInfo::Type::Metadata?\n\nThe driver needs updating, the patches I've sent for the kernel side can\nbe found at\nhttps://lore.kernel.org/all/20240301213231.10340-1-laurent.pinchart@ideasonboard.com/.\n\n> > +\n> > +                       if (streams_.edata) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal embedded data streams (\"\n> > +                                       << streams_.edata->sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       streams_.edata = { route.sink, route.source };\n> > +                       break;\n> > +\n> > +               default:\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       if (!imageStreamFound) {\n> > +               LOG(CameraSensor, Error) << \"No image stream found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug)\n> > +               << \"Found image stream \" << streams_.image.sink\n> > +               << \" -> \" << streams_.image.source;\n> > +\n> > +       if (streams_.edata)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Found embedded data stream \" << streams_.edata->sink\n> > +                       << \" -> \" << streams_.edata->source;\n> > +\n> > +       /*\n> > +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> > +        * array order for the image stream.\n> > +        */\n> > +\n> > +       /*\n> > +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> > +        * the internal image sink pad as it is guaranteed to expose a single\n> > +        * format, and is not affected by flips.\n> > +        */\n> > +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> > +       if (formats.size() != 1) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Image pad has \" << formats.size()\n> > +                       << \" formats, expected 1\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       uint32_t nativeFormat = formats.cbegin()->first;\n> > +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> > +       if (!bayerFormat.isValid()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Invalid native format \" << nativeFormat;\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       cfaPattern_ = bayerFormat.order;\n> \n> cfaPattern_ does not account for the current transform applied to the\n> sensor.\n\nCorrect, and here it is by design. The cfaPattern_ variable stores the\nnative bayer order without considering flips. Note how it is retrieved\nfrom the internal image pad, which always exposes the same format\nregardless of the current value of the flip controls. I need to document\nthis on the kernel side.\n\n> So we could end up with the wrong Bayer order.  Also related,\n> flipsAlterBayerOrder_ never gets set correctly.  I do have a patch to\n> fix both that can be squashed into this if you want.\n\nHmmmm indeed, flipsAlterBayerOrder_ seems to be an oversight. I'll fix\nit, that's easy.\n\n> > +\n> > +       /*\n> > +        * Retrieve and cache the media bus codes and sizes on the source image\n> > +        * stream.\n> > +        */\n> > +       formats_ = subdev_->formats(streams_.image.source);\n> > +       if (formats_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"No image format found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       /* Populate and sort the media bus codes and the sizes. */\n> > +       for (const auto &[code, ranges] : formats_) {\n> > +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> > +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> > +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> > +                       continue;\n> > +\n> > +               mbusCodes_.push_back(code);\n> > +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> > +                              [](const SizeRange &range) { return range.max; });\n> > +       }\n> > +\n> > +       if (mbusCodes_.empty()) {\n> > +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> > +       std::sort(sizes_.begin(), sizes_.end());\n> > +\n> > +       /*\n> > +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> > +        * they are the keys in the formats map.\n> > +        */\n> > +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> > +       sizes_.erase(last, sizes_.end());\n> > +\n> > +       /*\n> > +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> > +        * all the expected selection rectangles are supported.\n> > +        */\n> > +\n> > +       Rectangle rect;\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       pixelArraySize_ = rect.size();\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> > +                                   &activeArea_);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * 4. Verify that all required controls are present.\n> > +        */\n> > +\n> > +       const ControlIdMap &controls = subdev_->controls().idmap();\n> > +\n> > +       static constexpr uint32_t mandatoryControls[] = {\n> > +               V4L2_CID_ANALOGUE_GAIN,\n> > +               V4L2_CID_CAMERA_ORIENTATION,\n> > +               V4L2_CID_EXPOSURE,\n> > +               V4L2_CID_HBLANK,\n> > +               V4L2_CID_PIXEL_RATE,\n> > +               V4L2_CID_VBLANK,\n> > +       };\n> > +\n> > +       ret = 0;\n> > +\n> > +       for (uint32_t ctrl : mandatoryControls) {\n> > +               if (!controls.count(ctrl)) {\n> > +                       LOG(CameraSensor, Error)\n> > +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> > +                               << \" not available\";\n> > +                       ret = -EINVAL;\n> > +               }\n> > +       }\n> > +\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"The sensor kernel driver needs to be fixed\";\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * Verify if sensor supports horizontal/vertical flips\n> > +        *\n> > +        * \\todo Handle horizontal and vertical flips independently.\n> > +        */\n> > +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> > +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> > +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> > +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> > +               supportFlips_ = true;\n> > +\n> > +       if (!supportFlips_)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> > +\n> > +       /*\n> > +        * 5. Discover ancillary devices.\n> > +        *\n> > +        * \\todo This code may be shared by different V4L2 sensor classes.\n> > +        */\n> > +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> > +               switch (ancillary->function()) {\n> > +               case MEDIA_ENT_F_LENS:\n> > +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> > +                       ret = focusLens_->init();\n> > +                       if (ret) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Lens initialisation failed, lens disabled\";\n> > +                               focusLens_.reset();\n> > +                       }\n> > +                       break;\n> > +\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported ancillary entity function \"\n> > +                               << ancillary->function();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * 6. Initialize properties.\n> > +        */\n> > +\n> > +       ret = initProperties();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 7. Initialize controls.\n> > +        */\n> > +\n> > +       /*\n> > +        * Set HBLANK to the minimum to start with a well-defined line length,\n> > +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> > +        * minimum line length in their calculations.\n> > +        *\n> > +        * At present, there is no way of knowing if a control is read-only.\n> > +        * As a workaround, assume that if the minimum and maximum values of\n> > +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> > +        * is read-only.\n> > +        *\n> > +        * \\todo The control API ought to have a flag to specify if a control\n> > +        * is read-only which could be used below.\n> > +        */\n> > +       const ControlInfoMap &ctrls = subdev_->controls();\n> > +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> > +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> > +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> > +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> > +\n> > +               if (hblankMin != hblankMax) {\n> > +                       ControlList ctrl(subdev_->controls());\n> > +\n> > +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> > +                       ret = subdev_->setControls(&ctrl);\n> > +                       if (ret)\n> > +                               return { ret };\n> > +               }\n> > +       }\n> > +\n> > +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       return {};\n> > +}\n> > +\n> > +int CameraSensorRaw::initProperties()\n> > +{\n> > +       model_ = subdev_->model();\n> > +       properties_.set(properties::Model, utils::toAscii(model_));\n> > +\n> > +       /* Generate a unique ID for the sensor. */\n> > +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> > +       if (id_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /* Initialize the static properties from the sensor database. */\n> > +       initStaticProperties();\n> > +\n> > +       /* Retrieve and register properties from the kernel interface. */\n> > +       const ControlInfoMap &controls = subdev_->controls();\n> > +\n> > +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> > +       if (orientation != controls.end()) {\n> > +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> > +               int32_t propertyValue;\n> > +\n> > +               switch (v4l2Orientation) {\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported camera location \"\n> > +                               << v4l2Orientation << \", setting to External\";\n> > +                       [[fallthrough]];\n> > +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> > +                       propertyValue = properties::CameraLocationExternal;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> > +                       propertyValue = properties::CameraLocationFront;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_BACK:\n> > +                       propertyValue = properties::CameraLocationBack;\n> > +                       break;\n> > +               }\n> > +               properties_.set(properties::Location, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> > +       }\n> > +\n> > +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> > +       if (rotationControl != controls.end()) {\n> > +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> > +\n> > +               /*\n> > +                * Cache the Transform associated with the camera mounting\n> > +                * rotation for later use in computeTransform().\n> > +                */\n> > +               bool success;\n> > +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> > +               if (!success) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Invalid rotation of \" << propertyValue\n> > +                               << \" degrees - ignoring\";\n> > +                       mountingOrientation_ = Orientation::Rotate0;\n> > +               }\n> > +\n> > +               properties_.set(properties::Rotation, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning)\n> > +                       << \"Rotation control not available, default to 0 degrees\";\n> > +               properties_.set(properties::Rotation, 0);\n> > +               mountingOrientation_ = Orientation::Rotate0;\n> > +       }\n> > +\n> > +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> > +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> > +\n> > +       /* Color filter array pattern. */\n> > +       uint32_t cfa;\n> \n> GCC 12 complains about cfa possibly used without being initialised.\n> The compiler should really know that the switch below covers all enum\n> cases, but it does not.\n\nIndeed, this happens with gcc 12.3.1 in release builds. This annoys me a\nlittle bit, as I think there's value in *not* having a default case, in\norder to catch failures to extend this switch statement when BayerFormat\ngets extended.  Nonetheless, if we have a build failure with a real\ncompiler, fixing it has priority :-( I'll add a default case.\n\n> > +\n> > +       switch (cfaPattern_) {\n> > +       case BayerFormat::BGGR:\n> > +               cfa = properties::draft::BGGR;\n> > +               break;\n> > +       case BayerFormat::GBRG:\n> > +               cfa = properties::draft::GBRG;\n> > +               break;\n> > +       case BayerFormat::GRBG:\n> > +               cfa = properties::draft::GRBG;\n> > +               break;\n> > +       case BayerFormat::RGGB:\n> > +               cfa = properties::draft::RGGB;\n> > +               break;\n> > +       case BayerFormat::MONO:\n> > +               cfa = properties::draft::MONO;\n> > +               break;\n> > +       }\n> > +\n> > +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +void CameraSensorRaw::initStaticProperties()\n> > +{\n> > +       staticProps_ = CameraSensorProperties::get(model_);\n> > +       if (!staticProps_)\n> > +               return;\n> > +\n> > +       /* Register the properties retrieved from the sensor database. */\n> > +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> > +\n> > +       initTestPatternModes();\n> > +}\n> > +\n> > +void CameraSensorRaw::initTestPatternModes()\n> > +{\n> > +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> > +       if (v4l2TestPattern == controls().end()) {\n> > +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> > +               return;\n> > +       }\n> > +\n> > +       const auto &testPatternModes = staticProps_->testPatternModes;\n> > +       if (testPatternModes.empty()) {\n> > +               /*\n> > +                * The camera sensor supports test patterns but we don't know\n> > +                * how to map them so this should be fixed.\n> > +                */\n> > +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> > +                                        << model() << \"\\'\";\n> > +               return;\n> > +       }\n> > +\n> > +       /*\n> > +        * Create a map that associates the V4L2 control index to the test\n> > +        * pattern mode by reversing the testPatternModes map provided by the\n> > +        * camera sensor properties. This makes it easier to verify if the\n> > +        * control index is supported in the below for loop that creates the\n> > +        * list of supported test patterns.\n> > +        */\n> > +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> > +       for (const auto &it : testPatternModes)\n> > +               indexToTestPatternMode[it.second] = it.first;\n> > +\n> > +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> > +               const int32_t index = value.get<int32_t>();\n> > +\n> > +               const auto it = indexToTestPatternMode.find(index);\n> > +               if (it == indexToTestPatternMode.end()) {\n> > +                       LOG(CameraSensor, Debug)\n> > +                               << \"Test pattern mode \" << index << \" ignored\";\n> > +                       continue;\n> > +               }\n> > +\n> > +               testPatternModes_.push_back(it->second);\n> > +       }\n> > +}\n> > +\n> > +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> > +{\n> > +       std::vector<Size> sizes;\n> > +\n> > +       const auto &format = formats_.find(mbusCode);\n> > +       if (format == formats_.end())\n> > +               return sizes;\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> > +Size CameraSensorRaw::resolution() const\n> > +{\n> > +       return std::min(sizes_.back(), activeArea_.size());\n> > +}\n> > +\n> > +V4L2SubdeviceFormat\n> > +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                          const Size &size) const\n> > +{\n> > +       unsigned int desiredArea = size.width * size.height;\n> > +       unsigned int bestArea = UINT_MAX;\n> > +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> > +       float bestRatio = FLT_MAX;\n> > +       const Size *bestSize = nullptr;\n> > +       uint32_t bestCode = 0;\n> > +\n> > +       for (unsigned int code : mbusCodes) {\n> > +               const auto formats = formats_.find(code);\n> > +               if (formats == formats_.end())\n> > +                       continue;\n> > +\n> > +               for (const SizeRange &range : formats->second) {\n> > +                       const Size &sz = range.max;\n> > +\n> > +                       if (sz.width < size.width || sz.height < size.height)\n> > +                               continue;\n> > +\n> > +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> > +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> > +                       unsigned int area = sz.width * sz.height;\n> > +                       unsigned int areaDiff = area - desiredArea;\n> > +\n> > +                       if (ratioDiff > bestRatio)\n> > +                               continue;\n> > +\n> > +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> > +                               bestRatio = ratioDiff;\n> > +                               bestArea = areaDiff;\n> > +                               bestSize = &sz;\n> > +                               bestCode = code;\n> > +                       }\n> > +               }\n> > +       }\n> > +\n> > +       if (!bestSize) {\n> > +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> > +               return {};\n> > +       }\n> > +\n> > +       V4L2SubdeviceFormat format{\n> > +               .code = bestCode,\n> > +               .size = *bestSize,\n> > +               .colorSpace = ColorSpace::Raw,\n> > +       };\n> > +\n> > +       return format;\n> > +}\n> > +\n> > +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> > +{\n> > +       /* Configure flips if the sensor supports that. */\n> > +       if (supportFlips_) {\n> > +               ControlList flipCtrls(subdev_->controls());\n> > +\n> > +               flipCtrls.set(V4L2_CID_HFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> > +               flipCtrls.set(V4L2_CID_VFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> > +\n> > +               int ret = subdev_->setControls(&flipCtrls);\n> > +               if (ret)\n> > +                       return ret;\n> > +       }\n> > +\n> > +       /* Apply format on the subdev. */\n> > +       int ret = subdev_->setFormat(streams_.image.source, format);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       subdev_->updateControlInfo();\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> > +{\n> > +       return subdev_->setFormat(streams_.image.source, format,\n> > +                                 V4L2Subdevice::Whence::TryFormat);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> > +                                       Transform transform,\n> > +                                       V4L2SubdeviceFormat *sensorFormat)\n> > +{\n> > +       if (!config.isValid()) {\n> > +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       std::vector<unsigned int> filteredCodes;\n> > +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> > +                    std::back_inserter(filteredCodes),\n> > +                    [&config](unsigned int mbusCode) {\n> > +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> > +                            if (bayer.bitDepth == config.bitDepth)\n> > +                                    return true;\n> > +                            return false;\n> > +                    });\n> > +       if (filteredCodes.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Cannot find any format with bit depth \"\n> > +                       << config.bitDepth;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /*\n> > +        * Compute the sensor's data frame size by applying the cropping\n> > +        * rectangle, subsampling and output crop to the sensor's pixel array\n> > +        * size.\n> > +        *\n> > +        * \\todo The actual size computation is for now ignored and only the\n> > +        * output size is considered. This implies that resolutions obtained\n> > +        * with two different cropping/subsampling will look identical and\n> > +        * only the first found one will be considered.\n> > +        */\n> > +       V4L2SubdeviceFormat subdevFormat = {};\n> > +       for (unsigned int code : filteredCodes) {\n> > +               for (const Size &size : sizes(code)) {\n> > +                       if (size.width != config.outputSize.width ||\n> > +                           size.height != config.outputSize.height)\n> > +                               continue;\n> > +\n> > +                       subdevFormat.code = code;\n> > +                       subdevFormat.size = size;\n> > +                       break;\n> > +               }\n> > +       }\n> > +       if (!subdevFormat.code) {\n> > +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       int ret = setFormat(&subdevFormat, transform);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * Return to the caller the format actually applied to the sensor.\n> > +        * This is relevant if transform has changed the bayer pattern order.\n> > +        */\n> > +       if (sensorFormat)\n> > +               *sensorFormat = subdevFormat;\n> > +\n> > +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> > +       /* \\todo Handle scaling in the digital domain. */\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> > +{\n> > +       info->model = model();\n> > +\n> > +       /*\n> > +        * The active area size is a static property, while the crop\n> > +        * rectangle needs to be re-read as it depends on the sensor\n> > +        * configuration.\n> > +        */\n> > +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> > +\n> > +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                       &info->analogCrop);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> > +        * are defined relatively to the active pixel area, while V4L2's\n> > +        * TGT_CROP target is defined in respect to the full pixel array.\n> > +        *\n> > +        * Compensate it by subtracting the active area offset.\n> > +        */\n> > +       info->analogCrop.x -= activeArea_.x;\n> > +       info->analogCrop.y -= activeArea_.y;\n> > +\n> > +       /* The bit depth and image size depend on the currently applied format. */\n> > +       V4L2SubdeviceFormat format{};\n> > +       ret = subdev_->getFormat(streams_.image.source, &format);\n> > +       if (ret)\n> > +               return ret;\n> > +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> > +       info->outputSize = format.size;\n> > +\n> > +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> > +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> > +\n> > +       /*\n> > +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> > +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> > +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> > +        */\n> > +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> > +                                                  V4L2_CID_HBLANK,\n> > +                                                  V4L2_CID_VBLANK });\n> > +       if (ctrls.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Failed to retrieve camera info controls\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> > +\n> > +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> > +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> > +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> > +\n> > +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> > +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> > +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> > +{\n> > +       /*\n> > +        * If we cannot do any flips we cannot change the native camera mounting\n> > +        * orientation.\n> > +        */\n> > +       if (!supportFlips_) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       /*\n> > +        * Now compute the required transform to obtain 'orientation' starting\n> > +        * from the mounting rotation.\n> > +        *\n> > +        * As a note:\n> > +        *      orientation / mountingOrientation_ = transform\n> > +        *      mountingOrientation_ * transform = orientation\n> > +        */\n> > +       Transform transform = *orientation / mountingOrientation_;\n> > +\n> > +       /*\n> > +        * If transform contains any Transpose we cannot do it, so adjust\n> > +        * 'orientation' to report the image native orientation and return Identity.\n> > +        */\n> > +       if (!!(transform & Transform::Transpose)) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       return transform;\n> > +}\n> > +\n> > +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> > +{\n> > +       if (!flipsAlterBayerOrder_)\n> > +               return cfaPattern_;\n> > +\n> > +       /*\n> > +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> > +        * using the rest of the Bayer format supplied by the caller.\n> > +        */\n> > +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> > +       return format.transform(t).order;\n> > +}\n> > +\n> > +const ControlInfoMap &CameraSensorRaw::controls() const\n> > +{\n> > +       return subdev_->controls();\n> > +}\n> > +\n> > +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> > +{\n> > +       return subdev_->getControls(ids);\n> > +}\n> > +\n> > +int CameraSensorRaw::setControls(ControlList *ctrls)\n> > +{\n> > +       return subdev_->setControls(ctrls);\n> > +}\n> > +\n> > +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternMode_ == mode)\n> > +               return 0;\n> > +\n> > +       if (testPatternModes_.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Camera sensor does not support test pattern modes.\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       return applyTestPatternMode(mode);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternModes_.empty())\n> > +               return 0;\n> > +\n> > +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> > +                           mode);\n> > +       if (it == testPatternModes_.end()) {\n> > +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> > +                                        << mode;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> > +\n> > +       int32_t index = staticProps_->testPatternModes.at(mode);\n> > +       ControlList ctrls{ controls() };\n> > +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> > +\n> > +       int ret = setControls(&ctrls);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       testPatternMode_ = mode;\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +std::string CameraSensorRaw::logPrefix() const\n> > +{\n> > +       return \"'\" + entity_->name() + \"'\";\n> > +}\n> > +\n> > +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> > index e83020fc22c3..e3c39aaf13b8 100644\n> > --- a/src/libcamera/sensor/meson.build\n> > +++ b/src/libcamera/sensor/meson.build\n> > @@ -4,4 +4,5 @@ libcamera_sources += files([\n> >      'camera_sensor.cpp',\n> >      'camera_sensor_legacy.cpp',\n> >      'camera_sensor_properties.cpp',\n> > +    'camera_sensor_raw.cpp',\n> >  ])\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 F15EFBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 18:56:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B09E462C8B;\n\tWed, 13 Mar 2024 19:56:15 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2E6C662C80\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 19:56:14 +0100 (CET)","from pendragon.ideasonboard.com (213-209-177-254.ip.skylogicnet.com\n\t[213.209.177.254])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 72ED8A8F;\n\tWed, 13 Mar 2024 19:55:50 +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=\"Tyhq/V9f\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710356151;\n\tbh=BJ/4T51pDhNEM3vZ99ulVD+wS397h4b7A2Cbt0ku00w=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=Tyhq/V9fymaQFfMH0trPlUm4FeYkwJkEOGiZk9AwwCDH7mz9P7NZuMziM7TjOs8cv\n\tVRZskXZu+FvX7rYU56lHj6Ayo1EjzTvKjBjlRZQd/T+5qFGLKd+bzzQKFmN/0o1OI1\n\tKHNZnsPlnqaUcOV1qpAuoNVBiXhHUvOJhHAgdz34=","Date":"Wed, 13 Mar 2024 20:56:15 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Message-ID":"<20240313185615.GA8399@pendragon.ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28950,"web_url":"https://patchwork.libcamera.org/comment/28950/","msgid":"<20240313190146.GC8399@pendragon.ideasonboard.com>","date":"2024-03-13T19:01:46","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Wed, Mar 13, 2024 at 06:16:15PM +0200, Tomi Valkeinen wrote:\n> On 13/03/2024 14:46, Tomi Valkeinen wrote:\n> > On 13/03/2024 14:20, Naushir Patuck wrote:\n> > \n> >> ?> +               case MediaBusFormatInfo::Type::Metadata:\n> >>> +                       /*\n> >>> +                        * Skip metadata streams that are not sensor \n> >>> embedded\n> >>> +                        * data. The source stream reports a generic \n> >>> metadata\n> >>> +                        * format, check the sink stream for the \n> >>> exact format.\n> >>> +                        */\n> >>> +                       formats = subdev_->formats(route.sink);\n> >>> +                       if (formats.size() != 1)\n> >>> +                               continue;\n> >>\n> >> Should this test be if (!formats.size()) insead?  It might be possible\n> >> to have multiple metadata types.\n> > \n> > The driver in my branch is old and hacky. I should see what Laurent has \n> > done with the imx219 in his branch, and possibly just take that one.\n> > \n> > I think advertising only a single format makes sense here, as the \n> > embedded format is defined by the video format.\n> > \n> >>> +\n> >>> +                       if \n> >>> (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> >>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> >>> +                               continue;\n> >>\n> >> The IMX219 driver (from Tomi's kernel tree) advertises\n> >> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> >> data stream, which translates to a type of\n> >> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> >> should this check include MediaBusFormatInfo::Type::Metadata?\n> > \n> > Laurent's version should also report those same mbus formats. Hmm, oh, \n> > but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n> \n> This looks a bit odd. The driver gives MEDIA_BUS_FMT_CCS_EMBEDDED when \n> enumerating the mbus codes, but then always sets the format to META_8 or \n> META_10.\n\nSounds like a bug.\n\n> I'm guessing the driver is supposed to keep the internal pad's \n> format as MEDIA_BUS_FMT_CCS_EMBEDDED, and only the external pad would \n> use META_8/10...","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 6BA1DBD808\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 19:01:47 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 88D1E62C92;\n\tWed, 13 Mar 2024 20:01: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 412E662C8A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 20:01:45 +0100 (CET)","from pendragon.ideasonboard.com (213-209-177-254.ip.skylogicnet.com\n\t[213.209.177.254])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9015E720;\n\tWed, 13 Mar 2024 20:01:21 +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=\"bd6Lc5gJ\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710356482;\n\tbh=3eJla35TCdy9bEnmi3X1JQB4CglldEVghjR1VfaJWFc=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=bd6Lc5gJIqYoVvkRXFL9C06Gjc4kutotYT9eyMAaEwFQMOWEmS6ONR3Zrouf/W397\n\tvehEOhOIlc4GpOrpV7lTthUmi90dnOGxXBLCb0H4lMjjkW8ArcTDtEL6BHQ986AL/H\n\tCJI/haWbC1U9WdWcW6FEtaPCuucWDdACmZDpgTmc=","Date":"Wed, 13 Mar 2024 21:01:46 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Message-ID":"<20240313190146.GC8399@pendragon.ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>\n\t<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28951,"web_url":"https://patchwork.libcamera.org/comment/28951/","msgid":"<20240313191017.GD8399@pendragon.ideasonboard.com>","date":"2024-03-13T19:10:17","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Naush,\n\nOn Wed, Mar 13, 2024 at 12:31:22PM +0000, Naushir Patuck wrote:\n>  Sorry, one more thing in this patch that I forgot (last one I promise).\n> \n> On Fri, 1 Mar 2024 at 21:21, Laurent Pinchart wrote:\n> >\n> > Add a new CameraSensorRaw implementation of the CameraSensor interface\n> > tailored to devices that implement the new V4L2 raw camera sensors API.\n> >\n> > This new class duplicates code from the CameraSensorLegacy class. The\n> > two classes will be refactored to share code.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  Documentation/Doxyfile.in                  |    1 +\n> >  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n> >  src/libcamera/sensor/meson.build           |    1 +\n> >  3 files changed, 1065 insertions(+)\n> >  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n> >\n> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> > index 75326c1964e9..8bc55be60a59 100644\n> > --- a/Documentation/Doxyfile.in\n> > +++ b/Documentation/Doxyfile.in\n> > @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n> >                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n> >                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> > +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n> >                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n> >                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> > diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > new file mode 100644\n> > index 000000000000..8c17da5876a4\n> > --- /dev/null\n> > +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > @@ -0,0 +1,1063 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy.\n> > + *\n> > + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <float.h>\n> > +#include <iomanip>\n> > +#include <limits.h>\n> > +#include <map>\n> > +#include <math.h>\n> > +#include <memory>\n> > +#include <optional>\n> > +#include <string.h>\n> > +#include <string>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/base/class.h>\n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/utils.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/control_ids.h>\n> > +#include <libcamera/controls.h>\n> > +#include <libcamera/geometry.h>\n> > +#include <libcamera/orientation.h>\n> > +#include <libcamera/property_ids.h>\n> > +#include <libcamera/transform.h>\n> > +\n> > +#include <libcamera/ipa/core_ipa_interface.h>\n> > +\n> > +#include \"libcamera/internal/bayer_format.h\"\n> > +#include \"libcamera/internal/camera_lens.h\"\n> > +#include \"libcamera/internal/camera_sensor.h\"\n> > +#include \"libcamera/internal/camera_sensor_properties.h\"\n> > +#include \"libcamera/internal/formats.h\"\n> > +#include \"libcamera/internal/media_device.h\"\n> > +#include \"libcamera/internal/sysfs.h\"\n> > +#include \"libcamera/internal/v4l2_subdevice.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +class BayerFormat;\n> > +class CameraLens;\n> > +class MediaEntity;\n> > +class SensorConfiguration;\n> > +\n> > +struct CameraSensorProperties;\n> > +\n> > +enum class Orientation;\n> > +\n> > +LOG_DECLARE_CATEGORY(CameraSensor)\n> > +\n> > +class CameraSensorRaw : public CameraSensor, protected Loggable\n> > +{\n> > +public:\n> > +       CameraSensorRaw(const MediaEntity *entity);\n> > +       ~CameraSensorRaw();\n> > +\n> > +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> > +       match(MediaEntity *entity);\n> > +\n> > +       const std::string &model() const override { return model_; }\n> > +       const std::string &id() const override { return id_; }\n> > +\n> > +       const MediaEntity *entity() const override { return entity_; }\n> > +       V4L2Subdevice *device() override { return subdev_.get(); }\n> > +\n> > +       CameraLens *focusLens() override { return focusLens_.get(); }\n> > +\n> > +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> > +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> > +       Size resolution() const override;\n> > +\n> > +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                                     const Size &size) const override;\n> > +       int setFormat(V4L2SubdeviceFormat *format,\n> > +                     Transform transform = Transform::Identity) override;\n> > +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> > +\n> > +       int applyConfiguration(const SensorConfiguration &config,\n> > +                              Transform transform = Transform::Identity,\n> > +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> > +\n> > +       const ControlList &properties() const override { return properties_; }\n> > +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> > +       Transform computeTransform(Orientation *orientation) const override;\n> > +       BayerFormat::Order bayerOrder(Transform t) const override;\n> > +\n> > +       const ControlInfoMap &controls() const override;\n> > +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> > +       int setControls(ControlList *ctrls) override;\n> > +\n> > +       const std::vector<controls::draft::TestPatternModeEnum> &\n> > +       testPatternModes() const override { return testPatternModes_; }\n> > +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> > +\n> > +protected:\n> > +       std::string logPrefix() const override;\n> > +\n> > +private:\n> > +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> > +\n> > +       std::optional<int> init();\n> > +       int initProperties();\n> > +       void initStaticProperties();\n> > +       void initTestPatternModes();\n> > +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> > +\n> > +       const MediaEntity *entity_;\n> > +       std::unique_ptr<V4L2Subdevice> subdev_;\n> > +\n> > +       struct Streams {\n> > +               V4L2Subdevice::Stream sink;\n> > +               V4L2Subdevice::Stream source;\n> > +       };\n> > +\n> > +       struct {\n> > +               Streams image;\n> > +               std::optional<Streams> edata;\n> > +       } streams_;\n> > +\n> > +       const CameraSensorProperties *staticProps_;\n> > +\n> > +       std::string model_;\n> > +       std::string id_;\n> > +\n> > +       V4L2Subdevice::Formats formats_;\n> > +       std::vector<unsigned int> mbusCodes_;\n> > +       std::vector<Size> sizes_;\n> > +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> > +       controls::draft::TestPatternModeEnum testPatternMode_;\n> > +\n> > +       Size pixelArraySize_;\n> > +       Rectangle activeArea_;\n> > +       BayerFormat::Order cfaPattern_;\n> > +       bool supportFlips_;\n> > +       bool flipsAlterBayerOrder_;\n> > +       Orientation mountingOrientation_;\n> > +\n> > +       ControlList properties_;\n> > +\n> > +       std::unique_ptr<CameraLens> focusLens_;\n> > +};\n> > +\n> > +/**\n> > + * \\class CameraSensorRaw\n> > + * \\brief A camera sensor based on V4L2 subdevices\n> > + *\n> > + * This class supports single-subdev sensors with a single source pad and one\n> > + * or two internal sink pads (for the image and embedded data streams).\n> > + */\n> > +\n> > +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> > +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> > +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> > +{\n> > +}\n> > +\n> > +CameraSensorRaw::~CameraSensorRaw() = default;\n> > +\n> > +std::variant<std::unique_ptr<CameraSensor>, int>\n> > +CameraSensorRaw::match(MediaEntity *entity)\n> > +{\n> > +       /* Check the entity type. */\n> > +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> > +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported entity type (\"\n> > +                       << utils::to_underlying(entity->type())\n> > +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /* Count and check the number of pads. */\n> > +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> > +                                               | MEDIA_PAD_FL_SOURCE\n> > +                                               | MEDIA_PAD_FL_INTERNAL;\n> > +       unsigned int numSinks = 0;\n> > +       unsigned int numSources = 0;\n> > +\n> > +       for (const MediaPad *pad : entity->pads()) {\n> > +               switch (pad->flags() & kPadFlagsMask) {\n> > +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> > +                       numSinks++;\n> > +                       break;\n> > +\n> > +               case MEDIA_PAD_FL_SOURCE:\n> > +                       numSources++;\n> > +                       break;\n> > +\n> > +               default:\n> > +                       libcamera::LOG(CameraSensor, Debug)\n> > +                               << entity->name() << \": unsupported pad \" << pad->index()\n> > +                               << \" type \" << utils::hex(pad->flags());\n> > +                       return { 0 };\n> > +               }\n> > +       }\n> > +\n> > +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported number of sinks (\"\n> > +                       << numSinks << \") or sources (\" << numSources << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /*\n> > +        * The entity matches. Create the camera sensor and initialize it. The\n> > +        * init() function will perform further match checks.\n> > +        */\n> > +       std::unique_ptr<CameraSensorRaw> sensor =\n> > +               std::make_unique<CameraSensorRaw>(entity);\n> > +\n> > +       std::optional<int> err = sensor->init();\n> > +       if (err)\n> > +               return { *err };\n> > +\n> > +       return { std::move(sensor) };\n> > +}\n> > +\n> > +std::optional<int> CameraSensorRaw::init()\n> > +{\n> > +       /* Create and open the subdev. */\n> > +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> > +       int ret = subdev_->open();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 1. Identify the pads.\n> > +        */\n> > +\n> > +       /*\n> > +        * First locate the source pad. The match() function guarantees there\n> > +        * is one and only one source pad.\n> > +        */\n> > +       unsigned int sourcePad = UINT_MAX;\n> > +\n> > +       for (const MediaPad *pad : entity_->pads()) {\n> > +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> > +                       sourcePad = pad->index();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Iterate over the routes to identify the streams on the source pad,\n> > +        * and the internal sink pads.\n> > +        */\n> > +       V4L2Subdevice::Routing routing = {};\n> > +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       bool imageStreamFound = false;\n> > +\n> > +       for (const V4L2Subdevice::Route &route : routing) {\n> > +               if (route.source.pad != sourcePad) {\n> > +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> > +                       return { -EINVAL };\n> > +               }\n> > +\n> > +               /* Identify the stream type based on the supported formats. */\n> > +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> > +\n> > +               std::optional<MediaBusFormatInfo::Type> type;\n> > +\n> > +               for (const auto &[code, sizes] : formats) {\n> > +                       const MediaBusFormatInfo &info =\n> > +                               MediaBusFormatInfo::info(code);\n> > +                       if (info.isValid()) {\n> > +                               type = info.type;\n> > +                               break;\n> > +                       }\n> > +               }\n> > +\n> > +               if (!type) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"No known format on pad \" << route.source;\n> > +                       continue;\n> > +               }\n> > +\n> > +               switch (*type) {\n> > +               case MediaBusFormatInfo::Type::Image:\n> > +                       if (imageStreamFound) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal image streams (\"\n> > +                                       << streams_.image.sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       imageStreamFound = true;\n> > +                       streams_.image.sink = route.sink;\n> > +                       streams_.image.source = route.source;\n> > +                       break;\n> > +\n> > +               case MediaBusFormatInfo::Type::Metadata:\n> > +                       /*\n> > +                        * Skip metadata streams that are not sensor embedded\n> > +                        * data. The source stream reports a generic metadata\n> > +                        * format, check the sink stream for the exact format.\n> > +                        */\n> > +                       formats = subdev_->formats(route.sink);\n> > +                       if (formats.size() != 1)\n> > +                               continue;\n> > +\n> > +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> > +                           MediaBusFormatInfo::Type::EmbeddedData)\n> > +                               continue;\n> > +\n> > +                       if (streams_.edata) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal embedded data streams (\"\n> > +                                       << streams_.edata->sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       streams_.edata = { route.sink, route.source };\n> > +                       break;\n> > +\n> > +               default:\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       if (!imageStreamFound) {\n> > +               LOG(CameraSensor, Error) << \"No image stream found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug)\n> > +               << \"Found image stream \" << streams_.image.sink\n> > +               << \" -> \" << streams_.image.source;\n> > +\n> > +       if (streams_.edata)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Found embedded data stream \" << streams_.edata->sink\n> > +                       << \" -> \" << streams_.edata->source;\n> > +\n> > +       /*\n> > +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> > +        * array order for the image stream.\n> > +        */\n> > +\n> > +       /*\n> > +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> > +        * the internal image sink pad as it is guaranteed to expose a single\n> > +        * format, and is not affected by flips.\n> > +        */\n> > +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> > +       if (formats.size() != 1) {\n> \n> This will fail the IMX219 case as formats.size() == 2 (8/10-bits).\n> Perhaps we need \"if (!formats.size())\" here?\n\nOn the sink pad there should be a single format. As mentioned in a\ndifferent reply, you need an updated version of the imx219 driver.\n\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Image pad has \" << formats.size()\n> > +                       << \" formats, expected 1\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       uint32_t nativeFormat = formats.cbegin()->first;\n> > +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> > +       if (!bayerFormat.isValid()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Invalid native format \" << nativeFormat;\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       cfaPattern_ = bayerFormat.order;\n> > +\n> > +       /*\n> > +        * Retrieve and cache the media bus codes and sizes on the source image\n> > +        * stream.\n> > +        */\n> > +       formats_ = subdev_->formats(streams_.image.source);\n> > +       if (formats_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"No image format found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       /* Populate and sort the media bus codes and the sizes. */\n> > +       for (const auto &[code, ranges] : formats_) {\n> > +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> > +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> > +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> > +                       continue;\n> > +\n> > +               mbusCodes_.push_back(code);\n> > +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> > +                              [](const SizeRange &range) { return range.max; });\n> > +       }\n> > +\n> > +       if (mbusCodes_.empty()) {\n> > +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> > +       std::sort(sizes_.begin(), sizes_.end());\n> > +\n> > +       /*\n> > +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> > +        * they are the keys in the formats map.\n> > +        */\n> > +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> > +       sizes_.erase(last, sizes_.end());\n> > +\n> > +       /*\n> > +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> > +        * all the expected selection rectangles are supported.\n> > +        */\n> > +\n> > +       Rectangle rect;\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       pixelArraySize_ = rect.size();\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> > +                                   &activeArea_);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * 4. Verify that all required controls are present.\n> > +        */\n> > +\n> > +       const ControlIdMap &controls = subdev_->controls().idmap();\n> > +\n> > +       static constexpr uint32_t mandatoryControls[] = {\n> > +               V4L2_CID_ANALOGUE_GAIN,\n> > +               V4L2_CID_CAMERA_ORIENTATION,\n> > +               V4L2_CID_EXPOSURE,\n> > +               V4L2_CID_HBLANK,\n> > +               V4L2_CID_PIXEL_RATE,\n> > +               V4L2_CID_VBLANK,\n> > +       };\n> > +\n> > +       ret = 0;\n> > +\n> > +       for (uint32_t ctrl : mandatoryControls) {\n> > +               if (!controls.count(ctrl)) {\n> > +                       LOG(CameraSensor, Error)\n> > +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> > +                               << \" not available\";\n> > +                       ret = -EINVAL;\n> > +               }\n> > +       }\n> > +\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"The sensor kernel driver needs to be fixed\";\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * Verify if sensor supports horizontal/vertical flips\n> > +        *\n> > +        * \\todo Handle horizontal and vertical flips independently.\n> > +        */\n> > +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> > +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> > +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> > +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> > +               supportFlips_ = true;\n> > +\n> > +       if (!supportFlips_)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> > +\n> > +       /*\n> > +        * 5. Discover ancillary devices.\n> > +        *\n> > +        * \\todo This code may be shared by different V4L2 sensor classes.\n> > +        */\n> > +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> > +               switch (ancillary->function()) {\n> > +               case MEDIA_ENT_F_LENS:\n> > +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> > +                       ret = focusLens_->init();\n> > +                       if (ret) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Lens initialisation failed, lens disabled\";\n> > +                               focusLens_.reset();\n> > +                       }\n> > +                       break;\n> > +\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported ancillary entity function \"\n> > +                               << ancillary->function();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * 6. Initialize properties.\n> > +        */\n> > +\n> > +       ret = initProperties();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 7. Initialize controls.\n> > +        */\n> > +\n> > +       /*\n> > +        * Set HBLANK to the minimum to start with a well-defined line length,\n> > +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> > +        * minimum line length in their calculations.\n> > +        *\n> > +        * At present, there is no way of knowing if a control is read-only.\n> > +        * As a workaround, assume that if the minimum and maximum values of\n> > +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> > +        * is read-only.\n> > +        *\n> > +        * \\todo The control API ought to have a flag to specify if a control\n> > +        * is read-only which could be used below.\n> > +        */\n> > +       const ControlInfoMap &ctrls = subdev_->controls();\n> > +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> > +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> > +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> > +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> > +\n> > +               if (hblankMin != hblankMax) {\n> > +                       ControlList ctrl(subdev_->controls());\n> > +\n> > +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> > +                       ret = subdev_->setControls(&ctrl);\n> > +                       if (ret)\n> > +                               return { ret };\n> > +               }\n> > +       }\n> > +\n> > +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       return {};\n> > +}\n> > +\n> > +int CameraSensorRaw::initProperties()\n> > +{\n> > +       model_ = subdev_->model();\n> > +       properties_.set(properties::Model, utils::toAscii(model_));\n> > +\n> > +       /* Generate a unique ID for the sensor. */\n> > +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> > +       if (id_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /* Initialize the static properties from the sensor database. */\n> > +       initStaticProperties();\n> > +\n> > +       /* Retrieve and register properties from the kernel interface. */\n> > +       const ControlInfoMap &controls = subdev_->controls();\n> > +\n> > +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> > +       if (orientation != controls.end()) {\n> > +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> > +               int32_t propertyValue;\n> > +\n> > +               switch (v4l2Orientation) {\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported camera location \"\n> > +                               << v4l2Orientation << \", setting to External\";\n> > +                       [[fallthrough]];\n> > +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> > +                       propertyValue = properties::CameraLocationExternal;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> > +                       propertyValue = properties::CameraLocationFront;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_BACK:\n> > +                       propertyValue = properties::CameraLocationBack;\n> > +                       break;\n> > +               }\n> > +               properties_.set(properties::Location, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> > +       }\n> > +\n> > +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> > +       if (rotationControl != controls.end()) {\n> > +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> > +\n> > +               /*\n> > +                * Cache the Transform associated with the camera mounting\n> > +                * rotation for later use in computeTransform().\n> > +                */\n> > +               bool success;\n> > +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> > +               if (!success) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Invalid rotation of \" << propertyValue\n> > +                               << \" degrees - ignoring\";\n> > +                       mountingOrientation_ = Orientation::Rotate0;\n> > +               }\n> > +\n> > +               properties_.set(properties::Rotation, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning)\n> > +                       << \"Rotation control not available, default to 0 degrees\";\n> > +               properties_.set(properties::Rotation, 0);\n> > +               mountingOrientation_ = Orientation::Rotate0;\n> > +       }\n> > +\n> > +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> > +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> > +\n> > +       /* Color filter array pattern. */\n> > +       uint32_t cfa;\n> > +\n> > +       switch (cfaPattern_) {\n> > +       case BayerFormat::BGGR:\n> > +               cfa = properties::draft::BGGR;\n> > +               break;\n> > +       case BayerFormat::GBRG:\n> > +               cfa = properties::draft::GBRG;\n> > +               break;\n> > +       case BayerFormat::GRBG:\n> > +               cfa = properties::draft::GRBG;\n> > +               break;\n> > +       case BayerFormat::RGGB:\n> > +               cfa = properties::draft::RGGB;\n> > +               break;\n> > +       case BayerFormat::MONO:\n> > +               cfa = properties::draft::MONO;\n> > +               break;\n> > +       }\n> > +\n> > +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +void CameraSensorRaw::initStaticProperties()\n> > +{\n> > +       staticProps_ = CameraSensorProperties::get(model_);\n> > +       if (!staticProps_)\n> > +               return;\n> > +\n> > +       /* Register the properties retrieved from the sensor database. */\n> > +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> > +\n> > +       initTestPatternModes();\n> > +}\n> > +\n> > +void CameraSensorRaw::initTestPatternModes()\n> > +{\n> > +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> > +       if (v4l2TestPattern == controls().end()) {\n> > +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> > +               return;\n> > +       }\n> > +\n> > +       const auto &testPatternModes = staticProps_->testPatternModes;\n> > +       if (testPatternModes.empty()) {\n> > +               /*\n> > +                * The camera sensor supports test patterns but we don't know\n> > +                * how to map them so this should be fixed.\n> > +                */\n> > +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> > +                                        << model() << \"\\'\";\n> > +               return;\n> > +       }\n> > +\n> > +       /*\n> > +        * Create a map that associates the V4L2 control index to the test\n> > +        * pattern mode by reversing the testPatternModes map provided by the\n> > +        * camera sensor properties. This makes it easier to verify if the\n> > +        * control index is supported in the below for loop that creates the\n> > +        * list of supported test patterns.\n> > +        */\n> > +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> > +       for (const auto &it : testPatternModes)\n> > +               indexToTestPatternMode[it.second] = it.first;\n> > +\n> > +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> > +               const int32_t index = value.get<int32_t>();\n> > +\n> > +               const auto it = indexToTestPatternMode.find(index);\n> > +               if (it == indexToTestPatternMode.end()) {\n> > +                       LOG(CameraSensor, Debug)\n> > +                               << \"Test pattern mode \" << index << \" ignored\";\n> > +                       continue;\n> > +               }\n> > +\n> > +               testPatternModes_.push_back(it->second);\n> > +       }\n> > +}\n> > +\n> > +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> > +{\n> > +       std::vector<Size> sizes;\n> > +\n> > +       const auto &format = formats_.find(mbusCode);\n> > +       if (format == formats_.end())\n> > +               return sizes;\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> > +Size CameraSensorRaw::resolution() const\n> > +{\n> > +       return std::min(sizes_.back(), activeArea_.size());\n> > +}\n> > +\n> > +V4L2SubdeviceFormat\n> > +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                          const Size &size) const\n> > +{\n> > +       unsigned int desiredArea = size.width * size.height;\n> > +       unsigned int bestArea = UINT_MAX;\n> > +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> > +       float bestRatio = FLT_MAX;\n> > +       const Size *bestSize = nullptr;\n> > +       uint32_t bestCode = 0;\n> > +\n> > +       for (unsigned int code : mbusCodes) {\n> > +               const auto formats = formats_.find(code);\n> > +               if (formats == formats_.end())\n> > +                       continue;\n> > +\n> > +               for (const SizeRange &range : formats->second) {\n> > +                       const Size &sz = range.max;\n> > +\n> > +                       if (sz.width < size.width || sz.height < size.height)\n> > +                               continue;\n> > +\n> > +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> > +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> > +                       unsigned int area = sz.width * sz.height;\n> > +                       unsigned int areaDiff = area - desiredArea;\n> > +\n> > +                       if (ratioDiff > bestRatio)\n> > +                               continue;\n> > +\n> > +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> > +                               bestRatio = ratioDiff;\n> > +                               bestArea = areaDiff;\n> > +                               bestSize = &sz;\n> > +                               bestCode = code;\n> > +                       }\n> > +               }\n> > +       }\n> > +\n> > +       if (!bestSize) {\n> > +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> > +               return {};\n> > +       }\n> > +\n> > +       V4L2SubdeviceFormat format{\n> > +               .code = bestCode,\n> > +               .size = *bestSize,\n> > +               .colorSpace = ColorSpace::Raw,\n> > +       };\n> > +\n> > +       return format;\n> > +}\n> > +\n> > +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> > +{\n> > +       /* Configure flips if the sensor supports that. */\n> > +       if (supportFlips_) {\n> > +               ControlList flipCtrls(subdev_->controls());\n> > +\n> > +               flipCtrls.set(V4L2_CID_HFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> > +               flipCtrls.set(V4L2_CID_VFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> > +\n> > +               int ret = subdev_->setControls(&flipCtrls);\n> > +               if (ret)\n> > +                       return ret;\n> > +       }\n> > +\n> > +       /* Apply format on the subdev. */\n> > +       int ret = subdev_->setFormat(streams_.image.source, format);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       subdev_->updateControlInfo();\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> > +{\n> > +       return subdev_->setFormat(streams_.image.source, format,\n> > +                                 V4L2Subdevice::Whence::TryFormat);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> > +                                       Transform transform,\n> > +                                       V4L2SubdeviceFormat *sensorFormat)\n> > +{\n> > +       if (!config.isValid()) {\n> > +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       std::vector<unsigned int> filteredCodes;\n> > +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> > +                    std::back_inserter(filteredCodes),\n> > +                    [&config](unsigned int mbusCode) {\n> > +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> > +                            if (bayer.bitDepth == config.bitDepth)\n> > +                                    return true;\n> > +                            return false;\n> > +                    });\n> > +       if (filteredCodes.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Cannot find any format with bit depth \"\n> > +                       << config.bitDepth;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /*\n> > +        * Compute the sensor's data frame size by applying the cropping\n> > +        * rectangle, subsampling and output crop to the sensor's pixel array\n> > +        * size.\n> > +        *\n> > +        * \\todo The actual size computation is for now ignored and only the\n> > +        * output size is considered. This implies that resolutions obtained\n> > +        * with two different cropping/subsampling will look identical and\n> > +        * only the first found one will be considered.\n> > +        */\n> > +       V4L2SubdeviceFormat subdevFormat = {};\n> > +       for (unsigned int code : filteredCodes) {\n> > +               for (const Size &size : sizes(code)) {\n> > +                       if (size.width != config.outputSize.width ||\n> > +                           size.height != config.outputSize.height)\n> > +                               continue;\n> > +\n> > +                       subdevFormat.code = code;\n> > +                       subdevFormat.size = size;\n> > +                       break;\n> > +               }\n> > +       }\n> > +       if (!subdevFormat.code) {\n> > +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       int ret = setFormat(&subdevFormat, transform);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * Return to the caller the format actually applied to the sensor.\n> > +        * This is relevant if transform has changed the bayer pattern order.\n> > +        */\n> > +       if (sensorFormat)\n> > +               *sensorFormat = subdevFormat;\n> > +\n> > +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> > +       /* \\todo Handle scaling in the digital domain. */\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> > +{\n> > +       info->model = model();\n> > +\n> > +       /*\n> > +        * The active area size is a static property, while the crop\n> > +        * rectangle needs to be re-read as it depends on the sensor\n> > +        * configuration.\n> > +        */\n> > +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> > +\n> > +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                       &info->analogCrop);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> > +        * are defined relatively to the active pixel area, while V4L2's\n> > +        * TGT_CROP target is defined in respect to the full pixel array.\n> > +        *\n> > +        * Compensate it by subtracting the active area offset.\n> > +        */\n> > +       info->analogCrop.x -= activeArea_.x;\n> > +       info->analogCrop.y -= activeArea_.y;\n> > +\n> > +       /* The bit depth and image size depend on the currently applied format. */\n> > +       V4L2SubdeviceFormat format{};\n> > +       ret = subdev_->getFormat(streams_.image.source, &format);\n> > +       if (ret)\n> > +               return ret;\n> > +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> > +       info->outputSize = format.size;\n> > +\n> > +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> > +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> > +\n> > +       /*\n> > +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> > +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> > +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> > +        */\n> > +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> > +                                                  V4L2_CID_HBLANK,\n> > +                                                  V4L2_CID_VBLANK });\n> > +       if (ctrls.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Failed to retrieve camera info controls\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> > +\n> > +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> > +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> > +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> > +\n> > +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> > +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> > +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> > +{\n> > +       /*\n> > +        * If we cannot do any flips we cannot change the native camera mounting\n> > +        * orientation.\n> > +        */\n> > +       if (!supportFlips_) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       /*\n> > +        * Now compute the required transform to obtain 'orientation' starting\n> > +        * from the mounting rotation.\n> > +        *\n> > +        * As a note:\n> > +        *      orientation / mountingOrientation_ = transform\n> > +        *      mountingOrientation_ * transform = orientation\n> > +        */\n> > +       Transform transform = *orientation / mountingOrientation_;\n> > +\n> > +       /*\n> > +        * If transform contains any Transpose we cannot do it, so adjust\n> > +        * 'orientation' to report the image native orientation and return Identity.\n> > +        */\n> > +       if (!!(transform & Transform::Transpose)) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       return transform;\n> > +}\n> > +\n> > +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> > +{\n> > +       if (!flipsAlterBayerOrder_)\n> > +               return cfaPattern_;\n> > +\n> > +       /*\n> > +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> > +        * using the rest of the Bayer format supplied by the caller.\n> > +        */\n> > +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> > +       return format.transform(t).order;\n> > +}\n> > +\n> > +const ControlInfoMap &CameraSensorRaw::controls() const\n> > +{\n> > +       return subdev_->controls();\n> > +}\n> > +\n> > +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> > +{\n> > +       return subdev_->getControls(ids);\n> > +}\n> > +\n> > +int CameraSensorRaw::setControls(ControlList *ctrls)\n> > +{\n> > +       return subdev_->setControls(ctrls);\n> > +}\n> > +\n> > +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternMode_ == mode)\n> > +               return 0;\n> > +\n> > +       if (testPatternModes_.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Camera sensor does not support test pattern modes.\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       return applyTestPatternMode(mode);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternModes_.empty())\n> > +               return 0;\n> > +\n> > +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> > +                           mode);\n> > +       if (it == testPatternModes_.end()) {\n> > +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> > +                                        << mode;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> > +\n> > +       int32_t index = staticProps_->testPatternModes.at(mode);\n> > +       ControlList ctrls{ controls() };\n> > +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> > +\n> > +       int ret = setControls(&ctrls);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       testPatternMode_ = mode;\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +std::string CameraSensorRaw::logPrefix() const\n> > +{\n> > +       return \"'\" + entity_->name() + \"'\";\n> > +}\n> > +\n> > +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> > index e83020fc22c3..e3c39aaf13b8 100644\n> > --- a/src/libcamera/sensor/meson.build\n> > +++ b/src/libcamera/sensor/meson.build\n> > @@ -4,4 +4,5 @@ libcamera_sources += files([\n> >      'camera_sensor.cpp',\n> >      'camera_sensor_legacy.cpp',\n> >      'camera_sensor_properties.cpp',\n> > +    'camera_sensor_raw.cpp',\n> >  ])","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 30FA5BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 13 Mar 2024 19:10:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 505A462C94;\n\tWed, 13 Mar 2024 20:10: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 DE7EA62C8C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 13 Mar 2024 20:10:16 +0100 (CET)","from pendragon.ideasonboard.com (213-209-177-254.ip.skylogicnet.com\n\t[213.209.177.254])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 33628720;\n\tWed, 13 Mar 2024 20:09:53 +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=\"CM4yRZza\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710356994;\n\tbh=VzKxxIKL6QnbI8BL0q6R0OzKfhXZPlOWiZhuDjjmupU=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=CM4yRZzaqhj8i0Uhpd1HLNdaAa8J4nytFL4Vetv6T1okvSDBCp1Sz2gL6bnlmJlat\n\tZ5rHihdrFGJpWYeGf5bV9MisqinqAKmgcBKsywikYzYQHTy1VrMLAQ2zV2+HbfbMxu\n\tZvCY+vhfBJyAhWVzAuJ9RbMk6aGxqyixptgm//ug=","Date":"Wed, 13 Mar 2024 21:10:17 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Message-ID":"<20240313191017.GD8399@pendragon.ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqBNBkyig4Si_tJAZG-tcdZzDkoNv_BAHx5ejYrXvn7Pw@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAEmqJPqBNBkyig4Si_tJAZG-tcdZzDkoNv_BAHx5ejYrXvn7Pw@mail.gmail.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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28959,"web_url":"https://patchwork.libcamera.org/comment/28959/","msgid":"<CAEmqJPppZHqgJPzQRwmF9JOnEzUZvz2qh5A1qPvVmq8AXZRpEg@mail.gmail.com>","date":"2024-03-14T13:56:34","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Hi Laurent,\n\nOn Fri, 1 Mar 2024 at 21:21, Laurent Pinchart\n<laurent.pinchart@ideasonboard.com> wrote:\n>\n> Add a new CameraSensorRaw implementation of the CameraSensor interface\n> tailored to devices that implement the new V4L2 raw camera sensors API.\n>\n> This new class duplicates code from the CameraSensorLegacy class. The\n> two classes will be refactored to share code.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  Documentation/Doxyfile.in                  |    1 +\n>  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n>  src/libcamera/sensor/meson.build           |    1 +\n>  3 files changed, 1065 insertions(+)\n>  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n>\n> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> index 75326c1964e9..8bc55be60a59 100644\n> --- a/Documentation/Doxyfile.in\n> +++ b/Documentation/Doxyfile.in\n> @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n>                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n>                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n>                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n>                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n>                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> new file mode 100644\n> index 000000000000..8c17da5876a4\n> --- /dev/null\n> +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> @@ -0,0 +1,1063 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2024, Ideas on Board Oy.\n> + *\n> + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> + */\n> +\n> +#include <algorithm>\n> +#include <float.h>\n> +#include <iomanip>\n> +#include <limits.h>\n> +#include <map>\n> +#include <math.h>\n> +#include <memory>\n> +#include <optional>\n> +#include <string.h>\n> +#include <string>\n> +#include <vector>\n> +\n> +#include <libcamera/base/class.h>\n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/utils.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/control_ids.h>\n> +#include <libcamera/controls.h>\n> +#include <libcamera/geometry.h>\n> +#include <libcamera/orientation.h>\n> +#include <libcamera/property_ids.h>\n> +#include <libcamera/transform.h>\n> +\n> +#include <libcamera/ipa/core_ipa_interface.h>\n> +\n> +#include \"libcamera/internal/bayer_format.h\"\n> +#include \"libcamera/internal/camera_lens.h\"\n> +#include \"libcamera/internal/camera_sensor.h\"\n> +#include \"libcamera/internal/camera_sensor_properties.h\"\n> +#include \"libcamera/internal/formats.h\"\n> +#include \"libcamera/internal/media_device.h\"\n> +#include \"libcamera/internal/sysfs.h\"\n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +class BayerFormat;\n> +class CameraLens;\n> +class MediaEntity;\n> +class SensorConfiguration;\n> +\n> +struct CameraSensorProperties;\n> +\n> +enum class Orientation;\n> +\n> +LOG_DECLARE_CATEGORY(CameraSensor)\n> +\n> +class CameraSensorRaw : public CameraSensor, protected Loggable\n> +{\n> +public:\n> +       CameraSensorRaw(const MediaEntity *entity);\n> +       ~CameraSensorRaw();\n> +\n> +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> +       match(MediaEntity *entity);\n> +\n> +       const std::string &model() const override { return model_; }\n> +       const std::string &id() const override { return id_; }\n> +\n> +       const MediaEntity *entity() const override { return entity_; }\n> +       V4L2Subdevice *device() override { return subdev_.get(); }\n> +\n> +       CameraLens *focusLens() override { return focusLens_.get(); }\n> +\n> +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> +       Size resolution() const override;\n> +\n> +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                                     const Size &size) const override;\n> +       int setFormat(V4L2SubdeviceFormat *format,\n> +                     Transform transform = Transform::Identity) override;\n> +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> +\n> +       int applyConfiguration(const SensorConfiguration &config,\n> +                              Transform transform = Transform::Identity,\n> +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> +\n> +       const ControlList &properties() const override { return properties_; }\n> +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> +       Transform computeTransform(Orientation *orientation) const override;\n> +       BayerFormat::Order bayerOrder(Transform t) const override;\n> +\n> +       const ControlInfoMap &controls() const override;\n> +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> +       int setControls(ControlList *ctrls) override;\n> +\n> +       const std::vector<controls::draft::TestPatternModeEnum> &\n> +       testPatternModes() const override { return testPatternModes_; }\n> +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> +\n> +protected:\n> +       std::string logPrefix() const override;\n> +\n> +private:\n> +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> +\n> +       std::optional<int> init();\n> +       int initProperties();\n> +       void initStaticProperties();\n> +       void initTestPatternModes();\n> +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> +\n> +       const MediaEntity *entity_;\n> +       std::unique_ptr<V4L2Subdevice> subdev_;\n> +\n> +       struct Streams {\n> +               V4L2Subdevice::Stream sink;\n> +               V4L2Subdevice::Stream source;\n> +       };\n> +\n> +       struct {\n> +               Streams image;\n> +               std::optional<Streams> edata;\n> +       } streams_;\n> +\n> +       const CameraSensorProperties *staticProps_;\n> +\n> +       std::string model_;\n> +       std::string id_;\n> +\n> +       V4L2Subdevice::Formats formats_;\n> +       std::vector<unsigned int> mbusCodes_;\n> +       std::vector<Size> sizes_;\n> +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> +       controls::draft::TestPatternModeEnum testPatternMode_;\n> +\n> +       Size pixelArraySize_;\n> +       Rectangle activeArea_;\n> +       BayerFormat::Order cfaPattern_;\n> +       bool supportFlips_;\n> +       bool flipsAlterBayerOrder_;\n> +       Orientation mountingOrientation_;\n> +\n> +       ControlList properties_;\n> +\n> +       std::unique_ptr<CameraLens> focusLens_;\n> +};\n> +\n> +/**\n> + * \\class CameraSensorRaw\n> + * \\brief A camera sensor based on V4L2 subdevices\n> + *\n> + * This class supports single-subdev sensors with a single source pad and one\n> + * or two internal sink pads (for the image and embedded data streams).\n> + */\n> +\n> +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> +{\n> +}\n> +\n> +CameraSensorRaw::~CameraSensorRaw() = default;\n> +\n> +std::variant<std::unique_ptr<CameraSensor>, int>\n> +CameraSensorRaw::match(MediaEntity *entity)\n> +{\n> +       /* Check the entity type. */\n> +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported entity type (\"\n> +                       << utils::to_underlying(entity->type())\n> +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /* Count and check the number of pads. */\n> +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> +                                               | MEDIA_PAD_FL_SOURCE\n> +                                               | MEDIA_PAD_FL_INTERNAL;\n> +       unsigned int numSinks = 0;\n> +       unsigned int numSources = 0;\n> +\n> +       for (const MediaPad *pad : entity->pads()) {\n> +               switch (pad->flags() & kPadFlagsMask) {\n> +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> +                       numSinks++;\n> +                       break;\n> +\n> +               case MEDIA_PAD_FL_SOURCE:\n> +                       numSources++;\n> +                       break;\n> +\n> +               default:\n> +                       libcamera::LOG(CameraSensor, Debug)\n> +                               << entity->name() << \": unsupported pad \" << pad->index()\n> +                               << \" type \" << utils::hex(pad->flags());\n> +                       return { 0 };\n> +               }\n> +       }\n> +\n> +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> +               libcamera::LOG(CameraSensor, Debug)\n> +                       << entity->name() << \": unsupported number of sinks (\"\n> +                       << numSinks << \") or sources (\" << numSources << \")\";\n> +               return { 0 };\n> +       }\n> +\n> +       /*\n> +        * The entity matches. Create the camera sensor and initialize it. The\n> +        * init() function will perform further match checks.\n> +        */\n> +       std::unique_ptr<CameraSensorRaw> sensor =\n> +               std::make_unique<CameraSensorRaw>(entity);\n> +\n> +       std::optional<int> err = sensor->init();\n> +       if (err)\n> +               return { *err };\n> +\n> +       return { std::move(sensor) };\n> +}\n> +\n> +std::optional<int> CameraSensorRaw::init()\n> +{\n> +       /* Create and open the subdev. */\n> +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> +       int ret = subdev_->open();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 1. Identify the pads.\n> +        */\n> +\n> +       /*\n> +        * First locate the source pad. The match() function guarantees there\n> +        * is one and only one source pad.\n> +        */\n> +       unsigned int sourcePad = UINT_MAX;\n> +\n> +       for (const MediaPad *pad : entity_->pads()) {\n> +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> +                       sourcePad = pad->index();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * Iterate over the routes to identify the streams on the source pad,\n> +        * and the internal sink pads.\n> +        */\n> +       V4L2Subdevice::Routing routing = {};\n> +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       bool imageStreamFound = false;\n> +\n> +       for (const V4L2Subdevice::Route &route : routing) {\n> +               if (route.source.pad != sourcePad) {\n> +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> +                       return { -EINVAL };\n> +               }\n> +\n> +               /* Identify the stream type based on the supported formats. */\n> +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> +\n> +               std::optional<MediaBusFormatInfo::Type> type;\n> +\n> +               for (const auto &[code, sizes] : formats) {\n> +                       const MediaBusFormatInfo &info =\n> +                               MediaBusFormatInfo::info(code);\n> +                       if (info.isValid()) {\n> +                               type = info.type;\n> +                               break;\n> +                       }\n> +               }\n> +\n> +               if (!type) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"No known format on pad \" << route.source;\n> +                       continue;\n> +               }\n> +\n> +               switch (*type) {\n> +               case MediaBusFormatInfo::Type::Image:\n> +                       if (imageStreamFound) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal image streams (\"\n> +                                       << streams_.image.sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       imageStreamFound = true;\n> +                       streams_.image.sink = route.sink;\n> +                       streams_.image.source = route.source;\n> +                       break;\n> +\n> +               case MediaBusFormatInfo::Type::Metadata:\n> +                       /*\n> +                        * Skip metadata streams that are not sensor embedded\n> +                        * data. The source stream reports a generic metadata\n> +                        * format, check the sink stream for the exact format.\n> +                        */\n> +                       formats = subdev_->formats(route.sink);\n> +                       if (formats.size() != 1)\n> +                               continue;\n> +\n> +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> +                               continue;\n> +\n> +                       if (streams_.edata) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Multiple internal embedded data streams (\"\n> +                                       << streams_.edata->sink << \" and \"\n> +                                       << route.sink << \")\";\n> +                               return { -EINVAL };\n> +                       }\n> +\n> +                       streams_.edata = { route.sink, route.source };\n> +                       break;\n> +\n> +               default:\n> +                       break;\n> +               }\n> +       }\n> +\n> +       if (!imageStreamFound) {\n> +               LOG(CameraSensor, Error) << \"No image stream found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       LOG(CameraSensor, Debug)\n> +               << \"Found image stream \" << streams_.image.sink\n> +               << \" -> \" << streams_.image.source;\n> +\n> +       if (streams_.edata)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Found embedded data stream \" << streams_.edata->sink\n> +                       << \" -> \" << streams_.edata->source;\n> +\n> +       /*\n> +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> +        * array order for the image stream.\n> +        */\n> +\n> +       /*\n> +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> +        * the internal image sink pad as it is guaranteed to expose a single\n> +        * format, and is not affected by flips.\n> +        */\n> +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> +       if (formats.size() != 1) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Image pad has \" << formats.size()\n> +                       << \" formats, expected 1\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       uint32_t nativeFormat = formats.cbegin()->first;\n> +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> +       if (!bayerFormat.isValid()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Invalid native format \" << nativeFormat;\n> +               return { 0 };\n> +       }\n> +\n> +       cfaPattern_ = bayerFormat.order;\n> +\n> +       /*\n> +        * Retrieve and cache the media bus codes and sizes on the source image\n> +        * stream.\n> +        */\n> +       formats_ = subdev_->formats(streams_.image.source);\n> +       if (formats_.empty()) {\n> +               LOG(CameraSensor, Error) << \"No image format found\";\n> +               return { -EINVAL };\n> +       }\n> +\n> +       /* Populate and sort the media bus codes and the sizes. */\n> +       for (const auto &[code, ranges] : formats_) {\n> +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> +                       continue;\n> +\n> +               mbusCodes_.push_back(code);\n> +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> +                              [](const SizeRange &range) { return range.max; });\n> +       }\n> +\n> +       if (mbusCodes_.empty()) {\n> +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> +               return { 0 };\n> +       }\n> +\n> +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> +       std::sort(sizes_.begin(), sizes_.end());\n> +\n> +       /*\n> +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> +        * they are the keys in the formats map.\n> +        */\n> +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> +       sizes_.erase(last, sizes_.end());\n> +\n> +       /*\n> +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> +        * all the expected selection rectangles are supported.\n> +        */\n> +\n> +       Rectangle rect;\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> +               return { ret };\n> +       }\n> +\n> +       pixelArraySize_ = rect.size();\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> +                                   &activeArea_);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> +               return { ret };\n> +       }\n> +\n> +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                   &rect);\n> +       if (ret) {\n> +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * 4. Verify that all required controls are present.\n> +        */\n> +\n> +       const ControlIdMap &controls = subdev_->controls().idmap();\n> +\n> +       static constexpr uint32_t mandatoryControls[] = {\n> +               V4L2_CID_ANALOGUE_GAIN,\n> +               V4L2_CID_CAMERA_ORIENTATION,\n> +               V4L2_CID_EXPOSURE,\n> +               V4L2_CID_HBLANK,\n> +               V4L2_CID_PIXEL_RATE,\n> +               V4L2_CID_VBLANK,\n> +       };\n> +\n> +       ret = 0;\n> +\n> +       for (uint32_t ctrl : mandatoryControls) {\n> +               if (!controls.count(ctrl)) {\n> +                       LOG(CameraSensor, Error)\n> +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> +                               << \" not available\";\n> +                       ret = -EINVAL;\n> +               }\n> +       }\n> +\n> +       if (ret) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"The sensor kernel driver needs to be fixed\";\n> +               LOG(CameraSensor, Error)\n> +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> +               return { ret };\n> +       }\n> +\n> +       /*\n> +        * Verify if sensor supports horizontal/vertical flips\n> +        *\n> +        * \\todo Handle horizontal and vertical flips independently.\n> +        */\n> +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> +               supportFlips_ = true;\n> +\n> +       if (!supportFlips_)\n> +               LOG(CameraSensor, Debug)\n> +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> +\n> +       /*\n> +        * 5. Discover ancillary devices.\n> +        *\n> +        * \\todo This code may be shared by different V4L2 sensor classes.\n> +        */\n> +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> +               switch (ancillary->function()) {\n> +               case MEDIA_ENT_F_LENS:\n> +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> +                       ret = focusLens_->init();\n> +                       if (ret) {\n> +                               LOG(CameraSensor, Error)\n> +                                       << \"Lens initialisation failed, lens disabled\";\n> +                               focusLens_.reset();\n> +                       }\n> +                       break;\n> +\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported ancillary entity function \"\n> +                               << ancillary->function();\n> +                       break;\n> +               }\n> +       }\n> +\n> +       /*\n> +        * 6. Initialize properties.\n> +        */\n> +\n> +       ret = initProperties();\n> +       if (ret)\n> +               return { ret };\n> +\n> +       /*\n> +        * 7. Initialize controls.\n> +        */\n> +\n> +       /*\n> +        * Set HBLANK to the minimum to start with a well-defined line length,\n> +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> +        * minimum line length in their calculations.\n> +        *\n> +        * At present, there is no way of knowing if a control is read-only.\n> +        * As a workaround, assume that if the minimum and maximum values of\n> +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> +        * is read-only.\n> +        *\n> +        * \\todo The control API ought to have a flag to specify if a control\n> +        * is read-only which could be used below.\n> +        */\n> +       const ControlInfoMap &ctrls = subdev_->controls();\n> +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> +\n> +               if (hblankMin != hblankMax) {\n> +                       ControlList ctrl(subdev_->controls());\n> +\n> +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> +                       ret = subdev_->setControls(&ctrl);\n> +                       if (ret)\n> +                               return { ret };\n> +               }\n> +       }\n\nPresumably this will need changing to match what you did in\nhttps://patchwork.libcamera.org/patch/19608/ for\ncamera_sensor_legacy.cpp?\n\nNaush\n\n> +\n> +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> +       if (ret)\n> +               return { ret };\n> +\n> +       return {};\n> +}\n> +\n> +int CameraSensorRaw::initProperties()\n> +{\n> +       model_ = subdev_->model();\n> +       properties_.set(properties::Model, utils::toAscii(model_));\n> +\n> +       /* Generate a unique ID for the sensor. */\n> +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> +       if (id_.empty()) {\n> +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       /* Initialize the static properties from the sensor database. */\n> +       initStaticProperties();\n> +\n> +       /* Retrieve and register properties from the kernel interface. */\n> +       const ControlInfoMap &controls = subdev_->controls();\n> +\n> +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> +       if (orientation != controls.end()) {\n> +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> +               int32_t propertyValue;\n> +\n> +               switch (v4l2Orientation) {\n> +               default:\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Unsupported camera location \"\n> +                               << v4l2Orientation << \", setting to External\";\n> +                       [[fallthrough]];\n> +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> +                       propertyValue = properties::CameraLocationExternal;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> +                       propertyValue = properties::CameraLocationFront;\n> +                       break;\n> +               case V4L2_CAMERA_ORIENTATION_BACK:\n> +                       propertyValue = properties::CameraLocationBack;\n> +                       break;\n> +               }\n> +               properties_.set(properties::Location, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> +       }\n> +\n> +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> +       if (rotationControl != controls.end()) {\n> +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> +\n> +               /*\n> +                * Cache the Transform associated with the camera mounting\n> +                * rotation for later use in computeTransform().\n> +                */\n> +               bool success;\n> +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> +               if (!success) {\n> +                       LOG(CameraSensor, Warning)\n> +                               << \"Invalid rotation of \" << propertyValue\n> +                               << \" degrees - ignoring\";\n> +                       mountingOrientation_ = Orientation::Rotate0;\n> +               }\n> +\n> +               properties_.set(properties::Rotation, propertyValue);\n> +       } else {\n> +               LOG(CameraSensor, Warning)\n> +                       << \"Rotation control not available, default to 0 degrees\";\n> +               properties_.set(properties::Rotation, 0);\n> +               mountingOrientation_ = Orientation::Rotate0;\n> +       }\n> +\n> +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> +\n> +       /* Color filter array pattern. */\n> +       uint32_t cfa;\n> +\n> +       switch (cfaPattern_) {\n> +       case BayerFormat::BGGR:\n> +               cfa = properties::draft::BGGR;\n> +               break;\n> +       case BayerFormat::GBRG:\n> +               cfa = properties::draft::GBRG;\n> +               break;\n> +       case BayerFormat::GRBG:\n> +               cfa = properties::draft::GRBG;\n> +               break;\n> +       case BayerFormat::RGGB:\n> +               cfa = properties::draft::RGGB;\n> +               break;\n> +       case BayerFormat::MONO:\n> +               cfa = properties::draft::MONO;\n> +               break;\n> +       }\n> +\n> +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> +\n> +       return 0;\n> +}\n> +\n> +void CameraSensorRaw::initStaticProperties()\n> +{\n> +       staticProps_ = CameraSensorProperties::get(model_);\n> +       if (!staticProps_)\n> +               return;\n> +\n> +       /* Register the properties retrieved from the sensor database. */\n> +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> +\n> +       initTestPatternModes();\n> +}\n> +\n> +void CameraSensorRaw::initTestPatternModes()\n> +{\n> +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> +       if (v4l2TestPattern == controls().end()) {\n> +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> +               return;\n> +       }\n> +\n> +       const auto &testPatternModes = staticProps_->testPatternModes;\n> +       if (testPatternModes.empty()) {\n> +               /*\n> +                * The camera sensor supports test patterns but we don't know\n> +                * how to map them so this should be fixed.\n> +                */\n> +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> +                                        << model() << \"\\'\";\n> +               return;\n> +       }\n> +\n> +       /*\n> +        * Create a map that associates the V4L2 control index to the test\n> +        * pattern mode by reversing the testPatternModes map provided by the\n> +        * camera sensor properties. This makes it easier to verify if the\n> +        * control index is supported in the below for loop that creates the\n> +        * list of supported test patterns.\n> +        */\n> +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> +       for (const auto &it : testPatternModes)\n> +               indexToTestPatternMode[it.second] = it.first;\n> +\n> +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> +               const int32_t index = value.get<int32_t>();\n> +\n> +               const auto it = indexToTestPatternMode.find(index);\n> +               if (it == indexToTestPatternMode.end()) {\n> +                       LOG(CameraSensor, Debug)\n> +                               << \"Test pattern mode \" << index << \" ignored\";\n> +                       continue;\n> +               }\n> +\n> +               testPatternModes_.push_back(it->second);\n> +       }\n> +}\n> +\n> +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> +{\n> +       std::vector<Size> sizes;\n> +\n> +       const auto &format = formats_.find(mbusCode);\n> +       if (format == formats_.end())\n> +               return sizes;\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> +Size CameraSensorRaw::resolution() const\n> +{\n> +       return std::min(sizes_.back(), activeArea_.size());\n> +}\n> +\n> +V4L2SubdeviceFormat\n> +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> +                          const Size &size) const\n> +{\n> +       unsigned int desiredArea = size.width * size.height;\n> +       unsigned int bestArea = UINT_MAX;\n> +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> +       float bestRatio = FLT_MAX;\n> +       const Size *bestSize = nullptr;\n> +       uint32_t bestCode = 0;\n> +\n> +       for (unsigned int code : mbusCodes) {\n> +               const auto formats = formats_.find(code);\n> +               if (formats == formats_.end())\n> +                       continue;\n> +\n> +               for (const SizeRange &range : formats->second) {\n> +                       const Size &sz = range.max;\n> +\n> +                       if (sz.width < size.width || sz.height < size.height)\n> +                               continue;\n> +\n> +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> +                       unsigned int area = sz.width * sz.height;\n> +                       unsigned int areaDiff = area - desiredArea;\n> +\n> +                       if (ratioDiff > bestRatio)\n> +                               continue;\n> +\n> +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> +                               bestRatio = ratioDiff;\n> +                               bestArea = areaDiff;\n> +                               bestSize = &sz;\n> +                               bestCode = code;\n> +                       }\n> +               }\n> +       }\n> +\n> +       if (!bestSize) {\n> +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> +               return {};\n> +       }\n> +\n> +       V4L2SubdeviceFormat format{\n> +               .code = bestCode,\n> +               .size = *bestSize,\n> +               .colorSpace = ColorSpace::Raw,\n> +       };\n> +\n> +       return format;\n> +}\n> +\n> +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> +{\n> +       /* Configure flips if the sensor supports that. */\n> +       if (supportFlips_) {\n> +               ControlList flipCtrls(subdev_->controls());\n> +\n> +               flipCtrls.set(V4L2_CID_HFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> +               flipCtrls.set(V4L2_CID_VFLIP,\n> +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> +\n> +               int ret = subdev_->setControls(&flipCtrls);\n> +               if (ret)\n> +                       return ret;\n> +       }\n> +\n> +       /* Apply format on the subdev. */\n> +       int ret = subdev_->setFormat(streams_.image.source, format);\n> +       if (ret)\n> +               return ret;\n> +\n> +       subdev_->updateControlInfo();\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> +{\n> +       return subdev_->setFormat(streams_.image.source, format,\n> +                                 V4L2Subdevice::Whence::TryFormat);\n> +}\n> +\n> +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> +                                       Transform transform,\n> +                                       V4L2SubdeviceFormat *sensorFormat)\n> +{\n> +       if (!config.isValid()) {\n> +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       std::vector<unsigned int> filteredCodes;\n> +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> +                    std::back_inserter(filteredCodes),\n> +                    [&config](unsigned int mbusCode) {\n> +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> +                            if (bayer.bitDepth == config.bitDepth)\n> +                                    return true;\n> +                            return false;\n> +                    });\n> +       if (filteredCodes.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Cannot find any format with bit depth \"\n> +                       << config.bitDepth;\n> +               return -EINVAL;\n> +       }\n> +\n> +       /*\n> +        * Compute the sensor's data frame size by applying the cropping\n> +        * rectangle, subsampling and output crop to the sensor's pixel array\n> +        * size.\n> +        *\n> +        * \\todo The actual size computation is for now ignored and only the\n> +        * output size is considered. This implies that resolutions obtained\n> +        * with two different cropping/subsampling will look identical and\n> +        * only the first found one will be considered.\n> +        */\n> +       V4L2SubdeviceFormat subdevFormat = {};\n> +       for (unsigned int code : filteredCodes) {\n> +               for (const Size &size : sizes(code)) {\n> +                       if (size.width != config.outputSize.width ||\n> +                           size.height != config.outputSize.height)\n> +                               continue;\n> +\n> +                       subdevFormat.code = code;\n> +                       subdevFormat.size = size;\n> +                       break;\n> +               }\n> +       }\n> +       if (!subdevFormat.code) {\n> +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       int ret = setFormat(&subdevFormat, transform);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * Return to the caller the format actually applied to the sensor.\n> +        * This is relevant if transform has changed the bayer pattern order.\n> +        */\n> +       if (sensorFormat)\n> +               *sensorFormat = subdevFormat;\n> +\n> +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> +       /* \\todo Handle scaling in the digital domain. */\n> +\n> +       return 0;\n> +}\n> +\n> +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> +{\n> +       info->model = model();\n> +\n> +       /*\n> +        * The active area size is a static property, while the crop\n> +        * rectangle needs to be re-read as it depends on the sensor\n> +        * configuration.\n> +        */\n> +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> +\n> +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> +                                       &info->analogCrop);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> +        * are defined relatively to the active pixel area, while V4L2's\n> +        * TGT_CROP target is defined in respect to the full pixel array.\n> +        *\n> +        * Compensate it by subtracting the active area offset.\n> +        */\n> +       info->analogCrop.x -= activeArea_.x;\n> +       info->analogCrop.y -= activeArea_.y;\n> +\n> +       /* The bit depth and image size depend on the currently applied format. */\n> +       V4L2SubdeviceFormat format{};\n> +       ret = subdev_->getFormat(streams_.image.source, &format);\n> +       if (ret)\n> +               return ret;\n> +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> +       info->outputSize = format.size;\n> +\n> +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> +\n> +       /*\n> +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> +        */\n> +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> +                                                  V4L2_CID_HBLANK,\n> +                                                  V4L2_CID_VBLANK });\n> +       if (ctrls.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Failed to retrieve camera info controls\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> +\n> +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> +\n> +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> +\n> +       return 0;\n> +}\n> +\n> +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> +{\n> +       /*\n> +        * If we cannot do any flips we cannot change the native camera mounting\n> +        * orientation.\n> +        */\n> +       if (!supportFlips_) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       /*\n> +        * Now compute the required transform to obtain 'orientation' starting\n> +        * from the mounting rotation.\n> +        *\n> +        * As a note:\n> +        *      orientation / mountingOrientation_ = transform\n> +        *      mountingOrientation_ * transform = orientation\n> +        */\n> +       Transform transform = *orientation / mountingOrientation_;\n> +\n> +       /*\n> +        * If transform contains any Transpose we cannot do it, so adjust\n> +        * 'orientation' to report the image native orientation and return Identity.\n> +        */\n> +       if (!!(transform & Transform::Transpose)) {\n> +               *orientation = mountingOrientation_;\n> +               return Transform::Identity;\n> +       }\n> +\n> +       return transform;\n> +}\n> +\n> +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> +{\n> +       if (!flipsAlterBayerOrder_)\n> +               return cfaPattern_;\n> +\n> +       /*\n> +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> +        * using the rest of the Bayer format supplied by the caller.\n> +        */\n> +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> +       return format.transform(t).order;\n> +}\n> +\n> +const ControlInfoMap &CameraSensorRaw::controls() const\n> +{\n> +       return subdev_->controls();\n> +}\n> +\n> +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> +{\n> +       return subdev_->getControls(ids);\n> +}\n> +\n> +int CameraSensorRaw::setControls(ControlList *ctrls)\n> +{\n> +       return subdev_->setControls(ctrls);\n> +}\n> +\n> +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternMode_ == mode)\n> +               return 0;\n> +\n> +       if (testPatternModes_.empty()) {\n> +               LOG(CameraSensor, Error)\n> +                       << \"Camera sensor does not support test pattern modes.\";\n> +               return -EINVAL;\n> +       }\n> +\n> +       return applyTestPatternMode(mode);\n> +}\n> +\n> +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> +{\n> +       if (testPatternModes_.empty())\n> +               return 0;\n> +\n> +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> +                           mode);\n> +       if (it == testPatternModes_.end()) {\n> +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> +                                        << mode;\n> +               return -EINVAL;\n> +       }\n> +\n> +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> +\n> +       int32_t index = staticProps_->testPatternModes.at(mode);\n> +       ControlList ctrls{ controls() };\n> +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> +\n> +       int ret = setControls(&ctrls);\n> +       if (ret)\n> +               return ret;\n> +\n> +       testPatternMode_ = mode;\n> +\n> +       return 0;\n> +}\n> +\n> +std::string CameraSensorRaw::logPrefix() const\n> +{\n> +       return \"'\" + entity_->name() + \"'\";\n> +}\n> +\n> +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> index e83020fc22c3..e3c39aaf13b8 100644\n> --- a/src/libcamera/sensor/meson.build\n> +++ b/src/libcamera/sensor/meson.build\n> @@ -4,4 +4,5 @@ libcamera_sources += files([\n>      'camera_sensor.cpp',\n>      'camera_sensor_legacy.cpp',\n>      'camera_sensor_properties.cpp',\n> +    'camera_sensor_raw.cpp',\n>  ])\n> --\n> Regards,\n>\n> Laurent Pinchart\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 B18FDBD808\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 14 Mar 2024 13:57:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 923A162C9F;\n\tThu, 14 Mar 2024 14:57:14 +0100 (CET)","from mail-yw1-x112a.google.com (mail-yw1-x112a.google.com\n\t[IPv6:2607:f8b0:4864:20::112a])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CB6A062C98\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 14 Mar 2024 14:57:11 +0100 (CET)","by mail-yw1-x112a.google.com with SMTP id\n\t00721157ae682-609ed7ca444so10624927b3.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 14 Mar 2024 06:57:11 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"j8xDZCLw\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1710424630; x=1711029430;\n\tdarn=lists.libcamera.org; \n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=bshQ1JEeufIZgqXYX6z73j92/Gzq6IG5aJEGlYc/yvE=;\n\tb=j8xDZCLwLkxfRIrcdzmgfSfgKs1xJtNxznOBpWmJB14tWk1rKLmxqjauVzEz4XZTQv\n\tCJlQTxH41b1a1VVQbSvb/GLZcKepvBhBRd9lrUMEw4ejIEppvIr8Lo/AXr7S/wdlH0IL\n\tG/qu/wVo4gy++uv/CbBDLEByvi9fQ5pu8Yb7rWH1I68lKoH/tGRTQsBI2SS+8JCLN/wM\n\tP2XgCGxh0TV3hHZEDBxItzdxxg7GdUBENu521glpncbmWwbakh72dBJNH9BGlw2fLhxt\n\trLnKAUAA7jDAehBtZDGRWizagHxNEU4TLzb6TRaZ/xiwtSF0PJqAe5ShBNF2UB8gUbUa\n\tKOfA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1710424630; x=1711029430;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=bshQ1JEeufIZgqXYX6z73j92/Gzq6IG5aJEGlYc/yvE=;\n\tb=mYPx8fCQgjZyFmr2s9TuIMm+UDke8X1CjU6OsJJIxxUtE88nRn11yl8Rfo3M8Xe78h\n\tWI0hxvRtqSUVSJZQGKWZsAAwgAL5PUWSxm4qw8bc4xpJWDZKZpsD6/2NRIEG4cxjYakf\n\tdRZotzJ/M1AtAwtrEYIon8VlNGfZPa4a419yw26Int1/CcbGNEDu97DR8R17N3dgVPT3\n\tB7NSd9Lm1dB0VMeeh38tK7JaBtS7eNjHcMGDKDHYb6MwRNGb3AMfx7c4dftlNbrBsMMs\n\tOOVpF5/gVRXtI5ds2PTSzaTQJek+u27Yao36POFt1DYqoNP6OipluUa1uX4Hk0wPZ7dA\n\tPZ/g==","X-Gm-Message-State":"AOJu0YwVTC7OJhzZUwpO+LVaPdZgqT8g2WYku5ODysoinjQWBqB5udMS\n\tQFt0LqP2ajBNMGsyprXyBiClMcezARlO52Dgur2Y+zZ8C4t18VrAUJdFcYQsbcseOFCG4zkWS3b\n\tNQEMNs1YTqbcOagtCpKSzu03jMd9nvRokmYCw2w==","X-Google-Smtp-Source":"AGHT+IHQpuC9k0zTX7kt6OY6TbOEZwxJOlbl/QkDCLJugjxp3XyzNeUQeAO7wPRCWx2+rQDawxqdf/GuiJow4jQetxs=","X-Received":"by 2002:a81:9244:0:b0:609:ff22:1a88 with SMTP id\n\tj65-20020a819244000000b00609ff221a88mr1665249ywg.44.1710424630265;\n\tThu, 14 Mar 2024 06:57:10 -0700 (PDT)","MIME-Version":"1.0","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","In-Reply-To":"<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>","From":"Naushir Patuck <naush@raspberrypi.com>","Date":"Thu, 14 Mar 2024 13:56:34 +0000","Message-ID":"<CAEmqJPppZHqgJPzQRwmF9JOnEzUZvz2qh5A1qPvVmq8AXZRpEg@mail.gmail.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28966,"web_url":"https://patchwork.libcamera.org/comment/28966/","msgid":"<20240314232640.GA3498@pendragon.ideasonboard.com>","date":"2024-03-14T23:26:40","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Naush,\n\nOn Thu, Mar 14, 2024 at 01:56:34PM +0000, Naushir Patuck wrote:\n> On Fri, 1 Mar 2024 at 21:21, Laurent Pinchart wrote:\n> >\n> > Add a new CameraSensorRaw implementation of the CameraSensor interface\n> > tailored to devices that implement the new V4L2 raw camera sensors API.\n> >\n> > This new class duplicates code from the CameraSensorLegacy class. The\n> > two classes will be refactored to share code.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  Documentation/Doxyfile.in                  |    1 +\n> >  src/libcamera/sensor/camera_sensor_raw.cpp | 1063 ++++++++++++++++++++\n> >  src/libcamera/sensor/meson.build           |    1 +\n> >  3 files changed, 1065 insertions(+)\n> >  create mode 100644 src/libcamera/sensor/camera_sensor_raw.cpp\n> >\n> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in\n> > index 75326c1964e9..8bc55be60a59 100644\n> > --- a/Documentation/Doxyfile.in\n> > +++ b/Documentation/Doxyfile.in\n> > @@ -43,6 +43,7 @@ EXCLUDE                = @TOP_SRCDIR@/include/libcamera/base/span.h \\\n> >                           @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/pipeline/ \\\n> >                           @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_legacy.cpp \\\n> > +                         @TOP_SRCDIR@/src/libcamera/sensor/camera_sensor_raw.cpp \\\n> >                           @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \\\n> >                           @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \\\n> >                           @TOP_BUILDDIR@/src/libcamera/proxy/\n> > diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > new file mode 100644\n> > index 000000000000..8c17da5876a4\n> > --- /dev/null\n> > +++ b/src/libcamera/sensor/camera_sensor_raw.cpp\n> > @@ -0,0 +1,1063 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2024, Ideas on Board Oy.\n> > + *\n> > + * camera_sensor_raw.cpp - A raw camera sensor using the V4L2 streams API\n> > + */\n> > +\n> > +#include <algorithm>\n> > +#include <float.h>\n> > +#include <iomanip>\n> > +#include <limits.h>\n> > +#include <map>\n> > +#include <math.h>\n> > +#include <memory>\n> > +#include <optional>\n> > +#include <string.h>\n> > +#include <string>\n> > +#include <vector>\n> > +\n> > +#include <libcamera/base/class.h>\n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/utils.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/control_ids.h>\n> > +#include <libcamera/controls.h>\n> > +#include <libcamera/geometry.h>\n> > +#include <libcamera/orientation.h>\n> > +#include <libcamera/property_ids.h>\n> > +#include <libcamera/transform.h>\n> > +\n> > +#include <libcamera/ipa/core_ipa_interface.h>\n> > +\n> > +#include \"libcamera/internal/bayer_format.h\"\n> > +#include \"libcamera/internal/camera_lens.h\"\n> > +#include \"libcamera/internal/camera_sensor.h\"\n> > +#include \"libcamera/internal/camera_sensor_properties.h\"\n> > +#include \"libcamera/internal/formats.h\"\n> > +#include \"libcamera/internal/media_device.h\"\n> > +#include \"libcamera/internal/sysfs.h\"\n> > +#include \"libcamera/internal/v4l2_subdevice.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +class BayerFormat;\n> > +class CameraLens;\n> > +class MediaEntity;\n> > +class SensorConfiguration;\n> > +\n> > +struct CameraSensorProperties;\n> > +\n> > +enum class Orientation;\n> > +\n> > +LOG_DECLARE_CATEGORY(CameraSensor)\n> > +\n> > +class CameraSensorRaw : public CameraSensor, protected Loggable\n> > +{\n> > +public:\n> > +       CameraSensorRaw(const MediaEntity *entity);\n> > +       ~CameraSensorRaw();\n> > +\n> > +       static std::variant<std::unique_ptr<CameraSensor>, int>\n> > +       match(MediaEntity *entity);\n> > +\n> > +       const std::string &model() const override { return model_; }\n> > +       const std::string &id() const override { return id_; }\n> > +\n> > +       const MediaEntity *entity() const override { return entity_; }\n> > +       V4L2Subdevice *device() override { return subdev_.get(); }\n> > +\n> > +       CameraLens *focusLens() override { return focusLens_.get(); }\n> > +\n> > +       const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n> > +       std::vector<Size> sizes(unsigned int mbusCode) const override;\n> > +       Size resolution() const override;\n> > +\n> > +       V4L2SubdeviceFormat getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                                     const Size &size) const override;\n> > +       int setFormat(V4L2SubdeviceFormat *format,\n> > +                     Transform transform = Transform::Identity) override;\n> > +       int tryFormat(V4L2SubdeviceFormat *format) const override;\n> > +\n> > +       int applyConfiguration(const SensorConfiguration &config,\n> > +                              Transform transform = Transform::Identity,\n> > +                              V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n> > +\n> > +       const ControlList &properties() const override { return properties_; }\n> > +       int sensorInfo(IPACameraSensorInfo *info) const override;\n> > +       Transform computeTransform(Orientation *orientation) const override;\n> > +       BayerFormat::Order bayerOrder(Transform t) const override;\n> > +\n> > +       const ControlInfoMap &controls() const override;\n> > +       ControlList getControls(const std::vector<uint32_t> &ids) override;\n> > +       int setControls(ControlList *ctrls) override;\n> > +\n> > +       const std::vector<controls::draft::TestPatternModeEnum> &\n> > +       testPatternModes() const override { return testPatternModes_; }\n> > +       int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n> > +\n> > +protected:\n> > +       std::string logPrefix() const override;\n> > +\n> > +private:\n> > +       LIBCAMERA_DISABLE_COPY(CameraSensorRaw)\n> > +\n> > +       std::optional<int> init();\n> > +       int initProperties();\n> > +       void initStaticProperties();\n> > +       void initTestPatternModes();\n> > +       int applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n> > +\n> > +       const MediaEntity *entity_;\n> > +       std::unique_ptr<V4L2Subdevice> subdev_;\n> > +\n> > +       struct Streams {\n> > +               V4L2Subdevice::Stream sink;\n> > +               V4L2Subdevice::Stream source;\n> > +       };\n> > +\n> > +       struct {\n> > +               Streams image;\n> > +               std::optional<Streams> edata;\n> > +       } streams_;\n> > +\n> > +       const CameraSensorProperties *staticProps_;\n> > +\n> > +       std::string model_;\n> > +       std::string id_;\n> > +\n> > +       V4L2Subdevice::Formats formats_;\n> > +       std::vector<unsigned int> mbusCodes_;\n> > +       std::vector<Size> sizes_;\n> > +       std::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n> > +       controls::draft::TestPatternModeEnum testPatternMode_;\n> > +\n> > +       Size pixelArraySize_;\n> > +       Rectangle activeArea_;\n> > +       BayerFormat::Order cfaPattern_;\n> > +       bool supportFlips_;\n> > +       bool flipsAlterBayerOrder_;\n> > +       Orientation mountingOrientation_;\n> > +\n> > +       ControlList properties_;\n> > +\n> > +       std::unique_ptr<CameraLens> focusLens_;\n> > +};\n> > +\n> > +/**\n> > + * \\class CameraSensorRaw\n> > + * \\brief A camera sensor based on V4L2 subdevices\n> > + *\n> > + * This class supports single-subdev sensors with a single source pad and one\n> > + * or two internal sink pads (for the image and embedded data streams).\n> > + */\n> > +\n> > +CameraSensorRaw::CameraSensorRaw(const MediaEntity *entity)\n> > +       : entity_(entity), staticProps_(nullptr), supportFlips_(false),\n> > +         flipsAlterBayerOrder_(false), properties_(properties::properties)\n> > +{\n> > +}\n> > +\n> > +CameraSensorRaw::~CameraSensorRaw() = default;\n> > +\n> > +std::variant<std::unique_ptr<CameraSensor>, int>\n> > +CameraSensorRaw::match(MediaEntity *entity)\n> > +{\n> > +       /* Check the entity type. */\n> > +       if (entity->type() != MediaEntity::Type::V4L2Subdevice ||\n> > +           entity->function() != MEDIA_ENT_F_CAM_SENSOR) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported entity type (\"\n> > +                       << utils::to_underlying(entity->type())\n> > +                       << \") or function (\" << utils::hex(entity->function()) << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /* Count and check the number of pads. */\n> > +       static constexpr uint32_t kPadFlagsMask = MEDIA_PAD_FL_SINK\n> > +                                               | MEDIA_PAD_FL_SOURCE\n> > +                                               | MEDIA_PAD_FL_INTERNAL;\n> > +       unsigned int numSinks = 0;\n> > +       unsigned int numSources = 0;\n> > +\n> > +       for (const MediaPad *pad : entity->pads()) {\n> > +               switch (pad->flags() & kPadFlagsMask) {\n> > +               case MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_INTERNAL:\n> > +                       numSinks++;\n> > +                       break;\n> > +\n> > +               case MEDIA_PAD_FL_SOURCE:\n> > +                       numSources++;\n> > +                       break;\n> > +\n> > +               default:\n> > +                       libcamera::LOG(CameraSensor, Debug)\n> > +                               << entity->name() << \": unsupported pad \" << pad->index()\n> > +                               << \" type \" << utils::hex(pad->flags());\n> > +                       return { 0 };\n> > +               }\n> > +       }\n> > +\n> > +       if (numSinks < 1 || numSinks > 2 || numSources != 1) {\n> > +               libcamera::LOG(CameraSensor, Debug)\n> > +                       << entity->name() << \": unsupported number of sinks (\"\n> > +                       << numSinks << \") or sources (\" << numSources << \")\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       /*\n> > +        * The entity matches. Create the camera sensor and initialize it. The\n> > +        * init() function will perform further match checks.\n> > +        */\n> > +       std::unique_ptr<CameraSensorRaw> sensor =\n> > +               std::make_unique<CameraSensorRaw>(entity);\n> > +\n> > +       std::optional<int> err = sensor->init();\n> > +       if (err)\n> > +               return { *err };\n> > +\n> > +       return { std::move(sensor) };\n> > +}\n> > +\n> > +std::optional<int> CameraSensorRaw::init()\n> > +{\n> > +       /* Create and open the subdev. */\n> > +       subdev_ = std::make_unique<V4L2Subdevice>(entity_);\n> > +       int ret = subdev_->open();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 1. Identify the pads.\n> > +        */\n> > +\n> > +       /*\n> > +        * First locate the source pad. The match() function guarantees there\n> > +        * is one and only one source pad.\n> > +        */\n> > +       unsigned int sourcePad = UINT_MAX;\n> > +\n> > +       for (const MediaPad *pad : entity_->pads()) {\n> > +               if (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n> > +                       sourcePad = pad->index();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * Iterate over the routes to identify the streams on the source pad,\n> > +        * and the internal sink pads.\n> > +        */\n> > +       V4L2Subdevice::Routing routing = {};\n> > +       ret = subdev_->getRouting(&routing, V4L2Subdevice::TryFormat);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       bool imageStreamFound = false;\n> > +\n> > +       for (const V4L2Subdevice::Route &route : routing) {\n> > +               if (route.source.pad != sourcePad) {\n> > +                       LOG(CameraSensor, Error) << \"Invalid route \" << route;\n> > +                       return { -EINVAL };\n> > +               }\n> > +\n> > +               /* Identify the stream type based on the supported formats. */\n> > +               V4L2Subdevice::Formats formats = subdev_->formats(route.source);\n> > +\n> > +               std::optional<MediaBusFormatInfo::Type> type;\n> > +\n> > +               for (const auto &[code, sizes] : formats) {\n> > +                       const MediaBusFormatInfo &info =\n> > +                               MediaBusFormatInfo::info(code);\n> > +                       if (info.isValid()) {\n> > +                               type = info.type;\n> > +                               break;\n> > +                       }\n> > +               }\n> > +\n> > +               if (!type) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"No known format on pad \" << route.source;\n> > +                       continue;\n> > +               }\n> > +\n> > +               switch (*type) {\n> > +               case MediaBusFormatInfo::Type::Image:\n> > +                       if (imageStreamFound) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal image streams (\"\n> > +                                       << streams_.image.sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       imageStreamFound = true;\n> > +                       streams_.image.sink = route.sink;\n> > +                       streams_.image.source = route.source;\n> > +                       break;\n> > +\n> > +               case MediaBusFormatInfo::Type::Metadata:\n> > +                       /*\n> > +                        * Skip metadata streams that are not sensor embedded\n> > +                        * data. The source stream reports a generic metadata\n> > +                        * format, check the sink stream for the exact format.\n> > +                        */\n> > +                       formats = subdev_->formats(route.sink);\n> > +                       if (formats.size() != 1)\n> > +                               continue;\n> > +\n> > +                       if (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> > +                           MediaBusFormatInfo::Type::EmbeddedData)\n> > +                               continue;\n> > +\n> > +                       if (streams_.edata) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Multiple internal embedded data streams (\"\n> > +                                       << streams_.edata->sink << \" and \"\n> > +                                       << route.sink << \")\";\n> > +                               return { -EINVAL };\n> > +                       }\n> > +\n> > +                       streams_.edata = { route.sink, route.source };\n> > +                       break;\n> > +\n> > +               default:\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       if (!imageStreamFound) {\n> > +               LOG(CameraSensor, Error) << \"No image stream found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug)\n> > +               << \"Found image stream \" << streams_.image.sink\n> > +               << \" -> \" << streams_.image.source;\n> > +\n> > +       if (streams_.edata)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Found embedded data stream \" << streams_.edata->sink\n> > +                       << \" -> \" << streams_.edata->source;\n> > +\n> > +       /*\n> > +        * 2. Enumerate and cache the media bus codes, sizes and colour filter\n> > +        * array order for the image stream.\n> > +        */\n> > +\n> > +       /*\n> > +        * Get the native sensor CFA pattern. It is simpler to retrieve it from\n> > +        * the internal image sink pad as it is guaranteed to expose a single\n> > +        * format, and is not affected by flips.\n> > +        */\n> > +       V4L2Subdevice::Formats formats = subdev_->formats(streams_.image.sink);\n> > +       if (formats.size() != 1) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Image pad has \" << formats.size()\n> > +                       << \" formats, expected 1\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       uint32_t nativeFormat = formats.cbegin()->first;\n> > +       const BayerFormat &bayerFormat = BayerFormat::fromMbusCode(nativeFormat);\n> > +       if (!bayerFormat.isValid()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Invalid native format \" << nativeFormat;\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       cfaPattern_ = bayerFormat.order;\n> > +\n> > +       /*\n> > +        * Retrieve and cache the media bus codes and sizes on the source image\n> > +        * stream.\n> > +        */\n> > +       formats_ = subdev_->formats(streams_.image.source);\n> > +       if (formats_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"No image format found\";\n> > +               return { -EINVAL };\n> > +       }\n> > +\n> > +       /* Populate and sort the media bus codes and the sizes. */\n> > +       for (const auto &[code, ranges] : formats_) {\n> > +               /* Drop non-raw formats (in case we have a hybrid sensor). */\n> > +               const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code);\n> > +               if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW)\n> > +                       continue;\n> > +\n> > +               mbusCodes_.push_back(code);\n> > +               std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n> > +                              [](const SizeRange &range) { return range.max; });\n> > +       }\n> > +\n> > +       if (mbusCodes_.empty()) {\n> > +               LOG(CameraSensor, Debug) << \"No raw image formats found\";\n> > +               return { 0 };\n> > +       }\n> > +\n> > +       std::sort(mbusCodes_.begin(), mbusCodes_.end());\n> > +       std::sort(sizes_.begin(), sizes_.end());\n> > +\n> > +       /*\n> > +        * Remove duplicate sizes. There are no duplicate media bus codes as\n> > +        * they are the keys in the formats map.\n> > +        */\n> > +       auto last = std::unique(sizes_.begin(), sizes_.end());\n> > +       sizes_.erase(last, sizes_.end());\n> > +\n> > +       /*\n> > +        * 3. Query selection rectangles. Retrieve properties, and verify that\n> > +        * all the expected selection rectangles are supported.\n> > +        */\n> > +\n> > +       Rectangle rect;\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop bounds\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       pixelArraySize_ = rect.size();\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT,\n> > +                                   &activeArea_);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop default\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                   &rect);\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error) << \"No pixel array crop rectangle\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * 4. Verify that all required controls are present.\n> > +        */\n> > +\n> > +       const ControlIdMap &controls = subdev_->controls().idmap();\n> > +\n> > +       static constexpr uint32_t mandatoryControls[] = {\n> > +               V4L2_CID_ANALOGUE_GAIN,\n> > +               V4L2_CID_CAMERA_ORIENTATION,\n> > +               V4L2_CID_EXPOSURE,\n> > +               V4L2_CID_HBLANK,\n> > +               V4L2_CID_PIXEL_RATE,\n> > +               V4L2_CID_VBLANK,\n> > +       };\n> > +\n> > +       ret = 0;\n> > +\n> > +       for (uint32_t ctrl : mandatoryControls) {\n> > +               if (!controls.count(ctrl)) {\n> > +                       LOG(CameraSensor, Error)\n> > +                               << \"Mandatory V4L2 control \" << utils::hex(ctrl)\n> > +                               << \" not available\";\n> > +                       ret = -EINVAL;\n> > +               }\n> > +       }\n> > +\n> > +       if (ret) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"The sensor kernel driver needs to be fixed\";\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n> > +               return { ret };\n> > +       }\n> > +\n> > +       /*\n> > +        * Verify if sensor supports horizontal/vertical flips\n> > +        *\n> > +        * \\todo Handle horizontal and vertical flips independently.\n> > +        */\n> > +       const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n> > +       const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n> > +       if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n> > +           vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY))\n> > +               supportFlips_ = true;\n> > +\n> > +       if (!supportFlips_)\n> > +               LOG(CameraSensor, Debug)\n> > +                       << \"Camera sensor does not support horizontal/vertical flip\";\n> > +\n> > +       /*\n> > +        * 5. Discover ancillary devices.\n> > +        *\n> > +        * \\todo This code may be shared by different V4L2 sensor classes.\n> > +        */\n> > +       for (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n> > +               switch (ancillary->function()) {\n> > +               case MEDIA_ENT_F_LENS:\n> > +                       focusLens_ = std::make_unique<CameraLens>(ancillary);\n> > +                       ret = focusLens_->init();\n> > +                       if (ret) {\n> > +                               LOG(CameraSensor, Error)\n> > +                                       << \"Lens initialisation failed, lens disabled\";\n> > +                               focusLens_.reset();\n> > +                       }\n> > +                       break;\n> > +\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported ancillary entity function \"\n> > +                               << ancillary->function();\n> > +                       break;\n> > +               }\n> > +       }\n> > +\n> > +       /*\n> > +        * 6. Initialize properties.\n> > +        */\n> > +\n> > +       ret = initProperties();\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       /*\n> > +        * 7. Initialize controls.\n> > +        */\n> > +\n> > +       /*\n> > +        * Set HBLANK to the minimum to start with a well-defined line length,\n> > +        * allowing IPA modules that do not modify HBLANK to use the sensor\n> > +        * minimum line length in their calculations.\n> > +        *\n> > +        * At present, there is no way of knowing if a control is read-only.\n> > +        * As a workaround, assume that if the minimum and maximum values of\n> > +        * the V4L2_CID_HBLANK control are the same, it implies the control\n> > +        * is read-only.\n> > +        *\n> > +        * \\todo The control API ought to have a flag to specify if a control\n> > +        * is read-only which could be used below.\n> > +        */\n> > +       const ControlInfoMap &ctrls = subdev_->controls();\n> > +       if (ctrls.find(V4L2_CID_HBLANK) != ctrls.end()) {\n> > +               const ControlInfo hblank = ctrls.at(V4L2_CID_HBLANK);\n> > +               const int32_t hblankMin = hblank.min().get<int32_t>();\n> > +               const int32_t hblankMax = hblank.max().get<int32_t>();\n> > +\n> > +               if (hblankMin != hblankMax) {\n> > +                       ControlList ctrl(subdev_->controls());\n> > +\n> > +                       ctrl.set(V4L2_CID_HBLANK, hblankMin);\n> > +                       ret = subdev_->setControls(&ctrl);\n> > +                       if (ret)\n> > +                               return { ret };\n> > +               }\n> > +       }\n> \n> Presumably this will need changing to match what you did in\n> https://patchwork.libcamera.org/patch/19608/ for\n> camera_sensor_legacy.cpp?\n\nIndeed. I'll fix that.\n\n> > +\n> > +       ret = applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n> > +       if (ret)\n> > +               return { ret };\n> > +\n> > +       return {};\n> > +}\n> > +\n> > +int CameraSensorRaw::initProperties()\n> > +{\n> > +       model_ = subdev_->model();\n> > +       properties_.set(properties::Model, utils::toAscii(model_));\n> > +\n> > +       /* Generate a unique ID for the sensor. */\n> > +       id_ = sysfs::firmwareNodePath(subdev_->devicePath());\n> > +       if (id_.empty()) {\n> > +               LOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /* Initialize the static properties from the sensor database. */\n> > +       initStaticProperties();\n> > +\n> > +       /* Retrieve and register properties from the kernel interface. */\n> > +       const ControlInfoMap &controls = subdev_->controls();\n> > +\n> > +       const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n> > +       if (orientation != controls.end()) {\n> > +               int32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n> > +               int32_t propertyValue;\n> > +\n> > +               switch (v4l2Orientation) {\n> > +               default:\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Unsupported camera location \"\n> > +                               << v4l2Orientation << \", setting to External\";\n> > +                       [[fallthrough]];\n> > +               case V4L2_CAMERA_ORIENTATION_EXTERNAL:\n> > +                       propertyValue = properties::CameraLocationExternal;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_FRONT:\n> > +                       propertyValue = properties::CameraLocationFront;\n> > +                       break;\n> > +               case V4L2_CAMERA_ORIENTATION_BACK:\n> > +                       propertyValue = properties::CameraLocationBack;\n> > +                       break;\n> > +               }\n> > +               properties_.set(properties::Location, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n> > +       }\n> > +\n> > +       const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n> > +       if (rotationControl != controls.end()) {\n> > +               int32_t propertyValue = rotationControl->second.def().get<int32_t>();\n> > +\n> > +               /*\n> > +                * Cache the Transform associated with the camera mounting\n> > +                * rotation for later use in computeTransform().\n> > +                */\n> > +               bool success;\n> > +               mountingOrientation_ = orientationFromRotation(propertyValue, &success);\n> > +               if (!success) {\n> > +                       LOG(CameraSensor, Warning)\n> > +                               << \"Invalid rotation of \" << propertyValue\n> > +                               << \" degrees - ignoring\";\n> > +                       mountingOrientation_ = Orientation::Rotate0;\n> > +               }\n> > +\n> > +               properties_.set(properties::Rotation, propertyValue);\n> > +       } else {\n> > +               LOG(CameraSensor, Warning)\n> > +                       << \"Rotation control not available, default to 0 degrees\";\n> > +               properties_.set(properties::Rotation, 0);\n> > +               mountingOrientation_ = Orientation::Rotate0;\n> > +       }\n> > +\n> > +       properties_.set(properties::PixelArraySize, pixelArraySize_);\n> > +       properties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n> > +\n> > +       /* Color filter array pattern. */\n> > +       uint32_t cfa;\n> > +\n> > +       switch (cfaPattern_) {\n> > +       case BayerFormat::BGGR:\n> > +               cfa = properties::draft::BGGR;\n> > +               break;\n> > +       case BayerFormat::GBRG:\n> > +               cfa = properties::draft::GBRG;\n> > +               break;\n> > +       case BayerFormat::GRBG:\n> > +               cfa = properties::draft::GRBG;\n> > +               break;\n> > +       case BayerFormat::RGGB:\n> > +               cfa = properties::draft::RGGB;\n> > +               break;\n> > +       case BayerFormat::MONO:\n> > +               cfa = properties::draft::MONO;\n> > +               break;\n> > +       }\n> > +\n> > +       properties_.set(properties::draft::ColorFilterArrangement, cfa);\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +void CameraSensorRaw::initStaticProperties()\n> > +{\n> > +       staticProps_ = CameraSensorProperties::get(model_);\n> > +       if (!staticProps_)\n> > +               return;\n> > +\n> > +       /* Register the properties retrieved from the sensor database. */\n> > +       properties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n> > +\n> > +       initTestPatternModes();\n> > +}\n> > +\n> > +void CameraSensorRaw::initTestPatternModes()\n> > +{\n> > +       const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n> > +       if (v4l2TestPattern == controls().end()) {\n> > +               LOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n> > +               return;\n> > +       }\n> > +\n> > +       const auto &testPatternModes = staticProps_->testPatternModes;\n> > +       if (testPatternModes.empty()) {\n> > +               /*\n> > +                * The camera sensor supports test patterns but we don't know\n> > +                * how to map them so this should be fixed.\n> > +                */\n> > +               LOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n> > +                                        << model() << \"\\'\";\n> > +               return;\n> > +       }\n> > +\n> > +       /*\n> > +        * Create a map that associates the V4L2 control index to the test\n> > +        * pattern mode by reversing the testPatternModes map provided by the\n> > +        * camera sensor properties. This makes it easier to verify if the\n> > +        * control index is supported in the below for loop that creates the\n> > +        * list of supported test patterns.\n> > +        */\n> > +       std::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n> > +       for (const auto &it : testPatternModes)\n> > +               indexToTestPatternMode[it.second] = it.first;\n> > +\n> > +       for (const ControlValue &value : v4l2TestPattern->second.values()) {\n> > +               const int32_t index = value.get<int32_t>();\n> > +\n> > +               const auto it = indexToTestPatternMode.find(index);\n> > +               if (it == indexToTestPatternMode.end()) {\n> > +                       LOG(CameraSensor, Debug)\n> > +                               << \"Test pattern mode \" << index << \" ignored\";\n> > +                       continue;\n> > +               }\n> > +\n> > +               testPatternModes_.push_back(it->second);\n> > +       }\n> > +}\n> > +\n> > +std::vector<Size> CameraSensorRaw::sizes(unsigned int mbusCode) const\n> > +{\n> > +       std::vector<Size> sizes;\n> > +\n> > +       const auto &format = formats_.find(mbusCode);\n> > +       if (format == formats_.end())\n> > +               return sizes;\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> > +Size CameraSensorRaw::resolution() const\n> > +{\n> > +       return std::min(sizes_.back(), activeArea_.size());\n> > +}\n> > +\n> > +V4L2SubdeviceFormat\n> > +CameraSensorRaw::getFormat(const std::vector<unsigned int> &mbusCodes,\n> > +                          const Size &size) const\n> > +{\n> > +       unsigned int desiredArea = size.width * size.height;\n> > +       unsigned int bestArea = UINT_MAX;\n> > +       float desiredRatio = static_cast<float>(size.width) / size.height;\n> > +       float bestRatio = FLT_MAX;\n> > +       const Size *bestSize = nullptr;\n> > +       uint32_t bestCode = 0;\n> > +\n> > +       for (unsigned int code : mbusCodes) {\n> > +               const auto formats = formats_.find(code);\n> > +               if (formats == formats_.end())\n> > +                       continue;\n> > +\n> > +               for (const SizeRange &range : formats->second) {\n> > +                       const Size &sz = range.max;\n> > +\n> > +                       if (sz.width < size.width || sz.height < size.height)\n> > +                               continue;\n> > +\n> > +                       float ratio = static_cast<float>(sz.width) / sz.height;\n> > +                       float ratioDiff = fabsf(ratio - desiredRatio);\n> > +                       unsigned int area = sz.width * sz.height;\n> > +                       unsigned int areaDiff = area - desiredArea;\n> > +\n> > +                       if (ratioDiff > bestRatio)\n> > +                               continue;\n> > +\n> > +                       if (ratioDiff < bestRatio || areaDiff < bestArea) {\n> > +                               bestRatio = ratioDiff;\n> > +                               bestArea = areaDiff;\n> > +                               bestSize = &sz;\n> > +                               bestCode = code;\n> > +                       }\n> > +               }\n> > +       }\n> > +\n> > +       if (!bestSize) {\n> > +               LOG(CameraSensor, Debug) << \"No supported format or size found\";\n> > +               return {};\n> > +       }\n> > +\n> > +       V4L2SubdeviceFormat format{\n> > +               .code = bestCode,\n> > +               .size = *bestSize,\n> > +               .colorSpace = ColorSpace::Raw,\n> > +       };\n> > +\n> > +       return format;\n> > +}\n> > +\n> > +int CameraSensorRaw::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n> > +{\n> > +       /* Configure flips if the sensor supports that. */\n> > +       if (supportFlips_) {\n> > +               ControlList flipCtrls(subdev_->controls());\n> > +\n> > +               flipCtrls.set(V4L2_CID_HFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::HFlip)));\n> > +               flipCtrls.set(V4L2_CID_VFLIP,\n> > +                             static_cast<int32_t>(!!(transform & Transform::VFlip)));\n> > +\n> > +               int ret = subdev_->setControls(&flipCtrls);\n> > +               if (ret)\n> > +                       return ret;\n> > +       }\n> > +\n> > +       /* Apply format on the subdev. */\n> > +       int ret = subdev_->setFormat(streams_.image.source, format);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       subdev_->updateControlInfo();\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::tryFormat(V4L2SubdeviceFormat *format) const\n> > +{\n> > +       return subdev_->setFormat(streams_.image.source, format,\n> > +                                 V4L2Subdevice::Whence::TryFormat);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyConfiguration(const SensorConfiguration &config,\n> > +                                       Transform transform,\n> > +                                       V4L2SubdeviceFormat *sensorFormat)\n> > +{\n> > +       if (!config.isValid()) {\n> > +               LOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       std::vector<unsigned int> filteredCodes;\n> > +       std::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n> > +                    std::back_inserter(filteredCodes),\n> > +                    [&config](unsigned int mbusCode) {\n> > +                            BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n> > +                            if (bayer.bitDepth == config.bitDepth)\n> > +                                    return true;\n> > +                            return false;\n> > +                    });\n> > +       if (filteredCodes.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Cannot find any format with bit depth \"\n> > +                       << config.bitDepth;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       /*\n> > +        * Compute the sensor's data frame size by applying the cropping\n> > +        * rectangle, subsampling and output crop to the sensor's pixel array\n> > +        * size.\n> > +        *\n> > +        * \\todo The actual size computation is for now ignored and only the\n> > +        * output size is considered. This implies that resolutions obtained\n> > +        * with two different cropping/subsampling will look identical and\n> > +        * only the first found one will be considered.\n> > +        */\n> > +       V4L2SubdeviceFormat subdevFormat = {};\n> > +       for (unsigned int code : filteredCodes) {\n> > +               for (const Size &size : sizes(code)) {\n> > +                       if (size.width != config.outputSize.width ||\n> > +                           size.height != config.outputSize.height)\n> > +                               continue;\n> > +\n> > +                       subdevFormat.code = code;\n> > +                       subdevFormat.size = size;\n> > +                       break;\n> > +               }\n> > +       }\n> > +       if (!subdevFormat.code) {\n> > +               LOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       int ret = setFormat(&subdevFormat, transform);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * Return to the caller the format actually applied to the sensor.\n> > +        * This is relevant if transform has changed the bayer pattern order.\n> > +        */\n> > +       if (sensorFormat)\n> > +               *sensorFormat = subdevFormat;\n> > +\n> > +       /* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n> > +       /* \\todo Handle scaling in the digital domain. */\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const\n> > +{\n> > +       info->model = model();\n> > +\n> > +       /*\n> > +        * The active area size is a static property, while the crop\n> > +        * rectangle needs to be re-read as it depends on the sensor\n> > +        * configuration.\n> > +        */\n> > +       info->activeAreaSize = { activeArea_.width, activeArea_.height };\n> > +\n> > +       int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP,\n> > +                                       &info->analogCrop);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       /*\n> > +        * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n> > +        * are defined relatively to the active pixel area, while V4L2's\n> > +        * TGT_CROP target is defined in respect to the full pixel array.\n> > +        *\n> > +        * Compensate it by subtracting the active area offset.\n> > +        */\n> > +       info->analogCrop.x -= activeArea_.x;\n> > +       info->analogCrop.y -= activeArea_.y;\n> > +\n> > +       /* The bit depth and image size depend on the currently applied format. */\n> > +       V4L2SubdeviceFormat format{};\n> > +       ret = subdev_->getFormat(streams_.image.source, &format);\n> > +       if (ret)\n> > +               return ret;\n> > +       info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n> > +       info->outputSize = format.size;\n> > +\n> > +       std::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n> > +       info->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n> > +\n> > +       /*\n> > +        * Retrieve the pixel rate, line length and minimum/maximum frame\n> > +        * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n> > +        * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n> > +        */\n> > +       ControlList ctrls = subdev_->getControls({ V4L2_CID_PIXEL_RATE,\n> > +                                                  V4L2_CID_HBLANK,\n> > +                                                  V4L2_CID_VBLANK });\n> > +       if (ctrls.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Failed to retrieve camera info controls\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n> > +\n> > +       const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n> > +       info->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n> > +       info->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n> > +\n> > +       const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n> > +       info->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n> > +       info->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +Transform CameraSensorRaw::computeTransform(Orientation *orientation) const\n> > +{\n> > +       /*\n> > +        * If we cannot do any flips we cannot change the native camera mounting\n> > +        * orientation.\n> > +        */\n> > +       if (!supportFlips_) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       /*\n> > +        * Now compute the required transform to obtain 'orientation' starting\n> > +        * from the mounting rotation.\n> > +        *\n> > +        * As a note:\n> > +        *      orientation / mountingOrientation_ = transform\n> > +        *      mountingOrientation_ * transform = orientation\n> > +        */\n> > +       Transform transform = *orientation / mountingOrientation_;\n> > +\n> > +       /*\n> > +        * If transform contains any Transpose we cannot do it, so adjust\n> > +        * 'orientation' to report the image native orientation and return Identity.\n> > +        */\n> > +       if (!!(transform & Transform::Transpose)) {\n> > +               *orientation = mountingOrientation_;\n> > +               return Transform::Identity;\n> > +       }\n> > +\n> > +       return transform;\n> > +}\n> > +\n> > +BayerFormat::Order CameraSensorRaw::bayerOrder(Transform t) const\n> > +{\n> > +       if (!flipsAlterBayerOrder_)\n> > +               return cfaPattern_;\n> > +\n> > +       /*\n> > +        * Apply the transform to the native (i.e. untransformed) Bayer order,\n> > +        * using the rest of the Bayer format supplied by the caller.\n> > +        */\n> > +       BayerFormat format{ cfaPattern_, 8, BayerFormat::Packing::None };\n> > +       return format.transform(t).order;\n> > +}\n> > +\n> > +const ControlInfoMap &CameraSensorRaw::controls() const\n> > +{\n> > +       return subdev_->controls();\n> > +}\n> > +\n> > +ControlList CameraSensorRaw::getControls(const std::vector<uint32_t> &ids)\n> > +{\n> > +       return subdev_->getControls(ids);\n> > +}\n> > +\n> > +int CameraSensorRaw::setControls(ControlList *ctrls)\n> > +{\n> > +       return subdev_->setControls(ctrls);\n> > +}\n> > +\n> > +int CameraSensorRaw::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternMode_ == mode)\n> > +               return 0;\n> > +\n> > +       if (testPatternModes_.empty()) {\n> > +               LOG(CameraSensor, Error)\n> > +                       << \"Camera sensor does not support test pattern modes.\";\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       return applyTestPatternMode(mode);\n> > +}\n> > +\n> > +int CameraSensorRaw::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n> > +{\n> > +       if (testPatternModes_.empty())\n> > +               return 0;\n> > +\n> > +       auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n> > +                           mode);\n> > +       if (it == testPatternModes_.end()) {\n> > +               LOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n> > +                                        << mode;\n> > +               return -EINVAL;\n> > +       }\n> > +\n> > +       LOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n> > +\n> > +       int32_t index = staticProps_->testPatternModes.at(mode);\n> > +       ControlList ctrls{ controls() };\n> > +       ctrls.set(V4L2_CID_TEST_PATTERN, index);\n> > +\n> > +       int ret = setControls(&ctrls);\n> > +       if (ret)\n> > +               return ret;\n> > +\n> > +       testPatternMode_ = mode;\n> > +\n> > +       return 0;\n> > +}\n> > +\n> > +std::string CameraSensorRaw::logPrefix() const\n> > +{\n> > +       return \"'\" + entity_->name() + \"'\";\n> > +}\n> > +\n> > +REGISTER_CAMERA_SENSOR(CameraSensorRaw)\n> > +\n> > +} /* namespace libcamera */\n> > diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\n> > index e83020fc22c3..e3c39aaf13b8 100644\n> > --- a/src/libcamera/sensor/meson.build\n> > +++ b/src/libcamera/sensor/meson.build\n> > @@ -4,4 +4,5 @@ libcamera_sources += files([\n> >      'camera_sensor.cpp',\n> >      'camera_sensor_legacy.cpp',\n> >      'camera_sensor_properties.cpp',\n> > +    'camera_sensor_raw.cpp',\n> >  ])","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id CF9EBBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 14 Mar 2024 23:26:45 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AEB2E62C8C;\n\tFri, 15 Mar 2024 00:26:44 +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 F286062973\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 15 Mar 2024 00:26:42 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id EC3A8899;\n\tFri, 15 Mar 2024 00:26:18 +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=\"oIij8PsR\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1710458779;\n\tbh=6fCNulduL7E5Av4VCLSYV17g60fIXYBg6+NPw34jcnw=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=oIij8PsRxwpE7pb78OZJvw76bxS1s8stjuP00LrPd2frj0byRIeq37TlxT7xTzLjs\n\tu4ldtTdUE07mGopjA1LzHLJZJAZ2fonfQ35H6udlhvf8RHWPc/OURQXX9DVH54tFHq\n\tkaG2pNjW6tbMgSdZaNow6ks60vdtfr/bPu/uPIY4=","Date":"Fri, 15 Mar 2024 01:26:40 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Message-ID":"<20240314232640.GA3498@pendragon.ideasonboard.com>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPppZHqgJPzQRwmF9JOnEzUZvz2qh5A1qPvVmq8AXZRpEg@mail.gmail.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<CAEmqJPppZHqgJPzQRwmF9JOnEzUZvz2qh5A1qPvVmq8AXZRpEg@mail.gmail.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":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":32071,"web_url":"https://patchwork.libcamera.org/comment/32071/","msgid":"<zgjkb772dp6kxnopg25e3ar5opjyg66ctu5nkdo6yh4322cnrb@bnjbn5jhyd6l>","date":"2024-11-08T09:30:50","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Naush, Laurent, Tomi\n\nOn Wed, Mar 13, 2024 at 09:01:46PM +0200, Laurent Pinchart wrote:\n> On Wed, Mar 13, 2024 at 06:16:15PM +0200, Tomi Valkeinen wrote:\n> > On 13/03/2024 14:46, Tomi Valkeinen wrote:\n> > > On 13/03/2024 14:20, Naushir Patuck wrote:\n> > >\n> > >> ?> +               case MediaBusFormatInfo::Type::Metadata:\n> > >>> +                       /*\n> > >>> +                        * Skip metadata streams that are not sensor\n> > >>> embedded\n> > >>> +                        * data. The source stream reports a generic\n> > >>> metadata\n> > >>> +                        * format, check the sink stream for the\n> > >>> exact format.\n> > >>> +                        */\n> > >>> +                       formats = subdev_->formats(route.sink);\n> > >>> +                       if (formats.size() != 1)\n> > >>> +                               continue;\n> > >>\n> > >> Should this test be if (!formats.size()) insead?  It might be possible\n> > >> to have multiple metadata types.\n> > >\n> > > The driver in my branch is old and hacky. I should see what Laurent has\n> > > done with the imx219 in his branch, and possibly just take that one.\n> > >\n> > > I think advertising only a single format makes sense here, as the\n> > > embedded format is defined by the video format.\n> > >\n> > >>> +\n> > >>> +                       if\n> > >>> (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n> > >>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n> > >>> +                               continue;\n> > >>\n> > >> The IMX219 driver (from Tomi's kernel tree) advertises\n> > >> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n> > >> data stream, which translates to a type of\n> > >> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n> > >> should this check include MediaBusFormatInfo::Type::Metadata?\n> > >\n> > > Laurent's version should also report those same mbus formats. Hmm, oh,\n> > > but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n> >\n> > This looks a bit odd. The driver gives MEDIA_BUS_FMT_CCS_EMBEDDED when\n> > enumerating the mbus codes, but then always sets the format to META_8 or\n> > META_10.\n>\n> Sounds like a bug.\n>\n\nWith this fixed on the imx219 driver side, I confirm this patch works\nas expected.\n\nIn my understanding the expected driver behaviour is to:\n\n- MEDIA_BUS_FMT_CCS_EMBEDDED on the edata internal sink pad\n- MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 on the edata stream in the\n  source pad\n\nI'll re-send this patch broken out from this larger series.\n\n> > I'm guessing the driver is supposed to keep the internal pad's\n> > format as MEDIA_BUS_FMT_CCS_EMBEDDED, and only the external pad would\n> > use META_8/10...\n\nThat's my understanding too!\n\n>\n> --\n> Regards,\n>\n> Laurent Pinchart","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 5B532C323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  8 Nov 2024 09:30:56 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5AF5065495;\n\tFri,  8 Nov 2024 10:30:55 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3790760589\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Nov 2024 10:30:53 +0100 (CET)","from ideasonboard.com (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 58E7F353;\n\tFri,  8 Nov 2024 10:30:43 +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=\"dSLrRh3E\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1731058243;\n\tbh=qEofL+hitMsTVjGcVT/iJQHH9haU9PbzwBf7pD2v25U=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=dSLrRh3Egn0Brshm6jQUBLdgL4tA0B/mc6R0ZnfbpW2Uy9brobmBDH2DfVhmCIJ3a\n\t5qZ0QOjGjezFSZMhZuXe5QEXXISSHL0GC1mJmAd/nx8wLlelpfwzSIb/Vgc7XLKb7B\n\tLRFkGetXZJY9HYSdn9/4aRznbzb9nUN28age11z4=","Date":"Fri, 8 Nov 2024 10:30:50 +0100","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","Message-ID":"<zgjkb772dp6kxnopg25e3ar5opjyg66ctu5nkdo6yh4322cnrb@bnjbn5jhyd6l>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>\n\t<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@ideasonboard.com>\n\t<20240313190146.GC8399@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20240313190146.GC8399@pendragon.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>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":32072,"web_url":"https://patchwork.libcamera.org/comment/32072/","msgid":"<18fcaa8c-656f-4af5-a7e0-907e2dd72db0@ideasonboard.com>","date":"2024-11-08T09:45:53","subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","submitter":{"id":109,"url":"https://patchwork.libcamera.org/api/people/109/","name":"Tomi Valkeinen","email":"tomi.valkeinen@ideasonboard.com"},"content":"Hi,\n\nOn 08/11/2024 11:30, Jacopo Mondi wrote:\n> Hi Naush, Laurent, Tomi\n> \n> On Wed, Mar 13, 2024 at 09:01:46PM +0200, Laurent Pinchart wrote:\n>> On Wed, Mar 13, 2024 at 06:16:15PM +0200, Tomi Valkeinen wrote:\n>>> On 13/03/2024 14:46, Tomi Valkeinen wrote:\n>>>> On 13/03/2024 14:20, Naushir Patuck wrote:\n>>>>\n>>>>> ?> +               case MediaBusFormatInfo::Type::Metadata:\n>>>>>> +                       /*\n>>>>>> +                        * Skip metadata streams that are not sensor\n>>>>>> embedded\n>>>>>> +                        * data. The source stream reports a generic\n>>>>>> metadata\n>>>>>> +                        * format, check the sink stream for the\n>>>>>> exact format.\n>>>>>> +                        */\n>>>>>> +                       formats = subdev_->formats(route.sink);\n>>>>>> +                       if (formats.size() != 1)\n>>>>>> +                               continue;\n>>>>>\n>>>>> Should this test be if (!formats.size()) insead?  It might be possible\n>>>>> to have multiple metadata types.\n>>>>\n>>>> The driver in my branch is old and hacky. I should see what Laurent has\n>>>> done with the imx219 in his branch, and possibly just take that one.\n>>>>\n>>>> I think advertising only a single format makes sense here, as the\n>>>> embedded format is defined by the video format.\n>>>>\n>>>>>> +\n>>>>>> +                       if\n>>>>>> (MediaBusFormatInfo::info(formats.cbegin()->first).type !=\n>>>>>> +                           MediaBusFormatInfo::Type::EmbeddedData)\n>>>>>> +                               continue;\n>>>>>\n>>>>> The IMX219 driver (from Tomi's kernel tree) advertises\n>>>>> MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 formats for the embedded\n>>>>> data stream, which translates to a type of\n>>>>> MediaBusFormatInfo::Type::Metadata.  Does the driver need updating, or\n>>>>> should this check include MediaBusFormatInfo::Type::Metadata?\n>>>>\n>>>> Laurent's version should also report those same mbus formats. Hmm, oh,\n>>>> but it uses MEDIA_BUS_FMT_CCS_EMBEDDED for the internal pad...\n>>>\n>>> This looks a bit odd. The driver gives MEDIA_BUS_FMT_CCS_EMBEDDED when\n>>> enumerating the mbus codes, but then always sets the format to META_8 or\n>>> META_10.\n>>\n>> Sounds like a bug.\n>>\n> \n> With this fixed on the imx219 driver side, I confirm this patch works\n> as expected.\n> \n> In my understanding the expected driver behaviour is to:\n> \n> - MEDIA_BUS_FMT_CCS_EMBEDDED on the edata internal sink pad\n> - MEDIA_BUS_FMT_META_8 / MEDIA_BUS_FMT_META_10 on the edata stream in the\n>    source pad\n> \n> I'll re-send this patch broken out from this larger series.\n> \n>>> I'm guessing the driver is supposed to keep the internal pad's\n>>> format as MEDIA_BUS_FMT_CCS_EMBEDDED, and only the external pad would\n>>> use META_8/10...\n> \n> That's my understanding too!\n\nRight. The internal pad can (or \"should\", in most cases) use an mbus \nformat that describes the format's content and form, whereas the \nexternal pad only describes the form.\n\nThe point here is that while it's fine that 100 sensor drivers each have \ntheir own specific embedded mbus format, it's not fine that each and \nevery bridge driver should also support all those 100 formats, even if \nthe bridges just pass the data forward.\n\n  Tomi","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 A9582BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  8 Nov 2024 09:45:59 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A5BD76549A;\n\tFri,  8 Nov 2024 10:45:58 +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 4716565494\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  8 Nov 2024 10:45:57 +0100 (CET)","from [192.168.88.20] (91-157-155-49.elisa-laajakaista.fi\n\t[91.157.155.49])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F0D19353;\n\tFri,  8 Nov 2024 10:45:46 +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=\"fWf0VP6o\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1731059147;\n\tbh=ls/II69K75yxCDtztO2mv+g2XvuvrplCj36Lf9o1me0=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=fWf0VP6ocgj/B7ITCg1gSCA1JydfYKy+6epEPfI3sz86LicQ3YimSLZalVHskLtdj\n\tDbkRcLPxbjoZNOeA89VjLymCK/dUb28eqxq+x3lnHCP4m4sNxNfTsRHtJknBRQTOBE\n\tnssg31HQLFdpzqHNeQBaP2OW0GdarD7lNcbCYdQs=","Message-ID":"<18fcaa8c-656f-4af5-a7e0-907e2dd72db0@ideasonboard.com>","Date":"Fri, 8 Nov 2024 11:45:53 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH/RFC 22/32] libcamera: Add CameraSensor implementation for\n\traw V4L2 sensors","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, Sakari Ailus <sakari.ailus@iki.fi>","References":"<20240301212121.9072-1-laurent.pinchart@ideasonboard.com>\n\t<20240301212121.9072-23-laurent.pinchart@ideasonboard.com>\n\t<CAEmqJPqF41jx_WMAGwTgY6FSt+6DvEv2csZ+BVLUSJbvy66+Bw@mail.gmail.com>\n\t<e5f6f1a3-8201-41fd-8903-cc3c4f733f2c@ideasonboard.com>\n\t<521c5b8e-89b7-4e25-b0c7-fca96cc7c2e1@ideasonboard.com>\n\t<20240313190146.GC8399@pendragon.ideasonboard.com>\n\t<zgjkb772dp6kxnopg25e3ar5opjyg66ctu5nkdo6yh4322cnrb@bnjbn5jhyd6l>","Content-Language":"en-US","From":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Autocrypt":"addr=tomi.valkeinen@ideasonboard.com; keydata=\n\txsFNBE6ms0cBEACyizowecZqXfMZtnBniOieTuFdErHAUyxVgtmr0f5ZfIi9Z4l+uUN4Zdw2\n\twCEZjx3o0Z34diXBaMRJ3rAk9yB90UJAnLtb8A97Oq64DskLF81GCYB2P1i0qrG7UjpASgCA\n\tRu0lVvxsWyIwSfoYoLrazbT1wkWRs8YBkkXQFfL7Mn3ZMoGPcpfwYH9O7bV1NslbmyJzRCMO\n\teYV258gjCcwYlrkyIratlHCek4GrwV8Z9NQcjD5iLzrONjfafrWPwj6yn2RlL0mQEwt1lOvn\n\tLnI7QRtB3zxA3yB+FLsT1hx0va6xCHpX3QO2gBsyHCyVafFMrg3c/7IIWkDLngJxFgz6DLiA\n\tG4ld1QK/jsYqfP2GIMH1mFdjY+iagG4DqOsjip479HCWAptpNxSOCL6z3qxCU8MCz8iNOtZk\n\tDYXQWVscM5qgYSn+fmMM2qN+eoWlnCGVURZZLDjg387S2E1jT/dNTOsM/IqQj+ZROUZuRcF7\n\t0RTtuU5q1HnbRNwy+23xeoSGuwmLQ2UsUk7Q5CnrjYfiPo3wHze8avK95JBoSd+WIRmV3uoO\n\trXCoYOIRlDhg9XJTrbnQ3Ot5zOa0Y9c4IpyAlut6mDtxtKXr4+8OzjSVFww7tIwadTK3wDQv\n\tBus4jxHjS6dz1g2ypT65qnHen6mUUH63lhzewqO9peAHJ0SLrQARAQABzTBUb21pIFZhbGtl\n\taW5lbiA8dG9taS52YWxrZWluZW5AaWRlYXNvbmJvYXJkLmNvbT7CwY4EEwEIADgWIQTEOAw+\n\tll79gQef86f6PaqMvJYe9QUCX/HruAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRD6\n\tPaqMvJYe9WmFD/99NGoD5lBJhlFDHMZvO+Op8vCwnIRZdTsyrtGl72rVh9xRfcSgYPZUvBuT\n\tVDxE53mY9HaZyu1eGMccYRBaTLJSfCXl/g317CrMNdY0k40b9YeIX10feiRYEWoDIPQ3tMmA\n\t0nHDygzcnuPiPT68JYZ6tUOvAt7r6OX/litM+m2/E9mtp8xCoWOo/kYO4mOAIoMNvLB8vufi\n\tuBB4e/AvAjtny4ScuNV5c5q8MkfNIiOyag9QCiQ/JfoAqzXRjVb4VZG72AKaElwipiKCWEcU\n\tR4+Bu5Qbaxj7Cd36M/bI54OrbWWETJkVVSV1i0tghCd6HHyquTdFl7wYcz6cL1hn/6byVnD+\n\tsR3BLvSBHYp8WSwv0TCuf6tLiNgHAO1hWiQ1pOoXyMEsxZlgPXT+wb4dbNVunckwqFjGxRbl\n\tRz7apFT/ZRwbazEzEzNyrBOfB55xdipG/2+SmFn0oMFqFOBEszXLQVslh64lI0CMJm2OYYe3\n\tPxHqYaztyeXsx13Bfnq9+bUynAQ4uW1P5DJ3OIRZWKmbQd/Me3Fq6TU57LsvwRgE0Le9PFQs\n\tdcP2071rMTpqTUteEgODJS4VDf4lXJfY91u32BJkiqM7/62Cqatcz5UWWHq5xeF03MIUTqdE\n\tqHWk3RJEoWHWQRzQfcx6Fn2fDAUKhAddvoopfcjAHfpAWJ+ENc7BTQROprNHARAAx0aat8GU\n\thsusCLc4MIxOQwidecCTRc9Dz/7U2goUwhw2O5j9TPqLtp57VITmHILnvZf6q3QAho2QMQyE\n\tDDvHubrdtEoqaaSKxKkFie1uhWNNvXPhwkKLYieyL9m2JdU+b88HaDnpzdyTTR4uH7wk0bBa\n\tKbTSgIFDDe5lXInypewPO30TmYNkFSexnnM3n1PBCqiJXsJahE4ZQ+WnV5FbPUj8T2zXS2xk\n\t0LZ0+DwKmZ0ZDovvdEWRWrz3UzJ8DLHb7blPpGhmqj3ANXQXC7mb9qJ6J/VSl61GbxIO2Dwb\n\txPNkHk8fwnxlUBCOyBti/uD2uSTgKHNdabhVm2dgFNVuS1y3bBHbI/qjC3J7rWE0WiaHWEqy\n\tUVPk8rsph4rqITsj2RiY70vEW0SKePrChvET7D8P1UPqmveBNNtSS7In+DdZ5kUqLV7rJnM9\n\t/4cwy+uZUt8cuCZlcA5u8IsBCNJudxEqBG10GHg1B6h1RZIz9Q9XfiBdaqa5+CjyFs8ua01c\n\t9HmyfkuhXG2OLjfQuK+Ygd56mV3lq0aFdwbaX16DG22c6flkkBSjyWXYepFtHz9KsBS0DaZb\n\t4IkLmZwEXpZcIOQjQ71fqlpiXkXSIaQ6YMEs8WjBbpP81h7QxWIfWtp+VnwNGc6nq5IQDESH\n\tmvQcsFS7d3eGVI6eyjCFdcAO8eMAEQEAAcLBXwQYAQIACQUCTqazRwIbDAAKCRD6PaqMvJYe\n\t9fA7EACS6exUedsBKmt4pT7nqXBcRsqm6YzT6DeCM8PWMTeaVGHiR4TnNFiT3otD5UpYQI7S\n\tsuYxoTdHrrrBzdlKe5rUWpzoZkVK6p0s9OIvGzLT0lrb0HC9iNDWT3JgpYDnk4Z2mFi6tTbq\n\txKMtpVFRA6FjviGDRsfkfoURZI51nf2RSAk/A8BEDDZ7lgJHskYoklSpwyrXhkp9FHGMaYII\n\tm9EKuUTX9JPDG2FTthCBrdsgWYPdJQvM+zscq09vFMQ9Fykbx5N8z/oFEUy3ACyPqW2oyfvU\n\tCH5WDpWBG0s5BALp1gBJPytIAd/pY/5ZdNoi0Cx3+Z7jaBFEyYJdWy1hGddpkgnMjyOfLI7B\n\tCFrdecTZbR5upjNSDvQ7RG85SnpYJTIin+SAUazAeA2nS6gTZzumgtdw8XmVXZwdBfF+ICof\n\t92UkbYcYNbzWO/GHgsNT1WnM4sa9lwCSWH8Fw1o/3bX1VVPEsnESOfxkNdu+gAF5S6+I6n3a\n\tueeIlwJl5CpT5l8RpoZXEOVtXYn8zzOJ7oGZYINRV9Pf8qKGLf3Dft7zKBP832I3PQjeok7F\n\tyjt+9S+KgSFSHP3Pa4E7lsSdWhSlHYNdG/czhoUkSCN09C0rEK93wxACx3vtxPLjXu6RptBw\n\t3dRq7n+mQChEB1am0BueV1JZaBboIL0AGlSJkm23kw==","In-Reply-To":"<zgjkb772dp6kxnopg25e3ar5opjyg66ctu5nkdo6yh4322cnrb@bnjbn5jhyd6l>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]