[{"id":39835,"web_url":"https://patchwork.libcamera.org/comment/39835/","msgid":"<amNpiQKuqmAKeRNu@zed>","date":"2026-07-24T13:35:02","subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Thu, Jul 23, 2026 at 05:43:03PM +0200, Barnabás Pőcze wrote:\n> Move the extraction and preparation of `V4L2_CID_{EXPOSURE,ANALOGUE_GAIN}`\n> into separate functions. This also implements support for not having a\n> a camera sensor helper.\n\nI only checked the next patch, but these two helpers seems to not be\ndesigned to become part of the AgcAlgorithm class\n\nIf that's the case, and they will stay helpers for the rest of the\nseries, I'm not 100% sure it's worth it.\n\nEach helper saves a few lines, and for such reduced gain, I'm think\nit's more clear for the reader to see what happens instead of having\nto go and check what a function does.\n\nDo these helpers have any other use along the rest of the series ?\n\n>\n> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> ---\n>  src/ipa/ipu3/ipu3.cpp          | 14 +++++-----\n>  src/ipa/libipa/agc.h           | 49 ++++++++++++++++++++++++++++++++++\n>  src/ipa/libipa/meson.build     |  1 +\n>  src/ipa/mali-c55/mali-c55.cpp  | 17 +++++-------\n>  src/ipa/rkisp1/rkisp1.cpp      | 11 +++-----\n>  src/ipa/simple/soft_simple.cpp | 15 +++--------\n>  6 files changed, 72 insertions(+), 35 deletions(-)\n>  create mode 100644 src/ipa/libipa/agc.h\n>\n> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n> index 4bdc4b7677..51379808cd 100644\n> --- a/src/ipa/ipu3/ipu3.cpp\n> +++ b/src/ipa/ipu3/ipu3.cpp\n> @@ -36,6 +36,7 @@\n>  #include \"libcamera/internal/mapped_framebuffer.h\"\n>  #include \"libcamera/internal/yaml_parser.h\"\n>\n> +#include \"libipa/agc.h\"\n>  #include \"libipa/camera_sensor_helper.h\"\n>\n>  #include \"ipa_context.h\"\n> @@ -596,8 +597,8 @@ void IPAIPU3::processStats(const uint32_t frame,\n>\n>  \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>\n> -\tframeContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> -\tframeContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>\n>  \tControlList metadata(controls::controls);\n>\n> @@ -642,12 +643,11 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)\n>   */\n>  void IPAIPU3::setControls(unsigned int frame)\n>  {\n> -\tint32_t exposure = context_.activeState.agc.exposure;\n> -\tint32_t gain = camHelper_->gainCode(context_.activeState.agc.gain);\n> -\n>  \tControlList ctrls(sensorCtrls_);\n> -\tctrls.set(V4L2_CID_EXPOSURE, exposure);\n> -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gain);\n> +\tagc::prepareControls(\n> +\t\tctrls, camHelper_.get(),\n> +\t\tcontext_.activeState.agc.exposure, context_.activeState.agc.gain\n> +\t);\n>\n>  \tControlList lensCtrls(lensCtrls_);\n>  \tlensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,\n> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h\n> new file mode 100644\n> index 0000000000..5247425952\n> --- /dev/null\n> +++ b/src/ipa/libipa/agc.h\n> @@ -0,0 +1,49 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2026 Ideas on Board Oy\n> + *\n> + * AGC-related functionality\n> + */\n> +\n> +#pragma once\n> +\n> +#include <utility>\n> +\n> +#include <linux/v4l2-controls.h>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +#include \"camera_sensor_helper.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa {\n> +\n> +namespace agc {\n> +\n> +[[nodiscard]]\n> +inline std::pair<uint32_t, double>\n> +extractControls(const ControlList &controls, const CameraSensorHelper *sensor)\n> +{\n> +\tauto exposure = controls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> +\tauto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n> +\n> +\treturn {\n> +\t\tuint32_t(exposure),\n> +\t\tsensor ? sensor->gain(gainCode) : gainCode,\n> +\t};\n> +}\n> +\n> +inline void\n> +prepareControls(ControlList &controls, const CameraSensorHelper *sensor,\n> +\t\tint32_t exposure, double gain)\n> +{\n> +\tcontrols.set(V4L2_CID_EXPOSURE, exposure);\n> +\tcontrols.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain));\n> +}\n> +\n> +} /* namespace agc */\n> +\n> +} /* namespace ipa */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> index 963c5ee730..05f1a8749c 100644\n> --- a/src/ipa/libipa/meson.build\n> +++ b/src/ipa/libipa/meson.build\n> @@ -1,6 +1,7 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>\n>  libipa_headers = files([\n> +    'agc.h',\n>      'agc_mean_luminance.h',\n>      'algorithm.h',\n>      'awb_bayes.h',\n> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\n> index 1d3af0627f..65e5297766 100644\n> --- a/src/ipa/mali-c55/mali-c55.cpp\n> +++ b/src/ipa/mali-c55/mali-c55.cpp\n> @@ -27,6 +27,7 @@\n>  #include \"libcamera/internal/yaml_parser.h\"\n>\n>  #include \"algorithms/algorithm.h\"\n> +#include \"libipa/agc.h\"\n>  #include \"libipa/camera_sensor_helper.h\"\n>\n>  #include \"ipa_context.h\"\n> @@ -141,20 +142,18 @@ void IPAMaliC55::setControls()\n>  {\n>  \tIPAActiveState &activeState = context_.activeState;\n>  \tuint32_t exposure;\n> -\tuint32_t gain;\n> +\tdouble gain;\n>\n>  \tif (activeState.agc.autoEnabled) {\n>  \t\texposure = activeState.agc.automatic.exposure;\n> -\t\tgain = camHelper_->gainCode(activeState.agc.automatic.sensorGain);\n> +\t\tgain = activeState.agc.automatic.sensorGain;\n>  \t} else {\n>  \t\texposure = activeState.agc.manual.exposure;\n> -\t\tgain = camHelper_->gainCode(activeState.agc.manual.sensorGain);\n> +\t\tgain = activeState.agc.manual.sensorGain;\n>  \t}\n>\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> -\n> +\tagc::prepareControls(ctrls, camHelper_.get(), exposure, gain);\n>  \tsetSensorControls.emit(ctrls);\n>  }\n>\n> @@ -352,10 +351,8 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,\n>  \tstats = reinterpret_cast<mali_c55_stats_buffer *>(\n>  \t\tbuffers_.at(bufferId).planes()[0].data());\n>\n> -\tframeContext.agc.exposure =\n> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> -\tframeContext.agc.sensorGain =\n> -\t\tcamHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> +\tstd::tie(frameContext.agc.exposure, frameContext.agc.sensorGain)\n> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>\n>  \tControlList metadata(controls::controls);\n>\n> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\n> index 38f55b1d86..acb878d814 100644\n> --- a/src/ipa/rkisp1/rkisp1.cpp\n> +++ b/src/ipa/rkisp1/rkisp1.cpp\n> @@ -31,6 +31,7 @@\n>  #include \"libcamera/internal/yaml_parser.h\"\n>\n>  #include \"algorithms/algorithm.h\"\n> +#include \"libipa/agc.h\"\n>\n>  #include \"ipa_context.h\"\n>  #include \"params.h\"\n> @@ -328,10 +329,8 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId,\n>  \t\tstats = reinterpret_cast<rkisp1_stat_buffer *>(\n>  \t\t\tmappedBuffers_.at(bufferId).planes()[0].data());\n>\n> -\tframeContext.sensor.exposure =\n> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> -\tframeContext.sensor.gain =\n> -\t\tcontext_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> +\t\t= agc::extractControls(sensorControls, context_.camHelper.get());\n>\n>  \tControlList metadata(controls::controls);\n>\n> @@ -365,7 +364,6 @@ void IPARkISP1::setControls(unsigned int frame)\n>\n>  \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>  \tuint32_t exposure = frameContext.agc.exposure;\n> -\tuint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);\n>  \tuint32_t vblank = frameContext.agc.vblank;\n>\n>  \tLOG(IPARkISP1, Debug)\n> @@ -373,8 +371,7 @@ void IPARkISP1::setControls(unsigned int frame)\n>  \t\t<< \", gain \" << frameContext.agc.gain << \", vblank \" << vblank;\n>\n>  \tControlList ctrls(context_.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> +\tagc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain);\n>  \tctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));\n>\n>  \tsetSensorControls.emit(frame, ctrls);\n> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n> index 3932daaa1f..a38fcff7d8 100644\n> --- a/src/ipa/simple/soft_simple.cpp\n> +++ b/src/ipa/simple/soft_simple.cpp\n> @@ -27,6 +27,7 @@\n>  #include \"libcamera/internal/yaml_parser.h\"\n>\n>  #include \"algorithms/adjust.h\"\n> +#include \"libipa/agc.h\"\n>  #include \"libipa/camera_sensor_helper.h\"\n>\n>  #include \"module.h\"\n> @@ -301,10 +302,8 @@ void IPASoftSimple::processStats(const uint32_t frame,\n>  {\n>  \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>\n> -\tframeContext.sensor.exposure =\n> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> -\tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n> -\tframeContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;\n> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>\n>  \tControlList metadata(controls::controls);\n>  \tfor (const auto &algo : algorithms())\n> @@ -312,13 +311,7 @@ void IPASoftSimple::processStats(const uint32_t frame,\n>  \tmetadataReady.emit(frame, metadata);\n>\n>  \tControlList ctrls(sensorInfoMap_);\n> -\n> -\tint32_t againNew = camHelper_\n> -\t\t? camHelper_->gainCode(frameContext.agc.gain)\n> -\t\t: static_cast<int32_t>(frameContext.agc.gain);\n> -\tctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure);\n> -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew);\n> -\n> +\tagc::prepareControls(ctrls, camHelper_.get(), frameContext.agc.exposure, frameContext.agc.gain);\n>  \tsetSensorControls.emit(ctrls);\n>  }\n>\n> --\n> 2.55.0\n>","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 B1B52BDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 13:35:07 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0F0B167F0E;\n\tFri, 24 Jul 2026 15:35:07 +0200 (CEST)","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 0EC3267E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 15:35:06 +0200 (CEST)","from ideasonboard.com (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 41F2273B;\n\tFri, 24 Jul 2026 15:34:04 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"uoofk2M4\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784900044;\n\tbh=kqDsONMFTFNiJrfRjnvxr7eGXuIKuTQK8FOnMh3k5uM=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=uoofk2M4oeA+MPDo+j1FMMwTXhpATUX44Vd2lL7Z/GEw4lMwLbNvc78MztDJEqN8y\n\tZH0nU+/b2GyGUubpcCjRTzCttFdXCYgHuIUpLTgM5VntzdRO3ck8pe0aelkITK2Toj\n\thf/W1MQZsfmekCpLrOofpEEpGdy2xWPmXkKWpUis=","Date":"Fri, 24 Jul 2026 15:35:02 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","Message-ID":"<amNpiQKuqmAKeRNu@zed>","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-21-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20260723154327.1357866-21-barnabas.pocze@ideasonboard.com>","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>"}},{"id":39839,"web_url":"https://patchwork.libcamera.org/comment/39839/","msgid":"<8def2f5b-dc9d-4afa-96f0-c43f4f8591ef@ideasonboard.com>","date":"2026-07-24T13:58:12","subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 07. 24. 15:35 keltezéssel, Jacopo Mondi írta:\n> Hi Barnabás\n> \n> On Thu, Jul 23, 2026 at 05:43:03PM +0200, Barnabás Pőcze wrote:\n>> Move the extraction and preparation of `V4L2_CID_{EXPOSURE,ANALOGUE_GAIN}`\n>> into separate functions. This also implements support for not having a\n>> a camera sensor helper.\n> \n> I only checked the next patch, but these two helpers seems to not be\n> designed to become part of the AgcAlgorithm class\n> \n> If that's the case, and they will stay helpers for the rest of the\n> series, I'm not 100% sure it's worth it.\n> \n> Each helper saves a few lines, and for such reduced gain, I'm think\n> it's more clear for the reader to see what happens instead of having\n> to go and check what a function does.\n> \n> Do these helpers have any other use along the rest of the series ?\n\nThey will stay as they are. I found them to be helpful in reducing the duplication,\nespecially wrt. checking the `CameraSensorHelper` presence, and in providing consistent\ntypes everywhere (you need to get int32_t vs. uint32_t right).\n\n`prepareControls()` could be extended to take the `FrameContext` (thus fewer arguments)\nand set vblank as well. Do you think that would be worth it?\n\n\n> \n>>\n>> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n>> ---\n>>   src/ipa/ipu3/ipu3.cpp          | 14 +++++-----\n>>   src/ipa/libipa/agc.h           | 49 ++++++++++++++++++++++++++++++++++\n>>   src/ipa/libipa/meson.build     |  1 +\n>>   src/ipa/mali-c55/mali-c55.cpp  | 17 +++++-------\n>>   src/ipa/rkisp1/rkisp1.cpp      | 11 +++-----\n>>   src/ipa/simple/soft_simple.cpp | 15 +++--------\n>>   6 files changed, 72 insertions(+), 35 deletions(-)\n>>   create mode 100644 src/ipa/libipa/agc.h\n>>\n>> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n>> index 4bdc4b7677..51379808cd 100644\n>> --- a/src/ipa/ipu3/ipu3.cpp\n>> +++ b/src/ipa/ipu3/ipu3.cpp\n>> @@ -36,6 +36,7 @@\n>>   #include \"libcamera/internal/mapped_framebuffer.h\"\n>>   #include \"libcamera/internal/yaml_parser.h\"\n>>\n>> +#include \"libipa/agc.h\"\n>>   #include \"libipa/camera_sensor_helper.h\"\n>>\n>>   #include \"ipa_context.h\"\n>> @@ -596,8 +597,8 @@ void IPAIPU3::processStats(const uint32_t frame,\n>>\n>>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>>\n>> -\tframeContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n>> -\tframeContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n>> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n>> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>>\n>>   \tControlList metadata(controls::controls);\n>>\n>> @@ -642,12 +643,11 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)\n>>    */\n>>   void IPAIPU3::setControls(unsigned int frame)\n>>   {\n>> -\tint32_t exposure = context_.activeState.agc.exposure;\n>> -\tint32_t gain = camHelper_->gainCode(context_.activeState.agc.gain);\n>> -\n>>   \tControlList ctrls(sensorCtrls_);\n>> -\tctrls.set(V4L2_CID_EXPOSURE, exposure);\n>> -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gain);\n>> +\tagc::prepareControls(\n>> +\t\tctrls, camHelper_.get(),\n>> +\t\tcontext_.activeState.agc.exposure, context_.activeState.agc.gain\n>> +\t);\n>>\n>>   \tControlList lensCtrls(lensCtrls_);\n>>   \tlensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,\n>> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h\n>> new file mode 100644\n>> index 0000000000..5247425952\n>> --- /dev/null\n>> +++ b/src/ipa/libipa/agc.h\n>> @@ -0,0 +1,49 @@\n>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n>> +/*\n>> + * Copyright (C) 2026 Ideas on Board Oy\n>> + *\n>> + * AGC-related functionality\n>> + */\n>> +\n>> +#pragma once\n>> +\n>> +#include <utility>\n>> +\n>> +#include <linux/v4l2-controls.h>\n>> +\n>> +#include <libcamera/controls.h>\n>> +\n>> +#include \"camera_sensor_helper.h\"\n>> +\n>> +namespace libcamera {\n>> +\n>> +namespace ipa {\n>> +\n>> +namespace agc {\n>> +\n>> +[[nodiscard]]\n>> +inline std::pair<uint32_t, double>\n>> +extractControls(const ControlList &controls, const CameraSensorHelper *sensor)\n>> +{\n>> +\tauto exposure = controls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n>> +\tauto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n>> +\n>> +\treturn {\n>> +\t\tuint32_t(exposure),\n>> +\t\tsensor ? sensor->gain(gainCode) : gainCode,\n>> +\t};\n>> +}\n>> +\n>> +inline void\n>> +prepareControls(ControlList &controls, const CameraSensorHelper *sensor,\n>> +\t\tint32_t exposure, double gain)\n>> +{\n>> +\tcontrols.set(V4L2_CID_EXPOSURE, exposure);\n>> +\tcontrols.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain));\n>> +}\n>> +\n>> +} /* namespace agc */\n>> +\n>> +} /* namespace ipa */\n>> +\n>> +} /* namespace libcamera */\n>> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n>> index 963c5ee730..05f1a8749c 100644\n>> --- a/src/ipa/libipa/meson.build\n>> +++ b/src/ipa/libipa/meson.build\n>> @@ -1,6 +1,7 @@\n>>   # SPDX-License-Identifier: CC0-1.0\n>>\n>>   libipa_headers = files([\n>> +    'agc.h',\n>>       'agc_mean_luminance.h',\n>>       'algorithm.h',\n>>       'awb_bayes.h',\n>> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\n>> index 1d3af0627f..65e5297766 100644\n>> --- a/src/ipa/mali-c55/mali-c55.cpp\n>> +++ b/src/ipa/mali-c55/mali-c55.cpp\n>> @@ -27,6 +27,7 @@\n>>   #include \"libcamera/internal/yaml_parser.h\"\n>>\n>>   #include \"algorithms/algorithm.h\"\n>> +#include \"libipa/agc.h\"\n>>   #include \"libipa/camera_sensor_helper.h\"\n>>\n>>   #include \"ipa_context.h\"\n>> @@ -141,20 +142,18 @@ void IPAMaliC55::setControls()\n>>   {\n>>   \tIPAActiveState &activeState = context_.activeState;\n>>   \tuint32_t exposure;\n>> -\tuint32_t gain;\n>> +\tdouble gain;\n>>\n>>   \tif (activeState.agc.autoEnabled) {\n>>   \t\texposure = activeState.agc.automatic.exposure;\n>> -\t\tgain = camHelper_->gainCode(activeState.agc.automatic.sensorGain);\n>> +\t\tgain = activeState.agc.automatic.sensorGain;\n>>   \t} else {\n>>   \t\texposure = activeState.agc.manual.exposure;\n>> -\t\tgain = camHelper_->gainCode(activeState.agc.manual.sensorGain);\n>> +\t\tgain = activeState.agc.manual.sensorGain;\n>>   \t}\n>>\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>> -\n>> +\tagc::prepareControls(ctrls, camHelper_.get(), exposure, gain);\n>>   \tsetSensorControls.emit(ctrls);\n>>   }\n>>\n>> @@ -352,10 +351,8 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,\n>>   \tstats = reinterpret_cast<mali_c55_stats_buffer *>(\n>>   \t\tbuffers_.at(bufferId).planes()[0].data());\n>>\n>> -\tframeContext.agc.exposure =\n>> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n>> -\tframeContext.agc.sensorGain =\n>> -\t\tcamHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n>> +\tstd::tie(frameContext.agc.exposure, frameContext.agc.sensorGain)\n>> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>>\n>>   \tControlList metadata(controls::controls);\n>>\n>> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\n>> index 38f55b1d86..acb878d814 100644\n>> --- a/src/ipa/rkisp1/rkisp1.cpp\n>> +++ b/src/ipa/rkisp1/rkisp1.cpp\n>> @@ -31,6 +31,7 @@\n>>   #include \"libcamera/internal/yaml_parser.h\"\n>>\n>>   #include \"algorithms/algorithm.h\"\n>> +#include \"libipa/agc.h\"\n>>\n>>   #include \"ipa_context.h\"\n>>   #include \"params.h\"\n>> @@ -328,10 +329,8 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId,\n>>   \t\tstats = reinterpret_cast<rkisp1_stat_buffer *>(\n>>   \t\t\tmappedBuffers_.at(bufferId).planes()[0].data());\n>>\n>> -\tframeContext.sensor.exposure =\n>> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n>> -\tframeContext.sensor.gain =\n>> -\t\tcontext_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n>> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n>> +\t\t= agc::extractControls(sensorControls, context_.camHelper.get());\n>>\n>>   \tControlList metadata(controls::controls);\n>>\n>> @@ -365,7 +364,6 @@ void IPARkISP1::setControls(unsigned int frame)\n>>\n>>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>>   \tuint32_t exposure = frameContext.agc.exposure;\n>> -\tuint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);\n>>   \tuint32_t vblank = frameContext.agc.vblank;\n>>\n>>   \tLOG(IPARkISP1, Debug)\n>> @@ -373,8 +371,7 @@ void IPARkISP1::setControls(unsigned int frame)\n>>   \t\t<< \", gain \" << frameContext.agc.gain << \", vblank \" << vblank;\n>>\n>>   \tControlList ctrls(context_.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>> +\tagc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain);\n>>   \tctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));\n>>\n>>   \tsetSensorControls.emit(frame, ctrls);\n>> diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n>> index 3932daaa1f..a38fcff7d8 100644\n>> --- a/src/ipa/simple/soft_simple.cpp\n>> +++ b/src/ipa/simple/soft_simple.cpp\n>> @@ -27,6 +27,7 @@\n>>   #include \"libcamera/internal/yaml_parser.h\"\n>>\n>>   #include \"algorithms/adjust.h\"\n>> +#include \"libipa/agc.h\"\n>>   #include \"libipa/camera_sensor_helper.h\"\n>>\n>>   #include \"module.h\"\n>> @@ -301,10 +302,8 @@ void IPASoftSimple::processStats(const uint32_t frame,\n>>   {\n>>   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n>>\n>> -\tframeContext.sensor.exposure =\n>> -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n>> -\tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n>> -\tframeContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;\n>> +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n>> +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n>>\n>>   \tControlList metadata(controls::controls);\n>>   \tfor (const auto &algo : algorithms())\n>> @@ -312,13 +311,7 @@ void IPASoftSimple::processStats(const uint32_t frame,\n>>   \tmetadataReady.emit(frame, metadata);\n>>\n>>   \tControlList ctrls(sensorInfoMap_);\n>> -\n>> -\tint32_t againNew = camHelper_\n>> -\t\t? camHelper_->gainCode(frameContext.agc.gain)\n>> -\t\t: static_cast<int32_t>(frameContext.agc.gain);\n>> -\tctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure);\n>> -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew);\n>> -\n>> +\tagc::prepareControls(ctrls, camHelper_.get(), frameContext.agc.exposure, frameContext.agc.gain);\n>>   \tsetSensorControls.emit(ctrls);\n>>   }\n>>\n>> --\n>> 2.55.0\n>>","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 2F757BDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 13:58:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6ECA967F1C;\n\tFri, 24 Jul 2026 15:58:18 +0200 (CEST)","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 809B467E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 15:58:16 +0200 (CEST)","from [192.168.33.42] (185.182.215.156.nat.pool.zt.hu\n\t[185.182.215.156])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id CB36319C;\n\tFri, 24 Jul 2026 15:57:14 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"cSkEBit5\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784901434;\n\tbh=pTAKIcGOwJv2l1WLZHqm2HakIDNC1Gg4F2PNorrl/KY=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=cSkEBit5BNvIXiYup9pLk2/473b4rYJTYAa5DC4mxUMxInr3HzshXwgC0d7UZubeA\n\t8LiW0F/E1xidYzdddA3jcoJN5ZcBo9OMo8XsTr4RBixp2z37GtT+zg9qoImxn62Ejp\n\t7bS3qffsJp+bEeS+tpwFIGOVJ6sa9VFCU0wWKAuE=","Message-ID":"<8def2f5b-dc9d-4afa-96f0-c43f4f8591ef@ideasonboard.com>","Date":"Fri, 24 Jul 2026 15:58:12 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-21-barnabas.pocze@ideasonboard.com>\n\t<amNpiQKuqmAKeRNu@zed>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<amNpiQKuqmAKeRNu@zed>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","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>"}},{"id":39843,"web_url":"https://patchwork.libcamera.org/comment/39843/","msgid":"<amOA3nPrvg7v_FiC@zed>","date":"2026-07-24T15:14:58","subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Fri, Jul 24, 2026 at 03:58:12PM +0200, Barnabás Pőcze wrote:\n> 2026. 07. 24. 15:35 keltezéssel, Jacopo Mondi írta:\n> > Hi Barnabás\n> >\n> > On Thu, Jul 23, 2026 at 05:43:03PM +0200, Barnabás Pőcze wrote:\n> > > Move the extraction and preparation of `V4L2_CID_{EXPOSURE,ANALOGUE_GAIN}`\n> > > into separate functions. This also implements support for not having a\n> > > a camera sensor helper.\n> >\n> > I only checked the next patch, but these two helpers seems to not be\n> > designed to become part of the AgcAlgorithm class\n> >\n> > If that's the case, and they will stay helpers for the rest of the\n> > series, I'm not 100% sure it's worth it.\n> >\n> > Each helper saves a few lines, and for such reduced gain, I'm think\n> > it's more clear for the reader to see what happens instead of having\n> > to go and check what a function does.\n> >\n> > Do these helpers have any other use along the rest of the series ?\n>\n> They will stay as they are. I found them to be helpful in reducing the duplication,\n> especially wrt. checking the `CameraSensorHelper` presence, and in providing consistent\n> types everywhere (you need to get int32_t vs. uint32_t right).\n\nI don't know, I might be biased, but I don't like little helpers that\nrequire the reader to navigate to a different file to save 2 lines.\n\nLooking at this diff\n\n -\tint32_t exposure = context_.activeState.agc.exposure;\n -\tint32_t gain = camHelper_->gainCode(context_.activeState.agc.gain);\n -\tctrls.set(V4L2_CID_EXPOSURE, exposure);\n -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gain);\n +\tagc::prepareControls(\n +\t\tctrls, camHelper_.get(),\n +\t\tcontext_.activeState.agc.exposure, context_.activeState.agc.gain\n +\t);\n\nI'm not even sure it's much less to type in\n\n>\n> `prepareControls()` could be extended to take the `FrameContext` (thus fewer arguments)\n> and set vblank as well. Do you think that would be worth it?\n\nMaybe yes..\n\n>\n>\n> >\n> > >\n> > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n> > > ---\n> > >   src/ipa/ipu3/ipu3.cpp          | 14 +++++-----\n> > >   src/ipa/libipa/agc.h           | 49 ++++++++++++++++++++++++++++++++++\n> > >   src/ipa/libipa/meson.build     |  1 +\n> > >   src/ipa/mali-c55/mali-c55.cpp  | 17 +++++-------\n> > >   src/ipa/rkisp1/rkisp1.cpp      | 11 +++-----\n> > >   src/ipa/simple/soft_simple.cpp | 15 +++--------\n> > >   6 files changed, 72 insertions(+), 35 deletions(-)\n> > >   create mode 100644 src/ipa/libipa/agc.h\n> > >\n> > > diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp\n> > > index 4bdc4b7677..51379808cd 100644\n> > > --- a/src/ipa/ipu3/ipu3.cpp\n> > > +++ b/src/ipa/ipu3/ipu3.cpp\n> > > @@ -36,6 +36,7 @@\n> > >   #include \"libcamera/internal/mapped_framebuffer.h\"\n> > >   #include \"libcamera/internal/yaml_parser.h\"\n> > >\n> > > +#include \"libipa/agc.h\"\n> > >   #include \"libipa/camera_sensor_helper.h\"\n> > >\n> > >   #include \"ipa_context.h\"\n> > > @@ -596,8 +597,8 @@ void IPAIPU3::processStats(const uint32_t frame,\n> > >\n> > >   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n> > >\n> > > -\tframeContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> > > -\tframeContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> > > +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> > > +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n> > >\n> > >   \tControlList metadata(controls::controls);\n> > >\n> > > @@ -642,12 +643,11 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)\n> > >    */\n> > >   void IPAIPU3::setControls(unsigned int frame)\n> > >   {\n> > > -\tint32_t exposure = context_.activeState.agc.exposure;\n> > > -\tint32_t gain = camHelper_->gainCode(context_.activeState.agc.gain);\n> > > -\n> > >   \tControlList ctrls(sensorCtrls_);\n> > > -\tctrls.set(V4L2_CID_EXPOSURE, exposure);\n> > > -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, gain);\n> > > +\tagc::prepareControls(\n> > > +\t\tctrls, camHelper_.get(),\n> > > +\t\tcontext_.activeState.agc.exposure, context_.activeState.agc.gain\n> > > +\t);\n> > >\n> > >   \tControlList lensCtrls(lensCtrls_);\n> > >   \tlensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,\n> > > diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h\n> > > new file mode 100644\n> > > index 0000000000..5247425952\n> > > --- /dev/null\n> > > +++ b/src/ipa/libipa/agc.h\n> > > @@ -0,0 +1,49 @@\n> > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > > +/*\n> > > + * Copyright (C) 2026 Ideas on Board Oy\n> > > + *\n> > > + * AGC-related functionality\n> > > + */\n> > > +\n> > > +#pragma once\n> > > +\n> > > +#include <utility>\n> > > +\n> > > +#include <linux/v4l2-controls.h>\n> > > +\n> > > +#include <libcamera/controls.h>\n> > > +\n> > > +#include \"camera_sensor_helper.h\"\n> > > +\n> > > +namespace libcamera {\n> > > +\n> > > +namespace ipa {\n> > > +\n> > > +namespace agc {\n> > > +\n> > > +[[nodiscard]]\n> > > +inline std::pair<uint32_t, double>\n> > > +extractControls(const ControlList &controls, const CameraSensorHelper *sensor)\n> > > +{\n> > > +\tauto exposure = controls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> > > +\tauto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n> > > +\n> > > +\treturn {\n> > > +\t\tuint32_t(exposure),\n> > > +\t\tsensor ? sensor->gain(gainCode) : gainCode,\n> > > +\t};\n> > > +}\n> > > +\n> > > +inline void\n> > > +prepareControls(ControlList &controls, const CameraSensorHelper *sensor,\n> > > +\t\tint32_t exposure, double gain)\n> > > +{\n> > > +\tcontrols.set(V4L2_CID_EXPOSURE, exposure);\n> > > +\tcontrols.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain));\n> > > +}\n> > > +\n> > > +} /* namespace agc */\n> > > +\n> > > +} /* namespace ipa */\n> > > +\n> > > +} /* namespace libcamera */\n> > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\n> > > index 963c5ee730..05f1a8749c 100644\n> > > --- a/src/ipa/libipa/meson.build\n> > > +++ b/src/ipa/libipa/meson.build\n> > > @@ -1,6 +1,7 @@\n> > >   # SPDX-License-Identifier: CC0-1.0\n> > >\n> > >   libipa_headers = files([\n> > > +    'agc.h',\n> > >       'agc_mean_luminance.h',\n> > >       'algorithm.h',\n> > >       'awb_bayes.h',\n> > > diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\n> > > index 1d3af0627f..65e5297766 100644\n> > > --- a/src/ipa/mali-c55/mali-c55.cpp\n> > > +++ b/src/ipa/mali-c55/mali-c55.cpp\n> > > @@ -27,6 +27,7 @@\n> > >   #include \"libcamera/internal/yaml_parser.h\"\n> > >\n> > >   #include \"algorithms/algorithm.h\"\n> > > +#include \"libipa/agc.h\"\n> > >   #include \"libipa/camera_sensor_helper.h\"\n> > >\n> > >   #include \"ipa_context.h\"\n> > > @@ -141,20 +142,18 @@ void IPAMaliC55::setControls()\n> > >   {\n> > >   \tIPAActiveState &activeState = context_.activeState;\n> > >   \tuint32_t exposure;\n> > > -\tuint32_t gain;\n> > > +\tdouble gain;\n> > >\n> > >   \tif (activeState.agc.autoEnabled) {\n> > >   \t\texposure = activeState.agc.automatic.exposure;\n> > > -\t\tgain = camHelper_->gainCode(activeState.agc.automatic.sensorGain);\n> > > +\t\tgain = activeState.agc.automatic.sensorGain;\n> > >   \t} else {\n> > >   \t\texposure = activeState.agc.manual.exposure;\n> > > -\t\tgain = camHelper_->gainCode(activeState.agc.manual.sensorGain);\n> > > +\t\tgain = activeState.agc.manual.sensorGain;\n> > >   \t}\n> > >\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> > > -\n> > > +\tagc::prepareControls(ctrls, camHelper_.get(), exposure, gain);\n> > >   \tsetSensorControls.emit(ctrls);\n> > >   }\n> > >\n> > > @@ -352,10 +351,8 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,\n> > >   \tstats = reinterpret_cast<mali_c55_stats_buffer *>(\n> > >   \t\tbuffers_.at(bufferId).planes()[0].data());\n> > >\n> > > -\tframeContext.agc.exposure =\n> > > -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> > > -\tframeContext.agc.sensorGain =\n> > > -\t\tcamHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> > > +\tstd::tie(frameContext.agc.exposure, frameContext.agc.sensorGain)\n> > > +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n> > >\n> > >   \tControlList metadata(controls::controls);\n> > >\n> > > diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\n> > > index 38f55b1d86..acb878d814 100644\n> > > --- a/src/ipa/rkisp1/rkisp1.cpp\n> > > +++ b/src/ipa/rkisp1/rkisp1.cpp\n> > > @@ -31,6 +31,7 @@\n> > >   #include \"libcamera/internal/yaml_parser.h\"\n> > >\n> > >   #include \"algorithms/algorithm.h\"\n> > > +#include \"libipa/agc.h\"\n> > >\n> > >   #include \"ipa_context.h\"\n> > >   #include \"params.h\"\n> > > @@ -328,10 +329,8 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId,\n> > >   \t\tstats = reinterpret_cast<rkisp1_stat_buffer *>(\n> > >   \t\t\tmappedBuffers_.at(bufferId).planes()[0].data());\n> > >\n> > > -\tframeContext.sensor.exposure =\n> > > -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> > > -\tframeContext.sensor.gain =\n> > > -\t\tcontext_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n> > > +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> > > +\t\t= agc::extractControls(sensorControls, context_.camHelper.get());\n> > >\n> > >   \tControlList metadata(controls::controls);\n> > >\n> > > @@ -365,7 +364,6 @@ void IPARkISP1::setControls(unsigned int frame)\n> > >\n> > >   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n> > >   \tuint32_t exposure = frameContext.agc.exposure;\n> > > -\tuint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);\n> > >   \tuint32_t vblank = frameContext.agc.vblank;\n> > >\n> > >   \tLOG(IPARkISP1, Debug)\n> > > @@ -373,8 +371,7 @@ void IPARkISP1::setControls(unsigned int frame)\n> > >   \t\t<< \", gain \" << frameContext.agc.gain << \", vblank \" << vblank;\n> > >\n> > >   \tControlList ctrls(context_.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> > > +\tagc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain);\n> > >   \tctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));\n> > >\n> > >   \tsetSensorControls.emit(frame, ctrls);\n> > > diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\n> > > index 3932daaa1f..a38fcff7d8 100644\n> > > --- a/src/ipa/simple/soft_simple.cpp\n> > > +++ b/src/ipa/simple/soft_simple.cpp\n> > > @@ -27,6 +27,7 @@\n> > >   #include \"libcamera/internal/yaml_parser.h\"\n> > >\n> > >   #include \"algorithms/adjust.h\"\n> > > +#include \"libipa/agc.h\"\n> > >   #include \"libipa/camera_sensor_helper.h\"\n> > >\n> > >   #include \"module.h\"\n> > > @@ -301,10 +302,8 @@ void IPASoftSimple::processStats(const uint32_t frame,\n> > >   {\n> > >   \tIPAFrameContext &frameContext = context_.frameContexts.get(frame);\n> > >\n> > > -\tframeContext.sensor.exposure =\n> > > -\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n> > > -\tint32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();\n> > > -\tframeContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;\n> > > +\tstd::tie(frameContext.sensor.exposure, frameContext.sensor.gain)\n> > > +\t\t= agc::extractControls(sensorControls, camHelper_.get());\n> > >\n> > >   \tControlList metadata(controls::controls);\n> > >   \tfor (const auto &algo : algorithms())\n> > > @@ -312,13 +311,7 @@ void IPASoftSimple::processStats(const uint32_t frame,\n> > >   \tmetadataReady.emit(frame, metadata);\n> > >\n> > >   \tControlList ctrls(sensorInfoMap_);\n> > > -\n> > > -\tint32_t againNew = camHelper_\n> > > -\t\t? camHelper_->gainCode(frameContext.agc.gain)\n> > > -\t\t: static_cast<int32_t>(frameContext.agc.gain);\n> > > -\tctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure);\n> > > -\tctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew);\n> > > -\n> > > +\tagc::prepareControls(ctrls, camHelper_.get(), frameContext.agc.exposure, frameContext.agc.gain);\n> > >   \tsetSensorControls.emit(ctrls);\n> > >   }\n> > >\n> > > --\n> > > 2.55.0\n> > >\n>","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 4ED4FBDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Jul 2026 15:15:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3FAE967F28;\n\tFri, 24 Jul 2026 17:15:03 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D3D3367E89\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Jul 2026 17:15:01 +0200 (CEST)","from ideasonboard.com (93-46-82-201.ip106.fastwebnet.it\n\t[93.46.82.201])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0CEE9524;\n\tFri, 24 Jul 2026 17:14:00 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"VM0aNwXu\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784906040;\n\tbh=mRU9PMDtBqfpAdPtByCHwa4/5bSFBBuZrPeV8Cy7xEo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=VM0aNwXugkhZ6biBI+ayVEueDHJDAuagsGUCojzyW2A6j1Bp2v8BhLz6N0WoMMw/K\n\tOxl0UDk3KWeUgByJyF588uoddnZwOo34dX0wOMTXkNwPXGOwBr/sp1ooQIGX4w2luF\n\tTjM2+mfsalTGziIN1Z71wj4S/qx50PhOIQJfQFXY=","Date":"Fri, 24 Jul 2026 17:14:58 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [RFC PATCH v2 20/43] ipa: Simplify sensor exposure/gain\n\tsetting/getting","Message-ID":"<amOA3nPrvg7v_FiC@zed>","References":"<20260723154327.1357866-1-barnabas.pocze@ideasonboard.com>\n\t<20260723154327.1357866-21-barnabas.pocze@ideasonboard.com>\n\t<amNpiQKuqmAKeRNu@zed>\n\t<8def2f5b-dc9d-4afa-96f0-c43f4f8591ef@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<8def2f5b-dc9d-4afa-96f0-c43f4f8591ef@ideasonboard.com>","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>"}}]