[{"id":32585,"web_url":"https://patchwork.libcamera.org/comment/32585/","msgid":"<173349449002.3135963.2443802819162633627@ping.linuxembedded.co.uk>","date":"2024-12-06T14:14:50","subject":"Re: [PATCH v4 07/11] ipa: mali-c55: Add Agc algorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Daniel Scally (2024-11-15 12:25:36)\n> Add a new algorithm and associated infrastructure for Agc. The\n> tuning files for uncalibrated sensors is extended to enable the\n> algorithm.\n> \n> Acked-by: Nayden Kanchev <nayden.kanchev@arm.com>\n> Co-developed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>\n> ---\n> Changes in v4:\n> \n>         - None\n> \n> Changes in v3:\n> \n>         - Used the libipa helpers\n>         - used std::pow() instead of math.h pow()\n> \n> Changes in v2:\n> \n>         - Use the union rather than reinterpret_cast<>() to abstract the block\n> \n>  src/ipa/mali-c55/algorithms/agc.cpp     | 410 ++++++++++++++++++++++++\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, 577 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\n> \n> diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\n> new file mode 100644\n> index 00000000..195f2813\n> --- /dev/null\n> +++ b/src/ipa/mali-c55/algorithms/agc.cpp\n> @@ -0,0 +1,410 @@\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/colours.h\"\n> +#include \"libipa/fixedpoint.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> +       int exponent = (binVal & 0xf000) >> 12;\n> +       int mantissa = binVal & 0xfff;\n> +\n> +       if (!exponent)\n> +               return mantissa * 2;\n> +       else\n> +               return (mantissa + 4096) * std::pow(2, exponent);\n> +}\n\nIs this some custom floating point handling?\n\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> +       switch (bayerOrder) {\n> +       case BayerFormat::Order::RGGB:\n> +               rIndex_ = 0;\n> +               grIndex_ = 1;\n> +               gbIndex_ = 2;\n> +               bIndex_ = 3;\n> +               break;\n> +       case BayerFormat::Order::GRBG:\n> +               grIndex_ = 0;\n> +               rIndex_ = 1;\n> +               bIndex_ = 2;\n> +               gbIndex_ = 3;\n> +               break;\n> +       case BayerFormat::Order::GBRG:\n> +               gbIndex_ = 0;\n> +               bIndex_ = 1;\n> +               rIndex_ = 2;\n> +               grIndex_ = 3;\n> +               break;\n> +       case BayerFormat::Order::BGGR:\n> +               bIndex_ = 0;\n> +               gbIndex_ = 1;\n> +               grIndex_ = 2;\n> +               rIndex_ = 3;\n> +               break;\n> +       default:\n> +               LOG(MaliC55Agc, Error)\n> +                       << \"Invalid bayer format \" << bayerOrder;\n> +               return -EINVAL;\n> +       }\n> +\n> +       return 0;\n> +}\n> +\n> +void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats)\n> +{\n> +       uint32_t r[256], g[256], b[256], y[256];\n> +\n> +       /*\n> +        * We need to decode the bin values for each histogram from their 16-bit\n> +        * compressed values to a 32-bit value. We also take the average of the\n> +        * Gr/Gb values into a single green histogram.\n> +        */\n> +       for (unsigned int i = 0; i < 256; i++) {\n> +               r[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * rIndex_)]);\n> +               g[i] = (decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * grIndex_)]) +\n> +                       decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * gbIndex_)])) / 2;\n> +               b[i] = decodeBinValue(stats->ae_1024bin_hist.bins[i + (256 * bIndex_)]);\n> +\n> +               y[i] = rec601LuminanceFromRGB(r[i], g[i], b[i]);\n> +       }\n> +\n> +       rHist = Histogram(Span<uint32_t>(r, kNumHistogramBins));\n> +       gHist = Histogram(Span<uint32_t>(g, kNumHistogramBins));\n> +       bHist = Histogram(Span<uint32_t>(b, kNumHistogramBins));\n> +       yHist = Histogram(Span<uint32_t>(y, kNumHistogramBins));\n> +}\n> +\n> +Agc::Agc()\n> +       : AgcMeanLuminance()\n> +{\n> +}\n> +\n> +int Agc::init(IPAContext &context, const YamlObject &tuningData)\n> +{\n> +       int ret = parseTuningData(tuningData);\n> +       if (ret)\n> +               return ret;\n> +\n> +       context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true);\n> +       context.ctrlMap[&controls::DigitalGain] = ControlInfo(\n> +               static_cast<float>(kMinDigitalGain),\n> +               static_cast<float>(kMaxDigitalGain),\n> +               static_cast<float>(kMinDigitalGain)\n> +       );\n> +       context.ctrlMap.merge(controls());\n> +\n> +       return 0;\n> +}\n> +\n> +int Agc::configure(IPAContext &context,\n> +                  [[maybe_unused]] const IPACameraSensorInfo &configInfo)\n> +{\n> +       int ret = statistics_.setBayerOrderIndices(context.configuration.sensor.bayerOrder);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * Defaults; we use whatever the sensor's default exposure is and the\n> +        * minimum analogue gain. AEGC is _active_ by default.\n> +        */\n> +       context.activeState.agc.autoEnabled = true;\n> +       context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain;\n> +       context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure;\n> +       context.activeState.agc.automatic.ispGain = kMinDigitalGain;\n> +       context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain;\n> +       context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure;\n> +       context.activeState.agc.manual.ispGain = kMinDigitalGain;\n> +       context.activeState.agc.constraintMode = constraintModes().begin()->first;\n> +       context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first;\n> +\n> +       /* \\todo Run this again when FrameDurationLimits is passed in */\n> +       setLimits(context.configuration.agc.minShutterSpeed,\n> +                 context.configuration.agc.maxShutterSpeed,\n> +                 context.configuration.agc.minAnalogueGain,\n> +                 context.configuration.agc.maxAnalogueGain);\n> +\n> +       resetFrameCount();\n> +\n> +       return 0;\n> +}\n> +\n> +void Agc::queueRequest(IPAContext &context, const uint32_t frame,\n> +                      [[maybe_unused]] IPAFrameContext &frameContext,\n> +                      const ControlList &controls)\n> +{\n> +       auto &agc = context.activeState.agc;\n> +\n> +       const auto &constraintMode = controls.get(controls::AeConstraintMode);\n> +       agc.constraintMode = constraintMode.value_or(agc.constraintMode);\n> +\n> +       const auto &exposureMode = controls.get(controls::AeExposureMode);\n> +       agc.exposureMode = exposureMode.value_or(agc.exposureMode);\n> +\n> +       const auto &agcEnable = controls.get(controls::AeEnable);\n> +       if (agcEnable && *agcEnable != agc.autoEnabled) {\n> +               agc.autoEnabled = *agcEnable;\n> +\n> +               LOG(MaliC55Agc, Info)\n> +                       << (agc.autoEnabled ? \"Enabling\" : \"Disabling\")\n> +                       << \" AGC\";\n> +       }\n> +\n> +       /*\n> +        * If the automatic exposure and gain is enabled we have no further work\n> +        * to do here...\n> +        */\n> +       if (agc.autoEnabled)\n> +               return;\n> +\n> +       /*\n> +        * ...otherwise we need to look for exposure and gain controls and use\n> +        * those to set the activeState.\n> +        */\n> +       const auto &exposure = controls.get(controls::ExposureTime);\n> +       if (exposure) {\n> +               agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration;\n> +\n> +               LOG(MaliC55Agc, Debug)\n> +                       << \"Exposure set to \" << agc.manual.exposure\n> +                       << \" on request sequence \" << frame;\n> +       }\n> +\n> +       const auto &analogueGain = controls.get(controls::AnalogueGain);\n> +       if (analogueGain) {\n> +               agc.manual.sensorGain = *analogueGain;\n> +\n> +               LOG(MaliC55Agc, Debug)\n> +                       << \"Analogue gain set to \" << agc.manual.sensorGain\n> +                       << \" on request sequence \" << frame;\n> +       }\n> +\n> +       const auto &digitalGain = controls.get(controls::DigitalGain);\n> +       if (digitalGain) {\n> +               agc.manual.ispGain = *digitalGain;\n> +\n> +               LOG(MaliC55Agc, Debug)\n> +                       << \"Digital gain set to \" << agc.manual.ispGain\n> +                       << \" on request sequence \" << frame;\n> +       }\n\nI'm curious how much we could factor out the Request Control parsing\ninto the libipa Agc in the future, I think only the DigitalGain could be\ncustom here..\n\nBut this is fine to get things moving I believe.\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n> +}\n> +\n> +size_t Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext,\n> +                              mali_c55_params_block block)\n> +{\n> +       IPAActiveState &activeState = context.activeState;\n> +       double gain;\n> +\n> +       if (activeState.agc.autoEnabled)\n> +               gain = activeState.agc.automatic.ispGain;\n> +       else\n> +               gain = activeState.agc.manual.ispGain;\n> +\n> +       block.header->type = MALI_C55_PARAM_BLOCK_DIGITAL_GAIN;\n> +       block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n> +       block.header->size = sizeof(struct mali_c55_params_digital_gain);\n> +\n> +       block.digital_gain->gain = floatingToFixedPoint<5, 8, uint16_t, double>(gain);\n> +       frameContext.agc.ispGain = gain;\n> +\n> +       return block.header->size;\n> +}\n> +\n> +size_t Agc::fillParamsBuffer(mali_c55_params_block block,\n> +                            enum mali_c55_param_block_type type)\n> +{\n> +       block.header->type = type;\n> +       block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n> +       block.header->size = sizeof(struct mali_c55_params_aexp_hist);\n> +\n> +       /* Collect every 3rd pixel horizontally */\n> +       block.aexp_hist->skip_x = 1;\n> +       /* Start from first column */\n> +       block.aexp_hist->offset_x = 0;\n> +       /* Collect every pixel vertically */\n> +       block.aexp_hist->skip_y = 0;\n> +       /* Start from the first row */\n> +       block.aexp_hist->offset_y = 0;\n> +       /* 1x scaling (i.e. none) */\n> +       block.aexp_hist->scale_bottom = 0;\n> +       block.aexp_hist->scale_top = 0;\n> +       /* Collect all Bayer planes into 4 separate histograms */\n> +       block.aexp_hist->plane_mode = 1;\n> +       /* Tap the data immediately after the digital gain block */\n> +       block.aexp_hist->tap_point = MALI_C55_AEXP_HIST_TAP_FS;\n> +\n> +       return block.header->size;\n> +}\n> +\n> +size_t Agc::fillWeightsArrayBuffer(mali_c55_params_block block,\n> +                                  enum mali_c55_param_block_type type)\n> +{\n> +       block.header->type = type;\n> +       block.header->flags = MALI_C55_PARAM_BLOCK_FL_NONE;\n> +       block.header->size = sizeof(struct mali_c55_params_aexp_weights);\n> +\n> +       /* We use every zone - a 15x15 grid */\n> +       block.aexp_weights->nodes_used_horiz = 15;\n> +       block.aexp_weights->nodes_used_vert = 15;\n> +\n> +       /*\n> +        * We uniformly weight the zones to 1 - this results in the collected\n> +        * histograms containing a true pixel count, which we can then use to\n> +        * approximate colour channel averages for the image.\n> +        */\n> +       Span<uint8_t> weights{\n> +               block.aexp_weights->zone_weights,\n> +               MALI_C55_MAX_ZONES\n> +       };\n> +       std::fill(weights.begin(), weights.end(), 1);\n> +\n> +       return block.header->size;\n> +}\n> +\n> +void Agc::prepare(IPAContext &context, const uint32_t frame,\n> +                 IPAFrameContext &frameContext, mali_c55_params_buffer *params)\n> +{\n> +       mali_c55_params_block block;\n> +\n> +       block.data = &params->data[params->total_size];\n> +       params->total_size += fillGainParamBlock(context, frameContext, block);\n> +\n> +       if (frame > 0)\n> +               return;\n> +\n> +       block.data = &params->data[params->total_size];\n> +       params->total_size += fillParamsBuffer(block,\n> +                                              MALI_C55_PARAM_BLOCK_AEXP_HIST);\n> +\n> +       block.data = &params->data[params->total_size];\n> +       params->total_size += fillWeightsArrayBuffer(block,\n> +                                                    MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS);\n> +\n> +       block.data = &params->data[params->total_size];\n> +       params->total_size += fillParamsBuffer(block,\n> +                                              MALI_C55_PARAM_BLOCK_AEXP_IHIST);\n> +\n> +       block.data = &params->data[params->total_size];\n> +       params->total_size += fillWeightsArrayBuffer(block,\n> +                                                    MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS);\n> +}\n> +\n> +double Agc::estimateLuminance(const double gain) const\n> +{\n> +       double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain;\n> +       double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain;\n> +       double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain;\n> +       double yAvg = rec601LuminanceFromRGB(rAvg, gAvg, bAvg);\n> +\n> +       return yAvg / kNumHistogramBins;\n> +}\n> +\n> +void Agc::process(IPAContext &context,\n> +                 [[maybe_unused]] const uint32_t frame,\n> +                 IPAFrameContext &frameContext,\n> +                 const mali_c55_stats_buffer *stats,\n> +                 [[maybe_unused]] ControlList &metadata)\n> +{\n> +       IPASessionConfiguration &configuration = context.configuration;\n> +       IPAActiveState &activeState = context.activeState;\n> +\n> +       if (!stats) {\n> +               LOG(MaliC55Agc, Error) << \"No statistics buffer passed to Agc\";\n> +               return;\n> +       }\n> +\n> +       statistics_.parseStatistics(stats);\n> +       context.activeState.agc.temperatureK = estimateCCT(\n> +               statistics_.rHist.interQuantileMean(0, 1),\n> +               statistics_.gHist.interQuantileMean(0, 1),\n> +               statistics_.bHist.interQuantileMean(0, 1)\n> +       );\n> +\n> +       /*\n> +        * The Agc algorithm needs to know the effective exposure value that was\n> +        * applied to the sensor when the statistics were collected.\n> +        */\n> +       uint32_t exposure = frameContext.agc.exposure;\n> +       double analogueGain = frameContext.agc.sensorGain;\n> +       double digitalGain = frameContext.agc.ispGain;\n> +       double totalGain = analogueGain * digitalGain;\n> +       utils::Duration currentShutter = exposure * configuration.sensor.lineDuration;\n> +       utils::Duration effectiveExposureValue = currentShutter * totalGain;\n> +\n> +       utils::Duration shutterTime;\n> +       double aGain, dGain;\n> +       std::tie(shutterTime, aGain, dGain) =\n> +               calculateNewEv(activeState.agc.constraintMode,\n> +                              activeState.agc.exposureMode, statistics_.yHist,\n> +                              effectiveExposureValue);\n> +\n> +       dGain = std::clamp(dGain, kMinDigitalGain, kMaxDigitalGain);\n> +\n> +       LOG(MaliC55Agc, Debug)\n> +               << \"Divided up shutter, analogue gain and digital gain are \"\n> +               << shutterTime << \", \" << aGain << \" and \" << dGain;\n> +\n> +       activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;\n> +       activeState.agc.automatic.sensorGain = aGain;\n> +       activeState.agc.automatic.ispGain = dGain;\n> +\n> +       metadata.set(controls::ExposureTime, currentShutter.get<std::micro>());\n> +       metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain);\n> +       metadata.set(controls::DigitalGain, frameContext.agc.ispGain);\n> +       metadata.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 */\n> diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h\n> new file mode 100644\n> index 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> +       AgcStatistics()\n> +       {\n> +       }\n> +\n> +       int setBayerOrderIndices(BayerFormat::Order bayerOrder);\n> +       uint32_t decodeBinValue(uint16_t binVal);\n> +       void parseStatistics(const mali_c55_stats_buffer *stats);\n> +\n> +       Histogram rHist;\n> +       Histogram gHist;\n> +       Histogram bHist;\n> +       Histogram yHist;\n> +private:\n> +       unsigned int rIndex_;\n> +       unsigned int grIndex_;\n> +       unsigned int gbIndex_;\n> +       unsigned int bIndex_;\n> +};\n> +\n> +class Agc : public Algorithm, public AgcMeanLuminance\n> +{\n> +public:\n> +       Agc();\n> +       ~Agc() = default;\n> +\n> +       int init(IPAContext &context, const YamlObject &tuningData) override;\n> +       int configure(IPAContext &context,\n> +                     const IPACameraSensorInfo &configInfo) override;\n> +       void queueRequest(IPAContext &context, const uint32_t frame,\n> +                         IPAFrameContext &frameContext,\n> +                         const ControlList &controls) override;\n> +       void prepare(IPAContext &context, const uint32_t frame,\n> +                    IPAFrameContext &frameContext,\n> +                    mali_c55_params_buffer *params) override;\n> +       void process(IPAContext &context, const uint32_t frame,\n> +                    IPAFrameContext &frameContext,\n> +                    const mali_c55_stats_buffer *stats,\n> +                    ControlList &metadata) override;\n> +\n> +private:\n> +       double estimateLuminance(const double gain) const override;\n> +       size_t fillGainParamBlock(IPAContext &context,\n> +                                 IPAFrameContext &frameContext,\n> +                                 mali_c55_params_block block);\n> +       size_t fillParamsBuffer(mali_c55_params_block block,\n> +                               enum mali_c55_param_block_type type);\n> +       size_t fillWeightsArrayBuffer(mali_c55_params_block block,\n> +                                     enum mali_c55_param_block_type type);\n> +\n> +       AgcStatistics statistics_;\n> +};\n> +\n> +} /* namespace ipa::mali_c55::algorithms */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build\n> index 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>  ])\n> diff --git a/src/ipa/mali-c55/data/uncalibrated.yaml b/src/ipa/mali-c55/data/uncalibrated.yaml\n> index 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>  ...\n> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h\n> index 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> +       struct {\n> +               utils::Duration minShutterSpeed;\n> +               utils::Duration maxShutterSpeed;\n> +               uint32_t defaultExposure;\n> +               double minAnalogueGain;\n> +               double maxAnalogueGain;\n> +       } agc;\n> +\n> +       struct {\n> +               BayerFormat::Order bayerOrder;\n> +               utils::Duration lineDuration;\n> +       } sensor;\n>  };\n>  \n>  struct IPAActiveState {\n> +       struct {\n> +               struct {\n> +                       uint32_t exposure;\n> +                       double sensorGain;\n> +                       double ispGain;\n> +               } automatic;\n> +               struct {\n> +                       uint32_t exposure;\n> +                       double sensorGain;\n> +                       double ispGain;\n> +               } manual;\n> +               bool autoEnabled;\n> +               uint32_t constraintMode;\n> +               uint32_t exposureMode;\n> +               uint32_t temperatureK;\n> +       } agc;\n>  };\n>  \n>  struct IPAFrameContext : public FrameContext {\n>         struct {\n>                 uint32_t exposure;\n>                 double sensorGain;\n> +               double ispGain;\n>         } agc;\n>  };\n>  \n> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp\n> index 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>         std::string logPrefix() const override;\n>  \n>  private:\n> +       void updateSessionConfiguration(const IPACameraSensorInfo &info,\n> +                                       const ControlInfoMap &sensorControls,\n> +                                       BayerFormat::Order bayerOrder);\n>         void updateControls(const IPACameraSensorInfo &sensorInfo,\n>                             const ControlInfoMap &sensorControls,\n>                             ControlInfoMap *ipaControls);\n> @@ -131,7 +136,21 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig\n>  \n>  void IPAMaliC55::setControls()\n>  {\n> +       IPAActiveState &activeState = context_.activeState;\n> +       uint32_t exposure;\n> +       uint32_t gain;\n> +\n> +       if (activeState.agc.autoEnabled) {\n> +               exposure = activeState.agc.automatic.exposure;\n> +               gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain);\n> +       } else {\n> +               exposure = activeState.agc.manual.exposure;\n> +               gain = camHelper_->gainCode(activeState.agc.manual.sensorGain);\n> +       }\n> +\n>         ControlList ctrls(sensorControls_);\n> +       ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));\n> +       ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));\n>  \n>         setSensorControls.emit(ctrls);\n>  }\n> @@ -146,6 +165,36 @@ void IPAMaliC55::stop()\n>         context_.frameContexts.clear();\n>  }\n>  \n> +void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info,\n> +                                           const ControlInfoMap &sensorControls,\n> +                                           BayerFormat::Order bayerOrder)\n> +{\n> +       context_.configuration.sensor.bayerOrder = bayerOrder;\n> +\n> +       const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;\n> +       int32_t minExposure = v4l2Exposure.min().get<int32_t>();\n> +       int32_t maxExposure = v4l2Exposure.max().get<int32_t>();\n> +       int32_t defExposure = v4l2Exposure.def().get<int32_t>();\n> +\n> +       const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;\n> +       int32_t minGain = v4l2Gain.min().get<int32_t>();\n> +       int32_t maxGain = v4l2Gain.max().get<int32_t>();\n> +\n> +       /*\n> +        * When the AGC computes the new exposure values for a frame, it needs\n> +        * to know the limits for shutter speed and analogue gain.\n> +        * As it depends on the sensor, update it with the controls.\n> +        *\n> +        * \\todo take VBLANK into account for maximum shutter speed\n> +        */\n> +       context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate;\n> +       context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration;\n> +       context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration;\n> +       context_.configuration.agc.defaultExposure = defExposure;\n> +       context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain);\n> +       context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain);\n> +}\n> +\n>  void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo,\n>                                 const ControlInfoMap &sensorControls,\n>                                 ControlInfoMap *ipaControls)\n> @@ -207,8 +256,7 @@ void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo,\n>         *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);\n>  }\n>  \n> -int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig,\n> -                         [[maybe_unused]] uint8_t bayerOrder,\n> +int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder,\n>                           ControlInfoMap *ipaControls)\n>  {\n>         sensorControls_ = ipaConfig.sensorControls;\n> @@ -220,6 +268,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig,\n>  \n>         const IPACameraSensorInfo &info = ipaConfig.sensorInfo;\n>  \n> +       updateSessionConfiguration(info, ipaConfig.sensorControls,\n> +                                  static_cast<BayerFormat::Order>(bayerOrder));\n>         updateControls(info, ipaConfig.sensorControls, ipaControls);\n>  \n>         for (auto const &a : algorithms()) {\n> -- \n> 2.30.2\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 17786BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  6 Dec 2024 14:14:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 56C0466156;\n\tFri,  6 Dec 2024 15:14:53 +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 9A18C618B3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  6 Dec 2024 15:14:52 +0100 (CET)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D25AC641;\n\tFri,  6 Dec 2024 15:14:22 +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=\"clm+O5Xt\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1733494462;\n\tbh=mDC+bbh0ZkJOWe/ao8GBEPgB3fusmEpLMRUpapqqYg0=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=clm+O5XtIf8FtMwG2luuaRA6nkulTHiGQcpS2pGl5wDy9iGofjT15u9jaM7fpZV8P\n\tck1nwynZLLGltXhrP6WKgFgjv23IFKZbboOsKj7ihcHfiWIH3sd2cda+LxH7ZVtO5w\n\t/bcyWJ1k30QQ7ZBVB9yaNTHmTYDmhbi0wa+fFJmE=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20241115122540.478103-8-dan.scally@ideasonboard.com>","References":"<20241115122540.478103-1-dan.scally@ideasonboard.com>\n\t<20241115122540.478103-8-dan.scally@ideasonboard.com>","Subject":"Re: [PATCH v4 07/11] ipa: mali-c55: Add Agc algorithm","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","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>","To":"Daniel Scally <dan.scally@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 06 Dec 2024 14:14:50 +0000","Message-ID":"<173349449002.3135963.2443802819162633627@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","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>"}}]