{"id":21854,"url":"https://patchwork.libcamera.org/api/1.1/patches/21854/?format=json","web_url":"https://patchwork.libcamera.org/patch/21854/","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":"<20241107114819.57599-8-dan.scally@ideasonboard.com>","date":"2024-11-07T11:48:15","name":"[v3,07/11] ipa: mali-c55: Add Agc algorithm","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"8b5b34ee256856109976078b24ac49f9e4dd8651","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/1.1/people/156/?format=json","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/21854/mbox/","series":[{"id":4777,"url":"https://patchwork.libcamera.org/api/1.1/series/4777/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4777","date":"2024-11-07T11:48:08","name":"Add Mali-C55 IPA Module and Algorithms","version":3,"mbox":"https://patchwork.libcamera.org/series/4777/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/21854/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/21854/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 CFF39C32A9\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  7 Nov 2024 11:48:41 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F230B65497;\n\tThu,  7 Nov 2024 12:48:37 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 69ECD65467\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  7 Nov 2024 12:48:29 +0100 (CET)","from mail.ideasonboard.com\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 62774F88;\n\tThu,  7 Nov 2024 12:48:20 +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=\"HK3iLesM\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1730980100;\n\tbh=bPLxT0rE0qIersk0BxsdsjQbDeCq23R22GTfkqig/aw=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=HK3iLesMnJH3ubnHTeJ/Mlf8KX10pnSCei4H8uFJ1/VlrkmBGY/IYnsPyt1q89OSa\n\tj8vqVVXAz1Gz5dlGViILJSiKOZUv/oiyoEeCQFLTwDyhuVcgsXwLeCA5jFDa7jnBpH\n\tUJV8KfmodZBheFbQ0PLk3swhmnBGwXdWLqT18Fu0=","From":"Daniel Scally <dan.scally@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Anthony.McGivern@arm.com, Daniel Scally <dan.scally@ideasonboard.com>,\n\tNayden Kanchev <nayden.kanchev@arm.com>,\n\tJacopo Mondi <jacopo.mondi@ideasonboard.com>","Subject":"[PATCH v3 07/11] ipa: mali-c55: Add Agc algorithm","Date":"Thu,  7 Nov 2024 11:48:15 +0000","Message-Id":"<20241107114819.57599-8-dan.scally@ideasonboard.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20241107114819.57599-1-dan.scally@ideasonboard.com>","References":"<20241107114819.57599-1-dan.scally@ideasonboard.com>","MIME-Version":"1.0","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>"},"content":"Add a new algorithm and associated infrastructure for Agc. The\ntuning files for uncalibrated sensors is extended to enable the\nalgorithm.\n\nAcked-by: Nayden Kanchev <nayden.kanchev@arm.com>\nCo-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\nSigned-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n---\nChanges in v3:\n\n\t- Used the libipa helpers\n\t- used std::pow() instead of math.h pow()\n\nChanges in v2:\n\n\t- Use the union rather than reinterpret_cast<>() to abstract the block\n\n src/ipa/mali-c55/algorithms/agc.cpp     | 409 ++++++++++++++++++++++++\n src/ipa/mali-c55/algorithms/agc.h       |  81 +++++\n src/ipa/mali-c55/algorithms/meson.build |   1 +\n src/ipa/mali-c55/data/uncalibrated.yaml |   1 +\n src/ipa/mali-c55/ipa_context.h          |  32 ++\n src/ipa/mali-c55/mali-c55.cpp           |  54 +++-\n 6 files changed, 576 insertions(+), 2 deletions(-)\n create mode 100644 src/ipa/mali-c55/algorithms/agc.cpp\n create mode 100644 src/ipa/mali-c55/algorithms/agc.h","diff":"diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\nnew file mode 100644\nindex 00000000..fee1eaad\n--- /dev/null\n+++ b/src/ipa/mali-c55/algorithms/agc.cpp\n@@ -0,0 +1,409 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Ideas On Board Oy\n+ *\n+ * agc.cpp - AGC/AEC mean-based control algorithm\n+ */\n+\n+#include \"agc.h\"\n+\n+#include <cmath>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/property_ids.h>\n+\n+#include \"libipa/helpers.h\"\n+\n+namespace libcamera {\n+\n+using namespace std::literals::chrono_literals;\n+\n+namespace ipa::mali_c55::algorithms {\n+\n+LOG_DEFINE_CATEGORY(MaliC55Agc)\n+\n+/*\n+ * Number of histogram bins. This is only true for the specific configuration we\n+ * set to the ISP; 4 separate histograms of 256 bins each. If that configuration\n+ * ever changes then this constant will need updating.\n+ */\n+static constexpr unsigned int kNumHistogramBins = 256;\n+\n+/*\n+ * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8\n+ * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual\n+ * max value which is 8191 * 2^-8.\n+ */\n+static constexpr double kMinDigitalGain = 1.0;\n+static constexpr double kMaxDigitalGain = 31.99609375;\n+\n+uint32_t AgcStatistics::decodeBinValue(uint16_t binVal)\n+{\n+\tint exponent = (binVal & 0xf000) >> 12;\n+\tint mantissa = binVal & 0xfff;\n+\n+\tif (!exponent)\n+\t\treturn mantissa * 2;\n+\telse\n+\t\treturn (mantissa + 4096) * std::pow(2, exponent);\n+}\n+\n+/*\n+ * We configure the ISP to give us 4 histograms of 256 bins each, with\n+ * a single histogram per colour channel (R/Gr/Gb/B). The memory space\n+ * containing the data is a single block containing all 4 histograms\n+ * with the position of each colour's histogram within it dependent on\n+ * the bayer pattern of the data input to the ISP.\n+ *\n+ * NOTE: The validity of this function depends on the parameters we have\n+ * configured. With different skip/offset x, y values not all of the\n+ * colour channels would be populated, and they may not be in the same\n+ * planes as calculated here.\n+ */\n+int AgcStatistics::setBayerOrderIndices(BayerFormat::Order bayerOrder)\n+{\n+\tswitch (bayerOrder) {\n+\tcase BayerFormat::Order::RGGB:\n+\t\trIndex_ = 0;\n+\t\tgrIndex_ = 1;\n+\t\tgbIndex_ = 2;\n+\t\tbIndex_ = 3;\n+\t\tbreak;\n+\tcase BayerFormat::Order::GRBG:\n+\t\tgrIndex_ = 0;\n+\t\trIndex_ = 1;\n+\t\tbIndex_ = 2;\n+\t\tgbIndex_ = 3;\n+\t\tbreak;\n+\tcase BayerFormat::Order::GBRG:\n+\t\tgbIndex_ = 0;\n+\t\tbIndex_ = 1;\n+\t\trIndex_ = 2;\n+\t\tgrIndex_ = 3;\n+\t\tbreak;\n+\tcase BayerFormat::Order::BGGR:\n+\t\tbIndex_ = 0;\n+\t\tgbIndex_ = 1;\n+\t\tgrIndex_ = 2;\n+\t\trIndex_ = 3;\n+\t\tbreak;\n+\tdefault:\n+\t\tLOG(MaliC55Agc, Error)\n+\t\t\t<< \"Invalid bayer format \" << bayerOrder;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats)\n+{\n+\tuint32_t r[256], g[256], b[256], y[256];\n+\n+\t/*\n+\t * We need to decode the bin values for each histogram from their 16-bit\n+\t * compressed values to a 32-bit value. We also take the average of the\n+\t * Gr/Gb values into a single green histogram.\n+\t */\n+\tfor (unsigned int i = 0; i < 256; i++) {\n+\t\tr[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * rIndex_)]);\n+\t\tg[i] = (decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * grIndex_)]) +\n+\t\t\tdecodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * gbIndex_)])) / 2;\n+\t\tb[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * bIndex_)]);\n+\n+\t\ty[i] = ipa::rec601LuminanceFromRGB(r[i], g[i], b[i]);\n+\t}\n+\n+\trHist = Histogram(Span<uint32_t>(r, kNumHistogramBins));\n+\tgHist = Histogram(Span<uint32_t>(g, kNumHistogramBins));\n+\tbHist = Histogram(Span<uint32_t>(b, kNumHistogramBins));\n+\tyHist = Histogram(Span<uint32_t>(y, kNumHistogramBins));\n+}\n+\n+Agc::Agc()\n+\t: AgcMeanLuminance()\n+{\n+}\n+\n+int Agc::init(IPAContext &context, const YamlObject &tuningData)\n+{\n+\tint ret = parseTuningData(tuningData);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tcontext.ctrlMap[&controls::AeEnable] = ControlInfo(false, true);\n+\tcontext.ctrlMap[&controls::DigitalGain] = ControlInfo(\n+\t\tstatic_cast<float>(kMinDigitalGain),\n+\t\tstatic_cast<float>(kMaxDigitalGain),\n+\t\tstatic_cast<float>(kMinDigitalGain)\n+\t);\n+\tcontext.ctrlMap.merge(controls());\n+\n+\treturn 0;\n+}\n+\n+int Agc::configure(IPAContext &context,\n+\t\t   [[maybe_unused]] const IPACameraSensorInfo &configInfo)\n+{\n+\tint ret = statistics_.setBayerOrderIndices(context.configuration.sensor.bayerOrder);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Defaults; we use whatever the sensor's default exposure is and the\n+\t * minimum analogue gain. AEGC is _active_ by default.\n+\t */\n+\tcontext.activeState.agc.autoEnabled = true;\n+\tcontext.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain;\n+\tcontext.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure;\n+\tcontext.activeState.agc.automatic.ispGain = kMinDigitalGain;\n+\tcontext.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain;\n+\tcontext.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure;\n+\tcontext.activeState.agc.manual.ispGain = kMinDigitalGain;\n+\tcontext.activeState.agc.constraintMode = constraintModes().begin()->first;\n+\tcontext.activeState.agc.exposureMode = exposureModeHelpers().begin()->first;\n+\n+\t/* \\todo Run this again when FrameDurationLimits is passed in */\n+\tsetLimits(context.configuration.agc.minShutterSpeed,\n+\t\t  context.configuration.agc.maxShutterSpeed,\n+\t\t  context.configuration.agc.minAnalogueGain,\n+\t\t  context.configuration.agc.maxAnalogueGain);\n+\n+\tresetFrameCount();\n+\n+\treturn 0;\n+}\n+\n+void Agc::queueRequest(IPAContext &context, const uint32_t frame,\n+\t\t       [[maybe_unused]] IPAFrameContext &frameContext,\n+\t\t       const ControlList &controls)\n+{\n+\tauto &agc = context.activeState.agc;\n+\n+\tconst auto &constraintMode = controls.get(controls::AeConstraintMode);\n+\tagc.constraintMode = constraintMode.value_or(agc.constraintMode);\n+\n+\tconst auto &exposureMode = controls.get(controls::AeExposureMode);\n+\tagc.exposureMode = exposureMode.value_or(agc.exposureMode);\n+\n+\tconst auto &agcEnable = controls.get(controls::AeEnable);\n+\tif (agcEnable && *agcEnable != agc.autoEnabled) {\n+\t\tagc.autoEnabled = *agcEnable;\n+\n+\t\tLOG(MaliC55Agc, Info)\n+\t\t\t<< (agc.autoEnabled ? \"Enabling\" : \"Disabling\")\n+\t\t\t<< \" AGC\";\n+\t}\n+\n+\t/*\n+\t * If the automatic exposure and gain is enabled we have no further work\n+\t * to do here...\n+\t */\n+\tif (agc.autoEnabled)\n+\t\treturn;\n+\n+\t/*\n+\t * ...otherwise we need to look for exposure and gain controls and use\n+\t * those to set the activeState.\n+\t */\n+\tconst auto &exposure = controls.get(controls::ExposureTime);\n+\tif (exposure) {\n+\t\tagc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration;\n+\n+\t\tLOG(MaliC55Agc, Debug)\n+\t\t\t<< \"Exposure set to \" << agc.manual.exposure\n+\t\t\t<< \" on request sequence \" << frame;\n+\t}\n+\n+\tconst auto &analogueGain = controls.get(controls::AnalogueGain);\n+\tif (analogueGain) {\n+\t\tagc.manual.sensorGain = *analogueGain;\n+\n+\t\tLOG(MaliC55Agc, Debug)\n+\t\t\t<< \"Analogue gain set to \" << agc.manual.sensorGain\n+\t\t\t<< \" on request sequence \" << frame;\n+\t}\n+\n+\tconst auto &digitalGain = controls.get(controls::DigitalGain);\n+\tif (digitalGain) {\n+\t\tagc.manual.ispGain = *digitalGain;\n+\n+\t\tLOG(MaliC55Agc, Debug)\n+\t\t\t<< \"Digital gain set to \" << agc.manual.ispGain\n+\t\t\t<< \" on request sequence \" << frame;\n+\t}\n+}\n+\n+size_t Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext,\n+\t\t\t       mali_c55_params_block block)\n+{\n+\tIPAActiveState &activeState = context.activeState;\n+\tdouble gain;\n+\n+\tif (activeState.agc.autoEnabled)\n+\t\tgain = activeState.agc.automatic.ispGain;\n+\telse\n+\t\tgain = activeState.agc.manual.ispGain;\n+\n+\tblock.header->type = MALI_C55_PARAM_BLOCK_DIGITAL_GAIN;\n+\tblock.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n+\tblock.header->size = sizeof(struct mali_c55_params_digital_gain);\n+\n+\tblock.digital_gain->gain = ipa::toUnsignedQFormat(gain, 5, 8);\n+\tframeContext.agc.ispGain = gain;\n+\n+\treturn block.header->size;\n+}\n+\n+size_t Agc::fillParamsBuffer(mali_c55_params_block block,\n+\t\t\t     enum mali_c55_param_block_type type)\n+{\n+\tblock.header->type = type;\n+\tblock.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n+\tblock.header->size = sizeof(struct mali_c55_params_aexp_hist);\n+\n+\t/* Collect every 3rd pixel horizontally */\n+\tblock.aexp_hist->skip_x = 1;\n+\t/* Start from first column */\n+\tblock.aexp_hist->offset_x = 0;\n+\t/* Collect every pixel vertically */\n+\tblock.aexp_hist->skip_y = 0;\n+\t/* Start from the first row */\n+\tblock.aexp_hist->offset_y = 0;\n+\t/* 1x scaling (i.e. none) */\n+\tblock.aexp_hist->scale_bottom = 0;\n+\tblock.aexp_hist->scale_top = 0;\n+\t/* Collect all Bayer planes into 4 separate histograms */\n+\tblock.aexp_hist->plane_mode = 1;\n+\t/* Tap the data immediately after the digital gain block */\n+\tblock.aexp_hist->tap_point = MALI_C55_AEXP_HIST_TAP_FS;\n+\n+\treturn block.header->size;\n+}\n+\n+size_t Agc::fillWeightsArrayBuffer(mali_c55_params_block block,\n+\t\t\t\t   enum mali_c55_param_block_type type)\n+{\n+\tblock.header->type = type;\n+\tblock.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n+\tblock.header->size = sizeof(struct mali_c55_params_aexp_weights);\n+\n+\t/* We use every zone - a 15x15 grid */\n+\tblock.aexp_weights->nodes_used_horiz = 15;\n+\tblock.aexp_weights->nodes_used_vert = 15;\n+\n+\t/*\n+\t * We uniformly weight the zones to 1 - this results in the collected\n+\t * histograms containing a true pixel count, which we can then use to\n+\t * approximate colour channel averages for the image.\n+\t */\n+\tSpan<uint8_t> weights{\n+\t\tblock.aexp_weights->zone_weights,\n+\t\tMALI_C55_MAX_ZONES\n+\t};\n+\tstd::fill(weights.begin(), weights.end(), 1);\n+\n+\treturn block.header->size;\n+}\n+\n+void Agc::prepare(IPAContext &context, const uint32_t frame,\n+\t\t  IPAFrameContext &frameContext, mali_c55_params_buffer *params)\n+{\n+\tmali_c55_params_block block;\n+\n+\tblock.data = &params->data[params->total_size];\n+\tparams->total_size += fillGainParamBlock(context, frameContext, block);\n+\n+\tif (frame > 0)\n+\t\treturn;\n+\n+\tblock.data = &params->data[params->total_size];\n+\tparams->total_size += fillParamsBuffer(block,\n+\t\t\t\t\t       MALI_C55_PARAM_BLOCK_AEXP_HIST);\n+\n+\tblock.data = &params->data[params->total_size];\n+\tparams->total_size += fillWeightsArrayBuffer(block,\n+\t\t\t\t\t\t     MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS);\n+\n+\tblock.data = &params->data[params->total_size];\n+\tparams->total_size += fillParamsBuffer(block,\n+\t\t\t\t\t       MALI_C55_PARAM_BLOCK_AEXP_IHIST);\n+\n+\tblock.data = &params->data[params->total_size];\n+\tparams->total_size += fillWeightsArrayBuffer(block,\n+\t\t\t\t\t\t     MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS);\n+}\n+\n+double Agc::estimateLuminance(const double gain) const\n+{\n+\tdouble rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain;\n+\tdouble gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain;\n+\tdouble bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain;\n+\tdouble yAvg = ipa::rec601LuminanceFromRGB(rAvg, gAvg, bAvg);\n+\n+\treturn yAvg / kNumHistogramBins;\n+}\n+\n+void Agc::process(IPAContext &context,\n+\t\t  [[maybe_unused]] const uint32_t frame,\n+\t\t  IPAFrameContext &frameContext,\n+\t\t  const mali_c55_stats_buffer *stats,\n+\t\t  [[maybe_unused]] ControlList &metadata)\n+{\n+\tIPASessionConfiguration &configuration = context.configuration;\n+\tIPAActiveState &activeState = context.activeState;\n+\n+\tif (!stats) {\n+\t\tLOG(MaliC55Agc, Error) << \"No statistics buffer passed to Agc\";\n+\t\treturn;\n+\t}\n+\n+\tstatistics_.parseStatistics(stats);\n+\tcontext.activeState.agc.temperatureK = ipa::estimateCCT(\n+\t\tstatistics_.rHist.interQuantileMean(0, 1),\n+\t\tstatistics_.gHist.interQuantileMean(0, 1),\n+\t\tstatistics_.bHist.interQuantileMean(0, 1)\n+\t);\n+\n+\t/*\n+\t * The Agc algorithm needs to know the effective exposure value that was\n+\t * applied to the sensor when the statistics were collected.\n+\t */\n+\tuint32_t exposure = frameContext.agc.exposure;\n+\tdouble analogueGain = frameContext.agc.sensorGain;\n+\tdouble digitalGain = frameContext.agc.ispGain;\n+\tdouble totalGain = analogueGain * digitalGain;\n+\tutils::Duration currentShutter = exposure * configuration.sensor.lineDuration;\n+\tutils::Duration effectiveExposureValue = currentShutter * totalGain;\n+\n+\tutils::Duration shutterTime;\n+\tdouble aGain, dGain;\n+\tstd::tie(shutterTime, aGain, dGain) =\n+\t\tcalculateNewEv(activeState.agc.constraintMode,\n+\t\t\t       activeState.agc.exposureMode, statistics_.yHist,\n+\t\t\t       effectiveExposureValue);\n+\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+\n+\tactiveState.agc.automatic.exposure = shutterTime / configuration.sensor.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::AnalogueGain, frameContext.agc.sensorGain);\n+\tmetadata.set(controls::DigitalGain, frameContext.agc.ispGain);\n+\tmetadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK);\n+}\n+\n+REGISTER_IPA_ALGORITHM(Agc, \"Agc\")\n+\n+} /* namespace ipa::mali_c55::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h\nnew file mode 100644\nindex 00000000..c5c574e5\n--- /dev/null\n+++ b/src/ipa/mali-c55/algorithms/agc.h\n@@ -0,0 +1,81 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Ideas on Board Oy\n+ *\n+ * agc.h - Mali C55 AGC/AEC mean-based control algorithm\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/base/utils.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+\n+#include \"libipa/agc_mean_luminance.h\"\n+#include \"libipa/histogram.h\"\n+\n+#include \"algorithm.h\"\n+#include \"ipa_context.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa::mali_c55::algorithms {\n+\n+class AgcStatistics\n+{\n+public:\n+\tAgcStatistics()\n+\t{\n+\t}\n+\n+\tint setBayerOrderIndices(BayerFormat::Order bayerOrder);\n+\tuint32_t decodeBinValue(uint16_t binVal);\n+\tvoid parseStatistics(const mali_c55_stats_buffer *stats);\n+\n+\tHistogram rHist;\n+\tHistogram gHist;\n+\tHistogram bHist;\n+\tHistogram yHist;\n+private:\n+\tunsigned int rIndex_;\n+\tunsigned int grIndex_;\n+\tunsigned int gbIndex_;\n+\tunsigned int bIndex_;\n+};\n+\n+class Agc : public Algorithm, public AgcMeanLuminance\n+{\n+public:\n+\tAgc();\n+\t~Agc() = default;\n+\n+\tint init(IPAContext &context, const YamlObject &tuningData) override;\n+\tint configure(IPAContext &context,\n+\t\t      const IPACameraSensorInfo &configInfo) override;\n+\tvoid queueRequest(IPAContext &context, const uint32_t frame,\n+\t\t\t  IPAFrameContext &frameContext,\n+\t\t\t  const ControlList &controls) override;\n+\tvoid prepare(IPAContext &context, const uint32_t frame,\n+\t\t     IPAFrameContext &frameContext,\n+\t\t     mali_c55_params_buffer *params) override;\n+\tvoid process(IPAContext &context, const uint32_t frame,\n+\t\t     IPAFrameContext &frameContext,\n+\t\t     const mali_c55_stats_buffer *stats,\n+\t\t     ControlList &metadata) override;\n+\n+private:\n+\tdouble estimateLuminance(const double gain) const override;\n+\tsize_t fillGainParamBlock(IPAContext &context,\n+\t\t\t\t  IPAFrameContext &frameContext,\n+\t\t\t\t  mali_c55_params_block block);\n+\tsize_t fillParamsBuffer(mali_c55_params_block block,\n+\t\t\t\tenum mali_c55_param_block_type type);\n+\tsize_t fillWeightsArrayBuffer(mali_c55_params_block block,\n+\t\t\t\t      enum mali_c55_param_block_type type);\n+\n+\tAgcStatistics statistics_;\n+};\n+\n+} /* namespace ipa::mali_c55::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build\nindex f2203b15..3c872888 100644\n--- a/src/ipa/mali-c55/algorithms/meson.build\n+++ b/src/ipa/mali-c55/algorithms/meson.build\n@@ -1,4 +1,5 @@\n # SPDX-License-Identifier: CC0-1.0\n \n mali_c55_ipa_algorithms = files([\n+    'agc.cpp',\n ])\ndiff --git a/src/ipa/mali-c55/data/uncalibrated.yaml b/src/ipa/mali-c55/data/uncalibrated.yaml\nindex 2cdc39a8..6dcc0295 100644\n--- a/src/ipa/mali-c55/data/uncalibrated.yaml\n+++ b/src/ipa/mali-c55/data/uncalibrated.yaml\n@@ -3,4 +3,5 @@\n ---\n version: 1\n algorithms:\n+  - Agc:\n ...\ndiff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h\nindex 9e408a17..73a7cd78 100644\n--- a/src/ipa/mali-c55/ipa_context.h\n+++ b/src/ipa/mali-c55/ipa_context.h\n@@ -7,8 +7,11 @@\n \n #pragma once\n \n+#include <libcamera/base/utils.h>\n #include <libcamera/controls.h>\n \n+#include \"libcamera/internal/bayer_format.h\"\n+\n #include <libipa/fc_queue.h>\n \n namespace libcamera {\n@@ -16,15 +19,44 @@ namespace libcamera {\n namespace ipa::mali_c55 {\n \n struct IPASessionConfiguration {\n+\tstruct {\n+\t\tutils::Duration minShutterSpeed;\n+\t\tutils::Duration maxShutterSpeed;\n+\t\tuint32_t defaultExposure;\n+\t\tdouble minAnalogueGain;\n+\t\tdouble maxAnalogueGain;\n+\t} agc;\n+\n+\tstruct {\n+\t\tBayerFormat::Order bayerOrder;\n+\t\tutils::Duration lineDuration;\n+\t} sensor;\n };\n \n struct IPAActiveState {\n+\tstruct {\n+\t\tstruct {\n+\t\t\tuint32_t exposure;\n+\t\t\tdouble sensorGain;\n+\t\t\tdouble ispGain;\n+\t\t} automatic;\n+\t\tstruct {\n+\t\t\tuint32_t exposure;\n+\t\t\tdouble sensorGain;\n+\t\t\tdouble ispGain;\n+\t\t} manual;\n+\t\tbool autoEnabled;\n+\t\tuint32_t constraintMode;\n+\t\tuint32_t exposureMode;\n+\t\tuint32_t temperatureK;\n+\t} agc;\n };\n \n struct IPAFrameContext : public FrameContext {\n \tstruct {\n \t\tuint32_t exposure;\n \t\tdouble sensorGain;\n+\t\tdouble ispGain;\n \t} agc;\n };\n \ndiff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\nindex 7efc0124..55ef7b87 100644\n--- a/src/ipa/mali-c55/mali-c55.cpp\n+++ b/src/ipa/mali-c55/mali-c55.cpp\n@@ -33,6 +33,8 @@ namespace libcamera {\n \n LOG_DEFINE_CATEGORY(IPAMaliC55)\n \n+using namespace std::literals::chrono_literals;\n+\n namespace ipa::mali_c55 {\n \n /* Maximum number of frame contexts to be held */\n@@ -60,6 +62,9 @@ protected:\n \tstd::string logPrefix() const override;\n \n private:\n+\tvoid updateSessionConfiguration(const IPACameraSensorInfo &info,\n+\t\t\t\t\tconst ControlInfoMap &sensorControls,\n+\t\t\t\t\tBayerFormat::Order bayerOrder);\n \tvoid updateControls(const IPACameraSensorInfo &sensorInfo,\n \t\t\t    const ControlInfoMap &sensorControls,\n \t\t\t    ControlInfoMap *ipaControls);\n@@ -131,7 +136,21 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig\n \n void IPAMaliC55::setControls()\n {\n+\tIPAActiveState &activeState = context_.activeState;\n+\tuint32_t exposure;\n+\tuint32_t 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} else {\n+\t\texposure = activeState.agc.manual.exposure;\n+\t\tgain = camHelper_->gainCode(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 \tsetSensorControls.emit(ctrls);\n }\n@@ -146,6 +165,36 @@ void IPAMaliC55::stop()\n \tcontext_.frameContexts.clear();\n }\n \n+void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info,\n+\t\t\t\t\t    const ControlInfoMap &sensorControls,\n+\t\t\t\t\t    BayerFormat::Order bayerOrder)\n+{\n+\tcontext_.configuration.sensor.bayerOrder = bayerOrder;\n+\n+\tconst ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;\n+\tint32_t minExposure = v4l2Exposure.min().get<int32_t>();\n+\tint32_t maxExposure = v4l2Exposure.max().get<int32_t>();\n+\tint32_t defExposure = v4l2Exposure.def().get<int32_t>();\n+\n+\tconst ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;\n+\tint32_t minGain = v4l2Gain.min().get<int32_t>();\n+\tint32_t maxGain = v4l2Gain.max().get<int32_t>();\n+\n+\t/*\n+\t * When the AGC computes the new exposure values for a frame, it needs\n+\t * to know the limits for shutter speed and analogue gain.\n+\t * As it depends on the sensor, update it with the controls.\n+\t *\n+\t * \\todo take VBLANK into account for maximum shutter speed\n+\t */\n+\tcontext_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate;\n+\tcontext_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration;\n+\tcontext_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration;\n+\tcontext_.configuration.agc.defaultExposure = defExposure;\n+\tcontext_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain);\n+\tcontext_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain);\n+}\n+\n void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo,\n \t\t\t\tconst ControlInfoMap &sensorControls,\n \t\t\t\tControlInfoMap *ipaControls)\n@@ -207,8 +256,7 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo,\n \t*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);\n }\n \n-int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig,\n-\t\t\t  [[maybe_unused]] uint8_t bayerOrder,\n+int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder,\n \t\t\t  ControlInfoMap *ipaControls)\n {\n \tsensorControls_ = ipaConfig.sensorControls;\n@@ -220,6 +268,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig,\n \n \tconst IPACameraSensorInfo &info = ipaConfig.sensorInfo;\n \n+\tupdateSessionConfiguration(info, ipaConfig.sensorControls,\n+\t\t\t\t   static_cast<BayerFormat::Order>(bayerOrder));\n \tupdateControls(info, ipaConfig.sensorControls, ipaControls);\n \n \tfor (auto const &a : algorithms()) {\n","prefixes":["v3","07/11"]}