{"id":25071,"url":"https://patchwork.libcamera.org/api/1.1/patches/25071/?format=json","web_url":"https://patchwork.libcamera.org/patch/25071/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20251114-exposure-limits-v3-19-b7c07feba026@ideasonboard.com>","date":"2025-11-14T14:17:14","name":"[DNI,v3,19/19] ipa: mali: Handle FrameDurationLimits","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"c958b61c8bef8a2205b2a0e99ea06efc6fddf73d","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/1.1/people/143/?format=json","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/25071/mbox/","series":[{"id":5590,"url":"https://patchwork.libcamera.org/api/1.1/series/5590/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5590","date":"2025-11-14T14:16:55","name":"libipa: agc: Calculate exposure limits","version":3,"mbox":"https://patchwork.libcamera.org/series/5590/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/25071/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/25071/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id EA5FEC3317\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 14 Nov 2025 14:17:44 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8DB6460B36;\n\tFri, 14 Nov 2025 15:17:44 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3093760AAE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 14 Nov 2025 15:17:25 +0100 (CET)","from [192.168.1.101] (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3575A1440;\n\tFri, 14 Nov 2025 15:15:24 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"geZeZKZi\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1763129724;\n\tbh=/ayLsgiUn+4RgUbAlkJSWkzwC+YENlcz0ZqLsTLxqu4=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=geZeZKZiY+SQQZoALOsLVSnc7mr5ms4NsoV7rlj+NlvwYjUuEFoCRRQ0BLkHGo33L\n\t/PCzOTaVFJo085mTiqlp4deUC3j4ad8x8OilrbM9sIUZgziA7Cv4Nn98LU1189buTm\n\tvWxS2iwMZq/Kt3aWnPBaUfmMiuWQy92WV9FrglxI=","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Date":"Fri, 14 Nov 2025 15:17:14 +0100","Subject":"[PATCH DNI v3 19/19] ipa: mali: Handle FrameDurationLimits","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","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?= <niklas.soderlund@ragnatech.se>,\n\tRobert Mader <robert.mader@collabora.com>, \n\tlibcamera-devel@lists.libcamera.org","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","X-Mailer":"b4 0.14.2","X-Developer-Signature":"v=1; a=openpgp-sha256; l=7237;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=/ayLsgiUn+4RgUbAlkJSWkzwC+YENlcz0ZqLsTLxqu4=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpFznsDwh97j/J5MXWYFD/iURRijUefYb1vT/eg\n\twZcoeHI5XqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaRc57AAKCRByNAaPFqFW\n\tPPhYEACSKOSjRjHC1zpDf2MjDix7YcN1VqwI9DiI248OEcubTmOegrvdtza6j5MkdtsiG9R2RSc\n\tRDFWqwLo2NUdfVVuhj7Tjff0uWo/McgXXXKSyDGaaZSv0BAFGnGsfyeA0b/HXip/jBbPpxAYPps\n\tsBhuZMJOMAqLc8panIiNnPdSeLf0SOknxIJydtWM1wHRpCKMfWSzuE1WFYfUwDTRsqaI79YL6o5\n\tD0ee9nZMe3nlVpWZQ8tmWSuZ4iyhEk7zHN01vWjrlRbIniQKAkWPssMC6/UY2hGPmUEZXVLA0hU\n\tZUhEiU47wyhqRb15Fd6ngFRegWla3XPOb9N8I+SUQUpVoprZErbO2BaVIgbqb7mpXOZulWPMRQ4\n\taTZL/MALSM2MZaeuf/QJlnI4l6AmfgNyq8heJQ9k/AUmUVn48YAty4P7A/jqd2XvDLjRqSEFQ8k\n\t79guYYnIdG7AzjCmTSX1kXQKMKtIK2vYBJ4twQvHR8DmEZWjcYXW7eKxpcjEJUMDc0x/giQtVhd\n\tBuduXeRjQ4ga/xLKyjvEOEUkNjZd7uR02XYCKcckP9Y7MIb6LRrebAames0EpDTBKWpP3BHGwO4\n\tf6pxhAWBmUYwOWkDR0kvSjQjxWRqRmfbLs8MuAlIkCs80rai7tfDyOhT25/PH2EmRQfsz/ygwND\n\tBymxDwmr6JlFo4g==","X-Developer-Key":"i=jacopo.mondi@ideasonboard.com; a=openpgp;\n\tfpr=72392EDC88144A65C701EA9BA5826A2587AD026B","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Handle the FrameDurationLimits control for the Mali C55 platform.\n\nThe frame duration is:\n1) Adjuted at configure() time to what the AGC algorithm computed\n2) Stored at queueRequest() time in the active state and in the\n   per-frame context\n3) Computed at process() time by the AGC calculateNewEV() function\n   and used to compute the desired vblank\n\nThe VBLANK control is now handled by both the IPA and the pipeline\nhandler and correctly programmed on the sensor.\n\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n---\n src/ipa/mali-c55/algorithms/agc.cpp | 50 +++++++++++++++++++++++++++++++++----\n src/ipa/mali-c55/ipa_context.h      |  6 +++++\n src/ipa/mali-c55/mali-c55.cpp       | 10 +++++---\n 3 files changed, 58 insertions(+), 8 deletions(-)","diff":"diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\nindex a0b55694aad292f8a080d8266470797ac0cc2c25..92a58417f9cdf11aa1cd7ec7167bf4818060c85e 100644\n--- a/src/ipa/mali-c55/algorithms/agc.cpp\n+++ b/src/ipa/mali-c55/algorithms/agc.cpp\n@@ -181,9 +181,10 @@ int Agc::configure(IPAContext &context,\n \tsensorConfig.minAnalogueGain = context.configuration.sensor.minAnalogueGain;\n \tsensorConfig.maxAnalogueGain = context.configuration.sensor.maxAnalogueGain;\n \n-\tAgcMeanLuminance::configure(sensorConfig, context.camHelper.get());\n-\n-\t/* \\todo Update AGC limits when FrameDurationLimits is passed in */\n+\tcontext.activeState.agc.maxFrameDuration =\n+\t\tAgcMeanLuminance::configure(sensorConfig, context.camHelper.get());\n+\tcontext.activeState.agc.minFrameDuration =\n+\t\tcontext.configuration.sensor.minFrameDuration;\n \n \treturn 0;\n }\n@@ -209,6 +210,27 @@ void Agc::queueRequest(IPAContext &context, const uint32_t frame,\n \t\t\t<< \" AGC\";\n \t}\n \n+\tconst auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);\n+\tif (frameDurationLimits) {\n+\t\t/* Limit the control value to the sensor constraints. */\n+\t\tint64_t sensorMinFrameDuration =\n+\t\t\tcontext.configuration.sensor.minFrameDuration.get<std::micro>();\n+\t\tint64_t sensorMaxFrameDuration =\n+\t\t\tcontext.configuration.sensor.maxFrameDuration.get<std::micro>();\n+\n+\t\tint64_t minFrameDuration =\n+\t\t\tstd::clamp((*frameDurationLimits).front(),\n+\t\t\t\t   sensorMinFrameDuration, sensorMaxFrameDuration);\n+\t\tint64_t maxFrameDuration =\n+\t\t\tstd::clamp((*frameDurationLimits).back(),\n+\t\t\t\t   sensorMinFrameDuration, sensorMaxFrameDuration);\n+\n+\t\tagc.minFrameDuration = std::chrono::microseconds(minFrameDuration);\n+\t\tagc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration);\n+\t}\n+\tframeContext.agc.minFrameDuration = agc.minFrameDuration;\n+\tframeContext.agc.maxFrameDuration = agc.maxFrameDuration;\n+\n \t/*\n \t * If the automatic exposure and gain is enabled we have no further work\n \t * to do here...\n@@ -372,6 +394,14 @@ void Agc::process(IPAContext &context,\n \t\treturn;\n \t}\n \n+\t/*\n+\t * Update the AGC limits using the frame duration.\n+\t *\n+\t * \\todo Handle ExposureTime and AnalogueGain controls to support\n+\t * manual mode.\n+\t */\n+\tsetExposureLimits({}, {}, frameContext.agc.maxFrameDuration, {});\n+\n \tstatistics_.parseStatistics(stats);\n \tcontext.activeState.agc.temperatureK = estimateCCT({ { statistics_.rHist.interQuantileMean(0, 1),\n \t\t\t\t\t\t\t       statistics_.gHist.interQuantileMean(0, 1),\n@@ -401,11 +431,21 @@ void Agc::process(IPAContext &context,\n \t\t<< \"Divided up shutter, analogue gain and digital gain are \"\n \t\t<< shutterTime << \", \" << aGain << \" and \" << dGain;\n \n-\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\n+\t/* Use the frame duration to calculate the desired vblank. */\n+\tutils::Duration lineDuration = configuration.sensor.lineDuration;\n+\tutils::Duration frameDuration =\n+\t\tcontext.camHelper->minFrameDuration(shutterTime, lineDuration);\n+\n+\tframeContext.agc.vblank = (frameDuration / lineDuration)\n+\t\t\t\t- context.sensorInfo.outputSize.height;\n+\n+\t/* Populate the active state. */\n+\tactiveState.agc.automatic.exposure = shutterTime / lineDuration;\n \tactiveState.agc.automatic.sensorGain = aGain;\n \tactiveState.agc.automatic.ispGain = dGain;\n \n-\tmetadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n+\tmetadata.set(controls::FrameDuration, frameDuration.get<std::micro>());\n+\tmetadata.set(controls::ExposureTime, shutterTime.get<std::micro>());\n \tmetadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n \tmetadata.set(controls::DigitalGain, frameContext.agc.ispGain);\n \tmetadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK);\ndiff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h\nindex 828103f21451d9f7f4998c3faedc8fb6a1e7a2ec..4b76ac25ec4a2e1d2e07642148547303cf4c6031 100644\n--- a/src/ipa/mali-c55/ipa_context.h\n+++ b/src/ipa/mali-c55/ipa_context.h\n@@ -10,6 +10,8 @@\n #include <libcamera/base/utils.h>\n #include <libcamera/controls.h>\n \n+#include <libcamera/ipa/core_ipa_interface.h>\n+\n #include \"libcamera/internal/bayer_format.h\"\n \n #include <libipa/camera_sensor_helper.h>\n@@ -67,6 +69,9 @@ struct IPAFrameContext : public FrameContext {\n \t\tuint32_t exposure;\n \t\tdouble sensorGain;\n \t\tdouble ispGain;\n+\t\tuint32_t vblank;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n \t} agc;\n \n \tstruct {\n@@ -81,6 +86,7 @@ struct IPAContext {\n \t{\n \t}\n \n+\tIPACameraSensorInfo sensorInfo;\n \tIPASessionConfiguration configuration;\n \tIPAActiveState activeState;\n \ndiff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\nindex 02f5dfb76eae073858ec688746b7e12ec072e567..60b5ee8d3060e9f3a4794550fe4140d58125a925 100644\n--- a/src/ipa/mali-c55/mali-c55.cpp\n+++ b/src/ipa/mali-c55/mali-c55.cpp\n@@ -68,7 +68,7 @@ private:\n \tvoid updateControls(const IPACameraSensorInfo &sensorInfo,\n \t\t\t    const ControlInfoMap &sensorControls,\n \t\t\t    ControlInfoMap *ipaControls);\n-\tvoid setControls();\n+\tvoid setControls(unsigned int frame);\n \n \tstd::map<unsigned int, MappedFrameBuffer> buffers_;\n \n@@ -126,14 +126,17 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig\n \tif (ret)\n \t\treturn ret;\n \n+\tcontext_.sensorInfo = ipaConfig.sensorInfo;\n \tupdateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls);\n \n \treturn 0;\n }\n \n-void IPAMaliC55::setControls()\n+void IPAMaliC55::setControls(unsigned int frame)\n {\n+\tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n \tIPAActiveState &activeState = context_.activeState;\n+\tuint32_t vblank = frameContext.agc.vblank;\n \tuint32_t exposure;\n \tuint32_t gain;\n \n@@ -148,6 +151,7 @@ void IPAMaliC55::setControls()\n \tControlList ctrls(sensorControls_);\n \tctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));\n \tctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));\n+\tctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));\n \n \tsetSensorControls.emit(ctrls);\n }\n@@ -375,7 +379,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,\n \t\talgo->process(context_, request, frameContext, stats, metadata);\n \t}\n \n-\tsetControls();\n+\tsetControls(request);\n \n \tstatsProcessed.emit(request, metadata);\n }\n","prefixes":["DNI","v3","19/19"]}