From patchwork Wed Apr 1 16:25:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 26409 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 895CDC32F6 for ; Wed, 1 Apr 2026 16:26:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 323A362D73; Wed, 1 Apr 2026 18:26:06 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="aammw6Zc"; 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 9CFA162D5A for ; Wed, 1 Apr 2026 18:26:01 +0200 (CEST) Received: from [100.93.44.16] (net-93-65-100-155.cust.vodafonedsl.it [93.65.100.155]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 621B716AC; Wed, 1 Apr 2026 18:24:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1775060678; bh=jFpT13cBTk2VgxmG46TrkerQDDgq6237mXTx4G/Gn0A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=aammw6ZcoIRhvOq/66t0p/HX0u5dWerv3iMPqEjffNvb/jIaRXwBXhOBWy1gZTC61 sH72UgfC3K+c0oo1mipE1Q1gdjPTcvibW9wvxqTOXxGGf5ievR40yZpQHxBLSnpfbd 3F0s7rLpd8JAR/1+DdWJzgd8+bf055BI/MmQPq9o= From: Jacopo Mondi Date: Wed, 01 Apr 2026 18:25:52 +0200 Subject: [PATCH v8 3/8] libcamera: mali-c55: Split TPG and Inline camera handling MIME-Version: 1.0 Message-Id: <20260401-mali-cru-v8-3-44c48f990e28@ideasonboard.com> References: <20260401-mali-cru-v8-0-44c48f990e28@ideasonboard.com> In-Reply-To: <20260401-mali-cru-v8-0-44c48f990e28@ideasonboard.com> To: Daniel Scally , libcamera-devel@lists.libcamera.org Cc: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Jacopo Mondi X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=16182; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=jFpT13cBTk2VgxmG46TrkerQDDgq6237mXTx4G/Gn0A=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpzUcX6kAzbTnXWNWAjNSTk9ESFs9JuGQXLZpBU ylSdjOpnyKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCac1HFwAKCRByNAaPFqFW PI1KD/4lZC3bl7998gTv2z0hVp0gaWRcqiEzWHsrks9ejbVed2yYmXx47Ot+bjQdaQ7ZeiHgv7a GEV2tSPs/xTVcY56PKmONd3LdjoZ1BQZaRhIdbzpPDFKO0rKvLEYK1IxRIhKRZBMUg1tzkhRIZt dWHcFVXFqQsC0mhpZmnjYlHU7iZo3gEI6/r0v81OsIva47tAwIvJZYR8l3Eb0b1r5bjFHnbIep1 Rztl7oKbqMXJuBCun45B5UM3pWCdjzD6KkzeJHjWXhil13Liny+umjcQ+qWRV2PefDenxrIKJZ1 Wf/hAm74rVmdtQr9/HWZX4xqXuxMzVsmXyiwWJktdXwdmMEaP0dTbkFt7VDNmoNIVnvGhIEzc6S bM2IAg4RZsgYRKN5q2AAJQcq2qTpWLjbVWG+mrGkD0CfbFPek0KYlUdYW+Gh0zUwi/DdJ4IgE5Q WqwR0va6uZrXPhsEnG6r+usNC1oOQVsjfGznrJmxSrvQCglYnTFs/CoaY3W7M85HVx3gmY0mqC1 QoTawdYeSafNxjq04AlqQ/mdtaiV39RpGnNs86tTKP0xEjvewlPsV8XaxSnUuH9OWFn7t8w9Wj0 wVebsxpuPM38NIu4r9i3YCZQKYPepvu9vkEcfrXHak/6xlpKsF6hOungeAx49QL1v4jn/wsVCd4 /CdUY7FhUPr+xBA== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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" In order to prepare to support memory input cameras, split the handling of the TPG and Inline camera cases. The Mali C55 pipeline handler uses the entity and subdevice stored in the CameraData to support both the handling of the TPG and of the Inline (CSI-2 + sensor) use cases. Adding support for memory cameras by using the CRU unit would add yet-another special case making the code harder to follow and more prone to errors. Split the handling of the TPG and Inline cameras by introducing an std::variant<> variable and to deflect the functions called on the camera data to the correct type by using overloaded std::visit<>(). Signed-off-by: Jacopo Mondi Reviewed-by: Barnabás Pőcze --- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 278 ++++++++++++++++----------- 1 file changed, 162 insertions(+), 116 deletions(-) diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index c209b0b070b1..26dfd0aef73c 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -11,12 +11,14 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -92,17 +94,77 @@ struct MaliC55FrameInfo { class MaliC55CameraData : public Camera::Private { public: - MaliC55CameraData(PipelineHandler *pipe, MediaEntity *entity) - : Camera::Private(pipe), entity_(entity) + struct Tpg { + std::vector sizes(unsigned int mbusCode) const; + + Size resolution_; + std::unique_ptr sd_; + }; + + struct Inline { + std::unique_ptr csi2_; + std::unique_ptr sensor_; + }; + using CameraType = std::variant; + + MaliC55CameraData(PipelineHandler *pipe) + : Camera::Private(pipe) { } - int init(); int loadIPA(); - /* Deflect these functionalities to either TPG or CameraSensor. */ - std::vector sizes(unsigned int mbusCode) const; - Size resolution() const; + Tpg *initTpg(MediaEntity *entity); + Inline *initInline(MediaEntity *entity); + + std::vector sizes(unsigned int mbusCode) const + { + return std::visit(utils::overloaded{ + [&](const Tpg &tpg) -> std::vector { + return tpg.sizes(mbusCode); + }, + [&](const Inline &in) -> std::vector { + return in.sensor_->sizes(mbusCode); + } + }, input_); + } + + V4L2Subdevice *subdev() const + { + return std::visit(utils::overloaded{ + [&](const Tpg &tpg) -> V4L2Subdevice * { + return tpg.sd_.get(); + }, + [&](const Inline &in) -> V4L2Subdevice * { + return in.sensor_->device(); + }, + }, input_); + } + + CameraSensor *sensor() const + { + return std::visit(utils::overloaded{ + [&](auto &) -> CameraSensor * { + ASSERT(false); + return nullptr; + }, + [&](const Inline &in) -> CameraSensor * { + return in.sensor_.get(); + }, + }, input_); + } + + Size resolution() const + { + return std::visit(utils::overloaded{ + [&](const Tpg &tpg) -> Size { + return tpg.resolution_; + }, + [&](const Inline &in) -> Size { + return in.sensor_->resolution(); + }, + }, input_); + } int pixfmtToMbusCode(const PixelFormat &pixFmt) const; const PixelFormat &bestRawFormat() const; @@ -112,11 +174,6 @@ public: PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const; Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const; - std::unique_ptr sensor_; - - MediaEntity *entity_; - std::unique_ptr csi_; - std::unique_ptr sd_; Stream frStream_; Stream dsStream_; @@ -126,58 +183,28 @@ public: std::unique_ptr delayedCtrls_; + CameraType input_; + private: - void initTPGData(); void setSensorControls(const ControlList &sensorControls); - std::string id_; - Size tpgResolution_; }; -int MaliC55CameraData::init() +MaliC55CameraData::Tpg *MaliC55CameraData::initTpg(MediaEntity *entity) { - int ret; + Tpg tpg; - sd_ = std::make_unique(entity_); - ret = sd_->open(); + tpg.sd_ = std::make_unique(entity); + int ret = tpg.sd_->open(); if (ret) { - LOG(MaliC55, Error) << "Failed to open sensor subdevice"; - return ret; - } - - /* If this camera is created from TPG, we return here. */ - if (entity_->name() == "mali-c55 tpg") { - initTPGData(); - return 0; - } - - /* - * Register a CameraSensor if we connect to a sensor and create - * an entity for the connected CSI-2 receiver. - */ - sensor_ = CameraSensorFactoryBase::create(entity_); - if (!sensor_) - return -ENODEV; - - const MediaPad *sourcePad = entity_->getPadByIndex(0); - MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity(); - - csi_ = std::make_unique(csiEntity); - ret = csi_->open(); - if (ret) { - LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice"; - return ret; + LOG(MaliC55, Error) << "Failed to open TPG subdevice"; + return nullptr; } - return 0; -} - -void MaliC55CameraData::initTPGData() -{ /* Replicate the CameraSensor implementation for TPG. */ - V4L2Subdevice::Formats formats = sd_->formats(0); + V4L2Subdevice::Formats formats = tpg.sd_->formats(0); if (formats.empty()) - return; + return nullptr; std::vector tpgSizes; @@ -187,19 +214,35 @@ void MaliC55CameraData::initTPGData() [](const SizeRange &range) { return range.max; }); } - tpgResolution_ = tpgSizes.back(); + tpg.resolution_ = tpgSizes.back(); + + return &input_.emplace(std::move(tpg)); } -void MaliC55CameraData::setSensorControls(const ControlList &sensorControls) +MaliC55CameraData::Inline *MaliC55CameraData::initInline(MediaEntity *sensor) { - delayedCtrls_->push(sensorControls); + Inline in; + + /* Register a CameraSensor and create an entity for the CSI-2 receiver. */ + in.sensor_ = CameraSensorFactoryBase::create(sensor); + if (!in.sensor_) + return nullptr; + + const MediaPad *sourcePad = sensor->getPadByIndex(0); + MediaEntity *csiEntity = sourcePad->links()[0]->sink()->entity(); + + in.csi2_ = std::make_unique(csiEntity); + int ret = in.csi2_->open(); + if (ret) { + LOG(MaliC55, Error) << "Failed to open CSI-2 subdevice"; + return nullptr; + } + + return &input_.emplace(std::move(in)); } -std::vector MaliC55CameraData::sizes(unsigned int mbusCode) const +std::vector MaliC55CameraData::Tpg::sizes(unsigned int mbusCode) const { - if (sensor_) - return sensor_->sizes(mbusCode); - V4L2Subdevice::Formats formats = sd_->formats(0); if (formats.empty()) return {}; @@ -218,12 +261,9 @@ std::vector MaliC55CameraData::sizes(unsigned int mbusCode) const return sizes; } -Size MaliC55CameraData::resolution() const +void MaliC55CameraData::setSensorControls(const ControlList &sensorControls) { - if (sensor_) - return sensor_->resolution(); - - return tpgResolution_; + delayedCtrls_->push(sensorControls); } /* @@ -242,7 +282,7 @@ int MaliC55CameraData::pixfmtToMbusCode(const PixelFormat &pixFmt) const if (!bayerFormat.isValid()) return -EINVAL; - V4L2Subdevice::Formats formats = sd_->formats(0); + V4L2Subdevice::Formats formats = subdev()->formats(0); unsigned int sensorMbusCode = 0; unsigned int bitDepth = 0; @@ -280,7 +320,7 @@ const PixelFormat &MaliC55CameraData::bestRawFormat() const { static const PixelFormat invalidPixFmt = {}; - for (const auto &fmt : sd_->formats(0)) { + for (const auto &fmt : subdev()->formats(0)) { BayerFormat sensorBayer = BayerFormat::fromMbusCode(fmt.first); if (!sensorBayer.isValid()) @@ -302,11 +342,11 @@ const PixelFormat &MaliC55CameraData::bestRawFormat() const void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls) { - if (!sensor_) + if (std::holds_alternative(input_)) return; IPACameraSensorInfo sensorInfo; - int ret = sensor_->sensorInfo(&sensorInfo); + int ret = sensor()->sensorInfo(&sensorInfo); if (ret) { LOG(MaliC55, Error) << "Failed to retrieve sensor info"; return; @@ -379,7 +419,7 @@ int MaliC55CameraData::loadIPA() int ret; /* Do not initialize IPA for TPG. */ - if (!sensor_) + if (std::holds_alternative(input_)) return 0; ipa_ = IPAManager::createIPA(pipe(), 1, 1); @@ -388,20 +428,20 @@ int MaliC55CameraData::loadIPA() ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls); - std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml", + std::string ipaTuningFile = ipa_->configurationFile(sensor()->model() + ".yaml", "uncalibrated.yaml"); /* We need to inform the IPA of the sensor configuration */ ipa::mali_c55::IPAConfigInfo ipaConfig{}; - ret = sensor_->sensorInfo(&ipaConfig.sensorInfo); + ret = sensor()->sensorInfo(&ipaConfig.sensorInfo); if (ret) return ret; - ipaConfig.sensorControls = sensor_->controls(); + ipaConfig.sensorControls = sensor()->controls(); ControlInfoMap ipaControls; - ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig, + ret = ipa_->init({ ipaTuningFile, sensor()->model() }, ipaConfig, &ipaControls); if (ret) { LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA"; @@ -444,13 +484,13 @@ CameraConfiguration::Status MaliC55CameraConfiguration::validate() * The TPG doesn't support flips, so we only need to calculate a * transform if we have a sensor. */ - if (data_->sensor_) { + if (std::holds_alternative(data_->input_)) { + combinedTransform_ = Transform::Rot0; + } else { Orientation requestedOrientation = orientation; - combinedTransform_ = data_->sensor_->computeTransform(&orientation); + combinedTransform_ = data_->sensor()->computeTransform(&orientation); if (orientation != requestedOrientation) status = Adjusted; - } else { - combinedTransform_ = Transform::Rot0; } /* Only 2 streams available. */ @@ -927,39 +967,44 @@ int PipelineHandlerMaliC55::configure(Camera *camera, /* Link the graph depending if we are operating the TPG or a sensor. */ MaliC55CameraData *data = cameraData(camera); - if (data->csi_) { - const MediaEntity *csiEntity = data->csi_->entity(); - ret = csiEntity->getPadByIndex(1)->links()[0]->setEnabled(true); - } else { - ret = data->entity_->getPadByIndex(0)->links()[0]->setEnabled(true); - } + ret = std::visit(utils::overloaded{ + [](MaliC55CameraData::Tpg &tpg) { + const MediaEntity *tpgEntity = tpg.sd_->entity(); + return tpgEntity->getPadByIndex(0)->links()[0]->setEnabled(true); + }, + [](MaliC55CameraData::Inline &in) { + const MediaEntity *csi2Entity = in.csi2_->entity(); + return csi2Entity->getPadByIndex(1)->links()[0]->setEnabled(true); + }, + }, data->input_); if (ret) return ret; MaliC55CameraConfiguration *maliConfig = static_cast(config); V4L2SubdeviceFormat subdevFormat = maliConfig->sensorFormat_; - ret = data->sd_->getFormat(0, &subdevFormat); + + /* Apply format to the origin of the pipeline and propagate it. */ + ret = std::visit(utils::overloaded{ + [&](MaliC55CameraData::Tpg &tpg) { + return tpg.sd_->setFormat(0, &subdevFormat); + }, + [&](MaliC55CameraData::Inline &in) { + int r = in.sensor_->setFormat(&subdevFormat, + maliConfig->combinedTransform()); + if (r) + return r; + + r = in.csi2_->setFormat(0, &subdevFormat); + if (r) + return r; + + return in.csi2_->getFormat(1, &subdevFormat); + }, + }, data->input_); if (ret) return ret; - if (data->sensor_) { - ret = data->sensor_->setFormat(&subdevFormat, - maliConfig->combinedTransform()); - if (ret) - return ret; - } - - if (data->csi_) { - ret = data->csi_->setFormat(0, &subdevFormat); - if (ret) - return ret; - - ret = data->csi_->getFormat(1, &subdevFormat); - if (ret) - return ret; - } - V4L2DeviceFormat statsFormat; ret = stats_->getFormat(&statsFormat); if (ret) @@ -973,8 +1018,6 @@ int PipelineHandlerMaliC55::configure(Camera *camera, /* * Propagate the format to the ISP sink pad and configure the input * crop rectangle (no crop at the moment). - * - * \todo Configure the CSI-2 receiver. */ ret = isp_->setFormat(0, &subdevFormat); if (ret) @@ -1058,18 +1101,18 @@ int PipelineHandlerMaliC55::configure(Camera *camera, /* We need to inform the IPA of the sensor configuration */ ipa::mali_c55::IPAConfigInfo ipaConfig{}; - ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo); + ret = data->sensor()->sensorInfo(&ipaConfig.sensorInfo); if (ret) return ret; - ipaConfig.sensorControls = data->sensor_->controls(); + ipaConfig.sensorControls = data->sensor()->controls(); /* * And we also need to tell the IPA the bayerOrder of the data (as * affected by any flips that we've configured) */ const Transform &combinedTransform = maliConfig->combinedTransform(); - BayerFormat::Order bayerOrder = data->sensor_->bayerOrder(combinedTransform); + BayerFormat::Order bayerOrder = data->sensor()->bayerOrder(combinedTransform); ControlInfoMap ipaControls; ret = data->ipa_->configure(ipaConfig, utils::to_underlying(bayerOrder), @@ -1283,7 +1326,7 @@ void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera, if (!scalerCrop) return; - if (!data->sensor_) { + if (std::holds_alternative(data->input_)) { LOG(MaliC55, Error) << "ScalerCrop not supported for TPG"; return; } @@ -1291,7 +1334,7 @@ void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera, Rectangle nativeCrop = *scalerCrop; IPACameraSensorInfo sensorInfo; - int ret = data->sensor_->sensorInfo(&sensorInfo); + int ret = data->sensor()->sensorInfo(&sensorInfo); if (ret) { LOG(MaliC55, Error) << "Failed to retrieve sensor info"; return; @@ -1573,9 +1616,9 @@ bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link) } std::unique_ptr data = - std::make_unique(this, link->source()->entity()); + std::make_unique(this); - if (data->init()) + if (!data->initTpg(link->source()->entity())) return false; return registerMaliCamera(std::move(data), name); @@ -1600,21 +1643,24 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink) continue; std::unique_ptr data = - std::make_unique(this, sensor); - if (data->init()) + std::make_unique(this); + + auto *in = data->initInline(sensor); + if (!in) return false; - data->properties_ = data->sensor_->properties(); + data->properties_ = in->sensor_->properties(); - const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); + const CameraSensorProperties::SensorDelays &delays = + in->sensor_->sensorDelays(); std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, }; - data->delayedCtrls_ = - std::make_unique(data->sensor_->device(), - params); + V4L2Subdevice *sensorSubdev = in->sensor_->device(); + data->delayedCtrls_ = std::make_unique(sensorSubdev, + params); isp_->frameStart.connect(data->delayedCtrls_.get(), &DelayedControls::applyControls);