Show a patch.

GET /api/patches/24848/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 24848,
    "url": "https://patchwork.libcamera.org/api/patches/24848/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/24848/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20251028-exposure-limits-v2-10-a8b5a318323e@ideasonboard.com>",
    "date": "2025-10-28T09:31:56",
    "name": "[DNI,v2,10/10] ipa: mali: Handle FrameDurationLimits",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "93d71b1dabea24f9427993ab8d884fc8b5f6c2ce",
    "submitter": {
        "id": 143,
        "url": "https://patchwork.libcamera.org/api/people/143/?format=api",
        "name": "Jacopo Mondi",
        "email": "jacopo.mondi@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/24848/mbox/",
    "series": [
        {
            "id": 5536,
            "url": "https://patchwork.libcamera.org/api/series/5536/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5536",
            "date": "2025-10-28T09:31:46",
            "name": "libipa: agc: Calculate exposure limits",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/5536/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/24848/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/24848/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 A4199BE080\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 28 Oct 2025 09:32:31 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 577076079C;\n\tTue, 28 Oct 2025 10:32:31 +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 3CBC96079F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 28 Oct 2025 10:32:13 +0100 (CET)",
            "from [192.168.0.172] (mob-5-90-58-13.net.vodafone.it [5.90.58.13])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 813741F0E; \n\tTue, 28 Oct 2025 10:30: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=\"UNDHDc3T\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1761643824;\n\tbh=djORRSqv0qvw0i3v7L45OrPwtKtCDgKQJPDSZ33ZI6E=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=UNDHDc3TUzMkur5avlMDRSWBHIYqaaHedvyGukAfbpZJI35qLltYyJWML2ptfCEN2\n\teMlDa5zdHZ4QFeMhYYwNqwsWj2ROzMN/RHXWYcIG2uATEpL1ESi1m8YC6/uQDw13X4\n\tnkO3pE3Cv7QDm7ycN0VmOke2WeYOVxnX8fl6+RJw=",
        "From": "Jacopo Mondi <jacopo.mondi@ideasonboard.com>",
        "Date": "Tue, 28 Oct 2025 10:31:56 +0100",
        "Subject": "[PATCH DNI v2 10/10] ipa: mali: Handle FrameDurationLimits",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "Content-Transfer-Encoding": "7bit",
        "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?= <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=8448;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=djORRSqv0qvw0i3v7L45OrPwtKtCDgKQJPDSZ33ZI6E=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpAI2WUU8UnGdjl9pnH/U3cJj8U9CnYTtICrcPO\n\tMuyaMnKYCWJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaQCNlgAKCRByNAaPFqFW\n\tPPTjD/9xc7uC4H41/Rp/njYmXpmqLYsgU8pZ/DMtNA4hBpZYU4ZtDuE6kLkWvUmvKddyP2Axq/1\n\tzg0hLgUQG4kOijilR1ddpmM9+rG0ziKk7RVxP/DBMCJTgPQMMlvrxmg/NOhzHPYp0ILq410yt9w\n\t8mHKEx/wRiRAX5NVAmwB9DWnU3cwm4tSP388iPLdDisRNl8pL2hdNdWOq+NXeXLeJuNQwxzY6GD\n\tqOwE/VyIQYtTAuDk+HqZim9/Lie9ATcd0M5iNbwcX0mL/YiY17vs45C+9MPCII+5Fcxq+Z++BUG\n\tpeXMuPBDYovMr4DnM4TbsuHt4gBJZmx5ZevcD6tMl1cN/Y9WsWzXSpJPrF/QXEfsTwLT0JZCxAd\n\tMQiO4Sz+oIyBZZRP3GfbGPO+UuDRroOqKsYE9KENAA+DLHQZnQpfX03T2+IOc0guJZOpLHyrQCM\n\tLDkqm87m5a4fGbfJL4Ii4fXydpy5/C15FUT93Cw2HRsX9J3isQM0WIC/OcPDIRIODlXMOF9XRgE\n\tl9bmgAzT3JNkGzzcmUJ8qqvdKHClX6nWba90OzhUDyRW6nKigekQYq2kaBawuBy46llojkeMS0X\n\tL1YBk4LjIBZOVRWCYWUXoLn9bUcIviTUBVJhgc70qDjPn8BJZtGgiK3QyeomaaNuy9CwvqrxMLT\n\tSIrB86VeUbIUSbw==",
        "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 | 63 +++++++++++++++++++++++++++++++------\n src/ipa/mali-c55/ipa_context.h      |  6 ++++\n src/ipa/mali-c55/mali-c55.cpp       | 10 ++++--\n 3 files changed, 67 insertions(+), 12 deletions(-)",
    "diff": "diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\nindex 6ecb18d959c7f978966d619beb43e1c2cbfeec5f..60224cc0e261709000901ab9a91c3566cbace6c5 100644\n--- a/src/ipa/mali-c55/algorithms/agc.cpp\n+++ b/src/ipa/mali-c55/algorithms/agc.cpp\n@@ -170,21 +170,22 @@ int Agc::configure(IPAContext &context,\n \tcontext.activeState.agc.exposureMode = exposureModeHelpers().begin()->first;\n \n \tControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits];\n+\tcontext.activeState.agc.minFrameDuration =\n+\t\tstd::chrono::microseconds(frameDurationLimits.min().get<int64_t>());\n+\tcontext.activeState.agc.maxFrameDuration =\n+\t\tstd::chrono::microseconds(frameDurationLimits.max().get<int64_t>());\n \n \tAgcMeanLuminance::AgcSensorConfiguration sensorConfig;\n \tsensorConfig.lineDuration = context.configuration.sensor.lineDuration;\n \tsensorConfig.minExposureTime = context.configuration.agc.minShutterSpeed;\n \tsensorConfig.maxExposureTime = context.configuration.agc.maxShutterSpeed;\n-\tsensorConfig.minFrameDuration =\n-\t\tstd::chrono::microseconds(frameDurationLimits.min().get<int64_t>());\n-\tsensorConfig.maxFrameDuration =\n-\t\tstd::chrono::microseconds(frameDurationLimits.max().get<int64_t>());\n+\tsensorConfig.minFrameDuration = context.activeState.agc.minFrameDuration;\n+\tsensorConfig.maxFrameDuration = context.activeState.agc.maxFrameDuration;\n \tsensorConfig.minAnalogueGain = context.configuration.agc.minAnalogueGain;\n \tsensorConfig.maxAnalogueGain = context.configuration.agc.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 = sensorConfig.maxFrameDuration;\n \n \tresetFrameCount();\n \n@@ -212,6 +213,25 @@ 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 limits in ControlInfo */\n+\t\tControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits];\n+\t\tint64_t minFrameDuration =\n+\t\t\tstd::clamp((*frameDurationLimits).front(),\n+\t\t\t\t   limits.min().get<int64_t>(),\n+\t\t\t\t   limits.max().get<int64_t>());\n+\t\tint64_t maxFrameDuration =\n+\t\t\tstd::clamp((*frameDurationLimits).back(),\n+\t\t\t\t   limits.min().get<int64_t>(),\n+\t\t\t\t   limits.max().get<int64_t>());\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@@ -375,6 +395,21 @@ 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+\tutils::Duration minExposureTime = context.configuration.agc.minShutterSpeed;\n+\tutils::Duration maxExposureTime = context.configuration.agc.maxShutterSpeed;\n+\tutils::Duration maxFrameDuration = frameContext.agc.maxFrameDuration;\n+\n+\tsetLimits(minExposureTime, maxExposureTime, maxFrameDuration,\n+\t\t  context.configuration.agc.minAnalogueGain,\n+\t\t  context.configuration.agc.maxAnalogueGain,\n+\t\t  {});\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@@ -402,13 +437,23 @@ void Agc::process(IPAContext &context,\n \tdGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain);\n \n \tLOG(MaliC55Agc, Debug)\n-\t\t<< \"Divided up shutter, analogue gain and digital gain are \"\n-\t\t<< shutterTime << \", \" << aGain << \" and \" << dGain;\n+\t\t<< \"Divided up shutter, frame duration, analogue gain and digital gain are \"\n+\t\t<< shutterTime << \", \" << frameDuration << \", \" << aGain\n+\t\t<< \" and \" << dGain;\n+\n+\t/* Use the frame duration to calculate the desired vblank. */\n+\tutils::Duration lineDuration = configuration.sensor.lineDuration;\n+\tIPACameraSensorInfo &sensorInfo = context.sensorInfo;\n+\n+\tframeContext.agc.vblank = (frameDuration / lineDuration)\n+\t\t\t\t- sensorInfo.outputSize.height;\n \n-\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\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::FrameDuration, frameDuration.get<std::micro>());\n \tmetadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n \tmetadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n \tmetadata.set(controls::DigitalGain, frameContext.agc.ispGain);\ndiff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h\nindex ecb2f79c0dca0e41166b88f2608c22aa72adcf8a..107e55a2dab325e8d360da1281b212aa052ec724 100644\n--- a/src/ipa/mali-c55/ipa_context.h\n+++ b/src/ipa/mali-c55/ipa_context.h\n@@ -53,6 +53,8 @@ struct IPAActiveState {\n \t\tuint32_t constraintMode;\n \t\tuint32_t exposureMode;\n \t\tuint32_t temperatureK;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n \t} agc;\n \n \tstruct {\n@@ -66,6 +68,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@@ -80,6 +85,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 12cad7374520db8c6fa5ca233a0ef33dc7b2f287..7454e4b0d7c8703398cdb716f9a52ea551784371 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@@ -368,7 +372,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",
        "v2",
        "10/10"
    ]
}