Show a patch.

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

{
    "id": 25390,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/25390/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/25390/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20251209180954.332392-2-isaac.scott@ideasonboard.com>",
    "date": "2025-12-09T18:09:49",
    "name": "[RFC,1/6] camera_sensor: Add camera_sensor_basic",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "c3db89c8c6629fa3ba520d4e76225e2d01a80a8c",
    "submitter": {
        "id": 215,
        "url": "https://patchwork.libcamera.org/api/1.1/people/215/?format=api",
        "name": "Isaac Scott",
        "email": "isaac.scott@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/25390/mbox/",
    "series": [
        {
            "id": 5643,
            "url": "https://patchwork.libcamera.org/api/1.1/series/5643/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5643",
            "date": "2025-12-09T18:09:48",
            "name": "rkisp1: Add support for YUV bypass",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5643/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/25390/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/25390/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A680FBD1F1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  9 Dec 2025 18:10:08 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D20DB6141D;\n\tTue,  9 Dec 2025 19:10:05 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 46F0C6140A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  9 Dec 2025 19:10:04 +0100 (CET)",
            "from isaac-ThinkPad-T16-Gen-2.infra.iob\n\t(cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 71664710;\n\tTue,  9 Dec 2025 19:10:03 +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=\"LCT2hFR4\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1765303803;\n\tbh=R8ClsOS1rFJy8MquZ1fVPniOza9z2xfgQmDh0RqhyJQ=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=LCT2hFR4Z8btjvl33e9EYjRJ4sgtOEBHIPo792Re6294uXkTpSl51i9eO7wi5XA+1\n\tJPJAlBEhon6S6fIzOgHTrCRT1XJIu0/l4Un6wO1uvBZ+MnfGmEYawq889VHUf/R/n7\n\tVuiKZR8yghK4mMLdx5w8vQRFL5T8OqCDZu9rbbcY=",
        "From": "Isaac Scott <isaac.scott@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Isaac Scott <isaac.scott@ideasonboard.com>",
        "Subject": "[RFC PATCH 1/6] camera_sensor: Add camera_sensor_basic",
        "Date": "Tue,  9 Dec 2025 18:09:49 +0000",
        "Message-ID": "<20251209180954.332392-2-isaac.scott@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20251209180954.332392-1-isaac.scott@ideasonboard.com>",
        "References": "<20251209180954.332392-1-isaac.scott@ideasonboard.com>",
        "MIME-Version": "1.0",
        "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>"
    },
    "content": "camera_sensor_legacy assumes camera sensors support bayer formats, and\nrequires mandatory controls. This means other camera sensors which do\nnot have those controls cannot be used with libcamera.\n\nCreate a new type of camera_sensor that has no mandatory control\nrequirements other than HBLANK and VBLANK.\n\nSigned-off-by: Isaac Scott <isaac.scott@ideasonboard.com>\n---\n src/libcamera/sensor/camera_sensor_basic.cpp | 964 +++++++++++++++++++\n src/libcamera/sensor/meson.build             |   1 +\n 2 files changed, 965 insertions(+)\n create mode 100644 src/libcamera/sensor/camera_sensor_basic.cpp",
    "diff": "diff --git a/src/libcamera/sensor/camera_sensor_basic.cpp b/src/libcamera/sensor/camera_sensor_basic.cpp\nnew file mode 100644\nindex 000000000..57213a1ab\n--- /dev/null\n+++ b/src/libcamera/sensor/camera_sensor_basic.cpp\n@@ -0,0 +1,964 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * A V4L2-backed camera sensor\n+ */\n+\n+#include <algorithm>\n+#include <cmath>\n+#include <float.h>\n+#include <iomanip>\n+#include <limits.h>\n+#include <map>\n+#include <memory>\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 CameraSensorBasic : public CameraSensor, protected Loggable\n+{\n+public:\n+\tCameraSensorBasic(const MediaEntity *entity);\n+\t~CameraSensorBasic();\n+\n+\tstatic std::variant<std::unique_ptr<CameraSensor>, int>\n+\tmatch(MediaEntity *entity);\n+\n+\tconst std::string &model() const override { return model_; }\n+\tconst std::string &id() const override { return id_; }\n+\n+\tconst MediaEntity *entity() const override { return entity_; }\n+\tV4L2Subdevice *device() override { return subdev_.get(); }\n+\n+\tCameraLens *focusLens() override { return focusLens_.get(); }\n+\n+\tconst std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }\n+\tstd::vector<Size> sizes(unsigned int mbusCode) const override;\n+\tSize resolution() const override;\n+\n+\tV4L2SubdeviceFormat getFormat(Span<const unsigned int> mbusCodes,\n+\t\t\t\t      const Size &size,\n+\t\t\t\t      const Size maxSize) const override;\n+\tint setFormat(V4L2SubdeviceFormat *format,\n+\t\t      Transform transform = Transform::Identity) override;\n+\tint tryFormat(V4L2SubdeviceFormat *format) const override;\n+\n+\tint applyConfiguration(const SensorConfiguration &config,\n+\t\t\t       Transform transform = Transform::Identity,\n+\t\t\t       V4L2SubdeviceFormat *sensorFormat = nullptr) override;\n+\n+\tconst ControlList &properties() const override { return properties_; }\n+\tint sensorInfo(IPACameraSensorInfo *info) const override;\n+\tTransform computeTransform(Orientation *orientation) const override;\n+\n+\tconst ControlInfoMap &controls() const override;\n+\tControlList getControls(Span<const uint32_t> ids) override;\n+\tint setControls(ControlList *ctrls) override;\n+\n+\tconst std::vector<controls::draft::TestPatternModeEnum> &\n+\ttestPatternModes() const override { return testPatternModes_; }\n+\tint setTestPatternMode(controls::draft::TestPatternModeEnum mode) override;\n+\tconst CameraSensorProperties::SensorDelays &sensorDelays() override;\n+\tBayerFormat::Order bayerOrder(Transform t) const override;\n+\n+protected:\n+\tstd::string logPrefix() const override;\n+\n+private:\n+\tLIBCAMERA_DISABLE_COPY(CameraSensorBasic)\n+\n+\tint init();\n+\tint generateId();\n+\tint validateSensorDriver();\n+\tvoid initVimcDefaultProperties();\n+\tvoid initStaticProperties();\n+\tvoid initTestPatternModes();\n+\tint initProperties();\n+\tint applyTestPatternMode(controls::draft::TestPatternModeEnum mode);\n+\tint discoverAncillaryDevices();\n+\n+\tconst MediaEntity *entity_;\n+\tstd::unique_ptr<V4L2Subdevice> subdev_;\n+\tunsigned int pad_;\n+\n+\tconst CameraSensorProperties *staticProps_;\n+\n+\tstd::string model_;\n+\tstd::string id_;\n+\n+\tV4L2Subdevice::Formats formats_;\n+\tstd::vector<unsigned int> mbusCodes_;\n+\tstd::vector<Size> sizes_;\n+\tstd::vector<controls::draft::TestPatternModeEnum> testPatternModes_;\n+\tcontrols::draft::TestPatternModeEnum testPatternMode_;\n+\n+\tSize pixelArraySize_;\n+\tRectangle activeArea_;\n+\tbool supportFlips_;\n+\tOrientation mountingOrientation_;\n+\n+\tControlList properties_;\n+\n+\tstd::unique_ptr<CameraLens> focusLens_;\n+};\n+\n+/**\n+ * \\class CameraSensorBasic\n+ * \\brief A camera sensor based on V4L2 subdevices\n+ *\n+ * The implementation is currently limited to sensors that expose a single V4L2\n+ * subdevice with a single pad. It will be extended to support more complex\n+ * devices as the needs arise.\n+ */\n+\n+CameraSensorBasic::CameraSensorBasic(const MediaEntity *entity)\n+\t: entity_(entity), pad_(UINT_MAX), staticProps_(nullptr),\n+\t  supportFlips_(false), properties_(properties::properties)\n+{\n+}\n+\n+CameraSensorBasic::~CameraSensorBasic() = default;\n+\n+std::variant<std::unique_ptr<CameraSensor>, int>\n+CameraSensorBasic::match(MediaEntity *entity)\n+{\n+\tstd::unique_ptr<CameraSensorBasic> sensor =\n+\t\tstd::make_unique<CameraSensorBasic>(entity);\n+\n+\tint ret = sensor->init();\n+\tif (ret)\n+\t\treturn { ret };\n+\n+\treturn { std::move(sensor) };\n+}\n+\n+int CameraSensorBasic::init()\n+{\n+\tfor (const MediaPad *pad : entity_->pads()) {\n+\t\tif (pad->flags() & MEDIA_PAD_FL_SOURCE) {\n+\t\t\tpad_ = pad->index();\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (pad_ == UINT_MAX) {\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"Sensors with more than one pad are not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tswitch (entity_->function()) {\n+\tcase MEDIA_ENT_F_CAM_SENSOR:\n+\tcase MEDIA_ENT_F_PROC_VIDEO_ISP:\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"Invalid sensor function \"\n+\t\t\t<< utils::hex(entity_->function());\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Create and open the subdev. */\n+\tsubdev_ = std::make_unique<V4L2Subdevice>(entity_);\n+\tint ret = subdev_->open();\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Clear any flips to be sure we get the \"native\" Bayer order. This is\n+\t * harmless for sensors where the flips don't affect the Bayer order.\n+\t */\n+\tControlList ctrls(subdev_->controls());\n+\tif (subdev_->controls().find(V4L2_CID_HFLIP) != subdev_->controls().end())\n+\t\tctrls.set(V4L2_CID_HFLIP, 0);\n+\tif (subdev_->controls().find(V4L2_CID_VFLIP) != subdev_->controls().end())\n+\t\tctrls.set(V4L2_CID_VFLIP, 0);\n+\tsubdev_->setControls(&ctrls);\n+\n+\t/* Enumerate, sort and cache media bus codes and sizes. */\n+\tformats_ = subdev_->formats(pad_);\n+\tif (formats_.empty()) {\n+\t\tLOG(CameraSensor, Error) << \"No image format found\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmbusCodes_ = utils::map_keys(formats_);\n+\tstd::sort(mbusCodes_.begin(), mbusCodes_.end());\n+\n+\tfor (const auto &format : formats_) {\n+\t\tconst std::vector<SizeRange> &ranges = format.second;\n+\t\tstd::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_),\n+\t\t\t       [](const SizeRange &range) { return range.max; });\n+\t}\n+\n+\tstd::sort(sizes_.begin(), sizes_.end());\n+\n+\t/* Remove duplicates. */\n+\tauto last = std::unique(sizes_.begin(), sizes_.end());\n+\tsizes_.erase(last, sizes_.end());\n+\n+\t/*\n+\t * VIMC is a bit special, as it does not yet support all the mandatory\n+\t * requirements regular sensors have to respect.\n+\t *\n+\t * Do not validate the driver if it's VIMC and initialize the sensor\n+\t * properties with static information.\n+\t *\n+\t * \\todo Remove the special case once the VIMC driver has been\n+\t * updated in all test platforms.\n+\t */\n+\tif (entity_->device()->driver() == \"vimc\") {\n+\t\tinitVimcDefaultProperties();\n+\n+\t\tret = initProperties();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\treturn discoverAncillaryDevices();\n+\t}\n+\n+\tret = validateSensorDriver();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = initProperties();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = discoverAncillaryDevices();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Set HBLANK to the minimum to start with a well-defined line length,\n+\t * allowing IPA modules that do not modify HBLANK to use the sensor\n+\t * minimum line length in their calculations.\n+\t */\n+\tconst struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK);\n+\tif (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {\n+\t\tControlList ctrl(subdev_->controls());\n+\n+\t\tctrl.set(V4L2_CID_HBLANK, static_cast<int32_t>(hblankInfo->minimum));\n+\t\tret = subdev_->setControls(&ctrl);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff);\n+}\n+\n+int CameraSensorBasic::generateId()\n+{\n+\tconst std::string devPath = subdev_->devicePath();\n+\n+\t/* Try to get ID from firmware description. */\n+\tid_ = sysfs::firmwareNodePath(devPath);\n+\tif (!id_.empty())\n+\t\treturn 0;\n+\n+\t/*\n+\t * Virtual sensors not described in firmware\n+\t *\n+\t * Verify it's a platform device and construct ID from the device path\n+\t * and model of sensor.\n+\t */\n+\tif (devPath.find(\"/sys/devices/platform/\", 0) == 0) {\n+\t\tid_ = devPath.substr(strlen(\"/sys/devices/\")) + \" \" + model();\n+\t\treturn 0;\n+\t}\n+\n+\tLOG(CameraSensor, Error) << \"Can't generate sensor ID\";\n+\treturn -EINVAL;\n+}\n+\n+int CameraSensorBasic::validateSensorDriver()\n+{\n+\tint err = 0;\n+\n+\t/*\n+\t * Optional controls are used to register optional sensor properties. If\n+\t * not present, some values will be defaulted.\n+\t */\n+\tstatic constexpr uint32_t optionalControls[] = {\n+\t\tV4L2_CID_CAMERA_SENSOR_ROTATION,\n+\t};\n+\n+\tconst ControlIdMap &controls = subdev_->controls().idmap();\n+\tfor (uint32_t ctrl : optionalControls) {\n+\t\tif (!controls.count(ctrl))\n+\t\t\tLOG(CameraSensor, Debug)\n+\t\t\t\t<< \"Optional V4L2 control \" << utils::hex(ctrl)\n+\t\t\t\t<< \" not supported\";\n+\t}\n+\n+\t/*\n+\t * Recommended controls are similar to optional controls, but will\n+\t * become mandatory in the near future. Be loud if they're missing.\n+\t */\n+\tstatic constexpr uint32_t recommendedControls[] = {\n+\t\tV4L2_CID_CAMERA_ORIENTATION,\n+\t};\n+\n+\tfor (uint32_t ctrl : recommendedControls) {\n+\t\tif (!controls.count(ctrl)) {\n+\t\t\tLOG(CameraSensor, Warning)\n+\t\t\t\t<< \"Recommended V4L2 control \" << utils::hex(ctrl)\n+\t\t\t\t<< \" not supported\";\n+\t\t\terr = -EINVAL;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Verify if sensor supports horizontal/vertical flips\n+\t *\n+\t * \\todo Handle horizontal and vertical flips independently.\n+\t */\n+\tconst struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP);\n+\tconst struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP);\n+\tif (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) &&\n+\t    vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) {\n+\t\tsupportFlips_ = true;\n+\n+\t}\n+\n+\tif (!supportFlips_)\n+\t\tLOG(CameraSensor, Debug)\n+\t\t\t<< \"Camera sensor does not support horizontal/vertical flip\";\n+\n+\t/*\n+\t * Make sure the required selection targets are supported.\n+\t *\n+\t * Failures in reading any of the targets are not deemed to be fatal,\n+\t * but some properties and features, like constructing a\n+\t * IPACameraSensorInfo for the IPA module, won't be supported.\n+\t *\n+\t * \\todo Make support for selection targets mandatory as soon as all\n+\t * test platforms have been updated.\n+\t */\n+\tRectangle rect;\n+\tint ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &rect);\n+\tif (ret) {\n+\t\t/*\n+\t\t * Default the pixel array size to the largest size supported\n+\t\t * by the sensor. The sizes_ vector is sorted in ascending\n+\t\t * order, the largest size is thus the last element.\n+\t\t */\n+\t\tpixelArraySize_ = sizes_.back();\n+\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"The PixelArraySize property has been defaulted to \"\n+\t\t\t<< pixelArraySize_;\n+\t\terr = -EINVAL;\n+\t} else {\n+\t\tpixelArraySize_ = rect.size();\n+\t}\n+\n+\tret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &activeArea_);\n+\tif (ret) {\n+\t\tactiveArea_ = Rectangle(pixelArraySize_);\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"The PixelArrayActiveAreas property has been defaulted to \"\n+\t\t\t<< activeArea_;\n+\t\terr = -EINVAL;\n+\t}\n+\n+\tret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &rect);\n+\tif (ret) {\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"Failed to retrieve the sensor crop rectangle\";\n+\t\terr = -EINVAL;\n+\t}\n+\n+\tif (err) {\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"The sensor kernel driver needs to be fixed\";\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information\";\n+\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void CameraSensorBasic::initVimcDefaultProperties()\n+{\n+\t/* Use the largest supported size. */\n+\tpixelArraySize_ = sizes_.back();\n+\tactiveArea_ = Rectangle(pixelArraySize_);\n+}\n+\n+void CameraSensorBasic::initStaticProperties()\n+{\n+\tstaticProps_ = CameraSensorProperties::get(model_);\n+\tif (!staticProps_)\n+\t\treturn;\n+\n+\t/* Register the properties retrieved from the sensor database. */\n+\tproperties_.set(properties::UnitCellSize, staticProps_->unitCellSize);\n+\n+\tinitTestPatternModes();\n+}\n+\n+const CameraSensorProperties::SensorDelays &CameraSensorBasic::sensorDelays()\n+{\n+\tstatic constexpr CameraSensorProperties::SensorDelays defaultSensorDelays = {\n+\t\t.exposureDelay = 2,\n+\t\t.gainDelay = 1,\n+\t\t.vblankDelay = 2,\n+\t\t.hblankDelay = 2,\n+\t};\n+\n+\tif (!staticProps_ ||\n+\t    (!staticProps_->sensorDelays.exposureDelay &&\n+\t     !staticProps_->sensorDelays.gainDelay &&\n+\t     !staticProps_->sensorDelays.vblankDelay &&\n+\t     !staticProps_->sensorDelays.hblankDelay)) {\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"No sensor delays found in static properties. \"\n+\t\t\t   \"Assuming unverified defaults.\";\n+\n+\t\treturn defaultSensorDelays;\n+\t}\n+\n+\treturn staticProps_->sensorDelays;\n+}\n+\n+void CameraSensorBasic::initTestPatternModes()\n+{\n+\tconst auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN);\n+\tif (v4l2TestPattern == controls().end()) {\n+\t\tLOG(CameraSensor, Debug) << \"V4L2_CID_TEST_PATTERN is not supported\";\n+\t\treturn;\n+\t}\n+\n+\tconst auto &testPatternModes = staticProps_->testPatternModes;\n+\tif (testPatternModes.empty()) {\n+\t\t/*\n+\t\t * The camera sensor supports test patterns but we don't know\n+\t\t * how to map them so this should be fixed.\n+\t\t */\n+\t\tLOG(CameraSensor, Debug) << \"No static test pattern map for \\'\"\n+\t\t\t\t\t << model() << \"\\'\";\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * Create a map that associates the V4L2 control index to the test\n+\t * pattern mode by reversing the testPatternModes map provided by the\n+\t * camera sensor properties. This makes it easier to verify if the\n+\t * control index is supported in the below for loop that creates the\n+\t * list of supported test patterns.\n+\t */\n+\tstd::map<int32_t, controls::draft::TestPatternModeEnum> indexToTestPatternMode;\n+\tfor (const auto &it : testPatternModes)\n+\t\tindexToTestPatternMode[it.second] = it.first;\n+\n+\tfor (const ControlValue &value : v4l2TestPattern->second.values()) {\n+\t\tconst int32_t index = value.get<int32_t>();\n+\n+\t\tconst auto it = indexToTestPatternMode.find(index);\n+\t\tif (it == indexToTestPatternMode.end()) {\n+\t\t\tLOG(CameraSensor, Debug)\n+\t\t\t\t<< \"Test pattern mode \" << index << \" ignored\";\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\ttestPatternModes_.push_back(it->second);\n+\t}\n+}\n+\n+int CameraSensorBasic::initProperties()\n+{\n+\tmodel_ = subdev_->model();\n+\tproperties_.set(properties::Model, utils::toAscii(model_));\n+\n+\t/* Generate a unique ID for the sensor. */\n+\tint ret = generateId();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Initialize the static properties from the sensor database. */\n+\tinitStaticProperties();\n+\n+\t/* Retrieve and register properties from the kernel interface. */\n+\tconst ControlInfoMap &controls = subdev_->controls();\n+\n+\tconst auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION);\n+\tif (orientation != controls.end()) {\n+\t\tint32_t v4l2Orientation = orientation->second.def().get<int32_t>();\n+\t\tint32_t propertyValue;\n+\n+\t\tswitch (v4l2Orientation) {\n+\t\tdefault:\n+\t\t\tLOG(CameraSensor, Warning)\n+\t\t\t\t<< \"Unsupported camera location \"\n+\t\t\t\t<< v4l2Orientation << \", setting to External\";\n+\t\t\t[[fallthrough]];\n+\t\tcase V4L2_CAMERA_ORIENTATION_EXTERNAL:\n+\t\t\tpropertyValue = properties::CameraLocationExternal;\n+\t\t\tbreak;\n+\t\tcase V4L2_CAMERA_ORIENTATION_FRONT:\n+\t\t\tpropertyValue = properties::CameraLocationFront;\n+\t\t\tbreak;\n+\t\tcase V4L2_CAMERA_ORIENTATION_BACK:\n+\t\t\tpropertyValue = properties::CameraLocationBack;\n+\t\t\tbreak;\n+\t\t}\n+\t\tproperties_.set(properties::Location, propertyValue);\n+\t} else {\n+\t\tLOG(CameraSensor, Warning) << \"Failed to retrieve the camera location\";\n+\t}\n+\n+\tconst auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION);\n+\tif (rotationControl != controls.end()) {\n+\t\tint32_t propertyValue = rotationControl->second.def().get<int32_t>();\n+\n+\t\t/*\n+\t\t * Cache the Transform associated with the camera mounting\n+\t\t * rotation for later use in computeTransform().\n+\t\t */\n+\t\tbool success;\n+\t\tmountingOrientation_ = orientationFromRotation(propertyValue, &success);\n+\t\tif (!success) {\n+\t\t\tLOG(CameraSensor, Warning)\n+\t\t\t\t<< \"Invalid rotation of \" << propertyValue\n+\t\t\t\t<< \" degrees - ignoring\";\n+\t\t\tmountingOrientation_ = Orientation::Rotate0;\n+\t\t}\n+\n+\t\tproperties_.set(properties::Rotation, propertyValue);\n+\t} else {\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"Rotation control not available, default to 0 degrees\";\n+\t\tproperties_.set(properties::Rotation, 0);\n+\t\tmountingOrientation_ = Orientation::Rotate0;\n+\t}\n+\n+\tproperties_.set(properties::PixelArraySize, pixelArraySize_);\n+\tproperties_.set(properties::PixelArrayActiveAreas, { activeArea_ });\n+\n+\treturn 0;\n+}\n+\n+int CameraSensorBasic::discoverAncillaryDevices()\n+{\n+\tint ret;\n+\n+\tfor (MediaEntity *ancillary : entity_->ancillaryEntities()) {\n+\t\tswitch (ancillary->function()) {\n+\t\tcase MEDIA_ENT_F_LENS:\n+\t\t\tfocusLens_ = std::make_unique<CameraLens>(ancillary);\n+\t\t\tret = focusLens_->init();\n+\t\t\tif (ret) {\n+\t\t\t\tLOG(CameraSensor, Error)\n+\t\t\t\t\t<< \"Lens initialisation failed, lens disabled\";\n+\t\t\t\tfocusLens_.reset();\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tLOG(CameraSensor, Warning)\n+\t\t\t\t<< \"Unsupported ancillary entity function \"\n+\t\t\t\t<< ancillary->function();\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+std::vector<Size> CameraSensorBasic::sizes(unsigned int mbusCode) const\n+{\n+\tstd::vector<Size> sizes;\n+\n+\tconst auto &format = formats_.find(mbusCode);\n+\tif (format == formats_.end())\n+\t\treturn sizes;\n+\n+\tconst std::vector<SizeRange> &ranges = format->second;\n+\tstd::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes),\n+\t\t       [](const SizeRange &range) { return range.max; });\n+\n+\tstd::sort(sizes.begin(), sizes.end());\n+\n+\treturn sizes;\n+}\n+\n+Size CameraSensorBasic::resolution() const\n+{\n+\treturn std::min(sizes_.back(), activeArea_.size());\n+}\n+\n+V4L2SubdeviceFormat\n+CameraSensorBasic::getFormat(Span<const unsigned int> mbusCodes,\n+\t\t\t      const Size &size, Size maxSize) const\n+{\n+\tunsigned int desiredArea = size.width * size.height;\n+\tunsigned int bestArea = UINT_MAX;\n+\tfloat desiredRatio = static_cast<float>(size.width) / size.height;\n+\tfloat bestRatio = FLT_MAX;\n+\tconst Size *bestSize = nullptr;\n+\tuint32_t bestCode = 0;\n+\n+\tfor (unsigned int code : mbusCodes) {\n+\t\tconst auto formats = formats_.find(code);\n+\t\tif (formats == formats_.end())\n+\t\t\tcontinue;\n+\n+\t\tfor (const SizeRange &range : formats->second) {\n+\t\t\tconst Size &sz = range.max;\n+\n+\t\t\tif (!maxSize.isNull() &&\n+\t\t\t    (sz.width > maxSize.width || sz.height > maxSize.height))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (sz.width < size.width || sz.height < size.height)\n+\t\t\t\tcontinue;\n+\n+\t\t\tfloat ratio = static_cast<float>(sz.width) / sz.height;\n+\t\t\tfloat ratioDiff = std::abs(ratio - desiredRatio);\n+\t\t\tunsigned int area = sz.width * sz.height;\n+\t\t\tunsigned int areaDiff = area - desiredArea;\n+\n+\t\t\tif (ratioDiff > bestRatio)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (ratioDiff < bestRatio || areaDiff < bestArea) {\n+\t\t\t\tbestRatio = ratioDiff;\n+\t\t\t\tbestArea = areaDiff;\n+\t\t\t\tbestSize = &sz;\n+\t\t\t\tbestCode = code;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (!bestSize) {\n+\t\tLOG(CameraSensor, Debug) << \"No supported format or size found\";\n+\t\treturn {};\n+\t}\n+\n+\tV4L2SubdeviceFormat format{\n+\t\t.code = bestCode,\n+\t\t.size = *bestSize,\n+\t\t.colorSpace = ColorSpace::Raw,\n+\t};\n+\n+\treturn format;\n+}\n+\n+int CameraSensorBasic::setFormat(V4L2SubdeviceFormat *format, Transform transform)\n+{\n+\t/* Configure flips if the sensor supports that. */\n+\tif (supportFlips_) {\n+\t\tControlList flipCtrls(subdev_->controls());\n+\n+\t\tflipCtrls.set(V4L2_CID_HFLIP,\n+\t\t\t      static_cast<int32_t>(!!(transform & Transform::HFlip)));\n+\t\tflipCtrls.set(V4L2_CID_VFLIP,\n+\t\t\t      static_cast<int32_t>(!!(transform & Transform::VFlip)));\n+\n+\t\tint ret = subdev_->setControls(&flipCtrls);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\t/* Apply format on the subdev. */\n+\tint ret = subdev_->setFormat(pad_, format);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tsubdev_->updateControlInfo();\n+\treturn 0;\n+}\n+\n+int CameraSensorBasic::tryFormat(V4L2SubdeviceFormat *format) const\n+{\n+\treturn subdev_->setFormat(pad_, format,\n+\t\t\t\t  V4L2Subdevice::Whence::TryFormat);\n+}\n+\n+int CameraSensorBasic::applyConfiguration(const SensorConfiguration &config,\n+\t\t\t\t\t   Transform transform,\n+\t\t\t\t\t   V4L2SubdeviceFormat *sensorFormat)\n+{\n+\tif (!config.isValid()) {\n+\t\tLOG(CameraSensor, Error) << \"Invalid sensor configuration\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::vector<unsigned int> filteredCodes;\n+\tstd::copy_if(mbusCodes_.begin(), mbusCodes_.end(),\n+\t\t     std::back_inserter(filteredCodes),\n+\t\t     [&config](unsigned int mbusCode) {\n+\t\t\t     BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode);\n+\t\t\t     if (bayer.bitDepth == config.bitDepth)\n+\t\t\t\t     return true;\n+\t\t\t     return false;\n+\t\t     });\n+\tif (filteredCodes.empty()) {\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"Cannot find any format with bit depth \"\n+\t\t\t<< config.bitDepth;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Compute the sensor's data frame size by applying the cropping\n+\t * rectangle, subsampling and output crop to the sensor's pixel array\n+\t * size.\n+\t *\n+\t * \\todo The actual size computation is for now ignored and only the\n+\t * output size is considered. This implies that resolutions obtained\n+\t * with two different cropping/subsampling will look identical and\n+\t * only the first found one will be considered.\n+\t */\n+\tV4L2SubdeviceFormat subdevFormat = {};\n+\tfor (unsigned int code : filteredCodes) {\n+\t\tfor (const Size &size : sizes(code)) {\n+\t\t\tif (size.width != config.outputSize.width ||\n+\t\t\t    size.height != config.outputSize.height)\n+\t\t\t\tcontinue;\n+\n+\t\t\tsubdevFormat.code = code;\n+\t\t\tsubdevFormat.size = size;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tif (!subdevFormat.code) {\n+\t\tLOG(CameraSensor, Error) << \"Invalid output size in sensor configuration\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tint ret = setFormat(&subdevFormat, transform);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Return to the caller the format actually applied to the sensor.\n+\t * This is relevant if transform has changed the bayer pattern order.\n+\t */\n+\tif (sensorFormat)\n+\t\t*sensorFormat = subdevFormat;\n+\n+\t/* \\todo Handle AnalogCrop. Most sensors do not support set_selection */\n+\t/* \\todo Handle scaling in the digital domain. */\n+\n+\treturn 0;\n+}\n+\n+int CameraSensorBasic::sensorInfo(IPACameraSensorInfo *info) const\n+{\n+\tinfo->model = model();\n+\n+\t/*\n+\t * The active area size is a static property, while the crop\n+\t * rectangle needs to be re-read as it depends on the sensor\n+\t * configuration.\n+\t */\n+\tinfo->activeAreaSize = { activeArea_.width, activeArea_.height };\n+\n+\t/*\n+\t * \\todo Support for retreiving the crop rectangle is scheduled to\n+\t * become mandatory. For the time being use the default value if it has\n+\t * been initialized at sensor driver validation time.\n+\t */\n+\tint ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &info->analogCrop);\n+\tif (ret) {\n+\t\tinfo->analogCrop = activeArea_;\n+\t\tLOG(CameraSensor, Warning)\n+\t\t\t<< \"The analogue crop rectangle has been defaulted to the active area size\";\n+\t}\n+\n+\t/*\n+\t * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y\n+\t * are defined relatively to the active pixel area, while V4L2's\n+\t * TGT_CROP target is defined in respect to the full pixel array.\n+\t *\n+\t * Compensate it by subtracting the active area offset.\n+\t */\n+\tinfo->analogCrop.x -= activeArea_.x;\n+\tinfo->analogCrop.y -= activeArea_.y;\n+\n+\t/* The bit depth and image size depend on the currently applied format. */\n+\tV4L2SubdeviceFormat format{};\n+\tret = subdev_->getFormat(pad_, &format);\n+\tif (ret)\n+\t\treturn ret;\n+\tinfo->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel;\n+\tinfo->outputSize = format.size;\n+\n+\tstd::optional<int32_t> cfa = properties_.get(properties::draft::ColorFilterArrangement);\n+\tinfo->cfaPattern = cfa ? *cfa : properties::draft::RGB;\n+\n+\t/*\n+\t * Retrieve the pixel rate, line length and minimum/maximum frame\n+\t * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE,\n+\t * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory.\n+\t */\n+\tstatic constexpr uint32_t cids[] = {\n+\t\tV4L2_CID_PIXEL_RATE,\n+\t\tV4L2_CID_HBLANK,\n+\t\tV4L2_CID_VBLANK,\n+\t};\n+\n+\tControlList ctrls = subdev_->getControls(cids);\n+\tif (ctrls.empty()) {\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"Failed to retrieve camera info controls\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tinfo->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get<int64_t>();\n+\n+\tconst ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK);\n+\tinfo->minLineLength = info->outputSize.width + hblank.min().get<int32_t>();\n+\tinfo->maxLineLength = info->outputSize.width + hblank.max().get<int32_t>();\n+\n+\tconst ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK);\n+\tinfo->minFrameLength = info->outputSize.height + vblank.min().get<int32_t>();\n+\tinfo->maxFrameLength = info->outputSize.height + vblank.max().get<int32_t>();\n+\n+\treturn 0;\n+}\n+\n+Transform CameraSensorBasic::computeTransform(Orientation *orientation) const\n+{\n+\t/*\n+\t * If we cannot do any flips we cannot change the native camera mounting\n+\t * orientation.\n+\t */\n+\tif (!supportFlips_) {\n+\t\t*orientation = mountingOrientation_;\n+\t\treturn Transform::Identity;\n+\t}\n+\n+\t/*\n+\t * Now compute the required transform to obtain 'orientation' starting\n+\t * from the mounting rotation.\n+\t *\n+\t * As a note:\n+\t * \torientation / mountingOrientation_ = transform\n+\t * \tmountingOrientation_ * transform = orientation\n+\t */\n+\tTransform transform = *orientation / mountingOrientation_;\n+\n+\t/*\n+\t * If transform contains any Transpose we cannot do it, so adjust\n+\t * 'orientation' to report the image native orientation and return Identity.\n+\t */\n+\tif (!!(transform & Transform::Transpose)) {\n+\t\t*orientation = mountingOrientation_;\n+\t\treturn Transform::Identity;\n+\t}\n+\n+\treturn transform;\n+}\n+\n+BayerFormat::Order CameraSensorBasic::bayerOrder([[maybe_unused]] Transform t) const\n+{\n+\treturn BayerFormat::Order::RGGB;\n+}\n+\n+const ControlInfoMap &CameraSensorBasic::controls() const\n+{\n+\treturn subdev_->controls();\n+}\n+\n+ControlList CameraSensorBasic::getControls(Span<const uint32_t> ids)\n+{\n+\treturn subdev_->getControls(ids);\n+}\n+\n+int CameraSensorBasic::setControls(ControlList *ctrls)\n+{\n+\treturn subdev_->setControls(ctrls);\n+}\n+\n+int CameraSensorBasic::setTestPatternMode(controls::draft::TestPatternModeEnum mode)\n+{\n+\tif (testPatternMode_ == mode)\n+\t\treturn 0;\n+\n+\tif (testPatternModes_.empty()) {\n+\t\tLOG(CameraSensor, Error)\n+\t\t\t<< \"Camera sensor does not support test pattern modes.\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn applyTestPatternMode(mode);\n+}\n+\n+int CameraSensorBasic::applyTestPatternMode(controls::draft::TestPatternModeEnum mode)\n+{\n+\tif (testPatternModes_.empty())\n+\t\treturn 0;\n+\n+\tauto it = std::find(testPatternModes_.begin(), testPatternModes_.end(),\n+\t\t\t    mode);\n+\tif (it == testPatternModes_.end()) {\n+\t\tLOG(CameraSensor, Error) << \"Unsupported test pattern mode \"\n+\t\t\t\t\t << mode;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tLOG(CameraSensor, Debug) << \"Apply test pattern mode \" << mode;\n+\n+\tint32_t index = staticProps_->testPatternModes.at(mode);\n+\tControlList ctrls{ controls() };\n+\tctrls.set(V4L2_CID_TEST_PATTERN, index);\n+\n+\tint ret = setControls(&ctrls);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\ttestPatternMode_ = mode;\n+\n+\treturn 0;\n+}\n+\n+std::string CameraSensorBasic::logPrefix() const\n+{\n+\treturn \"'\" + entity_->name() + \"'\";\n+}\n+\n+REGISTER_CAMERA_SENSOR(CameraSensorBasic, -100)\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build\nindex dce74ed6a..9fe627713 100644\n--- a/src/libcamera/sensor/meson.build\n+++ b/src/libcamera/sensor/meson.build\n@@ -2,6 +2,7 @@\n \n libcamera_internal_sources += files([\n     'camera_sensor.cpp',\n+    'camera_sensor_basic.cpp',\n     'camera_sensor_legacy.cpp',\n     'camera_sensor_properties.cpp',\n     'camera_sensor_raw.cpp',\n",
    "prefixes": [
        "RFC",
        "1/6"
    ]
}