From patchwork Tue Dec 9 18:09:49 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25390 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id A680FBD1F1 for ; Tue, 9 Dec 2025 18:10:08 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D20DB6141D; Tue, 9 Dec 2025 19:10:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LCT2hFR4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 46F0C6140A for ; Tue, 9 Dec 2025 19:10:04 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 71664710; Tue, 9 Dec 2025 19:10:03 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303803; bh=R8ClsOS1rFJy8MquZ1fVPniOza9z2xfgQmDh0RqhyJQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LCT2hFR4Z8btjvl33e9EYjRJ4sgtOEBHIPo792Re6294uXkTpSl51i9eO7wi5XA+1 JPJAlBEhon6S6fIzOgHTrCRT1XJIu0/l4Un6wO1uvBZ+MnfGmEYawq889VHUf/R/n7 VuiKZR8yghK4mMLdx5w8vQRFL5T8OqCDZu9rbbcY= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott 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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" camera_sensor_legacy assumes camera sensors support bayer formats, and requires mandatory controls. This means other camera sensors which do not have those controls cannot be used with libcamera. Create a new type of camera_sensor that has no mandatory control requirements other than HBLANK and VBLANK. Signed-off-by: Isaac Scott --- src/libcamera/sensor/camera_sensor_basic.cpp | 964 +++++++++++++++++++ src/libcamera/sensor/meson.build | 1 + 2 files changed, 965 insertions(+) create mode 100644 src/libcamera/sensor/camera_sensor_basic.cpp diff --git a/src/libcamera/sensor/camera_sensor_basic.cpp b/src/libcamera/sensor/camera_sensor_basic.cpp new file mode 100644 index 000000000..57213a1ab --- /dev/null +++ b/src/libcamera/sensor/camera_sensor_basic.cpp @@ -0,0 +1,964 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * A V4L2-backed camera sensor + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libcamera/internal/bayer_format.h" +#include "libcamera/internal/camera_lens.h" +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/camera_sensor_properties.h" +#include "libcamera/internal/formats.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/sysfs.h" +#include "libcamera/internal/v4l2_subdevice.h" + +namespace libcamera { + +class BayerFormat; +class CameraLens; +class MediaEntity; +class SensorConfiguration; + +struct CameraSensorProperties; + +enum class Orientation; + +LOG_DECLARE_CATEGORY(CameraSensor) + +class CameraSensorBasic : public CameraSensor, protected Loggable +{ +public: + CameraSensorBasic(const MediaEntity *entity); + ~CameraSensorBasic(); + + static std::variant, int> + match(MediaEntity *entity); + + const std::string &model() const override { return model_; } + const std::string &id() const override { return id_; } + + const MediaEntity *entity() const override { return entity_; } + V4L2Subdevice *device() override { return subdev_.get(); } + + CameraLens *focusLens() override { return focusLens_.get(); } + + const std::vector &mbusCodes() const override { return mbusCodes_; } + std::vector sizes(unsigned int mbusCode) const override; + Size resolution() const override; + + V4L2SubdeviceFormat getFormat(Span mbusCodes, + const Size &size, + const Size maxSize) const override; + int setFormat(V4L2SubdeviceFormat *format, + Transform transform = Transform::Identity) override; + int tryFormat(V4L2SubdeviceFormat *format) const override; + + int applyConfiguration(const SensorConfiguration &config, + Transform transform = Transform::Identity, + V4L2SubdeviceFormat *sensorFormat = nullptr) override; + + const ControlList &properties() const override { return properties_; } + int sensorInfo(IPACameraSensorInfo *info) const override; + Transform computeTransform(Orientation *orientation) const override; + + const ControlInfoMap &controls() const override; + ControlList getControls(Span ids) override; + int setControls(ControlList *ctrls) override; + + const std::vector & + testPatternModes() const override { return testPatternModes_; } + int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override; + const CameraSensorProperties::SensorDelays &sensorDelays() override; + BayerFormat::Order bayerOrder(Transform t) const override; + +protected: + std::string logPrefix() const override; + +private: + LIBCAMERA_DISABLE_COPY(CameraSensorBasic) + + int init(); + int generateId(); + int validateSensorDriver(); + void initVimcDefaultProperties(); + void initStaticProperties(); + void initTestPatternModes(); + int initProperties(); + int applyTestPatternMode(controls::draft::TestPatternModeEnum mode); + int discoverAncillaryDevices(); + + const MediaEntity *entity_; + std::unique_ptr subdev_; + unsigned int pad_; + + const CameraSensorProperties *staticProps_; + + std::string model_; + std::string id_; + + V4L2Subdevice::Formats formats_; + std::vector mbusCodes_; + std::vector sizes_; + std::vector testPatternModes_; + controls::draft::TestPatternModeEnum testPatternMode_; + + Size pixelArraySize_; + Rectangle activeArea_; + bool supportFlips_; + Orientation mountingOrientation_; + + ControlList properties_; + + std::unique_ptr focusLens_; +}; + +/** + * \class CameraSensorBasic + * \brief A camera sensor based on V4L2 subdevices + * + * The implementation is currently limited to sensors that expose a single V4L2 + * subdevice with a single pad. It will be extended to support more complex + * devices as the needs arise. + */ + +CameraSensorBasic::CameraSensorBasic(const MediaEntity *entity) + : entity_(entity), pad_(UINT_MAX), staticProps_(nullptr), + supportFlips_(false), properties_(properties::properties) +{ +} + +CameraSensorBasic::~CameraSensorBasic() = default; + +std::variant, int> +CameraSensorBasic::match(MediaEntity *entity) +{ + std::unique_ptr sensor = + std::make_unique(entity); + + int ret = sensor->init(); + if (ret) + return { ret }; + + return { std::move(sensor) }; +} + +int CameraSensorBasic::init() +{ + for (const MediaPad *pad : entity_->pads()) { + if (pad->flags() & MEDIA_PAD_FL_SOURCE) { + pad_ = pad->index(); + break; + } + } + + if (pad_ == UINT_MAX) { + LOG(CameraSensor, Error) + << "Sensors with more than one pad are not supported"; + return -EINVAL; + } + + switch (entity_->function()) { + case MEDIA_ENT_F_CAM_SENSOR: + case MEDIA_ENT_F_PROC_VIDEO_ISP: + break; + + default: + LOG(CameraSensor, Error) + << "Invalid sensor function " + << utils::hex(entity_->function()); + return -EINVAL; + } + + /* Create and open the subdev. */ + subdev_ = std::make_unique(entity_); + int ret = subdev_->open(); + if (ret < 0) + return ret; + + /* + * Clear any flips to be sure we get the "native" Bayer order. This is + * harmless for sensors where the flips don't affect the Bayer order. + */ + ControlList ctrls(subdev_->controls()); + if (subdev_->controls().find(V4L2_CID_HFLIP) != subdev_->controls().end()) + ctrls.set(V4L2_CID_HFLIP, 0); + if (subdev_->controls().find(V4L2_CID_VFLIP) != subdev_->controls().end()) + ctrls.set(V4L2_CID_VFLIP, 0); + subdev_->setControls(&ctrls); + + /* Enumerate, sort and cache media bus codes and sizes. */ + formats_ = subdev_->formats(pad_); + if (formats_.empty()) { + LOG(CameraSensor, Error) << "No image format found"; + return -EINVAL; + } + + mbusCodes_ = utils::map_keys(formats_); + std::sort(mbusCodes_.begin(), mbusCodes_.end()); + + for (const auto &format : formats_) { + const std::vector &ranges = format.second; + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_), + [](const SizeRange &range) { return range.max; }); + } + + std::sort(sizes_.begin(), sizes_.end()); + + /* Remove duplicates. */ + auto last = std::unique(sizes_.begin(), sizes_.end()); + sizes_.erase(last, sizes_.end()); + + /* + * VIMC is a bit special, as it does not yet support all the mandatory + * requirements regular sensors have to respect. + * + * Do not validate the driver if it's VIMC and initialize the sensor + * properties with static information. + * + * \todo Remove the special case once the VIMC driver has been + * updated in all test platforms. + */ + if (entity_->device()->driver() == "vimc") { + initVimcDefaultProperties(); + + ret = initProperties(); + if (ret) + return ret; + + return discoverAncillaryDevices(); + } + + ret = validateSensorDriver(); + if (ret) + return ret; + + ret = initProperties(); + if (ret) + return ret; + + ret = discoverAncillaryDevices(); + if (ret) + return ret; + + /* + * Set HBLANK to the minimum to start with a well-defined line length, + * allowing IPA modules that do not modify HBLANK to use the sensor + * minimum line length in their calculations. + */ + const struct v4l2_query_ext_ctrl *hblankInfo = subdev_->controlInfo(V4L2_CID_HBLANK); + if (hblankInfo && !(hblankInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { + ControlList ctrl(subdev_->controls()); + + ctrl.set(V4L2_CID_HBLANK, static_cast(hblankInfo->minimum)); + ret = subdev_->setControls(&ctrl); + if (ret) + return ret; + } + + return applyTestPatternMode(controls::draft::TestPatternModeEnum::TestPatternModeOff); +} + +int CameraSensorBasic::generateId() +{ + const std::string devPath = subdev_->devicePath(); + + /* Try to get ID from firmware description. */ + id_ = sysfs::firmwareNodePath(devPath); + if (!id_.empty()) + return 0; + + /* + * Virtual sensors not described in firmware + * + * Verify it's a platform device and construct ID from the device path + * and model of sensor. + */ + if (devPath.find("/sys/devices/platform/", 0) == 0) { + id_ = devPath.substr(strlen("/sys/devices/")) + " " + model(); + return 0; + } + + LOG(CameraSensor, Error) << "Can't generate sensor ID"; + return -EINVAL; +} + +int CameraSensorBasic::validateSensorDriver() +{ + int err = 0; + + /* + * Optional controls are used to register optional sensor properties. If + * not present, some values will be defaulted. + */ + static constexpr uint32_t optionalControls[] = { + V4L2_CID_CAMERA_SENSOR_ROTATION, + }; + + const ControlIdMap &controls = subdev_->controls().idmap(); + for (uint32_t ctrl : optionalControls) { + if (!controls.count(ctrl)) + LOG(CameraSensor, Debug) + << "Optional V4L2 control " << utils::hex(ctrl) + << " not supported"; + } + + /* + * Recommended controls are similar to optional controls, but will + * become mandatory in the near future. Be loud if they're missing. + */ + static constexpr uint32_t recommendedControls[] = { + V4L2_CID_CAMERA_ORIENTATION, + }; + + for (uint32_t ctrl : recommendedControls) { + if (!controls.count(ctrl)) { + LOG(CameraSensor, Warning) + << "Recommended V4L2 control " << utils::hex(ctrl) + << " not supported"; + err = -EINVAL; + } + } + + /* + * Verify if sensor supports horizontal/vertical flips + * + * \todo Handle horizontal and vertical flips independently. + */ + const struct v4l2_query_ext_ctrl *hflipInfo = subdev_->controlInfo(V4L2_CID_HFLIP); + const struct v4l2_query_ext_ctrl *vflipInfo = subdev_->controlInfo(V4L2_CID_VFLIP); + if (hflipInfo && !(hflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) && + vflipInfo && !(vflipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY)) { + supportFlips_ = true; + + } + + if (!supportFlips_) + LOG(CameraSensor, Debug) + << "Camera sensor does not support horizontal/vertical flip"; + + /* + * Make sure the required selection targets are supported. + * + * Failures in reading any of the targets are not deemed to be fatal, + * but some properties and features, like constructing a + * IPACameraSensorInfo for the IPA module, won't be supported. + * + * \todo Make support for selection targets mandatory as soon as all + * test platforms have been updated. + */ + Rectangle rect; + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &rect); + if (ret) { + /* + * Default the pixel array size to the largest size supported + * by the sensor. The sizes_ vector is sorted in ascending + * order, the largest size is thus the last element. + */ + pixelArraySize_ = sizes_.back(); + + LOG(CameraSensor, Warning) + << "The PixelArraySize property has been defaulted to " + << pixelArraySize_; + err = -EINVAL; + } else { + pixelArraySize_ = rect.size(); + } + + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &activeArea_); + if (ret) { + activeArea_ = Rectangle(pixelArraySize_); + LOG(CameraSensor, Warning) + << "The PixelArrayActiveAreas property has been defaulted to " + << activeArea_; + err = -EINVAL; + } + + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &rect); + if (ret) { + LOG(CameraSensor, Warning) + << "Failed to retrieve the sensor crop rectangle"; + err = -EINVAL; + } + + if (err) { + LOG(CameraSensor, Error) + << "The sensor kernel driver needs to be fixed"; + LOG(CameraSensor, Error) + << "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information"; + return err; + } + + return 0; +} + +void CameraSensorBasic::initVimcDefaultProperties() +{ + /* Use the largest supported size. */ + pixelArraySize_ = sizes_.back(); + activeArea_ = Rectangle(pixelArraySize_); +} + +void CameraSensorBasic::initStaticProperties() +{ + staticProps_ = CameraSensorProperties::get(model_); + if (!staticProps_) + return; + + /* Register the properties retrieved from the sensor database. */ + properties_.set(properties::UnitCellSize, staticProps_->unitCellSize); + + initTestPatternModes(); +} + +const CameraSensorProperties::SensorDelays &CameraSensorBasic::sensorDelays() +{ + static constexpr CameraSensorProperties::SensorDelays defaultSensorDelays = { + .exposureDelay = 2, + .gainDelay = 1, + .vblankDelay = 2, + .hblankDelay = 2, + }; + + if (!staticProps_ || + (!staticProps_->sensorDelays.exposureDelay && + !staticProps_->sensorDelays.gainDelay && + !staticProps_->sensorDelays.vblankDelay && + !staticProps_->sensorDelays.hblankDelay)) { + LOG(CameraSensor, Warning) + << "No sensor delays found in static properties. " + "Assuming unverified defaults."; + + return defaultSensorDelays; + } + + return staticProps_->sensorDelays; +} + +void CameraSensorBasic::initTestPatternModes() +{ + const auto &v4l2TestPattern = controls().find(V4L2_CID_TEST_PATTERN); + if (v4l2TestPattern == controls().end()) { + LOG(CameraSensor, Debug) << "V4L2_CID_TEST_PATTERN is not supported"; + return; + } + + const auto &testPatternModes = staticProps_->testPatternModes; + if (testPatternModes.empty()) { + /* + * The camera sensor supports test patterns but we don't know + * how to map them so this should be fixed. + */ + LOG(CameraSensor, Debug) << "No static test pattern map for \'" + << model() << "\'"; + return; + } + + /* + * Create a map that associates the V4L2 control index to the test + * pattern mode by reversing the testPatternModes map provided by the + * camera sensor properties. This makes it easier to verify if the + * control index is supported in the below for loop that creates the + * list of supported test patterns. + */ + std::map indexToTestPatternMode; + for (const auto &it : testPatternModes) + indexToTestPatternMode[it.second] = it.first; + + for (const ControlValue &value : v4l2TestPattern->second.values()) { + const int32_t index = value.get(); + + const auto it = indexToTestPatternMode.find(index); + if (it == indexToTestPatternMode.end()) { + LOG(CameraSensor, Debug) + << "Test pattern mode " << index << " ignored"; + continue; + } + + testPatternModes_.push_back(it->second); + } +} + +int CameraSensorBasic::initProperties() +{ + model_ = subdev_->model(); + properties_.set(properties::Model, utils::toAscii(model_)); + + /* Generate a unique ID for the sensor. */ + int ret = generateId(); + if (ret) + return ret; + + /* Initialize the static properties from the sensor database. */ + initStaticProperties(); + + /* Retrieve and register properties from the kernel interface. */ + const ControlInfoMap &controls = subdev_->controls(); + + const auto &orientation = controls.find(V4L2_CID_CAMERA_ORIENTATION); + if (orientation != controls.end()) { + int32_t v4l2Orientation = orientation->second.def().get(); + int32_t propertyValue; + + switch (v4l2Orientation) { + default: + LOG(CameraSensor, Warning) + << "Unsupported camera location " + << v4l2Orientation << ", setting to External"; + [[fallthrough]]; + case V4L2_CAMERA_ORIENTATION_EXTERNAL: + propertyValue = properties::CameraLocationExternal; + break; + case V4L2_CAMERA_ORIENTATION_FRONT: + propertyValue = properties::CameraLocationFront; + break; + case V4L2_CAMERA_ORIENTATION_BACK: + propertyValue = properties::CameraLocationBack; + break; + } + properties_.set(properties::Location, propertyValue); + } else { + LOG(CameraSensor, Warning) << "Failed to retrieve the camera location"; + } + + const auto &rotationControl = controls.find(V4L2_CID_CAMERA_SENSOR_ROTATION); + if (rotationControl != controls.end()) { + int32_t propertyValue = rotationControl->second.def().get(); + + /* + * Cache the Transform associated with the camera mounting + * rotation for later use in computeTransform(). + */ + bool success; + mountingOrientation_ = orientationFromRotation(propertyValue, &success); + if (!success) { + LOG(CameraSensor, Warning) + << "Invalid rotation of " << propertyValue + << " degrees - ignoring"; + mountingOrientation_ = Orientation::Rotate0; + } + + properties_.set(properties::Rotation, propertyValue); + } else { + LOG(CameraSensor, Warning) + << "Rotation control not available, default to 0 degrees"; + properties_.set(properties::Rotation, 0); + mountingOrientation_ = Orientation::Rotate0; + } + + properties_.set(properties::PixelArraySize, pixelArraySize_); + properties_.set(properties::PixelArrayActiveAreas, { activeArea_ }); + + return 0; +} + +int CameraSensorBasic::discoverAncillaryDevices() +{ + int ret; + + for (MediaEntity *ancillary : entity_->ancillaryEntities()) { + switch (ancillary->function()) { + case MEDIA_ENT_F_LENS: + focusLens_ = std::make_unique(ancillary); + ret = focusLens_->init(); + if (ret) { + LOG(CameraSensor, Error) + << "Lens initialisation failed, lens disabled"; + focusLens_.reset(); + } + break; + + default: + LOG(CameraSensor, Warning) + << "Unsupported ancillary entity function " + << ancillary->function(); + break; + } + } + + return 0; +} + +std::vector CameraSensorBasic::sizes(unsigned int mbusCode) const +{ + std::vector sizes; + + const auto &format = formats_.find(mbusCode); + if (format == formats_.end()) + return sizes; + + const std::vector &ranges = format->second; + std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes), + [](const SizeRange &range) { return range.max; }); + + std::sort(sizes.begin(), sizes.end()); + + return sizes; +} + +Size CameraSensorBasic::resolution() const +{ + return std::min(sizes_.back(), activeArea_.size()); +} + +V4L2SubdeviceFormat +CameraSensorBasic::getFormat(Span mbusCodes, + const Size &size, Size maxSize) const +{ + unsigned int desiredArea = size.width * size.height; + unsigned int bestArea = UINT_MAX; + float desiredRatio = static_cast(size.width) / size.height; + float bestRatio = FLT_MAX; + const Size *bestSize = nullptr; + uint32_t bestCode = 0; + + for (unsigned int code : mbusCodes) { + const auto formats = formats_.find(code); + if (formats == formats_.end()) + continue; + + for (const SizeRange &range : formats->second) { + const Size &sz = range.max; + + if (!maxSize.isNull() && + (sz.width > maxSize.width || sz.height > maxSize.height)) + continue; + + if (sz.width < size.width || sz.height < size.height) + continue; + + float ratio = static_cast(sz.width) / sz.height; + float ratioDiff = std::abs(ratio - desiredRatio); + unsigned int area = sz.width * sz.height; + unsigned int areaDiff = area - desiredArea; + + if (ratioDiff > bestRatio) + continue; + + if (ratioDiff < bestRatio || areaDiff < bestArea) { + bestRatio = ratioDiff; + bestArea = areaDiff; + bestSize = &sz; + bestCode = code; + } + } + } + + if (!bestSize) { + LOG(CameraSensor, Debug) << "No supported format or size found"; + return {}; + } + + V4L2SubdeviceFormat format{ + .code = bestCode, + .size = *bestSize, + .colorSpace = ColorSpace::Raw, + }; + + return format; +} + +int CameraSensorBasic::setFormat(V4L2SubdeviceFormat *format, Transform transform) +{ + /* Configure flips if the sensor supports that. */ + if (supportFlips_) { + ControlList flipCtrls(subdev_->controls()); + + flipCtrls.set(V4L2_CID_HFLIP, + static_cast(!!(transform & Transform::HFlip))); + flipCtrls.set(V4L2_CID_VFLIP, + static_cast(!!(transform & Transform::VFlip))); + + int ret = subdev_->setControls(&flipCtrls); + if (ret) + return ret; + } + + /* Apply format on the subdev. */ + int ret = subdev_->setFormat(pad_, format); + if (ret) + return ret; + + subdev_->updateControlInfo(); + return 0; +} + +int CameraSensorBasic::tryFormat(V4L2SubdeviceFormat *format) const +{ + return subdev_->setFormat(pad_, format, + V4L2Subdevice::Whence::TryFormat); +} + +int CameraSensorBasic::applyConfiguration(const SensorConfiguration &config, + Transform transform, + V4L2SubdeviceFormat *sensorFormat) +{ + if (!config.isValid()) { + LOG(CameraSensor, Error) << "Invalid sensor configuration"; + return -EINVAL; + } + + std::vector filteredCodes; + std::copy_if(mbusCodes_.begin(), mbusCodes_.end(), + std::back_inserter(filteredCodes), + [&config](unsigned int mbusCode) { + BayerFormat bayer = BayerFormat::fromMbusCode(mbusCode); + if (bayer.bitDepth == config.bitDepth) + return true; + return false; + }); + if (filteredCodes.empty()) { + LOG(CameraSensor, Error) + << "Cannot find any format with bit depth " + << config.bitDepth; + return -EINVAL; + } + + /* + * Compute the sensor's data frame size by applying the cropping + * rectangle, subsampling and output crop to the sensor's pixel array + * size. + * + * \todo The actual size computation is for now ignored and only the + * output size is considered. This implies that resolutions obtained + * with two different cropping/subsampling will look identical and + * only the first found one will be considered. + */ + V4L2SubdeviceFormat subdevFormat = {}; + for (unsigned int code : filteredCodes) { + for (const Size &size : sizes(code)) { + if (size.width != config.outputSize.width || + size.height != config.outputSize.height) + continue; + + subdevFormat.code = code; + subdevFormat.size = size; + break; + } + } + if (!subdevFormat.code) { + LOG(CameraSensor, Error) << "Invalid output size in sensor configuration"; + return -EINVAL; + } + + int ret = setFormat(&subdevFormat, transform); + if (ret) + return ret; + + /* + * Return to the caller the format actually applied to the sensor. + * This is relevant if transform has changed the bayer pattern order. + */ + if (sensorFormat) + *sensorFormat = subdevFormat; + + /* \todo Handle AnalogCrop. Most sensors do not support set_selection */ + /* \todo Handle scaling in the digital domain. */ + + return 0; +} + +int CameraSensorBasic::sensorInfo(IPACameraSensorInfo *info) const +{ + info->model = model(); + + /* + * The active area size is a static property, while the crop + * rectangle needs to be re-read as it depends on the sensor + * configuration. + */ + info->activeAreaSize = { activeArea_.width, activeArea_.height }; + + /* + * \todo Support for retreiving the crop rectangle is scheduled to + * become mandatory. For the time being use the default value if it has + * been initialized at sensor driver validation time. + */ + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &info->analogCrop); + if (ret) { + info->analogCrop = activeArea_; + LOG(CameraSensor, Warning) + << "The analogue crop rectangle has been defaulted to the active area size"; + } + + /* + * IPACameraSensorInfo::analogCrop::x and IPACameraSensorInfo::analogCrop::y + * are defined relatively to the active pixel area, while V4L2's + * TGT_CROP target is defined in respect to the full pixel array. + * + * Compensate it by subtracting the active area offset. + */ + info->analogCrop.x -= activeArea_.x; + info->analogCrop.y -= activeArea_.y; + + /* The bit depth and image size depend on the currently applied format. */ + V4L2SubdeviceFormat format{}; + ret = subdev_->getFormat(pad_, &format); + if (ret) + return ret; + info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; + info->outputSize = format.size; + + std::optional cfa = properties_.get(properties::draft::ColorFilterArrangement); + info->cfaPattern = cfa ? *cfa : properties::draft::RGB; + + /* + * Retrieve the pixel rate, line length and minimum/maximum frame + * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, + * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. + */ + static constexpr uint32_t cids[] = { + V4L2_CID_PIXEL_RATE, + V4L2_CID_HBLANK, + V4L2_CID_VBLANK, + }; + + ControlList ctrls = subdev_->getControls(cids); + if (ctrls.empty()) { + LOG(CameraSensor, Error) + << "Failed to retrieve camera info controls"; + return -EINVAL; + } + + info->pixelRate = ctrls.get(V4L2_CID_PIXEL_RATE).get(); + + const ControlInfo hblank = ctrls.infoMap()->at(V4L2_CID_HBLANK); + info->minLineLength = info->outputSize.width + hblank.min().get(); + info->maxLineLength = info->outputSize.width + hblank.max().get(); + + const ControlInfo vblank = ctrls.infoMap()->at(V4L2_CID_VBLANK); + info->minFrameLength = info->outputSize.height + vblank.min().get(); + info->maxFrameLength = info->outputSize.height + vblank.max().get(); + + return 0; +} + +Transform CameraSensorBasic::computeTransform(Orientation *orientation) const +{ + /* + * If we cannot do any flips we cannot change the native camera mounting + * orientation. + */ + if (!supportFlips_) { + *orientation = mountingOrientation_; + return Transform::Identity; + } + + /* + * Now compute the required transform to obtain 'orientation' starting + * from the mounting rotation. + * + * As a note: + * orientation / mountingOrientation_ = transform + * mountingOrientation_ * transform = orientation + */ + Transform transform = *orientation / mountingOrientation_; + + /* + * If transform contains any Transpose we cannot do it, so adjust + * 'orientation' to report the image native orientation and return Identity. + */ + if (!!(transform & Transform::Transpose)) { + *orientation = mountingOrientation_; + return Transform::Identity; + } + + return transform; +} + +BayerFormat::Order CameraSensorBasic::bayerOrder([[maybe_unused]] Transform t) const +{ + return BayerFormat::Order::RGGB; +} + +const ControlInfoMap &CameraSensorBasic::controls() const +{ + return subdev_->controls(); +} + +ControlList CameraSensorBasic::getControls(Span ids) +{ + return subdev_->getControls(ids); +} + +int CameraSensorBasic::setControls(ControlList *ctrls) +{ + return subdev_->setControls(ctrls); +} + +int CameraSensorBasic::setTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternMode_ == mode) + return 0; + + if (testPatternModes_.empty()) { + LOG(CameraSensor, Error) + << "Camera sensor does not support test pattern modes."; + return -EINVAL; + } + + return applyTestPatternMode(mode); +} + +int CameraSensorBasic::applyTestPatternMode(controls::draft::TestPatternModeEnum mode) +{ + if (testPatternModes_.empty()) + return 0; + + auto it = std::find(testPatternModes_.begin(), testPatternModes_.end(), + mode); + if (it == testPatternModes_.end()) { + LOG(CameraSensor, Error) << "Unsupported test pattern mode " + << mode; + return -EINVAL; + } + + LOG(CameraSensor, Debug) << "Apply test pattern mode " << mode; + + int32_t index = staticProps_->testPatternModes.at(mode); + ControlList ctrls{ controls() }; + ctrls.set(V4L2_CID_TEST_PATTERN, index); + + int ret = setControls(&ctrls); + if (ret) + return ret; + + testPatternMode_ = mode; + + return 0; +} + +std::string CameraSensorBasic::logPrefix() const +{ + return "'" + entity_->name() + "'"; +} + +REGISTER_CAMERA_SENSOR(CameraSensorBasic, -100) + +} /* namespace libcamera */ diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build index dce74ed6a..9fe627713 100644 --- a/src/libcamera/sensor/meson.build +++ b/src/libcamera/sensor/meson.build @@ -2,6 +2,7 @@ libcamera_internal_sources += files([ 'camera_sensor.cpp', + 'camera_sensor_basic.cpp', 'camera_sensor_legacy.cpp', 'camera_sensor_properties.cpp', 'camera_sensor_raw.cpp', From patchwork Tue Dec 9 18:09:50 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25391 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D0F3CBD1F1 for ; Tue, 9 Dec 2025 18:10:10 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E40BA6142D; Tue, 9 Dec 2025 19:10:08 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="RJpoGrBd"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 52B4E61418 for ; Tue, 9 Dec 2025 19:10:05 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8744163B; Tue, 9 Dec 2025 19:10:04 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303804; bh=v4OEVzqe3atiP1slGBUgV57ioCK4aFm197YFbG2eKHw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RJpoGrBd6QUnfXt0dsoStPlmx4jv3T5hIFNjqFRCgZzZYAMCfLYwfadPijci3X1kt 7duSpKN3bD66vmq5FssPCVugscDXZAB4n13G8X/uLdhXP+eebn4jD5Bp77x7XtJBiZ DSha1bLnGBogbKDnlIQaOG4Ok/KPnlUqb4tw/42M= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott Subject: [RFC PATCH 2/6] ipa: rkisp1: Let all controls be optional Date: Tue, 9 Dec 2025 18:09:50 +0000 Message-ID: <20251209180954.332392-3-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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Some sensors do not have support for controls such as exposure and gain. These are represented by the new camera_sensor_basic class. To allow these sensors to be compatible with the rkisp1 pipeline handler, we must allow the rkisp1 IPA to be able to initialise with these controls not being required. Check the list of controls supported by the sensor before attempting to use them during IPA initialisation. Signed-off-by: Isaac Scott --- src/ipa/rkisp1/rkisp1.cpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 61d3d1f6f..9fee33de2 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -227,15 +227,24 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { + int32_t minExposure = 0; + int32_t minGain = 0; + int32_t maxExposure = 0; + int32_t maxGain = 0; + sensorControls_ = ipaConfig.sensorControls; const auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE); - int32_t minExposure = itExp->second.min().get(); - int32_t maxExposure = itExp->second.max().get(); + if (itExp != sensorControls_.end()) { + minExposure = itExp->second.min().get(); + maxExposure = itExp->second.max().get(); + } const auto itGain = sensorControls_.find(V4L2_CID_ANALOGUE_GAIN); - int32_t minGain = itGain->second.min().get(); - int32_t maxGain = itGain->second.max().get(); + if (itGain != sensorControls_.end()) { + minGain = itGain->second.min().get(); + maxGain = itGain->second.max().get(); + } LOG(IPARkISP1, Debug) << "Exposure: [" << minExposure << ", " << maxExposure @@ -249,8 +258,11 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); + if (sensorControls_.idmap().find(V4L2_CID_VBLANK) != sensorControls_.idmap().end()) { + const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; + context_.configuration.sensor.defVBlank = vBlank.def().get(); + } + context_.configuration.sensor.size = info.outputSize; context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; @@ -392,6 +404,12 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, { ControlInfoMap::Map ctrlMap = rkisp1Controls; + if (sensorControls.idmap().find(V4L2_CID_EXPOSURE) == sensorControls.idmap().end()) { + LOG(IPARkISP1, Debug) << "Sensor does not support V4L2_CID_EXPOSURE," + << " cannot compute exposure time limits"; + return; + } + /* * Compute exposure time limits from the V4L2_CID_EXPOSURE control * limits and the line duration. From patchwork Tue Dec 9 18:09:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25392 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 19EE0BD1F1 for ; Tue, 9 Dec 2025 18:10:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C8D9961429; Tue, 9 Dec 2025 19:10:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vsuGqn4j"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4426F61429 for ; Tue, 9 Dec 2025 19:10:06 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8C224710; Tue, 9 Dec 2025 19:10:05 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303805; bh=UD6fpWZwvBFwoCKR6V5tU+NV5XY0uUzW5yrhZrm2P24=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vsuGqn4jaFVuLG2OalJldOM+wCrmqpPZT31op0BdG/U63CM8aV5hSAVWicR9G0xJ/ exiP1+PSnByUe9jRSREte2wQbgu9diOQNWkeYSltlJYfOY/AlOiZ6Es1135yzl9cye wLoPq9k0WU0AP+heqaEn5w+pPWNwNHlcgkTEn6FE= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott Subject: [RFC PATCH 3/6] pipeline: rkisp1: Add support for YUV passthrough Date: Tue, 9 Dec 2025 18:09:51 +0000 Message-ID: <20251209180954.332392-4-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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" When the rkisp1 is configured to bypass ISP blocks, we will have no params buffers. Modify the rkisp1 pipeline handler to not wait for params buffers to become available before queueing buffers back to the rkisp1. Signed-off-by: Isaac Scott --- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 39 +++++++++++-------- src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 7 ++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 5fd110269..ad0f3af34 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -77,7 +77,7 @@ public: RkISP1Frames(PipelineHandler *pipe); RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request, - bool isRaw); + bool isIspBypassed); int destroy(unsigned int frame); void clear(); @@ -232,7 +232,7 @@ private: std::unique_ptr stat_; bool hasSelfPath_; - bool isRaw_; + bool isIspBypassed_; RkISP1MainPath mainPath_; RkISP1SelfPath selfPath_; @@ -257,7 +257,7 @@ RkISP1Frames::RkISP1Frames(PipelineHandler *pipe) } RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request, - bool isRaw) + bool isIspBypassed) { unsigned int frame = data->frame_; @@ -266,7 +266,7 @@ RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *req FrameBuffer *mainPathBuffer = nullptr; FrameBuffer *selfPathBuffer = nullptr; - if (!isRaw) { + if (!isIspBypassed) { if (pipe_->availableParamBuffers_.empty()) { LOG(RkISP1, Error) << "Parameters buffer underrun"; return nullptr; @@ -970,8 +970,15 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) Rectangle outputCrop = inputCrop; + /* We cannot convert YUV to bayer, so YUV must be passthrough */ + isIspBypassed_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW || + info.colourEncoding == PixelFormatInfo::ColourEncodingYUV; + + LOG(RkISP1, Debug) + << "ISP is " << (isIspBypassed_ ? "in " : "not in ") << "bypass"; + /* YUYV8_2X8 is required on the ISP source path pad for YUV output. */ - if (!isRaw_) + if (!isIspBypassed_) format.code = MEDIA_BUS_FMT_YUYV8_2X8; /* @@ -1047,7 +1054,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) * the isp output to the same size as the sensor output. */ StreamConfiguration ispCfg = cfg; - if (data->usesDewarper_) { + if (data->usesDewarper_ && !isIspBypassed_) { outputCfgs.push_back(const_cast(cfg)); ispCfg.bufferCount = kRkISP1MinBufferCount; @@ -1151,7 +1158,7 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera) mainPathBuffers_.clear(); } }; - if (!isRaw_) { + if (!isIspBypassed_) { ret = param_->allocateBuffers(kRkISP1MinBufferCount, ¶mBuffers_); if (ret < 0) return ret; @@ -1248,7 +1255,7 @@ int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlL data->frame_ = 0; - if (!isRaw_) { + if (!isIspBypassed_) { ret = param_->streamOn(); if (ret) { LOG(RkISP1, Error) @@ -1312,7 +1319,7 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera) selfPath_.stop(); mainPath_.stop(); - if (!isRaw_) { + if (!isIspBypassed_) { ret = stat_->streamOff(); if (ret) LOG(RkISP1, Warning) @@ -1339,12 +1346,12 @@ int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request) { RkISP1CameraData *data = cameraData(camera); - RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_); + RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isIspBypassed_); if (!info) return -ENOENT; data->ipa_->queueRequest(data->frame_, request->controls()); - if (isRaw_) { + if (isIspBypassed_) { if (info->mainPathBuffer) data->mainPath_->queueBuffer(info->mainPathBuffer); @@ -1588,10 +1595,10 @@ void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info) if (request->hasPendingBuffers()) return; - if (!info->metadataProcessed) + if (!isIspBypassed_ && !info->metadataProcessed) return; - if (!isRaw_ && !info->paramDequeued) + if (!isIspBypassed_ && !info->paramDequeued) return; data->frameInfo_.destroy(info->frame); @@ -1646,14 +1653,14 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) request->metadata().set(controls::SensorTimestamp, metadata.timestamp); - if (isRaw_) { + /* We do not have stats buffers in bypass mode */ + if (!isIspBypassed_) { const ControlList &ctrls = data->delayedCtrls_->get(metadata.sequence); data->ipa_->processStats(info->frame, 0, ctrls); } } else { - if (isRaw_) - info->metadataProcessed = true; + info->metadataProcessed = true; } if (!data->usesDewarper_) { diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp index ef9cfbdc3..903e1aaeb 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp @@ -238,11 +238,10 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, if (!rawFormat.isValid()) { LOG(RkISP1, Error) << "Sensor " << sensor->model() - << " doesn't support raw capture"; - return {}; + << " doesn't support raw/bypass capture"; + } else { + format = rawFormat; } - - format = rawFormat; } else { format = formats::NV12; } From patchwork Tue Dec 9 18:09:52 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25393 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id E447EC326B for ; Tue, 9 Dec 2025 18:10:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id DF1EF61435; Tue, 9 Dec 2025 19:10:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="JuhOnRNh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 243C7606D5 for ; Tue, 9 Dec 2025 19:10:07 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6DB7163B; Tue, 9 Dec 2025 19:10:06 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303806; bh=SE28YWuqt35kw8fU9oenowgvuG20yoJVM4usMRiMnrI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JuhOnRNhu1LwzgJB+vVCJDTcrD/h1RwN2/knk2vJRrlzIrHpoLxq4qydZ8Xxs+QUQ bn/gDKfm0gTytJblsxPS7S1xZOHmAwTIspH32Lhf4+q2w6c06T2lmyyDc3W84PDQuH aVcfl4BHoCzge2ii32p2rXSIPv7HJDQAzTk3V0oI= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott Subject: [RFC PATCH 4/6] pipeline: rkisp1: Add YUV bypass formats to list of 'raw' formats Date: Tue, 9 Dec 2025 18:09:52 +0000 Message-ID: <20251209180954.332392-5-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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" When ISP bypass is enabled, we need to be able to match YUV formats as YUV and RAW bypass should be handled the same way by the pipeline handler. Add them. Signed-off-by: Isaac Scott --- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 31 ++++++++++++++++---- src/libcamera/sensor/camera_sensor_basic.cpp | 1 + 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index ad0f3af34..0c8c15ea5 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -520,7 +520,9 @@ void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &meta namespace { /* Keep in sync with the supported raw formats in rkisp1_path.cpp. */ -const std::map rawFormats = { +const std::map bypassFormats = { + { formats::UYVY, MEDIA_BUS_FMT_UYVY8_1X16 }, + { formats::YUYV, MEDIA_BUS_FMT_YUYV8_1X16 }, { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 }, { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 }, { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 }, @@ -755,10 +757,27 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() std::vector mbusCodes; - if (isRaw) { - mbusCodes = { rawFormats.at(config_[0].pixelFormat) }; + if (isRaw) + mbusCodes = { bypassFormats.at(config_[0].pixelFormat) }; + + /* Select the sensor format. */ + PixelFormat bypassFormat; + Size maxSize; + + for (const StreamConfiguration &cfg : config_) { + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); + if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW || + info.colourEncoding == PixelFormatInfo::ColourEncodingYUV) + bypassFormat = cfg.pixelFormat; + + maxSize = std::max(maxSize, cfg.size); + } + + if (bypassFormat.isValid()) { + LOG(RkISP1, Info) << "Using bypass format " << bypassFormat; + mbusCodes = { bypassFormats.at(bypassFormat) }; } else { - std::transform(rawFormats.begin(), rawFormats.end(), + std::transform(bypassFormats.begin(), bypassFormats.end(), std::back_inserter(mbusCodes), [](const auto &value) { return value.second; }); } @@ -919,8 +938,8 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) const PixelFormat &streamFormat = config->at(0).pixelFormat; const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat); - isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW; - data->usesDewarper_ = data->canUseDewarper_ && !isRaw_; + isIspBypassed_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW; + data->usesDewarper_ = data->canUseDewarper_ && !isIspBypassed_; Transform transform = config->combinedTransform(); bool transposeAfterIsp = false; diff --git a/src/libcamera/sensor/camera_sensor_basic.cpp b/src/libcamera/sensor/camera_sensor_basic.cpp index 57213a1ab..9531fc192 100644 --- a/src/libcamera/sensor/camera_sensor_basic.cpp +++ b/src/libcamera/sensor/camera_sensor_basic.cpp @@ -97,6 +97,7 @@ public: int setTestPatternMode(controls::draft::TestPatternModeEnum mode) override; const CameraSensorProperties::SensorDelays &sensorDelays() override; BayerFormat::Order bayerOrder(Transform t) const override; + Orientation mountingOrientation() const override { return mountingOrientation_; } protected: std::string logPrefix() const override; From patchwork Tue Dec 9 18:09:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25394 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 2F1F9BD1F1 for ; Tue, 9 Dec 2025 18:10:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D554C6141D; Tue, 9 Dec 2025 19:10:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mguezhcr"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5503F61410 for ; Tue, 9 Dec 2025 19:10:08 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8DE7F82E; Tue, 9 Dec 2025 19:10:07 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303807; bh=efSWrNgpvzTSPc6PIY8X75DdN7ACWuAtq6GhyIR3nYg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mguezhcrVh9s3GJLJMNHKZQkVM78He9d7tGRQtAVO7/BRQ/zT7xM2uDNoLmQicDYC xVNn/VcQXNIA6fgKpKn2C4pkUGZE/trYFP0iWr1oXlMxRQ0YFkoPW5Llwv53aZdYmf v8p/CUf8yFXpB7ERXHm/DuDt81UltOqGOzdhwaa0= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott Subject: [RFC PATCH 5/6] rkisp1_path: Determine if the ISP is bypassed for YUV and RAW formats Date: Tue, 9 Dec 2025 18:09:53 +0000 Message-ID: <20251209180954.332392-6-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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" So as to not break existing use cases, in the viewfinder StreamRole RAW formats that can be converted to NV12 should be preferred, even if the bits per pixel is lower. Therefore, we should select the RAW format as our stream format to be configured on the pipeline by default. However, the user should be given the option to explicitly select if they wish to use a bypass format if available. This means we have to enumerate all possible output formats as well as their supported sizes. Update the rkisp1's generateConfiguration implementation to facilitate this. Signed-off-by: Isaac Scott --- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 13 +++-- src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 58 ++++++++++++------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 0c8c15ea5..51c57be50 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -761,19 +761,24 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate() mbusCodes = { bypassFormats.at(config_[0].pixelFormat) }; /* Select the sensor format. */ - PixelFormat bypassFormat; + PixelFormat bypassFormat, rawFormat; Size maxSize; for (const StreamConfiguration &cfg : config_) { const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); - if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW || - info.colourEncoding == PixelFormatInfo::ColourEncodingYUV) + if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) + rawFormat = cfg.pixelFormat; + else bypassFormat = cfg.pixelFormat; maxSize = std::max(maxSize, cfg.size); } - if (bypassFormat.isValid()) { + /* We want to prefer to use the RAW format if available */ + if (rawFormat.isValid()) { + LOG(RkISP1, Info) << "Using RAW format " << rawFormat; + mbusCodes = { bypassFormats.at(rawFormat) }; + } else if (bypassFormat.isValid()) { LOG(RkISP1, Info) << "Using bypass format " << bypassFormat; mbusCodes = { bypassFormats.at(bypassFormat) }; } else { diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp index 903e1aaeb..dce0ed1d3 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp @@ -176,7 +176,6 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, /* Min and max resolutions to populate the available stream formats. */ Size maxResolution = maxResolution_.boundedToAspectRatio(resolution) .boundedTo(resolution); - Size minResolution = minResolution_.expandedToAspectRatio(resolution); /* The desired stream size, bound to the max resolution. */ Size streamSize = size.boundedTo(maxResolution); @@ -184,6 +183,7 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, /* Create the list of supported stream formats. */ std::map> streamFormats; unsigned int rawBitsPerPixel = 0; + PixelFormat bypassFormat; PixelFormat rawFormat; for (const auto &format : streamFormats_) { @@ -193,8 +193,14 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW) { if (role == StreamRole::Raw) continue; + for (const auto &imageSize : sensor->sizes(formatToMediaBus.at(format))) { + if (imageSize.width > maxResolution_.width || + imageSize.height > maxResolution_.height) + continue; - streamFormats[format] = { { minResolution, maxResolution } }; + streamFormats[format].push_back({ imageSize, imageSize }); + bypassFormat = format; + } continue; } @@ -243,7 +249,8 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size, format = rawFormat; } } else { - format = formats::NV12; + /* Prefer RAW formats */ + format = rawFormat ? rawFormat : bypassFormat; } StreamFormats formats(streamFormats); @@ -269,18 +276,22 @@ RkISP1Path::validate(const CameraSensor *sensor, * Validate the pixel format. If the requested format isn't supported, * default to either NV12 (all versions of the ISP are guaranteed to * support NV12 on both the main and self paths) if the requested format - * is not a raw format, or to the supported raw format with the highest + * is not a raw or YUV format, or to the supported raw format with the highest * bits per pixel otherwise. */ - unsigned int rawBitsPerPixel = 0; - PixelFormat rawFormat; + unsigned int bypassBitsPerPixel = 0; + PixelFormat bypassFormat; bool found = false; for (const auto &format : streamFormats_) { const PixelFormatInfo &info = PixelFormatInfo::info(format); + /* + * Even if the YUV format is a higher BPP, we still want to prefer + * the raw format, so we can do the processing ourselves. + */ if (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) { - /* Skip raw formats not supported by the sensor. */ + /* Skip formats not supported by the sensor. */ uint32_t mbusCode = formatToMediaBus.at(format); if (std::find(mbusCodes.begin(), mbusCodes.end(), mbusCode) == mbusCodes.end()) @@ -295,9 +306,9 @@ RkISP1Path::validate(const CameraSensor *sensor, if (sensorConfig && info.bitsPerPixel != sensorConfig->bitDepth) continue; - if (info.bitsPerPixel > rawBitsPerPixel) { - rawBitsPerPixel = info.bitsPerPixel; - rawFormat = format; + if (info.bitsPerPixel > bypassBitsPerPixel) { + bypassBitsPerPixel = info.bitsPerPixel; + bypassFormat = format; } } @@ -307,26 +318,28 @@ RkISP1Path::validate(const CameraSensor *sensor, } } - if (sensorConfig && !rawFormat.isValid()) + if (sensorConfig && !bypassFormat.isValid()) return CameraConfiguration::Invalid; - bool isRaw = PixelFormatInfo::info(cfg->pixelFormat).colourEncoding == - PixelFormatInfo::ColourEncodingRAW; + bool isIspBypassed = PixelFormatInfo::info(cfg->pixelFormat).colourEncoding == + PixelFormatInfo::ColourEncodingRAW || + PixelFormatInfo::info(cfg->pixelFormat).colourEncoding == + PixelFormatInfo::ColourEncodingYUV; /* * If no raw format supported by the sensor has been found, use a * processed format. */ - if (!rawFormat.isValid()) - isRaw = false; + if (!bypassFormat.isValid()) + isIspBypassed = false; if (!found) - cfg->pixelFormat = isRaw ? rawFormat : formats::NV12; + cfg->pixelFormat = isIspBypassed ? bypassFormat : formats::NV12; Size minResolution; Size maxResolution; - if (isRaw) { + if (isIspBypassed) { /* * Use the sensor output size closest to the requested stream * size while ensuring the output size doesn't exceed ISP limits. @@ -338,11 +351,11 @@ RkISP1Path::validate(const CameraSensor *sensor, uint32_t mbusCode = formatToMediaBus.at(cfg->pixelFormat); cfg->size.boundTo(resolution); - Size rawSize = sensorConfig ? sensorConfig->outputSize + Size bypassSize = sensorConfig ? sensorConfig->outputSize : cfg->size; V4L2SubdeviceFormat sensorFormat = - sensor->getFormat(std::array{ mbusCode }, rawSize); + sensor->getFormat(std::array{ mbusCode }, bypassSize); if (sensorConfig && sensorConfig->outputSize != sensorFormat.size) @@ -352,7 +365,7 @@ RkISP1Path::validate(const CameraSensor *sensor, maxResolution = sensorFormat.size; } else if (sensorConfig) { /* - * We have already ensured 'rawFormat' has the matching bit + * We have already ensured 'bypassFormat' has the matching bit * depth with sensorConfig.bitDepth hence, only validate the * sensorConfig's output size here. */ @@ -361,7 +374,7 @@ RkISP1Path::validate(const CameraSensor *sensor, if (sensorSize > resolution) return CameraConfiguration::Invalid; - uint32_t mbusCode = formatToMediaBus.at(rawFormat); + uint32_t mbusCode = formatToMediaBus.at(bypassFormat); V4L2SubdeviceFormat sensorFormat = sensor->getFormat(std::array{ mbusCode }, sensorSize); @@ -524,7 +537,8 @@ void RkISP1Path::stop() namespace { constexpr Size RKISP1_RSZ_MP_SRC_MIN{ 32, 16 }; constexpr Size RKISP1_RSZ_MP_SRC_MAX{ 4416, 3312 }; -constexpr std::array RKISP1_RSZ_MP_FORMATS{ +constexpr std::array RKISP1_RSZ_MP_FORMATS{ + formats::UYVY, formats::YUYV, formats::NV16, formats::NV61, From patchwork Tue Dec 9 18:09:54 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Isaac Scott X-Patchwork-Id: 25395 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 943A6BD1F1 for ; Tue, 9 Dec 2025 18:10:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 45DDA61428; Tue, 9 Dec 2025 19:10:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CoaKUhQI"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 34A9F61424 for ; Tue, 9 Dec 2025 19:10:09 +0100 (CET) Received: from isaac-ThinkPad-T16-Gen-2.infra.iob (cpc90716-aztw32-2-0-cust408.18-1.cable.virginm.net [86.26.101.153]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 7A92663B; Tue, 9 Dec 2025 19:10:08 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1765303808; bh=9jyj2BrgKWbksGaynBXrrU6cv5b4GqT2L3evZK9s510=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CoaKUhQIg09QRUX6B33ku11eAxQniPeJq0DpMAL0z9U+pf9J/m+7R3ANI+/ncfi4Y 2zh3nlqRKcUdWbT+nmZB6IVORY62VedFkgBoWFtad6laXZckJbnpRVoznsnrfLjHLT gOGEQAqzggvT19dCYO+ANGk7sjfkokrlh8oW7o9c= From: Isaac Scott To: libcamera-devel@lists.libcamera.org Cc: Isaac Scott Subject: [RFC PATCH 6/6] rkisp1: Add bypass configurations to ipa_context Date: Tue, 9 Dec 2025 18:09:54 +0000 Message-ID: <20251209180954.332392-7-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 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" The rkisp1 supports ISP bypass for RAW formats. There are other formats that are not RAW which can also be used in this mode, which are not currently supported by the rkisp1 pipeline handler. Add a new IPA context member which tracks formats which are not RAW, so we can attempt to use them in bypass mode if they are supported. Signed-off-by: Isaac Scott --- src/ipa/rkisp1/ipa_context.h | 2 ++ src/ipa/rkisp1/rkisp1.cpp | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index b257cee55..28d25af18 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -70,7 +70,9 @@ struct IPASessionConfiguration { Size size; } sensor; + bool bypass; bool raw; + uint32_t paramFormat; }; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 9fee33de2..c195f0287 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -292,6 +292,19 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, return format.colourEncoding == PixelFormatInfo::ColourEncodingRAW; }); + /* + * Many formats are supported by the ISP in bypass mode, and are not RAW. Some support + * both RAW and bypass, but for now we can keep track of those that are not RAW, + * so we can attempt to use bypass for them. + */ + context_.configuration.bypass = std::any_of(streamConfig.begin(), streamConfig.end(), + [](auto &cfg) -> bool { + PixelFormat pixelFormat{ cfg.second.pixelFormat }; + const PixelFormatInfo &format = PixelFormatInfo::info(pixelFormat); + return format.colourEncoding != PixelFormatInfo::ColourEncodingRAW; + }); + + for (auto const &a : algorithms()) { Algorithm *algo = static_cast(a.get()); @@ -374,7 +387,7 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, * provided. */ const rkisp1_stat_buffer *stats = nullptr; - if (!context_.configuration.raw) + if (!context_.configuration.bypass) stats = reinterpret_cast( mappedBuffers_.at(bufferId).planes()[0].data());