From patchwork Tue Oct 28 09:31:56 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 24848 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 A4199BE080 for ; Tue, 28 Oct 2025 09:32:31 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 577076079C; Tue, 28 Oct 2025 10:32:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UNDHDc3T"; 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 3CBC96079F for ; Tue, 28 Oct 2025 10:32:13 +0100 (CET) Received: from [192.168.0.172] (mob-5-90-58-13.net.vodafone.it [5.90.58.13]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 813741F0E; Tue, 28 Oct 2025 10:30:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761643824; bh=djORRSqv0qvw0i3v7L45OrPwtKtCDgKQJPDSZ33ZI6E=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=UNDHDc3TUzMkur5avlMDRSWBHIYqaaHedvyGukAfbpZJI35qLltYyJWML2ptfCEN2 eMlDa5zdHZ4QFeMhYYwNqwsWj2ROzMN/RHXWYcIG2uATEpL1ESi1m8YC6/uQDw13X4 nkO3pE3Cv7QDm7ycN0VmOke2WeYOVxnX8fl6+RJw= From: Jacopo Mondi Date: Tue, 28 Oct 2025 10:31:56 +0100 Subject: [PATCH DNI v2 10/10] ipa: mali: Handle FrameDurationLimits MIME-Version: 1.0 Message-Id: <20251028-exposure-limits-v2-10-a8b5a318323e@ideasonboard.com> References: <20251028-exposure-limits-v2-0-a8b5a318323e@ideasonboard.com> In-Reply-To: <20251028-exposure-limits-v2-0-a8b5a318323e@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=8448; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=djORRSqv0qvw0i3v7L45OrPwtKtCDgKQJPDSZ33ZI6E=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpAI2WUU8UnGdjl9pnH/U3cJj8U9CnYTtICrcPO MuyaMnKYCWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaQCNlgAKCRByNAaPFqFW PPTjD/9xc7uC4H41/Rp/njYmXpmqLYsgU8pZ/DMtNA4hBpZYU4ZtDuE6kLkWvUmvKddyP2Axq/1 zg0hLgUQG4kOijilR1ddpmM9+rG0ziKk7RVxP/DBMCJTgPQMMlvrxmg/NOhzHPYp0ILq410yt9w 8mHKEx/wRiRAX5NVAmwB9DWnU3cwm4tSP388iPLdDisRNl8pL2hdNdWOq+NXeXLeJuNQwxzY6GD qOwE/VyIQYtTAuDk+HqZim9/Lie9ATcd0M5iNbwcX0mL/YiY17vs45C+9MPCII+5Fcxq+Z++BUG peXMuPBDYovMr4DnM4TbsuHt4gBJZmx5ZevcD6tMl1cN/Y9WsWzXSpJPrF/QXEfsTwLT0JZCxAd MQiO4Sz+oIyBZZRP3GfbGPO+UuDRroOqKsYE9KENAA+DLHQZnQpfX03T2+IOc0guJZOpLHyrQCM LDkqm87m5a4fGbfJL4Ii4fXydpy5/C15FUT93Cw2HRsX9J3isQM0WIC/OcPDIRIODlXMOF9XRgE l9bmgAzT3JNkGzzcmUJ8qqvdKHClX6nWba90OzhUDyRW6nKigekQYq2kaBawuBy46llojkeMS0X L1YBk4LjIBZOVRWCYWUXoLn9bUcIviTUBVJhgc70qDjPn8BJZtGgiK3QyeomaaNuy9CwvqrxMLT SIrB86VeUbIUSbw== 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 | 63 +++++++++++++++++++++++++++++++------ src/ipa/mali-c55/ipa_context.h | 6 ++++ src/ipa/mali-c55/mali-c55.cpp | 10 ++++-- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 6ecb18d959c7f978966d619beb43e1c2cbfeec5f..60224cc0e261709000901ab9a91c3566cbace6c5 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -170,21 +170,22 @@ int Agc::configure(IPAContext &context, context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; + context.activeState.agc.minFrameDuration = + std::chrono::microseconds(frameDurationLimits.min().get()); + context.activeState.agc.maxFrameDuration = + std::chrono::microseconds(frameDurationLimits.max().get()); AgcMeanLuminance::AgcSensorConfiguration sensorConfig; sensorConfig.lineDuration = context.configuration.sensor.lineDuration; sensorConfig.minExposureTime = context.configuration.agc.minShutterSpeed; sensorConfig.maxExposureTime = context.configuration.agc.maxShutterSpeed; - sensorConfig.minFrameDuration = - std::chrono::microseconds(frameDurationLimits.min().get()); - sensorConfig.maxFrameDuration = - std::chrono::microseconds(frameDurationLimits.max().get()); + sensorConfig.minFrameDuration = context.activeState.agc.minFrameDuration; + sensorConfig.maxFrameDuration = context.activeState.agc.maxFrameDuration; sensorConfig.minAnalogueGain = context.configuration.agc.minAnalogueGain; sensorConfig.maxAnalogueGain = context.configuration.agc.maxAnalogueGain; AgcMeanLuminance::configure(&sensorConfig, context.camHelper.get()); - - /* \todo Update AGC limits when FrameDurationLimits is passed in */ + context.activeState.agc.maxFrameDuration = sensorConfig.maxFrameDuration; resetFrameCount(); @@ -212,6 +213,25 @@ 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 limits in ControlInfo */ + ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; + int64_t minFrameDuration = + std::clamp((*frameDurationLimits).front(), + limits.min().get(), + limits.max().get()); + int64_t maxFrameDuration = + std::clamp((*frameDurationLimits).back(), + limits.min().get(), + limits.max().get()); + + 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... @@ -375,6 +395,21 @@ void Agc::process(IPAContext &context, return; } + /* + * Update the AGC limits using the frame duration. + * + * \todo Handle ExposureTime and AnalogueGain controls to support + * manual mode. + */ + utils::Duration minExposureTime = context.configuration.agc.minShutterSpeed; + utils::Duration maxExposureTime = context.configuration.agc.maxShutterSpeed; + utils::Duration maxFrameDuration = frameContext.agc.maxFrameDuration; + + setLimits(minExposureTime, maxExposureTime, maxFrameDuration, + context.configuration.agc.minAnalogueGain, + context.configuration.agc.maxAnalogueGain, + {}); + statistics_.parseStatistics(stats); context.activeState.agc.temperatureK = estimateCCT({ { statistics_.rHist.interQuantileMean(0, 1), statistics_.gHist.interQuantileMean(0, 1), @@ -402,13 +437,23 @@ void Agc::process(IPAContext &context, dGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain); LOG(MaliC55Agc, Debug) - << "Divided up shutter, analogue gain and digital gain are " - << shutterTime << ", " << aGain << " and " << dGain; + << "Divided up shutter, frame duration, analogue gain and digital gain are " + << shutterTime << ", " << frameDuration << ", " << aGain + << " and " << dGain; + + /* Use the frame duration to calculate the desired vblank. */ + utils::Duration lineDuration = configuration.sensor.lineDuration; + IPACameraSensorInfo &sensorInfo = context.sensorInfo; + + frameContext.agc.vblank = (frameDuration / lineDuration) + - sensorInfo.outputSize.height; - activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; + /* Populate the active state. */ + activeState.agc.automatic.exposure = shutterTime / lineDuration; activeState.agc.automatic.sensorGain = aGain; activeState.agc.automatic.ispGain = dGain; + metadata.set(controls::FrameDuration, frameDuration.get()); metadata.set(controls::ExposureTime, currentShutter.get()); metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); metadata.set(controls::DigitalGain, frameContext.agc.ispGain); diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ecb2f79c0dca0e41166b88f2608c22aa72adcf8a..107e55a2dab325e8d360da1281b212aa052ec724 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -53,6 +53,8 @@ struct IPAActiveState { uint32_t constraintMode; uint32_t exposureMode; uint32_t temperatureK; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; } agc; struct { @@ -66,6 +68,9 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double sensorGain; double ispGain; + uint32_t vblank; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; } agc; struct { @@ -80,6 +85,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 12cad7374520db8c6fa5ca233a0ef33dc7b2f287..7454e4b0d7c8703398cdb716f9a52ea551784371 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); } @@ -368,7 +372,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, algo->process(context_, request, frameContext, stats, metadata); } - setControls(); + setControls(request); statsProcessed.emit(request, metadata); }