| Message ID | 20260623-libipa-algorithms-v2-8-f97433f12e4e@ideasonboard.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Quoting Jacopo Mondi (2026-06-23 14:55:03) > Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. > > Mali has configurable colour gains as part of the CCM hardware block. > Fix them to 1.0 for the time being as white balance is performed by the > dedicated Awb algorithm in the Bayer colour space. > > The existing Mali Ccm algorithm implementation is a bit more strict on > when to re-interpolate the CCM coefficients. Keep the algorithm a little > more strict and only interpolate on 20% color temperature changes. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/mali-c55/algorithms/ccm.cpp | 175 ++++++++++++++++++++++++++++++++ > src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ > src/ipa/mali-c55/algorithms/meson.build | 1 + > src/ipa/mali-c55/ipa_context.h | 3 + > src/ipa/mali-c55/params.h | 2 + > 5 files changed, 247 insertions(+) > > diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp > new file mode 100644 > index 000000000000..008ab2d1e31e > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/ccm.cpp > @@ -0,0 +1,175 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * Mali C55 Color Correction Matrix control algorithm > + */ > + > +#include "ccm.h" > + > +#include <libcamera/base/log.h> > + > +/** > + * \file ccm.h > + * \brief Mali-C55 CCM implementation > + */ > + > +namespace libcamera { > + > +namespace ipa::mali_c55::algorithms { > + > +LOG_DEFINE_CATEGORY(MaliC55Ccm) > + > +/** > + * \class Ccm > + * \brief Mali-C55 color correction matrix algorithm > + */ > + > +/** > + * \copydoc libcamera::ipa::Algorithm::init > + */ > +int Ccm::init(IPAContext &context, const ValueNode &tuningData) > +{ > + auto &cmap = context.ctrlMap; > + int ret = ccmAlgo_.init(tuningData, cmap); > + if (ret) > + return ret; > + > + /* > + * Mali-C55 allows to perform WB in the RGB color space as part of the > + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer > + * domain. > + */ > + gain_ = 1.0; > + > + return 0; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::configure > + */ > +int Ccm::configure(IPAContext &context, > + [[maybe_unused]] const IPACameraSensorInfo &configInfo) > +{ > + lastCt_ = context.activeState.awb.automatic.temperatureK; > + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); > +} > + > +void Ccm::queueRequest(IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) > +{ > + /* Nothing to do here, the ccm will be calculated in prepare() */ > + if (frameContext.awb.autoEnabled) > + return; > + > + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); > +} > + > +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) > +{ > + auto config = params->block<MaliC55Blocks::Ccm>(); > + > + for (unsigned int i = 0; i < 3; i++) > + config->gains[i] = UQ<4, 8>(gain_).quantized(); > + > + for (unsigned int i = 0; i < 3; i++) > + config->offs[i] = frameContext.ccm.offsets[i][0]; > + > + const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm; > + for (unsigned int i = 0; i < 3; i++) { > + for (unsigned int j = 0; j < 3; j++) { > + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); > + > + if (val && ccm[i][j] < 0) { > + /* > + * CCM coefficients are expected to be in > + * sign/magnitude format. > + * > + * As we're here handling the case where > + * ccm[i][j] is negative, its quantized > + * representation is stored in 'val' as > + * 2's complement. > + * > + * We need to invert the 2's complement by > + * re-complementing it to 1 and adding 1 back. > + * > + * Let's make a practical example on how to > + * reverse the 2's complement of -7 with 4 bits > + * and BIT(5) for sign. > + * > + * 2's complement (ignore sign bit) > + * - 1's complement of abs(-7) > + * 2^4 - 1 - 7 = 8 -> 1000 > + * - Add one: 8 + 1 = 9 -> 1001 > + * > + * -7 is then [1 1001] in memory in 2's complement > + * > + * Reverse 2's complement (ignore sign bit) > + * - 1's complement of the 2's complement representation > + * 2^4 - 1 - 9 = 6 -> 0110 > + * - add one: 6 + 1 = 7 -> 0111 > + * > + * -7 is then [1 0111] in memory in Sign/Magnitude > + */ > + val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); Optional, but that looks reaally compressed. I'd put the block comment above the line and remove the braces to give your text another indent level of space: /* * CCM coefficients are expected to be in sign / * magnitude format. * As we're here handling the case where ccm[i][j] is * negative, its quantized representation is stored in * 'val' as 2's complement. * .... */ if (val && ccm[i][j] < 0) val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); config->coeffs[i][j] = val; Doesn't make much difference overall though. > + } > + config->coeffs[i][j] = val; > + } > + } > + > + config.setEnabled(true); > + > + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " > + << frameContext.ccm.offsets > + << " with fixed gain " << gain_; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::prepare > + */ > +void Ccm::prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, MaliC55Params *params) > +{ > + if (!frameContext.awb.autoEnabled) { > + setParameters(params, frameContext); > + return; > + } > + > + /* > + * The Mali-C55 Ccm implementation is slightly stricter than the > + * CcmAlgorithm class and only re-interpolates if the colour temperature > + * changes of a certain amount. changes by a certain amount > + */ > + float ct = frameContext.awb.temperatureK * 1.0f; > + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { > + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; > + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; > + lastCt_ = ct; > + > + return; > + } > + > + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); > + setParameters(params, frameContext); > + lastCt_ = ct; Duplicated, so lastCt_ could be assigned higher in the function, but assigning before doing the 'work' feels a little odd, so I can understand this ordering too. Seems I only have trivial comments so: Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::process > + */ > +void Ccm::process([[maybe_unused]] IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + [[maybe_unused]] const mali_c55_stats_buffer *stats, > + ControlList &metadata) > +{ > + ccmAlgo_.process(frameContext.ccm, metadata); > +} > + > +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h > new file mode 100644 > index 000000000000..73649204a7ee > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/ccm.h > @@ -0,0 +1,66 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * Mali C55 Color Correction Matrix control algorithm > + */ > + > +#pragma once > + > +#include <linux/media/arm/mali-c55-config.h> > + > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/value_node.h" > + > +#include "libipa/ccm.h" > +#include "libipa/fixedpoint.h" > + > +#include "algorithm.h" > +#include "ipa_context.h" > +#include "params.h" > + > +namespace libcamera { > + > +namespace ipa::mali_c55::algorithms { > + > +class Ccm : public Algorithm > +{ > +public: > + Ccm() {} > + ~Ccm() = default; > + > + int init(IPAContext &context, const ValueNode &tuningData) override; > + int configure(IPAContext &context, > + const IPACameraSensorInfo &configInfo) override; > + void queueRequest(IPAContext &context, > + const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) override; > + void prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + MaliC55Params *params) override; > + void process(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats, > + ControlList &metadata) override; > + > +private: > + void setParameters(MaliC55Params *params, const IPAFrameContext &context); > + > + /* > + * The CCM coefficient registers are said to be in Q<4,8> but this > + * doesn't include the sign bit as the register is 13 bits wide > + * (Q-format TI variant). > + * > + * As the Quantized class uses the ARM variant of the Q-format notation, > + * make it <5, 8> to include the sign bit. > + */ > + CcmAlgorithm<Q<5, 8>> ccmAlgo_; > + float gain_; > + float lastCt_; > +}; > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build > index 1665da071634..24a27ce66562 100644 > --- a/src/ipa/mali-c55/algorithms/meson.build > +++ b/src/ipa/mali-c55/algorithms/meson.build > @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ > 'agc.cpp', > 'awb.cpp', > 'blc.cpp', > + 'ccm.cpp', > 'lsc.cpp', > ]) > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > index f6c6938b3cb3..aae4a543c1b7 100644 > --- a/src/ipa/mali-c55/ipa_context.h > +++ b/src/ipa/mali-c55/ipa_context.h > @@ -16,6 +16,7 @@ > #include <libipa/fc_queue.h> > > #include "libipa/awb.h" > +#include "libipa/ccm.h" > #include "libipa/fixedpoint.h" > > namespace libcamera { > @@ -57,6 +58,7 @@ struct IPAActiveState { > } agc; > > ipa::awb::ActiveState awb; > + ipa::ccm::ActiveState ccm; > }; > > struct IPAFrameContext : public FrameContext { > @@ -67,6 +69,7 @@ struct IPAFrameContext : public FrameContext { > } agc; > > ipa::awb::FrameContext awb; > + ipa::ccm::FrameContext ccm; > }; > > struct IPAContext { > diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h > index 3abcb7f94916..2ce55262031d 100644 > --- a/src/ipa/mali-c55/params.h > +++ b/src/ipa/mali-c55/params.h > @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { > AwbConfig, > MeshShadingConfig, > MeshShadingSel, > + Ccm, > }; > > namespace details { > @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); > MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); > +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); > > struct param_traits { > using id_type = MaliC55Blocks; > > -- > 2.54.0 >
Hi Jacopo On 23/06/2026 14:55, Jacopo Mondi wrote: > Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. > > Mali has configurable colour gains as part of the CCM hardware block. > Fix them to 1.0 for the time being as white balance is performed by the > dedicated Awb algorithm in the Bayer colour space. > > The existing Mali Ccm algorithm implementation is a bit more strict on > when to re-interpolate the CCM coefficients. Keep the algorithm a little > more strict and only interpolate on 20% color temperature changes. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/mali-c55/algorithms/ccm.cpp | 175 ++++++++++++++++++++++++++++++++ > src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ > src/ipa/mali-c55/algorithms/meson.build | 1 + > src/ipa/mali-c55/ipa_context.h | 3 + > src/ipa/mali-c55/params.h | 2 + > 5 files changed, 247 insertions(+) > > diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp > new file mode 100644 > index 000000000000..008ab2d1e31e > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/ccm.cpp > @@ -0,0 +1,175 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * Mali C55 Color Correction Matrix control algorithm > + */ > + > +#include "ccm.h" > + > +#include <libcamera/base/log.h> > + > +/** > + * \file ccm.h > + * \brief Mali-C55 CCM implementation > + */ > + > +namespace libcamera { > + > +namespace ipa::mali_c55::algorithms { > + > +LOG_DEFINE_CATEGORY(MaliC55Ccm) > + > +/** > + * \class Ccm > + * \brief Mali-C55 color correction matrix algorithm > + */ > + > +/** > + * \copydoc libcamera::ipa::Algorithm::init > + */ > +int Ccm::init(IPAContext &context, const ValueNode &tuningData) > +{ > + auto &cmap = context.ctrlMap; > + int ret = ccmAlgo_.init(tuningData, cmap); > + if (ret) > + return ret; > + > + /* > + * Mali-C55 allows to perform WB in the RGB color space as part of the > + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer > + * domain. > + */ > + gain_ = 1.0; > + > + return 0; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::configure > + */ > +int Ccm::configure(IPAContext &context, > + [[maybe_unused]] const IPACameraSensorInfo &configInfo) > +{ > + lastCt_ = context.activeState.awb.automatic.temperatureK; > + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); > +} > + > +void Ccm::queueRequest(IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) > +{ > + /* Nothing to do here, the ccm will be calculated in prepare() */ > + if (frameContext.awb.autoEnabled) > + return; > + > + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); > +} > + > +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) > +{ > + auto config = params->block<MaliC55Blocks::Ccm>(); > + > + for (unsigned int i = 0; i < 3; i++) > + config->gains[i] = UQ<4, 8>(gain_).quantized(); > + > + for (unsigned int i = 0; i < 3; i++) > + config->offs[i] = frameContext.ccm.offsets[i][0]; > + > + const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm; > + for (unsigned int i = 0; i < 3; i++) { > + for (unsigned int j = 0; j < 3; j++) { > + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); > + > + if (val && ccm[i][j] < 0) { > + /* > + * CCM coefficients are expected to be in > + * sign/magnitude format. > + * > + * As we're here handling the case where > + * ccm[i][j] is negative, its quantized > + * representation is stored in 'val' as > + * 2's complement. > + * > + * We need to invert the 2's complement by > + * re-complementing it to 1 and adding 1 back. > + * > + * Let's make a practical example on how to > + * reverse the 2's complement of -7 with 4 bits > + * and BIT(5) for sign. > + * > + * 2's complement (ignore sign bit) > + * - 1's complement of abs(-7) > + * 2^4 - 1 - 7 = 8 -> 1000 > + * - Add one: 8 + 1 = 9 -> 1001 > + * > + * -7 is then [1 1001] in memory in 2's complement > + * > + * Reverse 2's complement (ignore sign bit) > + * - 1's complement of the 2's complement representation > + * 2^4 - 1 - 9 = 6 -> 0110 > + * - add one: 6 + 1 = 7 -> 0111 > + * > + * -7 is then [1 0111] in memory in Sign/Magnitude > + */ > + val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); The rest of the operation seems fine, but I don't quite understand why it's only keeping the 9 LSBs? Won't that cap the integer part of the representation to 1? Dan > + } > + config->coeffs[i][j] = val; > + } > + } > + > + config.setEnabled(true); > + > + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " > + << frameContext.ccm.offsets > + << " with fixed gain " << gain_; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::prepare > + */ > +void Ccm::prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, MaliC55Params *params) > +{ > + if (!frameContext.awb.autoEnabled) { > + setParameters(params, frameContext); > + return; > + } > + > + /* > + * The Mali-C55 Ccm implementation is slightly stricter than the > + * CcmAlgorithm class and only re-interpolates if the colour temperature > + * changes of a certain amount. > + */ > + float ct = frameContext.awb.temperatureK * 1.0f; > + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { > + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; > + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; > + lastCt_ = ct; > + > + return; > + } > + > + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); > + setParameters(params, frameContext); > + lastCt_ = ct; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::process > + */ > +void Ccm::process([[maybe_unused]] IPAContext &context, > + [[maybe_unused]] const uint32_t frame, > + IPAFrameContext &frameContext, > + [[maybe_unused]] const mali_c55_stats_buffer *stats, > + ControlList &metadata) > +{ > + ccmAlgo_.process(frameContext.ccm, metadata); > +} > + > +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h > new file mode 100644 > index 000000000000..73649204a7ee > --- /dev/null > +++ b/src/ipa/mali-c55/algorithms/ccm.h > @@ -0,0 +1,66 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * Mali C55 Color Correction Matrix control algorithm > + */ > + > +#pragma once > + > +#include <linux/media/arm/mali-c55-config.h> > + > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/value_node.h" > + > +#include "libipa/ccm.h" > +#include "libipa/fixedpoint.h" > + > +#include "algorithm.h" > +#include "ipa_context.h" > +#include "params.h" > + > +namespace libcamera { > + > +namespace ipa::mali_c55::algorithms { > + > +class Ccm : public Algorithm > +{ > +public: > + Ccm() {} > + ~Ccm() = default; > + > + int init(IPAContext &context, const ValueNode &tuningData) override; > + int configure(IPAContext &context, > + const IPACameraSensorInfo &configInfo) override; > + void queueRequest(IPAContext &context, > + const uint32_t frame, > + IPAFrameContext &frameContext, > + const ControlList &controls) override; > + void prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + MaliC55Params *params) override; > + void process(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + const mali_c55_stats_buffer *stats, > + ControlList &metadata) override; > + > +private: > + void setParameters(MaliC55Params *params, const IPAFrameContext &context); > + > + /* > + * The CCM coefficient registers are said to be in Q<4,8> but this > + * doesn't include the sign bit as the register is 13 bits wide > + * (Q-format TI variant). > + * > + * As the Quantized class uses the ARM variant of the Q-format notation, > + * make it <5, 8> to include the sign bit. > + */ > + CcmAlgorithm<Q<5, 8>> ccmAlgo_; > + float gain_; > + float lastCt_; > +}; > + > +} /* namespace ipa::mali_c55::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build > index 1665da071634..24a27ce66562 100644 > --- a/src/ipa/mali-c55/algorithms/meson.build > +++ b/src/ipa/mali-c55/algorithms/meson.build > @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ > 'agc.cpp', > 'awb.cpp', > 'blc.cpp', > + 'ccm.cpp', > 'lsc.cpp', > ]) > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > index f6c6938b3cb3..aae4a543c1b7 100644 > --- a/src/ipa/mali-c55/ipa_context.h > +++ b/src/ipa/mali-c55/ipa_context.h > @@ -16,6 +16,7 @@ > #include <libipa/fc_queue.h> > > #include "libipa/awb.h" > +#include "libipa/ccm.h" > #include "libipa/fixedpoint.h" > > namespace libcamera { > @@ -57,6 +58,7 @@ struct IPAActiveState { > } agc; > > ipa::awb::ActiveState awb; > + ipa::ccm::ActiveState ccm; > }; > > struct IPAFrameContext : public FrameContext { > @@ -67,6 +69,7 @@ struct IPAFrameContext : public FrameContext { > } agc; > > ipa::awb::FrameContext awb; > + ipa::ccm::FrameContext ccm; > }; > > struct IPAContext { > diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h > index 3abcb7f94916..2ce55262031d 100644 > --- a/src/ipa/mali-c55/params.h > +++ b/src/ipa/mali-c55/params.h > @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { > AwbConfig, > MeshShadingConfig, > MeshShadingSel, > + Ccm, > }; > > namespace details { > @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); > MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); > +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); > > struct param_traits { > using id_type = MaliC55Blocks; >
Hi Dan On Wed, Jun 24, 2026 at 11:16:48PM +0100, Dan Scally wrote: > Hi Jacopo > > On 23/06/2026 14:55, Jacopo Mondi wrote: > > Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. > > > > Mali has configurable colour gains as part of the CCM hardware block. > > Fix them to 1.0 for the time being as white balance is performed by the > > dedicated Awb algorithm in the Bayer colour space. > > > > The existing Mali Ccm algorithm implementation is a bit more strict on > > when to re-interpolate the CCM coefficients. Keep the algorithm a little > > more strict and only interpolate on 20% color temperature changes. > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > --- > > src/ipa/mali-c55/algorithms/ccm.cpp | 175 ++++++++++++++++++++++++++++++++ > > src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ > > src/ipa/mali-c55/algorithms/meson.build | 1 + > > src/ipa/mali-c55/ipa_context.h | 3 + > > src/ipa/mali-c55/params.h | 2 + > > 5 files changed, 247 insertions(+) > > > > diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp > > new file mode 100644 > > index 000000000000..008ab2d1e31e > > --- /dev/null > > +++ b/src/ipa/mali-c55/algorithms/ccm.cpp > > @@ -0,0 +1,175 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026, Ideas On Board > > + * > > + * Mali C55 Color Correction Matrix control algorithm > > + */ > > + > > +#include "ccm.h" > > + > > +#include <libcamera/base/log.h> > > + > > +/** > > + * \file ccm.h > > + * \brief Mali-C55 CCM implementation > > + */ > > + > > +namespace libcamera { > > + > > +namespace ipa::mali_c55::algorithms { > > + > > +LOG_DEFINE_CATEGORY(MaliC55Ccm) > > + > > +/** > > + * \class Ccm > > + * \brief Mali-C55 color correction matrix algorithm > > + */ > > + > > +/** > > + * \copydoc libcamera::ipa::Algorithm::init > > + */ > > +int Ccm::init(IPAContext &context, const ValueNode &tuningData) > > +{ > > + auto &cmap = context.ctrlMap; > > + int ret = ccmAlgo_.init(tuningData, cmap); > > + if (ret) > > + return ret; > > + > > + /* > > + * Mali-C55 allows to perform WB in the RGB color space as part of the > > + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer > > + * domain. > > + */ > > + gain_ = 1.0; > > + > > + return 0; > > +} > > + > > +/** > > + * \copydoc libcamera::ipa::Algorithm::configure > > + */ > > +int Ccm::configure(IPAContext &context, > > + [[maybe_unused]] const IPACameraSensorInfo &configInfo) > > +{ > > + lastCt_ = context.activeState.awb.automatic.temperatureK; > > + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); > > +} > > + > > +void Ccm::queueRequest(IPAContext &context, > > + [[maybe_unused]] const uint32_t frame, > > + IPAFrameContext &frameContext, > > + const ControlList &controls) > > +{ > > + /* Nothing to do here, the ccm will be calculated in prepare() */ > > + if (frameContext.awb.autoEnabled) > > + return; > > + > > + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); > > +} > > + > > +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) > > +{ > > + auto config = params->block<MaliC55Blocks::Ccm>(); > > + > > + for (unsigned int i = 0; i < 3; i++) > > + config->gains[i] = UQ<4, 8>(gain_).quantized(); > > + > > + for (unsigned int i = 0; i < 3; i++) > > + config->offs[i] = frameContext.ccm.offsets[i][0]; > > + > > + const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm; > > + for (unsigned int i = 0; i < 3; i++) { > > + for (unsigned int j = 0; j < 3; j++) { > > + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); > > + > > + if (val && ccm[i][j] < 0) { > > + /* > > + * CCM coefficients are expected to be in > > + * sign/magnitude format. > > + * > > + * As we're here handling the case where > > + * ccm[i][j] is negative, its quantized > > + * representation is stored in 'val' as > > + * 2's complement. > > + * > > + * We need to invert the 2's complement by > > + * re-complementing it to 1 and adding 1 back. > > + * > > + * Let's make a practical example on how to > > + * reverse the 2's complement of -7 with 4 bits > > + * and BIT(5) for sign. > > + * > > + * 2's complement (ignore sign bit) > > + * - 1's complement of abs(-7) > > + * 2^4 - 1 - 7 = 8 -> 1000 > > + * - Add one: 8 + 1 = 9 -> 1001 > > + * > > + * -7 is then [1 1001] in memory in 2's complement > > + * > > + * Reverse 2's complement (ignore sign bit) > > + * - 1's complement of the 2's complement representation > > + * 2^4 - 1 - 9 = 6 -> 0110 > > + * - add one: 6 + 1 = 7 -> 0111 > > + * > > + * -7 is then [1 0111] in memory in Sign/Magnitude > > + */ > > + val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); > > The rest of the operation seems fine, but I don't quite understand why it's > only keeping the 9 LSBs? Won't that cap the integer part of the > representation to 1? Goooood catch! Am I missing one 'f' ?? I see the computed register indeed not change whenever I got below -1.9999. The CCMs values that get calculated by in auto mode never go below that value, but indeed if I manually set it what I see is: with 0x1ff INFO:server:ColourCorrectionMatrix = [-1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] [0:11:49.072803294] [738] ERROR default ccm.cpp:117 4352 INFO:server:ColourCorrectionMatrix = [-2.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] [0:11:53.741847713] [738] ERROR default ccm.cpp:117 4608 (curious because -2 hits 0x1ff but we add 1 just after so this get past 0x1ff) INFO: Change ColourCorrectionMatrix = [-3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] [0:11:55.341674589] [738] ERROR default ccm.cpp:117 4352 (while this simply is missing BIT(9) with 0x1fff INFO: Change ColourCorrectionMatrix = [-3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] [0:10:40.046278636] [692] ERROR default ccm.cpp:117 4864 which is correct. Curiously, setting -1, -2 or -3 even if the register value is correct I don't see any visual difference. I checked with rwmem and I see the reigster value being changed accordingly to the value programmed by libcamera root@fakemachine:~/rwmem/build/rwmem# ./rwmem 0x1609B080 0x1609b080 (+0x0) = 0x00001300 which is 4864 == -3 Anyway, yes, I should fix the mask. Thanks for the review! > > Dan > > + } > > + config->coeffs[i][j] = val; > > + } > > + } > > + > > + config.setEnabled(true); > > + > > + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " > > + << frameContext.ccm.offsets > > + << " with fixed gain " << gain_; > > +} > > + > > +/** > > + * \copydoc libcamera::ipa::Algorithm::prepare > > + */ > > +void Ccm::prepare(IPAContext &context, const uint32_t frame, > > + IPAFrameContext &frameContext, MaliC55Params *params) > > +{ > > + if (!frameContext.awb.autoEnabled) { > > + setParameters(params, frameContext); > > + return; > > + } > > + > > + /* > > + * The Mali-C55 Ccm implementation is slightly stricter than the > > + * CcmAlgorithm class and only re-interpolates if the colour temperature > > + * changes of a certain amount. > > + */ > > + float ct = frameContext.awb.temperatureK * 1.0f; > > + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { > > + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; > > + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; > > + lastCt_ = ct; > > + > > + return; > > + } > > + > > + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); > > + setParameters(params, frameContext); > > + lastCt_ = ct; > > +} > > + > > +/** > > + * \copydoc libcamera::ipa::Algorithm::process > > + */ > > +void Ccm::process([[maybe_unused]] IPAContext &context, > > + [[maybe_unused]] const uint32_t frame, > > + IPAFrameContext &frameContext, > > + [[maybe_unused]] const mali_c55_stats_buffer *stats, > > + ControlList &metadata) > > +{ > > + ccmAlgo_.process(frameContext.ccm, metadata); > > +} > > + > > +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") > > + > > +} /* namespace ipa::mali_c55::algorithms */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h > > new file mode 100644 > > index 000000000000..73649204a7ee > > --- /dev/null > > +++ b/src/ipa/mali-c55/algorithms/ccm.h > > @@ -0,0 +1,66 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026, Ideas On Board > > + * > > + * Mali C55 Color Correction Matrix control algorithm > > + */ > > + > > +#pragma once > > + > > +#include <linux/media/arm/mali-c55-config.h> > > + > > +#include <libcamera/controls.h> > > + > > +#include "libcamera/internal/value_node.h" > > + > > +#include "libipa/ccm.h" > > +#include "libipa/fixedpoint.h" > > + > > +#include "algorithm.h" > > +#include "ipa_context.h" > > +#include "params.h" > > + > > +namespace libcamera { > > + > > +namespace ipa::mali_c55::algorithms { > > + > > +class Ccm : public Algorithm > > +{ > > +public: > > + Ccm() {} > > + ~Ccm() = default; > > + > > + int init(IPAContext &context, const ValueNode &tuningData) override; > > + int configure(IPAContext &context, > > + const IPACameraSensorInfo &configInfo) override; > > + void queueRequest(IPAContext &context, > > + const uint32_t frame, > > + IPAFrameContext &frameContext, > > + const ControlList &controls) override; > > + void prepare(IPAContext &context, const uint32_t frame, > > + IPAFrameContext &frameContext, > > + MaliC55Params *params) override; > > + void process(IPAContext &context, const uint32_t frame, > > + IPAFrameContext &frameContext, > > + const mali_c55_stats_buffer *stats, > > + ControlList &metadata) override; > > + > > +private: > > + void setParameters(MaliC55Params *params, const IPAFrameContext &context); > > + > > + /* > > + * The CCM coefficient registers are said to be in Q<4,8> but this > > + * doesn't include the sign bit as the register is 13 bits wide > > + * (Q-format TI variant). > > + * > > + * As the Quantized class uses the ARM variant of the Q-format notation, > > + * make it <5, 8> to include the sign bit. > > + */ > > + CcmAlgorithm<Q<5, 8>> ccmAlgo_; > > + float gain_; > > + float lastCt_; > > +}; > > + > > +} /* namespace ipa::mali_c55::algorithms */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build > > index 1665da071634..24a27ce66562 100644 > > --- a/src/ipa/mali-c55/algorithms/meson.build > > +++ b/src/ipa/mali-c55/algorithms/meson.build > > @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ > > 'agc.cpp', > > 'awb.cpp', > > 'blc.cpp', > > + 'ccm.cpp', > > 'lsc.cpp', > > ]) > > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h > > index f6c6938b3cb3..aae4a543c1b7 100644 > > --- a/src/ipa/mali-c55/ipa_context.h > > +++ b/src/ipa/mali-c55/ipa_context.h > > @@ -16,6 +16,7 @@ > > #include <libipa/fc_queue.h> > > #include "libipa/awb.h" > > +#include "libipa/ccm.h" > > #include "libipa/fixedpoint.h" > > namespace libcamera { > > @@ -57,6 +58,7 @@ struct IPAActiveState { > > } agc; > > ipa::awb::ActiveState awb; > > + ipa::ccm::ActiveState ccm; > > }; > > struct IPAFrameContext : public FrameContext { > > @@ -67,6 +69,7 @@ struct IPAFrameContext : public FrameContext { > > } agc; > > ipa::awb::FrameContext awb; > > + ipa::ccm::FrameContext ccm; > > }; > > struct IPAContext { > > diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h > > index 3abcb7f94916..2ce55262031d 100644 > > --- a/src/ipa/mali-c55/params.h > > +++ b/src/ipa/mali-c55/params.h > > @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { > > AwbConfig, > > MeshShadingConfig, > > MeshShadingSel, > > + Ccm, > > }; > > namespace details { > > @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); > > MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); > > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); > > MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); > > +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); > > struct param_traits { > > using id_type = MaliC55Blocks; > > >
Hi Jacopo On 26/06/2026 13:15, Jacopo Mondi wrote: > Hi Dan > > On Wed, Jun 24, 2026 at 11:16:48PM +0100, Dan Scally wrote: >> Hi Jacopo >> >> On 23/06/2026 14:55, Jacopo Mondi wrote: >>> Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. >>> >>> Mali has configurable colour gains as part of the CCM hardware block. >>> Fix them to 1.0 for the time being as white balance is performed by the >>> dedicated Awb algorithm in the Bayer colour space. >>> >>> The existing Mali Ccm algorithm implementation is a bit more strict on >>> when to re-interpolate the CCM coefficients. Keep the algorithm a little >>> more strict and only interpolate on 20% color temperature changes. >>> >>> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> >>> --- >>> src/ipa/mali-c55/algorithms/ccm.cpp | 175 ++++++++++++++++++++++++++++++++ >>> src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ >>> src/ipa/mali-c55/algorithms/meson.build | 1 + >>> src/ipa/mali-c55/ipa_context.h | 3 + >>> src/ipa/mali-c55/params.h | 2 + >>> 5 files changed, 247 insertions(+) >>> >>> diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp >>> new file mode 100644 >>> index 000000000000..008ab2d1e31e >>> --- /dev/null >>> +++ b/src/ipa/mali-c55/algorithms/ccm.cpp >>> @@ -0,0 +1,175 @@ >>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >>> +/* >>> + * Copyright (C) 2026, Ideas On Board >>> + * >>> + * Mali C55 Color Correction Matrix control algorithm >>> + */ >>> + >>> +#include "ccm.h" >>> + >>> +#include <libcamera/base/log.h> >>> + >>> +/** >>> + * \file ccm.h >>> + * \brief Mali-C55 CCM implementation >>> + */ >>> + >>> +namespace libcamera { >>> + >>> +namespace ipa::mali_c55::algorithms { >>> + >>> +LOG_DEFINE_CATEGORY(MaliC55Ccm) >>> + >>> +/** >>> + * \class Ccm >>> + * \brief Mali-C55 color correction matrix algorithm >>> + */ >>> + >>> +/** >>> + * \copydoc libcamera::ipa::Algorithm::init >>> + */ >>> +int Ccm::init(IPAContext &context, const ValueNode &tuningData) >>> +{ >>> + auto &cmap = context.ctrlMap; >>> + int ret = ccmAlgo_.init(tuningData, cmap); >>> + if (ret) >>> + return ret; >>> + >>> + /* >>> + * Mali-C55 allows to perform WB in the RGB color space as part of the >>> + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer >>> + * domain. >>> + */ >>> + gain_ = 1.0; >>> + >>> + return 0; >>> +} >>> + >>> +/** >>> + * \copydoc libcamera::ipa::Algorithm::configure >>> + */ >>> +int Ccm::configure(IPAContext &context, >>> + [[maybe_unused]] const IPACameraSensorInfo &configInfo) >>> +{ >>> + lastCt_ = context.activeState.awb.automatic.temperatureK; >>> + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); >>> +} >>> + >>> +void Ccm::queueRequest(IPAContext &context, >>> + [[maybe_unused]] const uint32_t frame, >>> + IPAFrameContext &frameContext, >>> + const ControlList &controls) >>> +{ >>> + /* Nothing to do here, the ccm will be calculated in prepare() */ >>> + if (frameContext.awb.autoEnabled) >>> + return; >>> + >>> + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); >>> +} >>> + >>> +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) >>> +{ >>> + auto config = params->block<MaliC55Blocks::Ccm>(); >>> + >>> + for (unsigned int i = 0; i < 3; i++) >>> + config->gains[i] = UQ<4, 8>(gain_).quantized(); >>> + >>> + for (unsigned int i = 0; i < 3; i++) >>> + config->offs[i] = frameContext.ccm.offsets[i][0]; >>> + >>> + const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm; >>> + for (unsigned int i = 0; i < 3; i++) { >>> + for (unsigned int j = 0; j < 3; j++) { >>> + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); >>> + >>> + if (val && ccm[i][j] < 0) { >>> + /* >>> + * CCM coefficients are expected to be in >>> + * sign/magnitude format. >>> + * >>> + * As we're here handling the case where >>> + * ccm[i][j] is negative, its quantized >>> + * representation is stored in 'val' as >>> + * 2's complement. >>> + * >>> + * We need to invert the 2's complement by >>> + * re-complementing it to 1 and adding 1 back. >>> + * >>> + * Let's make a practical example on how to >>> + * reverse the 2's complement of -7 with 4 bits >>> + * and BIT(5) for sign. >>> + * >>> + * 2's complement (ignore sign bit) >>> + * - 1's complement of abs(-7) >>> + * 2^4 - 1 - 7 = 8 -> 1000 >>> + * - Add one: 8 + 1 = 9 -> 1001 >>> + * >>> + * -7 is then [1 1001] in memory in 2's complement >>> + * >>> + * Reverse 2's complement (ignore sign bit) >>> + * - 1's complement of the 2's complement representation >>> + * 2^4 - 1 - 9 = 6 -> 0110 >>> + * - add one: 6 + 1 = 7 -> 0111 >>> + * >>> + * -7 is then [1 0111] in memory in Sign/Magnitude >>> + */ >>> + val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); >> >> The rest of the operation seems fine, but I don't quite understand why it's >> only keeping the 9 LSBs? Won't that cap the integer part of the >> representation to 1? > > Goooood catch! > > Am I missing one 'f' ?? > I see the computed register indeed not change whenever I got below > -1.9999. > > The CCMs values that get calculated by in auto mode never go below > that value, but indeed if I manually set it what I see is: > > with 0x1ff > > INFO:server:ColourCorrectionMatrix = [-1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] > [0:11:49.072803294] [738] ERROR default ccm.cpp:117 4352 > > INFO:server:ColourCorrectionMatrix = [-2.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] > [0:11:53.741847713] [738] ERROR default ccm.cpp:117 4608 > > (curious because -2 hits 0x1ff but we add 1 just after so this > get past 0x1ff) > > INFO: Change ColourCorrectionMatrix = [-3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] > [0:11:55.341674589] [738] ERROR default ccm.cpp:117 4352 > > (while this simply is missing BIT(9) > > with 0x1fff > > INFO: Change ColourCorrectionMatrix = [-3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] > [0:10:40.046278636] [692] ERROR default ccm.cpp:117 4864 > > which is correct. > > Curiously, setting -1, -2 or -3 even if the register value is correct > I don't see any visual difference. I think you wouldn't see any visual difference, because you're setting a negative adjustment to the red component of a red pixel, so -1.0 is already "take away all the red"...or maybe I'm misunderstanding how CCM works, but I thought that -1.0 is the effective limit anyway. > > I checked with rwmem and I see the reigster value being changed > accordingly to the value programmed by libcamera > > root@fakemachine:~/rwmem/build/rwmem# ./rwmem 0x1609B080 > 0x1609b080 (+0x0) = 0x00001300 > > which is 4864 == -3 > > Anyway, yes, I should fix the mask. > Thanks for the review! Welcome! Dan > >> >> Dan >>> + } >>> + config->coeffs[i][j] = val; >>> + } >>> + } >>> + >>> + config.setEnabled(true); >>> + >>> + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " >>> + << frameContext.ccm.offsets >>> + << " with fixed gain " << gain_; >>> +} >>> + >>> +/** >>> + * \copydoc libcamera::ipa::Algorithm::prepare >>> + */ >>> +void Ccm::prepare(IPAContext &context, const uint32_t frame, >>> + IPAFrameContext &frameContext, MaliC55Params *params) >>> +{ >>> + if (!frameContext.awb.autoEnabled) { >>> + setParameters(params, frameContext); >>> + return; >>> + } >>> + >>> + /* >>> + * The Mali-C55 Ccm implementation is slightly stricter than the >>> + * CcmAlgorithm class and only re-interpolates if the colour temperature >>> + * changes of a certain amount. >>> + */ >>> + float ct = frameContext.awb.temperatureK * 1.0f; >>> + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { >>> + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; >>> + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; >>> + lastCt_ = ct; >>> + >>> + return; >>> + } >>> + >>> + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); >>> + setParameters(params, frameContext); >>> + lastCt_ = ct; >>> +} >>> + >>> +/** >>> + * \copydoc libcamera::ipa::Algorithm::process >>> + */ >>> +void Ccm::process([[maybe_unused]] IPAContext &context, >>> + [[maybe_unused]] const uint32_t frame, >>> + IPAFrameContext &frameContext, >>> + [[maybe_unused]] const mali_c55_stats_buffer *stats, >>> + ControlList &metadata) >>> +{ >>> + ccmAlgo_.process(frameContext.ccm, metadata); >>> +} >>> + >>> +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") >>> + >>> +} /* namespace ipa::mali_c55::algorithms */ >>> + >>> +} /* namespace libcamera */ >>> diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h >>> new file mode 100644 >>> index 000000000000..73649204a7ee >>> --- /dev/null >>> +++ b/src/ipa/mali-c55/algorithms/ccm.h >>> @@ -0,0 +1,66 @@ >>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >>> +/* >>> + * Copyright (C) 2026, Ideas On Board >>> + * >>> + * Mali C55 Color Correction Matrix control algorithm >>> + */ >>> + >>> +#pragma once >>> + >>> +#include <linux/media/arm/mali-c55-config.h> >>> + >>> +#include <libcamera/controls.h> >>> + >>> +#include "libcamera/internal/value_node.h" >>> + >>> +#include "libipa/ccm.h" >>> +#include "libipa/fixedpoint.h" >>> + >>> +#include "algorithm.h" >>> +#include "ipa_context.h" >>> +#include "params.h" >>> + >>> +namespace libcamera { >>> + >>> +namespace ipa::mali_c55::algorithms { >>> + >>> +class Ccm : public Algorithm >>> +{ >>> +public: >>> + Ccm() {} >>> + ~Ccm() = default; >>> + >>> + int init(IPAContext &context, const ValueNode &tuningData) override; >>> + int configure(IPAContext &context, >>> + const IPACameraSensorInfo &configInfo) override; >>> + void queueRequest(IPAContext &context, >>> + const uint32_t frame, >>> + IPAFrameContext &frameContext, >>> + const ControlList &controls) override; >>> + void prepare(IPAContext &context, const uint32_t frame, >>> + IPAFrameContext &frameContext, >>> + MaliC55Params *params) override; >>> + void process(IPAContext &context, const uint32_t frame, >>> + IPAFrameContext &frameContext, >>> + const mali_c55_stats_buffer *stats, >>> + ControlList &metadata) override; >>> + >>> +private: >>> + void setParameters(MaliC55Params *params, const IPAFrameContext &context); >>> + >>> + /* >>> + * The CCM coefficient registers are said to be in Q<4,8> but this >>> + * doesn't include the sign bit as the register is 13 bits wide >>> + * (Q-format TI variant). >>> + * >>> + * As the Quantized class uses the ARM variant of the Q-format notation, >>> + * make it <5, 8> to include the sign bit. >>> + */ >>> + CcmAlgorithm<Q<5, 8>> ccmAlgo_; >>> + float gain_; >>> + float lastCt_; >>> +}; >>> + >>> +} /* namespace ipa::mali_c55::algorithms */ >>> + >>> +} /* namespace libcamera */ >>> diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build >>> index 1665da071634..24a27ce66562 100644 >>> --- a/src/ipa/mali-c55/algorithms/meson.build >>> +++ b/src/ipa/mali-c55/algorithms/meson.build >>> @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ >>> 'agc.cpp', >>> 'awb.cpp', >>> 'blc.cpp', >>> + 'ccm.cpp', >>> 'lsc.cpp', >>> ]) >>> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h >>> index f6c6938b3cb3..aae4a543c1b7 100644 >>> --- a/src/ipa/mali-c55/ipa_context.h >>> +++ b/src/ipa/mali-c55/ipa_context.h >>> @@ -16,6 +16,7 @@ >>> #include <libipa/fc_queue.h> >>> #include "libipa/awb.h" >>> +#include "libipa/ccm.h" >>> #include "libipa/fixedpoint.h" >>> namespace libcamera { >>> @@ -57,6 +58,7 @@ struct IPAActiveState { >>> } agc; >>> ipa::awb::ActiveState awb; >>> + ipa::ccm::ActiveState ccm; >>> }; >>> struct IPAFrameContext : public FrameContext { >>> @@ -67,6 +69,7 @@ struct IPAFrameContext : public FrameContext { >>> } agc; >>> ipa::awb::FrameContext awb; >>> + ipa::ccm::FrameContext ccm; >>> }; >>> struct IPAContext { >>> diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h >>> index 3abcb7f94916..2ce55262031d 100644 >>> --- a/src/ipa/mali-c55/params.h >>> +++ b/src/ipa/mali-c55/params.h >>> @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { >>> AwbConfig, >>> MeshShadingConfig, >>> MeshShadingSel, >>> + Ccm, >>> }; >>> namespace details { >>> @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); >>> MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); >>> MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); >>> MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); >>> +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); >>> struct param_traits { >>> using id_type = MaliC55Blocks; >>> >>
diff --git a/src/ipa/mali-c55/algorithms/ccm.cpp b/src/ipa/mali-c55/algorithms/ccm.cpp new file mode 100644 index 000000000000..008ab2d1e31e --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.cpp @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#include "ccm.h" + +#include <libcamera/base/log.h> + +/** + * \file ccm.h + * \brief Mali-C55 CCM implementation + */ + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +LOG_DEFINE_CATEGORY(MaliC55Ccm) + +/** + * \class Ccm + * \brief Mali-C55 color correction matrix algorithm + */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int Ccm::init(IPAContext &context, const ValueNode &tuningData) +{ + auto &cmap = context.ctrlMap; + int ret = ccmAlgo_.init(tuningData, cmap); + if (ret) + return ret; + + /* + * Mali-C55 allows to perform WB in the RGB color space as part of the + * CCM. Fix the gains at 1.0 as we perform White Balance in the Bayer + * domain. + */ + gain_ = 1.0; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::configure + */ +int Ccm::configure(IPAContext &context, + [[maybe_unused]] const IPACameraSensorInfo &configInfo) +{ + lastCt_ = context.activeState.awb.automatic.temperatureK; + return ccmAlgo_.configure(context.activeState.ccm, lastCt_); +} + +void Ccm::queueRequest(IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) +{ + /* Nothing to do here, the ccm will be calculated in prepare() */ + if (frameContext.awb.autoEnabled) + return; + + ccmAlgo_.queueRequest(context.activeState.ccm, frameContext.ccm, controls); +} + +void Ccm::setParameters(MaliC55Params *params, const IPAFrameContext &frameContext) +{ + auto config = params->block<MaliC55Blocks::Ccm>(); + + for (unsigned int i = 0; i < 3; i++) + config->gains[i] = UQ<4, 8>(gain_).quantized(); + + for (unsigned int i = 0; i < 3; i++) + config->offs[i] = frameContext.ccm.offsets[i][0]; + + const Matrix<float, 3, 3> &ccm = frameContext.ccm.ccm; + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) { + uint16_t val = Q<5, 8>(ccm[i][j]).quantized(); + + if (val && ccm[i][j] < 0) { + /* + * CCM coefficients are expected to be in + * sign/magnitude format. + * + * As we're here handling the case where + * ccm[i][j] is negative, its quantized + * representation is stored in 'val' as + * 2's complement. + * + * We need to invert the 2's complement by + * re-complementing it to 1 and adding 1 back. + * + * Let's make a practical example on how to + * reverse the 2's complement of -7 with 4 bits + * and BIT(5) for sign. + * + * 2's complement (ignore sign bit) + * - 1's complement of abs(-7) + * 2^4 - 1 - 7 = 8 -> 1000 + * - Add one: 8 + 1 = 9 -> 1001 + * + * -7 is then [1 1001] in memory in 2's complement + * + * Reverse 2's complement (ignore sign bit) + * - 1's complement of the 2's complement representation + * 2^4 - 1 - 9 = 6 -> 0110 + * - add one: 6 + 1 = 7 -> 0111 + * + * -7 is then [1 0111] in memory in Sign/Magnitude + */ + val = ((~(val & ~(1 << 12)) & 0x1ff) + 1) | (1 << 12); + } + config->coeffs[i][j] = val; + } + } + + config.setEnabled(true); + + LOG(MaliC55Ccm, Debug) << "Setting ccm: " << ccm << " offsets: " + << frameContext.ccm.offsets + << " with fixed gain " << gain_; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Ccm::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) +{ + if (!frameContext.awb.autoEnabled) { + setParameters(params, frameContext); + return; + } + + /* + * The Mali-C55 Ccm implementation is slightly stricter than the + * CcmAlgorithm class and only re-interpolates if the colour temperature + * changes of a certain amount. + */ + float ct = frameContext.awb.temperatureK * 1.0f; + if (frame > 0 && (ct < lastCt_ * 1.2 && ct > lastCt_ * 0.8)) { + frameContext.ccm.ccm = context.activeState.ccm.automatic.ccm; + frameContext.ccm.offsets = context.activeState.ccm.automatic.offsets; + lastCt_ = ct; + + return; + } + + ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame, ct); + setParameters(params, frameContext); + lastCt_ = ct; +} + +/** + * \copydoc libcamera::ipa::Algorithm::process + */ +void Ccm::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] const mali_c55_stats_buffer *stats, + ControlList &metadata) +{ + ccmAlgo_.process(frameContext.ccm, metadata); +} + +REGISTER_IPA_ALGORITHM(Ccm, "Ccm") + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/ccm.h b/src/ipa/mali-c55/algorithms/ccm.h new file mode 100644 index 000000000000..73649204a7ee --- /dev/null +++ b/src/ipa/mali-c55/algorithms/ccm.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * Mali C55 Color Correction Matrix control algorithm + */ + +#pragma once + +#include <linux/media/arm/mali-c55-config.h> + +#include <libcamera/controls.h> + +#include "libcamera/internal/value_node.h" + +#include "libipa/ccm.h" +#include "libipa/fixedpoint.h" + +#include "algorithm.h" +#include "ipa_context.h" +#include "params.h" + +namespace libcamera { + +namespace ipa::mali_c55::algorithms { + +class Ccm : public Algorithm +{ +public: + Ccm() {} + ~Ccm() = default; + + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, + const IPACameraSensorInfo &configInfo) override; + void queueRequest(IPAContext &context, + const uint32_t frame, + IPAFrameContext &frameContext, + const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + MaliC55Params *params) override; + void process(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + const mali_c55_stats_buffer *stats, + ControlList &metadata) override; + +private: + void setParameters(MaliC55Params *params, const IPAFrameContext &context); + + /* + * The CCM coefficient registers are said to be in Q<4,8> but this + * doesn't include the sign bit as the register is 13 bits wide + * (Q-format TI variant). + * + * As the Quantized class uses the ARM variant of the Q-format notation, + * make it <5, 8> to include the sign bit. + */ + CcmAlgorithm<Q<5, 8>> ccmAlgo_; + float gain_; + float lastCt_; +}; + +} /* namespace ipa::mali_c55::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/mali-c55/algorithms/meson.build b/src/ipa/mali-c55/algorithms/meson.build index 1665da071634..24a27ce66562 100644 --- a/src/ipa/mali-c55/algorithms/meson.build +++ b/src/ipa/mali-c55/algorithms/meson.build @@ -4,5 +4,6 @@ mali_c55_ipa_algorithms = files([ 'agc.cpp', 'awb.cpp', 'blc.cpp', + 'ccm.cpp', 'lsc.cpp', ]) diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index f6c6938b3cb3..aae4a543c1b7 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -16,6 +16,7 @@ #include <libipa/fc_queue.h> #include "libipa/awb.h" +#include "libipa/ccm.h" #include "libipa/fixedpoint.h" namespace libcamera { @@ -57,6 +58,7 @@ struct IPAActiveState { } agc; ipa::awb::ActiveState awb; + ipa::ccm::ActiveState ccm; }; struct IPAFrameContext : public FrameContext { @@ -67,6 +69,7 @@ struct IPAFrameContext : public FrameContext { } agc; ipa::awb::FrameContext awb; + ipa::ccm::FrameContext ccm; }; struct IPAContext { diff --git a/src/ipa/mali-c55/params.h b/src/ipa/mali-c55/params.h index 3abcb7f94916..2ce55262031d 100644 --- a/src/ipa/mali-c55/params.h +++ b/src/ipa/mali-c55/params.h @@ -29,6 +29,7 @@ enum class MaliC55Blocks : uint16_t { AwbConfig, MeshShadingConfig, MeshShadingSel, + Ccm, }; namespace details { @@ -55,6 +56,7 @@ MALI_C55_DEFINE_BLOCK_TYPE(AwbGains, awb_gains, BLOCK_AWB_GAINS); MALI_C55_DEFINE_BLOCK_TYPE(AwbConfig, awb_config, BLOCK_AWB_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingConfig, mesh_shading_config, MESH_SHADING_CONFIG); MALI_C55_DEFINE_BLOCK_TYPE(MeshShadingSel, mesh_shading_selection, MESH_SHADING_SELECTION); +MALI_C55_DEFINE_BLOCK_TYPE(Ccm, ccm, BLOCK_CCM); struct param_traits { using id_type = MaliC55Blocks;
Implement Ccm algorithm for Mali-C55 base on the libipa CcmAlgorithm class. Mali has configurable colour gains as part of the CCM hardware block. Fix them to 1.0 for the time being as white balance is performed by the dedicated Awb algorithm in the Bayer colour space. The existing Mali Ccm algorithm implementation is a bit more strict on when to re-interpolate the CCM coefficients. Keep the algorithm a little more strict and only interpolate on 20% color temperature changes. Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> --- src/ipa/mali-c55/algorithms/ccm.cpp | 175 ++++++++++++++++++++++++++++++++ src/ipa/mali-c55/algorithms/ccm.h | 66 ++++++++++++ src/ipa/mali-c55/algorithms/meson.build | 1 + src/ipa/mali-c55/ipa_context.h | 3 + src/ipa/mali-c55/params.h | 2 + 5 files changed, 247 insertions(+)