[{"id":39650,"web_url":"https://patchwork.libcamera.org/comment/39650/","msgid":"<178359543866.2174993.5243521918576743801@ping.linuxembedded.co.uk>","date":"2026-07-09T11:10:38","subject":"Re: [PATCH v5 08/36] ipa: mali-c55: Implement Ccm algorithm","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Jacopo Mondi (2026-07-08 16:50:50)\n> Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class.\n> \n> Mali has configurable colour gains as part of the CCM hardware block.\n> Fix them to 1.0 for the time being as white balance is performed by the\n> dedicated Awb algorithm in the Bayer colour space.\n> \n> The existing Mali Ccm algorithm implementation is a bit more strict on\n> when to re-interpolate the CCM coefficients. Keep the algorithm a little\n> more strict and only interpolate on 20% color temperature changes.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n\nI gave this on v2, and I think it still applies so:\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> ---\n>  src/ipa/mali-c55/algorithms/ccm.cpp     | 173 ++++++++++++++++++++++++++++++++\n>  src/ipa/mali-c55/algorithms/ccm.h       |  66 ++++++++++++\n>  src/ipa/mali-c55/algorithms/meson.build |   1 +\n>  src/ipa/mali-c55/ipa_context.h          |   3 +\n>  src/ipa/mali-c55/params.h               |   2 +\n>  5 files changed, 245 insertions(+)\n> \n> diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp\n> new file mode 100644\n> index 000000000000..b5196a7ee85b\n> --- /dev/null\n> +++ b/src/ipa/mali-c55/algorithms/ccm.cpp\n> @@ -0,0 +1,173 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2026, Ideas On Board\n> + *\n> + * Mali C55 Color Correction Matrix control algorithm\n> + */\n> +\n> +#include \"ccm.h\"\n> +\n> +#include <libcamera/base/log.h>\n> +\n> +/**\n> + * \\file ccm.h\n> + * \\brief Mali-C55 CCM implementation\n> + */\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::mali_c55::algorithms {\n> +\n> +LOG_DEFINE_CATEGORY(MaliC55Ccm)\n> +\n> +/**\n> + * \\class Ccm\n> + * \\brief Mali-C55 color correction matrix algorithm\n> + */\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::init\n> + */\n> +int Ccm::init(IPAContext &context, const ValueNode &tuningData)\n> +{\n> +       auto &cmap = context.ctrlMap;\n> +       int ret = ccmAlgo_.init(tuningData, cmap);\n> +       if (ret)\n> +               return ret;\n> +\n> +       /*\n> +        * Mali-C55 allows to perform WB in the RGB color space as part of the\n> +        * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer\n> +        * domain.\n> +        */\n> +       gain_ = 1.0;\n> +\n> +       return 0;\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::configure\n> + */\n> +int Ccm::configure(IPAContext &context,\n> +                  [[maybe_unused]] const IPACameraSensorInfo &configInfo)\n> +{\n> +       lastCt_ = context.activeState.awb.automatic.colourTemperature;\n> +       return ccmAlgo_.configure(context.activeState.ccm, lastCt_);\n> +}\n> +\n> +void Ccm::queueRequest(IPAContext &context,\n> +                      [[maybe_unused]] const uint32_t frame,\n> +                      IPAFrameContext &frameContext,\n> +                      const ControlList &controls)\n> +{\n> +       /* Nothing to do here, the ccm will be calculated in prepare() */\n> +       if (frameContext.awb.autoEnabled)\n> +               return;\n> +\n> +       ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls);\n> +}\n> +\n> +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext)\n> +{\n> +       auto config = params->block<MaliC55Blocks::Ccm>();\n> +\n> +       for (unsigned int i = 0; i < 3; i++)\n> +               config->gains[i] = UQ<4, 8>(gain_).quantized();\n> +\n> +       for (unsigned int i = 0; i < 3; i++)\n> +               config->offs[i] = frameContext.ccm.offsets[i][0];\n> +\n> +       const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm;\n> +       for (unsigned int i = 0; i < 3; i++) {\n> +               for (unsigned int j = 0; j < 3; j++) {\n> +                       uint16_t val = Q<5, 8>(ccm[i][j]).quantized();\n> +\n> +                       /*\n> +                        * CCM coefficients are expected to be in sign/magnitude\n> +                        * format.\n> +                        *\n> +                        * As we're here handling the case where ccm[i][j] is\n> +                        * negative, its quantized representation is stored in\n> +                        * 'val' as 2's complement.\n> +                        *\n> +                        * We need to invert the 2's complement by\n> +                        * re-complementing it to 1 and adding 1 back.\n> +                        *\n> +                        * Let's make a practical example on how to reverse the\n> +                        * 2's complement of -7 with 4 bits and BIT(5) for sign.\n> +                        *\n> +                        * 2's complement (ignore sign bit)\n> +                        *  - 1's complement of abs(-7)\n> +                        *    2^4 - 1 - 7 = 8 ->        1000\n> +                        *  - Add one: 8 + 1 = 9 ->     1001\n> +                        *\n> +                        * -7 is then [1 1001] in memory in 2's complement\n> +                        *\n> +                        * Reverse 2's complement (ignore sign bit)\n> +                        *  - 1's complement of the 2's complement representation\n> +                        *    2^4 - 1 - 9 = 6 ->        0110\n> +                        *  - add one: 6 + 1 = 7 ->     0111\n> +                        *\n> +                        *  -7 is then [1 0111] in memory in Sign/Magnitude\n> +                        */\n> +                       if (val && ccm[i][j] < 0)\n> +                               val = ((~(val & ~(1 << 12)) & 0x1fff) + 1) | (1 << 12);\n> +\n> +                       config->coeffs[i][j] = val;\n> +               }\n> +       }\n> +\n> +       config.setEnabled(true);\n> +\n> +       LOG(MaliC55Ccm, Debug) << \"Setting ccm: \" << ccm << \" offsets: \"\n> +                              << frameContext.ccm.offsets\n> +                              << \" with fixed gain \" << gain_;\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::prepare\n> + */\n> +void Ccm::prepare(IPAContext &context, const uint32_t frame,\n> +                 IPAFrameContext &frameContext, MaliC55Params *params)\n> +{\n> +       if (!frameContext.awb.autoEnabled) {\n> +               setParameters(params, frameContext);\n> +               return;\n> +       }\n> +\n> +       /*\n> +        * The Mali-C55 Ccm implementation is slightly stricter than the\n> +        * CcmAlgorithm class and only re-interpolates if the colour temperature\n> +        * changes of a certain amount.\n> +        */\n> +       float ct = frameContext.awb.colourTemperature * 1.0f;\n> +       if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) {\n> +               frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm;\n> +               frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets;\n> +               lastCt_ = ct;\n> +\n> +               return;\n> +       }\n> +\n> +       ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct);\n> +       setParameters(params, frameContext);\n> +       lastCt_ = ct;\n> +}\n> +\n> +/**\n> + * \\copydoc libcamera::ipa::Algorithm::process\n> + */\n> +void Ccm::process([[maybe_unused]] IPAContext &context,\n> +                 [[maybe_unused]] const uint32_t frame,\n> +                 IPAFrameContext &frameContext,\n> +                 [[maybe_unused]] const mali_c55_stats_buffer *stats,\n> +                 ControlList &metadata)\n> +{\n> +       ccmAlgo_.process(frameContext.ccm, metadata);\n> +}\n> +\n> +REGISTER_IPA_ALGORITHM(Ccm, \"Ccm\")\n> +\n> +} /* namespace ipa::mali_c55::algorithms */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h\n> new file mode 100644\n> index 000000000000..73649204a7ee\n> --- /dev/null\n> +++ b/src/ipa/mali-c55/algorithms/ccm.h\n> @@ -0,0 +1,66 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2026, Ideas On Board\n> + *\n> + * Mali C55 Color Correction Matrix control algorithm\n> + */\n> +\n> +#pragma once\n> +\n> +#include <linux/media/arm/mali-c55-config.h>\n> +\n> +#include <libcamera/controls.h>\n> +\n> +#include \"libcamera/internal/value_node.h\"\n> +\n> +#include \"libipa/ccm.h\"\n> +#include \"libipa/fixedpoint.h\"\n> +\n> +#include \"algorithm.h\"\n> +#include \"ipa_context.h\"\n> +#include \"params.h\"\n> +\n> +namespace libcamera {\n> +\n> +namespace ipa::mali_c55::algorithms {\n> +\n> +class Ccm : public Algorithm\n> +{\n> +public:\n> +       Ccm() {}\n> +       ~Ccm() = default;\n> +\n> +       int init(IPAContext &context, const ValueNode &tuningData) override;\n> +       int configure(IPAContext &context,\n> +                     const IPACameraSensorInfo &configInfo) override;\n> +       void queueRequest(IPAContext &context,\n> +                         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> +                    MaliC55Params *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> +       void setParameters(MaliC55Params *params, const IPAFrameContext &context);\n> +\n> +       /*\n> +        * The CCM coefficient registers are said to be in Q<4,8> but this\n> +        * doesn't include the sign bit as the register is 13 bits wide\n> +        * (Q-format TI variant).\n> +        *\n> +        * As the Quantized class uses the ARM variant of the Q-format notation,\n> +        * make it <5, 8> to include the sign bit.\n> +        */\n> +       CcmAlgorithm<Q<5, 8>> ccmAlgo_;\n> +       float gain_;\n> +       float lastCt_;\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 1665da071634..24a27ce66562 100644\n> --- a/src/ipa/mali-c55/algorithms/meson.build\n> +++ b/src/ipa/mali-c55/algorithms/meson.build\n> @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([\n>      'agc.cpp',\n>      'awb.cpp',\n>      'blc.cpp',\n> +    'ccm.cpp',\n>      'lsc.cpp',\n>  ])\n> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h\n> index 7dc765a3542b..ef3188e347d7 100644\n> --- a/src/ipa/mali-c55/ipa_context.h\n> +++ b/src/ipa/mali-c55/ipa_context.h\n> @@ -15,6 +15,7 @@\n>  #include <libipa/fc_queue.h>\n>  \n>  #include \"libipa/awb.h\"\n> +#include \"libipa/ccm.h\"\n>  #include \"libipa/fixedpoint.h\"\n>  \n>  namespace libcamera {\n> @@ -56,6 +57,7 @@ struct IPAActiveState {\n>         } agc;\n>  \n>         ipa::awb::ActiveState awb;\n> +       ipa::ccm::ActiveState ccm;\n>  };\n>  \n>  struct IPAFrameContext : public FrameContext {\n> @@ -66,6 +68,7 @@ struct IPAFrameContext : public FrameContext {\n>         } agc;\n>  \n>         ipa::awb::FrameContext awb;\n> +       ipa::ccm::FrameContext ccm;\n>  };\n>  \n>  struct IPAContext {\n> diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h\n> index 3abcb7f94916..2ce55262031d 100644\n> --- a/src/ipa/mali-c55/params.h\n> +++ b/src/ipa/mali-c55/params.h\n> @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t {\n>         AwbConfig,\n>         MeshShadingConfig,\n>         MeshShadingSel,\n> +       Ccm,\n>  };\n>  \n>  namespace details {\n> @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains,          awb_gains,              BLOCK_AWB_GAINS);\n>  MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig,          awb_config,             BLOCK_AWB_CONFIG);\n>  MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig,  mesh_shading_config,    MESH_SHADING_CONFIG);\n>  MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel,     mesh_shading_selection, MESH_SHADING_SELECTION);\n> +MALI_C55_DEFINE_BLOCK_TYPE(Ccm,                        ccm,                    BLOCK_CCM);\n>  \n>  struct param_traits {\n>         using id_type = MaliC55Blocks;\n> \n> -- \n> 2.54.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 D4893C3301\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Jul 2026 11:10:44 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 46B9D65FA2;\n\tThu,  9 Jul 2026 13:10:43 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id ADA8465FA2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Jul 2026 13:10:41 +0200 (CEST)","from monstersaurus.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 13742448;\n\tThu,  9 Jul 2026 13:09:51 +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=\"pm4ip4++\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783595391;\n\tbh=a+7Y2k1a94XjOD9YhOSWkaV6eDHQUgLW607YszTGXG8=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=pm4ip4++cY+tw7sVuGqog9A/wAEzUm0u8TMj+HJqpRX4vGlsU+5vYQpkUS2Uzv/9/\n\tI3jxMAxB4/rpNOEe7zWlB9mRS7DMHvECTbjPi2vqrepnwwUB+5MWO0OKOz13QFRlW3\n\tslY/gOISAptmx4ZC/bFrLJbIcafgppcxiJlFOmcE=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20260708-libipa-algorithms-v5-8-0759d0359f52@ideasonboard.com>","References":"<20260708-libipa-algorithms-v5-0-0759d0359f52@ideasonboard.com>\n\t<20260708-libipa-algorithms-v5-8-0759d0359f52@ideasonboard.com>","Subject":"Re: [PATCH v5 08/36] ipa: mali-c55: Implement Ccm algorithm","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tStefan Klug <stefan.klug@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Thu, 09 Jul 2026 12:10:38 +0100","Message-ID":"<178359543866.2174993.5243521918576743801@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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>"}}]