[04/10] ipa: ipu3: ccm: Add a Colour Correction Matrix algorithm
diff mbox series

Message ID 20260616-ipu3-libipa-rework-v1-4-d4448b54f1d8@ideasonboard.com
State Superseded
Headers show
Series
  • libipa: Re-work IPU3 IPA to use libipa algorithms
Related show

Commit Message

Dan Scally June 16, 2026, 6:41 a.m. UTC
Add a Colour Correction Matrix algorithm that uses the new libipa
implementation. The module isn't well documented in the kernel but
the default values from the driver suggest a Q3.13 format.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
 src/ipa/ipu3/algorithms/ccm.cpp     | 116 ++++++++++++++++++++++++++++++++++++
 src/ipa/ipu3/algorithms/ccm.h       |  43 +++++++++++++
 src/ipa/ipu3/algorithms/meson.build |   1 +
 src/ipa/ipu3/ipa_context.cpp        |  10 ++++
 src/ipa/ipu3/ipa_context.h          |   3 +
 5 files changed, 173 insertions(+)

Comments

Kieran Bingham June 16, 2026, 6:32 p.m. UTC | #1
Quoting Daniel Scally (2026-06-16 07:41:38)
> Add a Colour Correction Matrix algorithm that uses the new libipa
> implementation. The module isn't well documented in the kernel but
> the default values from the driver suggest a Q3.13 format.
> 
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
>  src/ipa/ipu3/algorithms/ccm.cpp     | 116 ++++++++++++++++++++++++++++++++++++
>  src/ipa/ipu3/algorithms/ccm.h       |  43 +++++++++++++
>  src/ipa/ipu3/algorithms/meson.build |   1 +
>  src/ipa/ipu3/ipa_context.cpp        |  10 ++++
>  src/ipa/ipu3/ipa_context.h          |   3 +
>  5 files changed, 173 insertions(+)
> 
> diff --git a/src/ipa/ipu3/algorithms/ccm.cpp b/src/ipa/ipu3/algorithms/ccm.cpp
> new file mode 100644
> index 0000000000000000000000000000000000000000..44b8c107b079c544c5afd1fce3cbe355f6dacd76
> --- /dev/null
> +++ b/src/ipa/ipu3/algorithms/ccm.cpp
> @@ -0,0 +1,116 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Ideas On Board
> + *
> + * IPU3 Colour correction matrix algorithm
> + */
> +
> +#include "ccm.h"
> +
> +/**
> + * \file ccm.h
> + */
> +
> +namespace libcamera {
> +
> +namespace ipa::ipu3::algorithms {
> +
> +/**
> + * \class Ccm
> + * \brief The IPU3 color correction matrix algorithm
> + */
> +
> +LOG_DEFINE_CATEGORY(IPU3Ccm)
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::init
> + */
> +int Ccm::init(IPAContext &context, const ValueNode &tuningData)
> +{
> +       return ccmAlgo_.init(tuningData, context.ctrlMap);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::configure
> + */
> +int Ccm::configure(IPAContext &context,
> +                  [[maybe_unused]] const IPAConfigInfo &configInfo)
> +{
> +       return ccmAlgo_.configure(context.activeState.ccm,
> +                                 context.activeState.awb.automatic.temperatureK);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +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);
> +}

I still think it's awesome seeing how easy the implementations become
with the composition patterns here.


I got to the bottom of the file with nothing else to add, so I've come
back up here to say:

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

Will be very interesting to see some calibration through the lightbox to
test this.



> +
> +void Ccm::setParameters(ipu3_uapi_params *params, IPAFrameContext &context)
> +{
> +       const Matrix<float, 3, 3> &matrix = context.ccm.ccm;
> +       const Matrix<int16_t, 3, 1> &offsets = context.ccm.offsets;
> +
> +       params->use.acc_ccm = 1;
> +
> +       params->acc_param.ccm.coeff_m11 = Q<3, 13>(matrix[0][0]).quantized();
> +       params->acc_param.ccm.coeff_m12 = Q<3, 13>(matrix[0][1]).quantized();
> +       params->acc_param.ccm.coeff_m13 = Q<3, 13>(matrix[0][2]).quantized();
> +       params->acc_param.ccm.coeff_o_r = offsets[0][0];
> +
> +       params->acc_param.ccm.coeff_m21 = Q<3, 13>(matrix[1][0]).quantized();
> +       params->acc_param.ccm.coeff_m22 = Q<3, 13>(matrix[1][1]).quantized();
> +       params->acc_param.ccm.coeff_m23 = Q<3, 13>(matrix[1][2]).quantized();
> +       params->acc_param.ccm.coeff_o_g = offsets[1][0];
> +
> +       params->acc_param.ccm.coeff_m31 = Q<3, 13>(matrix[2][0]).quantized();
> +       params->acc_param.ccm.coeff_m32 = Q<3, 13>(matrix[2][1]).quantized();
> +       params->acc_param.ccm.coeff_m33 = Q<3, 13>(matrix[2][2]).quantized();
> +       params->acc_param.ccm.coeff_o_b = offsets[2][0];
> +
> +       LOG(IPU3Ccm, Debug) << "Setting matrix " << matrix;
> +       LOG(IPU3Ccm, Debug) << "Setting offsets " << offsets;
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::prepare
> + */
> +void Ccm::prepare(IPAContext &context, const uint32_t frame,
> +                 IPAFrameContext &frameContext, ipu3_uapi_params *params)
> +{
> +       if (!frameContext.awb.autoEnabled) {
> +               setParameters(params, frameContext);
> +               return;
> +       }
> +
> +       ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame,
> +                        frameContext.awb.temperatureK);
> +
> +       setParameters(params, frameContext);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::process
> + */
> +void Ccm::process([[maybe_unused]] IPAContext &context,
> +                 [[maybe_unused]] const uint32_t frame,
> +                 IPAFrameContext &frameContext,
> +                 [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
> +                 ControlList &metadata)
> +{
> +       ccmAlgo_.process(frameContext.ccm, metadata);
> +}
> +
> +REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
> +
> +} /* namespace ipa::ipu3::algorithms */
> +
> +} /* namespace libcamera */
> diff --git a/src/ipa/ipu3/algorithms/ccm.h b/src/ipa/ipu3/algorithms/ccm.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..39719c986267aff02e3129aad9dbe4baeec8f8a1
> --- /dev/null
> +++ b/src/ipa/ipu3/algorithms/ccm.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Ideas On Board
> + *
> + * IPU3 Colour correction matrix algorithm
> + */
> +
> +#pragma once
> +
> +#include <linux/intel-ipu3.h>
> +
> +#include <libipa/ccm.h>
> +#include <libipa/fixedpoint.h>
> +
> +#include "algorithm.h"
> +
> +namespace libcamera {
> +
> +namespace ipa::ipu3::algorithms {
> +
> +class Ccm : public Algorithm
> +{
> +public:
> +       int init(IPAContext &context, const ValueNode &tuningData) override;
> +       int configure(IPAContext &context, const IPAConfigInfo &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,
> +                    ipu3_uapi_params *params) override;
> +       void process(IPAContext &context, const uint32_t frame,
> +                    IPAFrameContext &frameContext,
> +                    const ipu3_uapi_stats_3a *stats,
> +                    ControlList &metadata) override;
> +
> +private:
> +       void setParameters(ipu3_uapi_params *params, IPAFrameContext &context);
> +       CcmAlgorithm<Q<3, 13>> ccmAlgo_;
> +};
> +
> +} /* namespace ipa::ipu3::algorithms */
> +} /* namespace libcamera */
> diff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build
> index b70a551cacf6cbdedf3ab41771a34ddcdae9fd8b..3dafd2fda9897942cf87d9640665c4fcff383859 100644
> --- a/src/ipa/ipu3/algorithms/meson.build
> +++ b/src/ipa/ipu3/algorithms/meson.build
> @@ -5,5 +5,6 @@ ipu3_ipa_algorithms = files([
>      'agc.cpp',
>      'awb.cpp',
>      'blc.cpp',
> +    'ccm.cpp',
>      'tone_mapping.cpp',
>  ])
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index b35c925d959027c540257e47944047c238f85571..dc43bc0877ed5c2e7287414e12667374f5ee1c80 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -114,6 +114,11 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Active auto-white balance parameters for the IPA
>   */
>  
> +/**
> + * \var IPAActiveState::ccm
> + * \brief Active colour Correction Matrix parameters for the IPA
> + */
> +
>  /**
>   * \var IPASessionConfiguration::sensor
>   * \brief Sensor-specific configuration of the IPA
> @@ -182,4 +187,9 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Per-frame auto-white balance parameters for the IPA
>   */
>  
> +/**
> + * \var IPAFrameContext::ccm
> + * \brief Per-frame colour Correction Matrix parameters for the IPA
> + */
> +
>  } /* namespace libcamera::ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index 245cf8b50b270a61df863e314128bede40d30541..be626d30d966b1bdaa322e5154f95f745f799976 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -16,6 +16,7 @@
>  #include <libcamera/geometry.h>
>  
>  #include <libipa/awb.h>
> +#include <libipa/ccm.h>
>  #include <libipa/fc_queue.h>
>  
>  namespace libcamera {
> @@ -64,6 +65,7 @@ struct IPAActiveState {
>         } agc;
>  
>         ipa::awb::ActiveState awb;
> +       ipa::ccm::ActiveState ccm;
>  
>         struct {
>                 double gamma;
> @@ -78,6 +80,7 @@ struct IPAFrameContext : public FrameContext {
>         } sensor;
>  
>         ipa::awb::FrameContext awb;
> +       ipa::ccm::FrameContext ccm;
>  };
>  
>  struct IPAContext {
> 
> -- 
> 2.43.0
>
Jacopo Mondi June 17, 2026, 8:43 a.m. UTC | #2
Hi Dan

On Tue, Jun 16, 2026 at 07:41:38AM +0100, Daniel Scally wrote:
> Add a Colour Correction Matrix algorithm that uses the new libipa
> implementation. The module isn't well documented in the kernel but
> the default values from the driver suggest a Q3.13 format.
>
> Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
> ---
>  src/ipa/ipu3/algorithms/ccm.cpp     | 116 ++++++++++++++++++++++++++++++++++++
>  src/ipa/ipu3/algorithms/ccm.h       |  43 +++++++++++++
>  src/ipa/ipu3/algorithms/meson.build |   1 +
>  src/ipa/ipu3/ipa_context.cpp        |  10 ++++
>  src/ipa/ipu3/ipa_context.h          |   3 +
>  5 files changed, 173 insertions(+)
>
> diff --git a/src/ipa/ipu3/algorithms/ccm.cpp b/src/ipa/ipu3/algorithms/ccm.cpp
> new file mode 100644
> index 0000000000000000000000000000000000000000..44b8c107b079c544c5afd1fce3cbe355f6dacd76
> --- /dev/null
> +++ b/src/ipa/ipu3/algorithms/ccm.cpp
> @@ -0,0 +1,116 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Ideas On Board
> + *
> + * IPU3 Colour correction matrix algorithm
> + */
> +
> +#include "ccm.h"
> +
> +/**
> + * \file ccm.h
> + */
> +
> +namespace libcamera {
> +
> +namespace ipa::ipu3::algorithms {
> +
> +/**
> + * \class Ccm
> + * \brief The IPU3 color correction matrix algorithm
> + */
> +
> +LOG_DEFINE_CATEGORY(IPU3Ccm)
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::init
> + */
> +int Ccm::init(IPAContext &context, const ValueNode &tuningData)
> +{
> +	return ccmAlgo_.init(tuningData, context.ctrlMap);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::configure
> + */
> +int Ccm::configure(IPAContext &context,
> +		   [[maybe_unused]] const IPAConfigInfo &configInfo)
> +{
> +	return ccmAlgo_.configure(context.activeState.ccm,
> +				  context.activeState.awb.automatic.temperatureK);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +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(ipu3_uapi_params *params, IPAFrameContext &context)
> +{
> +	const Matrix<float, 3, 3> &matrix = context.ccm.ccm;
> +	const Matrix<int16_t, 3, 1> &offsets = context.ccm.offsets;
> +
> +	params->use.acc_ccm = 1;
> +
> +	params->acc_param.ccm.coeff_m11 = Q<3, 13>(matrix[0][0]).quantized();
> +	params->acc_param.ccm.coeff_m12 = Q<3, 13>(matrix[0][1]).quantized();
> +	params->acc_param.ccm.coeff_m13 = Q<3, 13>(matrix[0][2]).quantized();
> +	params->acc_param.ccm.coeff_o_r = offsets[0][0];
> +
> +	params->acc_param.ccm.coeff_m21 = Q<3, 13>(matrix[1][0]).quantized();
> +	params->acc_param.ccm.coeff_m22 = Q<3, 13>(matrix[1][1]).quantized();
> +	params->acc_param.ccm.coeff_m23 = Q<3, 13>(matrix[1][2]).quantized();
> +	params->acc_param.ccm.coeff_o_g = offsets[1][0];
> +
> +	params->acc_param.ccm.coeff_m31 = Q<3, 13>(matrix[2][0]).quantized();
> +	params->acc_param.ccm.coeff_m32 = Q<3, 13>(matrix[2][1]).quantized();
> +	params->acc_param.ccm.coeff_m33 = Q<3, 13>(matrix[2][2]).quantized();
> +	params->acc_param.ccm.coeff_o_b = offsets[2][0];
> +
> +	LOG(IPU3Ccm, Debug) << "Setting matrix " << matrix;
> +	LOG(IPU3Ccm, Debug) << "Setting offsets " << offsets;
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::prepare
> + */
> +void Ccm::prepare(IPAContext &context, const uint32_t frame,
> +		  IPAFrameContext &frameContext, ipu3_uapi_params *params)
> +{
> +	if (!frameContext.awb.autoEnabled) {
> +		setParameters(params, frameContext);
> +		return;
> +	}
> +
> +	ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame,
> +			 frameContext.awb.temperatureK);
> +
> +	setParameters(params, frameContext);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::process
> + */
> +void Ccm::process([[maybe_unused]] IPAContext &context,
> +		  [[maybe_unused]] const uint32_t frame,
> +		  IPAFrameContext &frameContext,
> +		  [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
> +		  ControlList &metadata)
> +{
> +	ccmAlgo_.process(frameContext.ccm, metadata);
> +}
> +
> +REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
> +
> +} /* namespace ipa::ipu3::algorithms */
> +
> +} /* namespace libcamera */

splendid!

> diff --git a/src/ipa/ipu3/algorithms/ccm.h b/src/ipa/ipu3/algorithms/ccm.h
> new file mode 100644
> index 0000000000000000000000000000000000000000..39719c986267aff02e3129aad9dbe4baeec8f8a1
> --- /dev/null
> +++ b/src/ipa/ipu3/algorithms/ccm.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026, Ideas On Board
> + *
> + * IPU3 Colour correction matrix algorithm
> + */
> +
> +#pragma once
> +
> +#include <linux/intel-ipu3.h>

Missing includes for ControlList and ValueNode

#include <libcamera/controls.h>

#include "libcamera/internal/value_node.h"

> +
> +#include <libipa/ccm.h>
> +#include <libipa/fixedpoint.h>
> +
> +#include "algorithm.h"

Missing include for IPAContext

#include "ipa_context.h"

Please check the same includes are there in awb.h from the previous
patch

> +
> +namespace libcamera {
> +
> +namespace ipa::ipu3::algorithms {
> +
> +class Ccm : public Algorithm
> +{
> +public:
> +	int init(IPAContext &context, const ValueNode &tuningData) override;
> +	int configure(IPAContext &context, const IPAConfigInfo &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,
> +		     ipu3_uapi_params *params) override;
> +	void process(IPAContext &context, const uint32_t frame,
> +		     IPAFrameContext &frameContext,
> +		     const ipu3_uapi_stats_3a *stats,
> +		     ControlList &metadata) override;
> +
> +private:
> +	void setParameters(ipu3_uapi_params *params, IPAFrameContext &context);
> +	CcmAlgorithm<Q<3, 13>> ccmAlgo_;
> +};
> +
> +} /* namespace ipa::ipu3::algorithms */
> +} /* namespace libcamera */
> diff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build
> index b70a551cacf6cbdedf3ab41771a34ddcdae9fd8b..3dafd2fda9897942cf87d9640665c4fcff383859 100644
> --- a/src/ipa/ipu3/algorithms/meson.build
> +++ b/src/ipa/ipu3/algorithms/meson.build
> @@ -5,5 +5,6 @@ ipu3_ipa_algorithms = files([
>      'agc.cpp',
>      'awb.cpp',
>      'blc.cpp',
> +    'ccm.cpp',
>      'tone_mapping.cpp',
>  ])
> diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
> index b35c925d959027c540257e47944047c238f85571..dc43bc0877ed5c2e7287414e12667374f5ee1c80 100644
> --- a/src/ipa/ipu3/ipa_context.cpp
> +++ b/src/ipa/ipu3/ipa_context.cpp
> @@ -114,6 +114,11 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Active auto-white balance parameters for the IPA
>   */
>
> +/**
> + * \var IPAActiveState::ccm
> + * \brief Active colour Correction Matrix parameters for the IPA
> + */
> +
>  /**
>   * \var IPASessionConfiguration::sensor
>   * \brief Sensor-specific configuration of the IPA
> @@ -182,4 +187,9 @@ namespace libcamera::ipa::ipu3 {
>   * \brief Per-frame auto-white balance parameters for the IPA
>   */
>
> +/**
> + * \var IPAFrameContext::ccm
> + * \brief Per-frame colour Correction Matrix parameters for the IPA
> + */
> +
>  } /* namespace libcamera::ipa::ipu3 */
> diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
> index 245cf8b50b270a61df863e314128bede40d30541..be626d30d966b1bdaa322e5154f95f745f799976 100644
> --- a/src/ipa/ipu3/ipa_context.h
> +++ b/src/ipa/ipu3/ipa_context.h
> @@ -16,6 +16,7 @@
>  #include <libcamera/geometry.h>
>
>  #include <libipa/awb.h>
> +#include <libipa/ccm.h>
>  #include <libipa/fc_queue.h>
>
>  namespace libcamera {
> @@ -64,6 +65,7 @@ struct IPAActiveState {
>  	} agc;
>
>  	ipa::awb::ActiveState awb;
> +	ipa::ccm::ActiveState ccm;
>
>  	struct {
>  		double gamma;
> @@ -78,6 +80,7 @@ struct IPAFrameContext : public FrameContext {
>  	} sensor;
>
>  	ipa::awb::FrameContext awb;
> +	ipa::ccm::FrameContext ccm;
>  };
>
>  struct IPAContext {
>

Ship it!
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Thanks
  j

> --
> 2.43.0
>

Patch
diff mbox series

diff --git a/src/ipa/ipu3/algorithms/ccm.cpp b/src/ipa/ipu3/algorithms/ccm.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..44b8c107b079c544c5afd1fce3cbe355f6dacd76
--- /dev/null
+++ b/src/ipa/ipu3/algorithms/ccm.cpp
@@ -0,0 +1,116 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * IPU3 Colour correction matrix algorithm
+ */
+
+#include "ccm.h"
+
+/**
+ * \file ccm.h
+ */
+
+namespace libcamera {
+
+namespace ipa::ipu3::algorithms {
+
+/**
+ * \class Ccm
+ * \brief The IPU3 color correction matrix algorithm
+ */
+
+LOG_DEFINE_CATEGORY(IPU3Ccm)
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::init
+ */
+int Ccm::init(IPAContext &context, const ValueNode &tuningData)
+{
+	return ccmAlgo_.init(tuningData, context.ctrlMap);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::configure
+ */
+int Ccm::configure(IPAContext &context,
+		   [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+	return ccmAlgo_.configure(context.activeState.ccm,
+				  context.activeState.awb.automatic.temperatureK);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+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(ipu3_uapi_params *params, IPAFrameContext &context)
+{
+	const Matrix<float, 3, 3> &matrix = context.ccm.ccm;
+	const Matrix<int16_t, 3, 1> &offsets = context.ccm.offsets;
+
+	params->use.acc_ccm = 1;
+
+	params->acc_param.ccm.coeff_m11 = Q<3, 13>(matrix[0][0]).quantized();
+	params->acc_param.ccm.coeff_m12 = Q<3, 13>(matrix[0][1]).quantized();
+	params->acc_param.ccm.coeff_m13 = Q<3, 13>(matrix[0][2]).quantized();
+	params->acc_param.ccm.coeff_o_r = offsets[0][0];
+
+	params->acc_param.ccm.coeff_m21 = Q<3, 13>(matrix[1][0]).quantized();
+	params->acc_param.ccm.coeff_m22 = Q<3, 13>(matrix[1][1]).quantized();
+	params->acc_param.ccm.coeff_m23 = Q<3, 13>(matrix[1][2]).quantized();
+	params->acc_param.ccm.coeff_o_g = offsets[1][0];
+
+	params->acc_param.ccm.coeff_m31 = Q<3, 13>(matrix[2][0]).quantized();
+	params->acc_param.ccm.coeff_m32 = Q<3, 13>(matrix[2][1]).quantized();
+	params->acc_param.ccm.coeff_m33 = Q<3, 13>(matrix[2][2]).quantized();
+	params->acc_param.ccm.coeff_o_b = offsets[2][0];
+
+	LOG(IPU3Ccm, Debug) << "Setting matrix " << matrix;
+	LOG(IPU3Ccm, Debug) << "Setting offsets " << offsets;
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::prepare
+ */
+void Ccm::prepare(IPAContext &context, const uint32_t frame,
+		  IPAFrameContext &frameContext, ipu3_uapi_params *params)
+{
+	if (!frameContext.awb.autoEnabled) {
+		setParameters(params, frameContext);
+		return;
+	}
+
+	ccmAlgo_.prepare(context.activeState.ccm, frameContext.ccm, frame,
+			 frameContext.awb.temperatureK);
+
+	setParameters(params, frameContext);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void Ccm::process([[maybe_unused]] IPAContext &context,
+		  [[maybe_unused]] const uint32_t frame,
+		  IPAFrameContext &frameContext,
+		  [[maybe_unused]] const ipu3_uapi_stats_3a *stats,
+		  ControlList &metadata)
+{
+	ccmAlgo_.process(frameContext.ccm, metadata);
+}
+
+REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
+
+} /* namespace ipa::ipu3::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/ccm.h b/src/ipa/ipu3/algorithms/ccm.h
new file mode 100644
index 0000000000000000000000000000000000000000..39719c986267aff02e3129aad9dbe4baeec8f8a1
--- /dev/null
+++ b/src/ipa/ipu3/algorithms/ccm.h
@@ -0,0 +1,43 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026, Ideas On Board
+ *
+ * IPU3 Colour correction matrix algorithm
+ */
+
+#pragma once
+
+#include <linux/intel-ipu3.h>
+
+#include <libipa/ccm.h>
+#include <libipa/fixedpoint.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::ipu3::algorithms {
+
+class Ccm : public Algorithm
+{
+public:
+	int init(IPAContext &context, const ValueNode &tuningData) override;
+	int configure(IPAContext &context, const IPAConfigInfo &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,
+		     ipu3_uapi_params *params) override;
+	void process(IPAContext &context, const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     const ipu3_uapi_stats_3a *stats,
+		     ControlList &metadata) override;
+
+private:
+	void setParameters(ipu3_uapi_params *params, IPAFrameContext &context);
+	CcmAlgorithm<Q<3, 13>> ccmAlgo_;
+};
+
+} /* namespace ipa::ipu3::algorithms */
+} /* namespace libcamera */
diff --git a/src/ipa/ipu3/algorithms/meson.build b/src/ipa/ipu3/algorithms/meson.build
index b70a551cacf6cbdedf3ab41771a34ddcdae9fd8b..3dafd2fda9897942cf87d9640665c4fcff383859 100644
--- a/src/ipa/ipu3/algorithms/meson.build
+++ b/src/ipa/ipu3/algorithms/meson.build
@@ -5,5 +5,6 @@  ipu3_ipa_algorithms = files([
     'agc.cpp',
     'awb.cpp',
     'blc.cpp',
+    'ccm.cpp',
     'tone_mapping.cpp',
 ])
diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
index b35c925d959027c540257e47944047c238f85571..dc43bc0877ed5c2e7287414e12667374f5ee1c80 100644
--- a/src/ipa/ipu3/ipa_context.cpp
+++ b/src/ipa/ipu3/ipa_context.cpp
@@ -114,6 +114,11 @@  namespace libcamera::ipa::ipu3 {
  * \brief Active auto-white balance parameters for the IPA
  */
 
+/**
+ * \var IPAActiveState::ccm
+ * \brief Active colour Correction Matrix parameters for the IPA
+ */
+
 /**
  * \var IPASessionConfiguration::sensor
  * \brief Sensor-specific configuration of the IPA
@@ -182,4 +187,9 @@  namespace libcamera::ipa::ipu3 {
  * \brief Per-frame auto-white balance parameters for the IPA
  */
 
+/**
+ * \var IPAFrameContext::ccm
+ * \brief Per-frame colour Correction Matrix parameters for the IPA
+ */
+
 } /* namespace libcamera::ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
index 245cf8b50b270a61df863e314128bede40d30541..be626d30d966b1bdaa322e5154f95f745f799976 100644
--- a/src/ipa/ipu3/ipa_context.h
+++ b/src/ipa/ipu3/ipa_context.h
@@ -16,6 +16,7 @@ 
 #include <libcamera/geometry.h>
 
 #include <libipa/awb.h>
+#include <libipa/ccm.h>
 #include <libipa/fc_queue.h>
 
 namespace libcamera {
@@ -64,6 +65,7 @@  struct IPAActiveState {
 	} agc;
 
 	ipa::awb::ActiveState awb;
+	ipa::ccm::ActiveState ccm;
 
 	struct {
 		double gamma;
@@ -78,6 +80,7 @@  struct IPAFrameContext : public FrameContext {
 	} sensor;
 
 	ipa::awb::FrameContext awb;
+	ipa::ccm::FrameContext ccm;
 };
 
 struct IPAContext {