From patchwork Fri Nov 14 14:17:14 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 25071 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 EA5FEC3317 for ; Fri, 14 Nov 2025 14:17:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8DB6460B36; Fri, 14 Nov 2025 15:17:44 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="geZeZKZi"; 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 3093760AAE for ; Fri, 14 Nov 2025 15:17:25 +0100 (CET) Received: from [192.168.1.101] (93-61-96-190.ip145.fastwebnet.it [93.61.96.190]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3575A1440; Fri, 14 Nov 2025 15:15:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1763129724; bh=/ayLsgiUn+4RgUbAlkJSWkzwC+YENlcz0ZqLsTLxqu4=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=geZeZKZiY+SQQZoALOsLVSnc7mr5ms4NsoV7rlj+NlvwYjUuEFoCRRQ0BLkHGo33L /PCzOTaVFJo085mTiqlp4deUC3j4ad8x8OilrbM9sIUZgziA7Cv4Nn98LU1189buTm vWxS2iwMZq/Kt3aWnPBaUfmMiuWQy92WV9FrglxI= From: Jacopo Mondi Date: Fri, 14 Nov 2025 15:17:14 +0100 Subject: [PATCH DNI v3 19/19] ipa: mali: Handle FrameDurationLimits MIME-Version: 1.0 Message-Id: <20251114-exposure-limits-v3-19-b7c07feba026@ideasonboard.com> References: <20251114-exposure-limits-v3-0-b7c07feba026@ideasonboard.com> In-Reply-To: <20251114-exposure-limits-v3-0-b7c07feba026@ideasonboard.com> To: =?utf-8?q?Niklas_S=C3=B6derlund?= , Robert Mader , libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=7237; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=/ayLsgiUn+4RgUbAlkJSWkzwC+YENlcz0ZqLsTLxqu4=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpFznsDwh97j/J5MXWYFD/iURRijUefYb1vT/eg wZcoeHI5XqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaRc57AAKCRByNAaPFqFW PPhYEACSKOSjRjHC1zpDf2MjDix7YcN1VqwI9DiI248OEcubTmOegrvdtza6j5MkdtsiG9R2RSc RDFWqwLo2NUdfVVuhj7Tjff0uWo/McgXXXKSyDGaaZSv0BAFGnGsfyeA0b/HXip/jBbPpxAYPps sBhuZMJOMAqLc8panIiNnPdSeLf0SOknxIJydtWM1wHRpCKMfWSzuE1WFYfUwDTRsqaI79YL6o5 D0ee9nZMe3nlVpWZQ8tmWSuZ4iyhEk7zHN01vWjrlRbIniQKAkWPssMC6/UY2hGPmUEZXVLA0hU ZUhEiU47wyhqRb15Fd6ngFRegWla3XPOb9N8I+SUQUpVoprZErbO2BaVIgbqb7mpXOZulWPMRQ4 aTZL/MALSM2MZaeuf/QJlnI4l6AmfgNyq8heJQ9k/AUmUVn48YAty4P7A/jqd2XvDLjRqSEFQ8k 79guYYnIdG7AzjCmTSX1kXQKMKtIK2vYBJ4twQvHR8DmEZWjcYXW7eKxpcjEJUMDc0x/giQtVhd BuduXeRjQ4ga/xLKyjvEOEUkNjZd7uR02XYCKcckP9Y7MIb6LRrebAames0EpDTBKWpP3BHGwO4 f6pxhAWBmUYwOWkDR0kvSjQjxWRqRmfbLs8MuAlIkCs80rai7tfDyOhT25/PH2EmRQfsz/ygwND BymxDwmr6JlFo4g== 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" Handle the FrameDurationLimits control for the Mali C55 platform. The frame duration is: 1) Adjuted at configure() time to what the AGC algorithm computed 2) Stored at queueRequest() time in the active state and in the per-frame context 3) Computed at process() time by the AGC calculateNewEV() function and used to compute the desired vblank The VBLANK control is now handled by both the IPA and the pipeline handler and correctly programmed on the sensor. Signed-off-by: Jacopo Mondi --- src/ipa/mali-c55/algorithms/agc.cpp | 50 +++++++++++++++++++++++++++++++++---- src/ipa/mali-c55/ipa_context.h | 6 +++++ src/ipa/mali-c55/mali-c55.cpp | 10 +++++--- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index a0b55694aad292f8a080d8266470797ac0cc2c25..92a58417f9cdf11aa1cd7ec7167bf4818060c85e 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -181,9 +181,10 @@ int Agc::configure(IPAContext &context, sensorConfig.minAnalogueGain = context.configuration.sensor.minAnalogueGain; sensorConfig.maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; - AgcMeanLuminance::configure(sensorConfig, context.camHelper.get()); - - /* \todo Update AGC limits when FrameDurationLimits is passed in */ + context.activeState.agc.maxFrameDuration = + AgcMeanLuminance::configure(sensorConfig, context.camHelper.get()); + context.activeState.agc.minFrameDuration = + context.configuration.sensor.minFrameDuration; return 0; } @@ -209,6 +210,27 @@ void Agc::queueRequest(IPAContext &context, const uint32_t frame, << " AGC"; } + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the sensor constraints. */ + int64_t sensorMinFrameDuration = + context.configuration.sensor.minFrameDuration.get(); + int64_t sensorMaxFrameDuration = + context.configuration.sensor.maxFrameDuration.get(); + + int64_t minFrameDuration = + std::clamp((*frameDurationLimits).front(), + sensorMinFrameDuration, sensorMaxFrameDuration); + int64_t maxFrameDuration = + std::clamp((*frameDurationLimits).back(), + sensorMinFrameDuration, sensorMaxFrameDuration); + + agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); + agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); + } + frameContext.agc.minFrameDuration = agc.minFrameDuration; + frameContext.agc.maxFrameDuration = agc.maxFrameDuration; + /* * If the automatic exposure and gain is enabled we have no further work * to do here... @@ -372,6 +394,14 @@ void Agc::process(IPAContext &context, return; } + /* + * Update the AGC limits using the frame duration. + * + * \todo Handle ExposureTime and AnalogueGain controls to support + * manual mode. + */ + setExposureLimits({}, {}, frameContext.agc.maxFrameDuration, {}); + statistics_.parseStatistics(stats); context.activeState.agc.temperatureK = estimateCCT({ { statistics_.rHist.interQuantileMean(0, 1), statistics_.gHist.interQuantileMean(0, 1), @@ -401,11 +431,21 @@ void Agc::process(IPAContext &context, << "Divided up shutter, analogue gain and digital gain are " << shutterTime << ", " << aGain << " and " << dGain; - activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; + /* Use the frame duration to calculate the desired vblank. */ + utils::Duration lineDuration = configuration.sensor.lineDuration; + utils::Duration frameDuration = + context.camHelper->minFrameDuration(shutterTime, lineDuration); + + frameContext.agc.vblank = (frameDuration / lineDuration) + - context.sensorInfo.outputSize.height; + + /* Populate the active state. */ + activeState.agc.automatic.exposure = shutterTime / lineDuration; activeState.agc.automatic.sensorGain = aGain; activeState.agc.automatic.ispGain = dGain; - metadata.set(controls::ExposureTime, currentShutter.get()); + metadata.set(controls::FrameDuration, frameDuration.get()); + metadata.set(controls::ExposureTime, shutterTime.get()); metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); metadata.set(controls::DigitalGain, frameContext.agc.ispGain); metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 828103f21451d9f7f4998c3faedc8fb6a1e7a2ec..4b76ac25ec4a2e1d2e07642148547303cf4c6031 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -10,6 +10,8 @@ #include #include +#include + #include "libcamera/internal/bayer_format.h" #include @@ -67,6 +69,9 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double sensorGain; double ispGain; + uint32_t vblank; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; } agc; struct { @@ -81,6 +86,7 @@ struct IPAContext { { } + IPACameraSensorInfo sensorInfo; IPASessionConfiguration configuration; IPAActiveState activeState; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 02f5dfb76eae073858ec688746b7e12ec072e567..60b5ee8d3060e9f3a4794550fe4140d58125a925 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -68,7 +68,7 @@ private: void updateControls(const IPACameraSensorInfo &sensorInfo, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); - void setControls(); + void setControls(unsigned int frame); std::map buffers_; @@ -126,14 +126,17 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig if (ret) return ret; + context_.sensorInfo = ipaConfig.sensorInfo; updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls); return 0; } -void IPAMaliC55::setControls() +void IPAMaliC55::setControls(unsigned int frame) { + IPAFrameContext &frameContext = context_.frameContexts.get(frame); IPAActiveState &activeState = context_.activeState; + uint32_t vblank = frameContext.agc.vblank; uint32_t exposure; uint32_t gain; @@ -148,6 +151,7 @@ void IPAMaliC55::setControls() ControlList ctrls(sensorControls_); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); + ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); setSensorControls.emit(ctrls); } @@ -375,7 +379,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, algo->process(context_, request, frameContext, stats, metadata); } - setControls(); + setControls(request); statsProcessed.emit(request, metadata); }