[11/11] ipa: mali-c55: Port to use LscAlgorithm
diff mbox series

Message ID 20260615-libipa-algorithms-v1-11-e949c937422e@ideasonboard.com
State Superseded
Headers show
Series
  • ipa: libipa: Introduce libipa algorithms
Related show

Commit Message

Jacopo Mondi June 15, 2026, 2:05 p.m. UTC
Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++--------------
 src/ipa/mali-c55/algorithms/lsc.h   |  38 ++++++---
 src/ipa/mali-c55/ipa_context.h      |   5 ++
 3 files changed, 130 insertions(+), 76 deletions(-)

Comments

Kieran Bingham June 22, 2026, 6:14 p.m. UTC | #1
Quoting Jacopo Mondi (2026-06-15 15:05:36)
> Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++--------------
>  src/ipa/mali-c55/algorithms/lsc.h   |  38 ++++++---
>  src/ipa/mali-c55/ipa_context.h      |   5 ++
>  3 files changed, 130 insertions(+), 76 deletions(-)
> 
> diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
> index 36e50163aecc..624b393b80e5 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.cpp
> +++ b/src/ipa/mali-c55/algorithms/lsc.cpp
> @@ -9,39 +9,74 @@
>  
>  #include <libcamera/base/utils.h>
>  
> -#include "libcamera/internal/value_node.h"
> -
>  namespace libcamera {
>  
>  namespace ipa::mali_c55::algorithms {
>  
>  LOG_DEFINE_CATEGORY(MaliC55Lsc)
>  
> -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> +/* Gain values in [1, 5] range */
> +static constexpr unsigned int kMeshScale = 6;
> +
> +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */
> +static constexpr unsigned int kMeshSize = 32;
> +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize;
> +
> +/* Per-colour component page offsets in the mesh table. */
> +static constexpr unsigned int kRedOffset = 0;
> +static constexpr unsigned int kGreenOffset = 1024;
> +static constexpr unsigned int kBlueOffset = 2048;
> +
> +/*
> + * \todo Mali-C55 supports up to 4 colour temperatures.
> + *
> + * Unfortunately the uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072)
> + * gain elements, which correspond to three pages of 1024 (32x32) entries.
> + */
> +static constexpr unsigned int kMaxColourTemperature = 3;
> +
> +/*
> + * The LSC algorithm implementation only supports 32x32 grids. Create a list of
> + * positions from the grid size.
> + */
> +std::vector<double> Lsc::segmentsToPosition() const
>  {
> -       if (!tuningData.contains("meshScale")) {
> -               LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
> -               return -EINVAL;
> -       }
> +       std::vector<double> positions(kMeshSize);
> +       for (double i = 0.0; i < kMeshSize; ++i)
> +               positions[i] = i / (kMeshSize - 1);
>  
> -       meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
> +       return positions;
> +}
>  
> -       const ValueNode &yamlSets = tuningData["sets"];
> -       if (!yamlSets.isList()) {
> -               LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
> -               return -EINVAL;
> -       }
> +int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> +{
> +       gridPos_ = segmentsToPosition();
> +
> +       return lscAlgo_.init(tuningData, context.ctrlMap, {
> +                            .keys = { "r", "g", "b" },
> +                            .numHCells = kMeshSize,
> +                            .numVCells = kMeshSize,
> +                            .sensorSize = context.sensorInfo.activeAreaSize
> +                       });
> +}
>  
> -       size_t tableSize = 0;
> -       const auto &sets = yamlSets.asList();
> -       for (const auto &yamlSet : sets) {
> -               uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
> +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> +{
> +       int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,
> +                                    gridPos_, gridPos_);
> +       if (ret)
> +               return ret;
>  
> -               if (!ct) {
> -                       LOG(MaliC55Lsc, Error) << "Invalid colour temperature";
> -                       return -EINVAL;
> -               }
> +       /* Re-initialize the mesh tables and reserve space for enough entries. */
> +       mesh_ = std::vector<uint32_t>(kGridSize * kMaxColourTemperature);
> +       colourTemperatures_.clear();
>  
> +       /*
> +        * Get the lsc tables per colour components and populate mesh_ with
> +        * their content.
> +        */
> +       const auto &components = lscAlgo_.getComponents();
> +       for (auto const &[ct, component] : components) {
>                 if (std::count(colourTemperatures_.begin(),
>                                colourTemperatures_.end(), ct)) {
>                         LOG(MaliC55Lsc, Error)
> @@ -49,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>                         return -EINVAL;
>                 }
>  
> -               std::vector<uint8_t> rTable =
> -                       yamlSet["r"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -               std::vector<uint8_t> gTable =
> -                       yamlSet["g"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -               std::vector<uint8_t> bTable =
> -                       yamlSet["b"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -
> -               /*
> -                * Some validation to do; only 16x16 and 32x32 tables of
> -                * coefficients are acceptable, and all tables across all of the
> -                * sets must be the same size. The first time we encounter a
> -                * table we check that it is an acceptable size and if so make
> -                * sure all other tables are of equal size.
> -                */
> -               if (!tableSize) {
> -                       if (rTable.size() != 256 && rTable.size() != 1024) {
> -                               LOG(MaliC55Lsc, Error)
> -                                       << "Invalid table size for colour temperature " << ct;
> -                               return -EINVAL;
> -                       }
> -                       tableSize = rTable.size();
> -               }
> +               std::vector<uint8_t> rTable = component.at("r");
> +               std::vector<uint8_t> gTable = component.at("g");
> +               std::vector<uint8_t> bTable = component.at("b");
>  
> -               if (rTable.size() != tableSize ||
> -                   gTable.size() != tableSize ||
> -                   bTable.size() != tableSize) {
> +               /* Only 32x32 tables of coefficients are accepted. */
> +               if (rTable.size() != kGridSize || gTable.size() != kGridSize ||
> +                   bTable.size() != kGridSize) {
>                         LOG(MaliC55Lsc, Error)
> -                               << "Invalid or mismatched table size for colour temperature " << ct;
> +                               << "Invalid table size for colour temperature " << ct;
>                         return -EINVAL;
>                 }
>  
> -               if (colourTemperatures_.size() >= 3) {
> +               if (colourTemperatures_.size() >= kMaxColourTemperature) {
>                         LOG(MaliC55Lsc, Error)
>                                 << "A maximum of 3 colour temperatures are supported";
>                         return -EINVAL;
>                 }
>  
> -               for (unsigned int i = 0; i < tableSize; i++) {
> +               /*
> +                * Create the mesh table entries by assembling up to 3 gains per
> +                * colour temperature in a u32 word.
> +                */
> +               for (unsigned int i = 0; i < kGridSize; i++) {
>                         mesh_[kRedOffset + i] |=
>                                 (rTable[i] << (colourTemperatures_.size() * 8));
>                         mesh_[kGreenOffset + i] |=
> @@ -98,34 +118,35 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>                 colourTemperatures_.push_back(ct);
>         }
>  
> -       /*
> -        * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it
> -        * is based on the number of values in the tuning data's table.
> -        */
> -       if (tableSize == 256)
> -               meshSize_ = 15;
> -       else
> -               meshSize_ = 31;
> -
>         return 0;
>  }
>  
> -void Lsc::fillConfigParamsBlock(MaliC55Params *params) const
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> +                      IPAFrameContext &frameContext, const ControlList &controls)
> +{
> +       lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
> +                             controls);
> +}
> +
> +void Lsc::programMeshTables(MaliC55Params *params) const
>  {
>         auto block = params->block<MaliC55Blocks::MeshShadingConfig>();
>  
>         block->mesh_show = false;
> -       block->mesh_scale = meshScale_;
> +       block->mesh_scale = kMeshScale;
>         block->mesh_page_r = 0;
>         block->mesh_page_g = 1;
>         block->mesh_page_b = 2;
> -       block->mesh_width = meshSize_;
> -       block->mesh_height = meshSize_;
> +       block->mesh_width = kMeshSize - 1;
> +       block->mesh_height = kMeshSize - 1;
>  
>         std::copy(mesh_.begin(), mesh_.end(), block->mesh);
>  }
>  
> -void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank,
> +void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank,
>                                      uint8_t alpha) const
>  {
>         auto block = params->block<MaliC55Blocks::MeshShadingSel>();
> @@ -187,7 +208,7 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>                 std::tie(bank, alpha) = findBankAndAlpha(temperatureK);
>         }
>  
> -       fillSelectionParamsBlock(params, bank, alpha);
> +       configureMeshBlending(params, bank, alpha);
>  
>         if (frame > 0)
>                 return;
> @@ -196,7 +217,19 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>          * If this is the first frame, we need to load the parsed coefficient
>          * tables from tuning data to the ISP.
>          */
> -       fillConfigParamsBlock(params);
> +       programMeshTables(params);

A bit like with AwbMeas - I haven't seen a need to rename here. Was
there a reason you didn't like the existing names ? Does that match
something else ?

Only concern is that it's adding more changes into a big patchset, but
otherwise nothing seems wrong that I can see, and we get our common
parsing and handling so:


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


> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::process
> + */
> +void Lsc::process([[maybe_unused]] IPAContext &context,
> +                 [[maybe_unused]] const uint32_t frame,
> +                 IPAFrameContext &frameContext,
> +                 [[maybe_unused]] const mali_c55_stats_buffer *stats,
> +                 ControlList &metadata)
> +{
> +       lscAlgo_.process(frameContext.lsc, metadata);
>  }
>  
>  REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
> diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
> index 3d35fd72bfa8..79ad42094c42 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.h
> +++ b/src/ipa/mali-c55/algorithms/lsc.h
> @@ -5,10 +5,19 @@
>   * Mali-C55 Lens shading correction algorithm
>   */
>  
> -#include <map>
> +#include <vector>
>  #include <tuple>
>  
> +#include <linux/media/arm/mali-c55-config.h>
> +
> +#include "libcamera/internal/value_node.h"
> +
> +#include "libipa/fixedpoint.h"
> +#include "libipa/lsc.h"
> +
>  #include "algorithm.h"
> +#include "ipa_context.h"
> +#include "params.h"
>  
>  namespace libcamera {
>  
> @@ -21,23 +30,30 @@ public:
>         ~Lsc() = 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:
> -       static constexpr unsigned int kRedOffset = 0;
> -       static constexpr unsigned int kGreenOffset = 1024;
> -       static constexpr unsigned int kBlueOffset = 2048;
> -
> -       void fillConfigParamsBlock(MaliC55Params *params) const;
> -       void fillSelectionParamsBlock(MaliC55Params *params,
> -                                     uint8_t bank, uint8_t alpha) const;
> +       std::vector<double> segmentsToPosition() const;
> +       void programMeshTables(MaliC55Params *params) const;
> +       void configureMeshBlending(MaliC55Params *params,
> +                                  uint8_t bank, uint8_t alpha) const;
>         std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const;
>  
> -       std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072);
>         std::vector<uint32_t> colourTemperatures_;
> -       uint32_t meshScale_;
> -       uint32_t meshSize_;
> +       std::vector<uint32_t> mesh_;
> +
> +       std::vector<double> gridPos_;
> +
> +       LscAlgorithm<uint8_t, UQ<2, 6>> lscAlgo_;
>  };
>  
>  } /* namespace ipa::mali_c55::algorithms */
> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
> index 2f2092677fbf..749309115d84 100644
> --- a/src/ipa/mali-c55/ipa_context.h
> +++ b/src/ipa/mali-c55/ipa_context.h
> @@ -20,6 +20,7 @@
>  #include "libipa/awb.h"
>  #include "libipa/ccm.h"
>  #include "libipa/fixedpoint.h"
> +#include "libipa/lsc.h"
>  
>  namespace libcamera {
>  
> @@ -64,6 +65,8 @@ struct IPAActiveState {
>         ipa::awb::ActiveState awb;
>  
>         ipa::ccm::ActiveState ccm;
> +
> +       ipa::lsc::ActiveState lsc;
>  };
>  
>  struct IPAFrameContext : public FrameContext {
> @@ -76,6 +79,8 @@ struct IPAFrameContext : public FrameContext {
>         ipa::awb::FrameContext awb;
>  
>         ipa::ccm::FrameContext ccm;
> +
> +       ipa::lsc::FrameContext lsc;
>  };
>  
>  struct IPAContext {
> 
> -- 
> 2.54.0
>
Jacopo Mondi June 23, 2026, 6:34 a.m. UTC | #2
Hi Kieran

On Mon, Jun 22, 2026 at 07:14:21PM +0100, Kieran Bingham wrote:
> Quoting Jacopo Mondi (2026-06-15 15:05:36)
> > Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > ---
> >  src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++--------------
> >  src/ipa/mali-c55/algorithms/lsc.h   |  38 ++++++---
> >  src/ipa/mali-c55/ipa_context.h      |   5 ++
> >  3 files changed, 130 insertions(+), 76 deletions(-)
> >
> > diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
> > index 36e50163aecc..624b393b80e5 100644
> > --- a/src/ipa/mali-c55/algorithms/lsc.cpp
> > +++ b/src/ipa/mali-c55/algorithms/lsc.cpp
> > @@ -9,39 +9,74 @@
> >
> >  #include <libcamera/base/utils.h>
> >
> > -#include "libcamera/internal/value_node.h"
> > -
> >  namespace libcamera {
> >
> >  namespace ipa::mali_c55::algorithms {
> >
> >  LOG_DEFINE_CATEGORY(MaliC55Lsc)
> >
> > -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> > +/* Gain values in [1, 5] range */
> > +static constexpr unsigned int kMeshScale = 6;
> > +
> > +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */
> > +static constexpr unsigned int kMeshSize = 32;
> > +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize;
> > +
> > +/* Per-colour component page offsets in the mesh table. */
> > +static constexpr unsigned int kRedOffset = 0;
> > +static constexpr unsigned int kGreenOffset = 1024;
> > +static constexpr unsigned int kBlueOffset = 2048;
> > +
> > +/*
> > + * \todo Mali-C55 supports up to 4 colour temperatures.
> > + *
> > + * Unfortunately the uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072)
> > + * gain elements, which correspond to three pages of 1024 (32x32) entries.
> > + */
> > +static constexpr unsigned int kMaxColourTemperature = 3;
> > +
> > +/*
> > + * The LSC algorithm implementation only supports 32x32 grids. Create a list of
> > + * positions from the grid size.
> > + */
> > +std::vector<double> Lsc::segmentsToPosition() const
> >  {
> > -       if (!tuningData.contains("meshScale")) {
> > -               LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
> > -               return -EINVAL;
> > -       }
> > +       std::vector<double> positions(kMeshSize);
> > +       for (double i = 0.0; i < kMeshSize; ++i)
> > +               positions[i] = i / (kMeshSize - 1);
> >
> > -       meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
> > +       return positions;
> > +}
> >
> > -       const ValueNode &yamlSets = tuningData["sets"];
> > -       if (!yamlSets.isList()) {
> > -               LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
> > -               return -EINVAL;
> > -       }
> > +int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> > +{
> > +       gridPos_ = segmentsToPosition();
> > +
> > +       return lscAlgo_.init(tuningData, context.ctrlMap, {
> > +                            .keys = { "r", "g", "b" },
> > +                            .numHCells = kMeshSize,
> > +                            .numVCells = kMeshSize,
> > +                            .sensorSize = context.sensorInfo.activeAreaSize
> > +                       });
> > +}
> >
> > -       size_t tableSize = 0;
> > -       const auto &sets = yamlSets.asList();
> > -       for (const auto &yamlSet : sets) {
> > -               uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
> > +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > +{
> > +       int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,
> > +                                    gridPos_, gridPos_);
> > +       if (ret)
> > +               return ret;
> >
> > -               if (!ct) {
> > -                       LOG(MaliC55Lsc, Error) << "Invalid colour temperature";
> > -                       return -EINVAL;
> > -               }
> > +       /* Re-initialize the mesh tables and reserve space for enough entries. */
> > +       mesh_ = std::vector<uint32_t>(kGridSize * kMaxColourTemperature);
> > +       colourTemperatures_.clear();
> >
> > +       /*
> > +        * Get the lsc tables per colour components and populate mesh_ with
> > +        * their content.
> > +        */
> > +       const auto &components = lscAlgo_.getComponents();
> > +       for (auto const &[ct, component] : components) {
> >                 if (std::count(colourTemperatures_.begin(),
> >                                colourTemperatures_.end(), ct)) {
> >                         LOG(MaliC55Lsc, Error)
> > @@ -49,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> >                         return -EINVAL;
> >                 }
> >
> > -               std::vector<uint8_t> rTable =
> > -                       yamlSet["r"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -               std::vector<uint8_t> gTable =
> > -                       yamlSet["g"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -               std::vector<uint8_t> bTable =
> > -                       yamlSet["b"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -
> > -               /*
> > -                * Some validation to do; only 16x16 and 32x32 tables of
> > -                * coefficients are acceptable, and all tables across all of the
> > -                * sets must be the same size. The first time we encounter a
> > -                * table we check that it is an acceptable size and if so make
> > -                * sure all other tables are of equal size.
> > -                */
> > -               if (!tableSize) {
> > -                       if (rTable.size() != 256 && rTable.size() != 1024) {
> > -                               LOG(MaliC55Lsc, Error)
> > -                                       << "Invalid table size for colour temperature " << ct;
> > -                               return -EINVAL;
> > -                       }
> > -                       tableSize = rTable.size();
> > -               }
> > +               std::vector<uint8_t> rTable = component.at("r");
> > +               std::vector<uint8_t> gTable = component.at("g");
> > +               std::vector<uint8_t> bTable = component.at("b");
> >
> > -               if (rTable.size() != tableSize ||
> > -                   gTable.size() != tableSize ||
> > -                   bTable.size() != tableSize) {
> > +               /* Only 32x32 tables of coefficients are accepted. */
> > +               if (rTable.size() != kGridSize || gTable.size() != kGridSize ||
> > +                   bTable.size() != kGridSize) {
> >                         LOG(MaliC55Lsc, Error)
> > -                               << "Invalid or mismatched table size for colour temperature " << ct;
> > +                               << "Invalid table size for colour temperature " << ct;
> >                         return -EINVAL;
> >                 }
> >
> > -               if (colourTemperatures_.size() >= 3) {
> > +               if (colourTemperatures_.size() >= kMaxColourTemperature) {
> >                         LOG(MaliC55Lsc, Error)
> >                                 << "A maximum of 3 colour temperatures are supported";
> >                         return -EINVAL;
> >                 }
> >
> > -               for (unsigned int i = 0; i < tableSize; i++) {
> > +               /*
> > +                * Create the mesh table entries by assembling up to 3 gains per
> > +                * colour temperature in a u32 word.
> > +                */
> > +               for (unsigned int i = 0; i < kGridSize; i++) {
> >                         mesh_[kRedOffset + i] |=
> >                                 (rTable[i] << (colourTemperatures_.size() * 8));
> >                         mesh_[kGreenOffset + i] |=
> > @@ -98,34 +118,35 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> >                 colourTemperatures_.push_back(ct);
> >         }
> >
> > -       /*
> > -        * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it
> > -        * is based on the number of values in the tuning data's table.
> > -        */
> > -       if (tableSize == 256)
> > -               meshSize_ = 15;
> > -       else
> > -               meshSize_ = 31;
> > -
> >         return 0;
> >  }
> >
> > -void Lsc::fillConfigParamsBlock(MaliC55Params *params) const
> > +/**
> > + * \copydoc libcamera::ipa::Algorithm::queueRequest
> > + */
> > +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > +                      IPAFrameContext &frameContext, const ControlList &controls)
> > +{
> > +       lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
> > +                             controls);
> > +}
> > +
> > +void Lsc::programMeshTables(MaliC55Params *params) const
> >  {
> >         auto block = params->block<MaliC55Blocks::MeshShadingConfig>();
> >
> >         block->mesh_show = false;
> > -       block->mesh_scale = meshScale_;
> > +       block->mesh_scale = kMeshScale;
> >         block->mesh_page_r = 0;
> >         block->mesh_page_g = 1;
> >         block->mesh_page_b = 2;
> > -       block->mesh_width = meshSize_;
> > -       block->mesh_height = meshSize_;
> > +       block->mesh_width = kMeshSize - 1;
> > +       block->mesh_height = kMeshSize - 1;
> >
> >         std::copy(mesh_.begin(), mesh_.end(), block->mesh);
> >  }
> >
> > -void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank,
> > +void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank,
> >                                      uint8_t alpha) const
> >  {
> >         auto block = params->block<MaliC55Blocks::MeshShadingSel>();
> > @@ -187,7 +208,7 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> >                 std::tie(bank, alpha) = findBankAndAlpha(temperatureK);
> >         }
> >
> > -       fillSelectionParamsBlock(params, bank, alpha);
> > +       configureMeshBlending(params, bank, alpha);
> >
> >         if (frame > 0)
> >                 return;
> > @@ -196,7 +217,19 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> >          * If this is the first frame, we need to load the parsed coefficient
> >          * tables from tuning data to the ISP.
> >          */
> > -       fillConfigParamsBlock(params);
> > +       programMeshTables(params);
>
> A bit like with AwbMeas - I haven't seen a need to rename here. Was
> there a reason you didn't like the existing names ? Does that match
> something else ?

I admit it's totally arbitrary from my side, but I can't correlate
fillConfigParamsBlock() and fillSelectionParamsBlock() with their
function which is better (imho) expressed by programMeshTables() and
configureMeshBlending() respectively.

>
> Only concern is that it's adding more changes into a big patchset, but
> otherwise nothing seems wrong that I can see, and we get our common
> parsing and handling so:

I admit the name change is unrelated, but the third time I had to look
at the function body to know what they were doing, I decide to rename.

I can backtrack indeed.

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

Thanks
   j

>
>
> > +}
> > +
> > +/**
> > + * \copydoc libcamera::ipa::Algorithm::process
> > + */
> > +void Lsc::process([[maybe_unused]] IPAContext &context,
> > +                 [[maybe_unused]] const uint32_t frame,
> > +                 IPAFrameContext &frameContext,
> > +                 [[maybe_unused]] const mali_c55_stats_buffer *stats,
> > +                 ControlList &metadata)
> > +{
> > +       lscAlgo_.process(frameContext.lsc, metadata);
> >  }
> >
> >  REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
> > diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
> > index 3d35fd72bfa8..79ad42094c42 100644
> > --- a/src/ipa/mali-c55/algorithms/lsc.h
> > +++ b/src/ipa/mali-c55/algorithms/lsc.h
> > @@ -5,10 +5,19 @@
> >   * Mali-C55 Lens shading correction algorithm
> >   */
> >
> > -#include <map>
> > +#include <vector>
> >  #include <tuple>
> >
> > +#include <linux/media/arm/mali-c55-config.h>
> > +
> > +#include "libcamera/internal/value_node.h"
> > +
> > +#include "libipa/fixedpoint.h"
> > +#include "libipa/lsc.h"
> > +
> >  #include "algorithm.h"
> > +#include "ipa_context.h"
> > +#include "params.h"
> >
> >  namespace libcamera {
> >
> > @@ -21,23 +30,30 @@ public:
> >         ~Lsc() = 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:
> > -       static constexpr unsigned int kRedOffset = 0;
> > -       static constexpr unsigned int kGreenOffset = 1024;
> > -       static constexpr unsigned int kBlueOffset = 2048;
> > -
> > -       void fillConfigParamsBlock(MaliC55Params *params) const;
> > -       void fillSelectionParamsBlock(MaliC55Params *params,
> > -                                     uint8_t bank, uint8_t alpha) const;
> > +       std::vector<double> segmentsToPosition() const;
> > +       void programMeshTables(MaliC55Params *params) const;
> > +       void configureMeshBlending(MaliC55Params *params,
> > +                                  uint8_t bank, uint8_t alpha) const;
> >         std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const;
> >
> > -       std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072);
> >         std::vector<uint32_t> colourTemperatures_;
> > -       uint32_t meshScale_;
> > -       uint32_t meshSize_;
> > +       std::vector<uint32_t> mesh_;
> > +
> > +       std::vector<double> gridPos_;
> > +
> > +       LscAlgorithm<uint8_t, UQ<2, 6>> lscAlgo_;
> >  };
> >
> >  } /* namespace ipa::mali_c55::algorithms */
> > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
> > index 2f2092677fbf..749309115d84 100644
> > --- a/src/ipa/mali-c55/ipa_context.h
> > +++ b/src/ipa/mali-c55/ipa_context.h
> > @@ -20,6 +20,7 @@
> >  #include "libipa/awb.h"
> >  #include "libipa/ccm.h"
> >  #include "libipa/fixedpoint.h"
> > +#include "libipa/lsc.h"
> >
> >  namespace libcamera {
> >
> > @@ -64,6 +65,8 @@ struct IPAActiveState {
> >         ipa::awb::ActiveState awb;
> >
> >         ipa::ccm::ActiveState ccm;
> > +
> > +       ipa::lsc::ActiveState lsc;
> >  };
> >
> >  struct IPAFrameContext : public FrameContext {
> > @@ -76,6 +79,8 @@ struct IPAFrameContext : public FrameContext {
> >         ipa::awb::FrameContext awb;
> >
> >         ipa::ccm::FrameContext ccm;
> > +
> > +       ipa::lsc::FrameContext lsc;
> >  };
> >
> >  struct IPAContext {
> >
> > --
> > 2.54.0
> >
Dan Scally June 23, 2026, 10:06 a.m. UTC | #3
Hi Jacopo

On 15/06/2026 15:05, Jacopo Mondi wrote:
> Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>   src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++--------------
>   src/ipa/mali-c55/algorithms/lsc.h   |  38 ++++++---
>   src/ipa/mali-c55/ipa_context.h      |   5 ++
>   3 files changed, 130 insertions(+), 76 deletions(-)
> 
> diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
> index 36e50163aecc..624b393b80e5 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.cpp
> +++ b/src/ipa/mali-c55/algorithms/lsc.cpp
> @@ -9,39 +9,74 @@
>   
>   #include <libcamera/base/utils.h>
>   
> -#include "libcamera/internal/value_node.h"
> -
>   namespace libcamera {
>   
>   namespace ipa::mali_c55::algorithms {
>   
>   LOG_DEFINE_CATEGORY(MaliC55Lsc)
>   
> -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> +/* Gain values in [1, 5] range */
> +static constexpr unsigned int kMeshScale = 6;

The IPU3 lens shading block actually has the concept of a configurable scale too, so I wonder if 
this is something that could be done in the base algorithms. It could be done on top though; and 
5.0x is probably quite a high gain for LSC anyway

> +
> +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */
> +static constexpr unsigned int kMeshSize = 32;
> +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize;
> +
> +/* Per-colour component page offsets in the mesh table. */
> +static constexpr unsigned int kRedOffset = 0;
> +static constexpr unsigned int kGreenOffset = 1024;
> +static constexpr unsigned int kBlueOffset = 2048;
> +
> +/*
> + * \todo Mali-C55 supports up to 4 colour temperatures.
> + *
> + * Unfortunately the uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072)
> + * gain elements, which correspond to three pages of 1024 (32x32) entries.
> + */

I think that the 4th colour temperature is not supposed to be usable, despite its declaration in the 
documentation.

> +static constexpr unsigned int kMaxColourTemperature = 3;

s/kMaxColourTemperature/kMaxColourTemperatures

> +
> +/*
> + * The LSC algorithm implementation only supports 32x32 grids. Create a list of
> + * positions from the grid size.
> + */
> +std::vector<double> Lsc::segmentsToPosition() const
>   {
> -	if (!tuningData.contains("meshScale")) {
> -		LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
> -		return -EINVAL;
> -	}
> +	std::vector<double> positions(kMeshSize);
> +	for (double i = 0.0; i < kMeshSize; ++i)
> +		positions[i] = i / (kMeshSize - 1);
>   
> -	meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
> +	return positions;
> +}
>   
> -	const ValueNode &yamlSets = tuningData["sets"];
> -	if (!yamlSets.isList()) {
> -		LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
> -		return -EINVAL;
> -	}
> +int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)

context is used, so the decorator can be dropped.

> +{
> +	gridPos_ = segmentsToPosition();
> +
> +	return lscAlgo_.init(tuningData, context.ctrlMap, {
> +			     .keys = { "r", "g", "b" },
> +			     .numHCells = kMeshSize,
> +			     .numVCells = kMeshSize,
> +			     .sensorSize = context.sensorInfo.activeAreaSize
> +			});
> +}
>   
> -	size_t tableSize = 0;
> -	const auto &sets = yamlSets.asList();
> -	for (const auto &yamlSet : sets) {
> -		uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
> +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> +{
> +	int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,
> +				     gridPos_, gridPos_);
> +	if (ret)
> +		return ret;
>   
> -		if (!ct) {
> -			LOG(MaliC55Lsc, Error) << "Invalid colour temperature";
> -			return -EINVAL;
> -		}
> +	/* Re-initialize the mesh tables and reserve space for enough entries. */
> +	mesh_ = std::vector<uint32_t>(kGridSize * kMaxColourTemperature);
> +	colourTemperatures_.clear();
>   
> +	/*
> +	 * Get the lsc tables per colour components and populate mesh_ with
> +	 * their content.
> +	 */
> +	const auto &components = lscAlgo_.getComponents();
> +	for (auto const &[ct, component] : components) {
>   		if (std::count(colourTemperatures_.begin(),
>   			       colourTemperatures_.end(), ct)) {
>   			LOG(MaliC55Lsc, Error)
> @@ -49,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   			return -EINVAL;
>   		}
>   
> -		std::vector<uint8_t> rTable =
> -			yamlSet["r"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -		std::vector<uint8_t> gTable =
> -			yamlSet["g"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -		std::vector<uint8_t> bTable =
> -			yamlSet["b"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> -
> -		/*
> -		 * Some validation to do; only 16x16 and 32x32 tables of
> -		 * coefficients are acceptable, and all tables across all of the
> -		 * sets must be the same size. The first time we encounter a
> -		 * table we check that it is an acceptable size and if so make
> -		 * sure all other tables are of equal size.
> -		 */
> -		if (!tableSize) {
> -			if (rTable.size() != 256 && rTable.size() != 1024) {
> -				LOG(MaliC55Lsc, Error)
> -					<< "Invalid table size for colour temperature " << ct;
> -				return -EINVAL;
> -			}
> -			tableSize = rTable.size();
> -		}
> +		std::vector<uint8_t> rTable = component.at("r");
> +		std::vector<uint8_t> gTable = component.at("g");
> +		std::vector<uint8_t> bTable = component.at("b");
>   
> -		if (rTable.size() != tableSize ||
> -		    gTable.size() != tableSize ||
> -		    bTable.size() != tableSize) {
> +		/* Only 32x32 tables of coefficients are accepted. */
> +		if (rTable.size() != kGridSize || gTable.size() != kGridSize ||
> +		    bTable.size() != kGridSize) {
>   			LOG(MaliC55Lsc, Error)
> -				<< "Invalid or mismatched table size for colour temperature " << ct;
> +				<< "Invalid table size for colour temperature " << ct;
>   			return -EINVAL;
>   		}
>   
> -		if (colourTemperatures_.size() >= 3) {
> +		if (colourTemperatures_.size() >= kMaxColourTemperature) {
>   			LOG(MaliC55Lsc, Error)
>   				<< "A maximum of 3 colour temperatures are supported";
>   			return -EINVAL;
>   		}
>   
> -		for (unsigned int i = 0; i < tableSize; i++) {
> +		/*
> +		 * Create the mesh table entries by assembling up to 3 gains per
> +		 * colour temperature in a u32 word.
> +		 */
> +		for (unsigned int i = 0; i < kGridSize; i++) {
>   			mesh_[kRedOffset + i] |=
>   				(rTable[i] << (colourTemperatures_.size() * 8));
>   			mesh_[kGreenOffset + i] |=
> @@ -98,34 +118,35 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>   		colourTemperatures_.push_back(ct);
>   	}
>   
> -	/*
> -	 * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it
> -	 * is based on the number of values in the tuning data's table.
> -	 */
> -	if (tableSize == 256)
> -		meshSize_ = 15;
> -	else
> -		meshSize_ = 31;
> -
>   	return 0;
>   }
>   
> -void Lsc::fillConfigParamsBlock(MaliC55Params *params) const
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> +		       IPAFrameContext &frameContext, const ControlList &controls)
> +{
> +	lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
> +			      controls);
> +}
> +
> +void Lsc::programMeshTables(MaliC55Params *params) const
>   {
>   	auto block = params->block<MaliC55Blocks::MeshShadingConfig>();
>   
>   	block->mesh_show = false;
> -	block->mesh_scale = meshScale_;
> +	block->mesh_scale = kMeshScale;
>   	block->mesh_page_r = 0;
>   	block->mesh_page_g = 1;
>   	block->mesh_page_b = 2;
> -	block->mesh_width = meshSize_;
> -	block->mesh_height = meshSize_;
> +	block->mesh_width = kMeshSize - 1;
> +	block->mesh_height = kMeshSize - 1;
>   
>   	std::copy(mesh_.begin(), mesh_.end(), block->mesh);
>   }
>   
> -void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank,
> +void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank,
>   				     uint8_t alpha) const
>   {
>   	auto block = params->block<MaliC55Blocks::MeshShadingSel>();
> @@ -187,7 +208,7 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>   		std::tie(bank, alpha) = findBankAndAlpha(temperatureK);
>   	}
>   
> -	fillSelectionParamsBlock(params, bank, alpha);
> +	configureMeshBlending(params, bank, alpha);
>   
>   	if (frame > 0)
>   		return;
> @@ -196,7 +217,19 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>   	 * If this is the first frame, we need to load the parsed coefficient
>   	 * tables from tuning data to the ISP.
>   	 */
> -	fillConfigParamsBlock(params);
> +	programMeshTables(params);
> +}
> +
> +/**
> + * \copydoc libcamera::ipa::Algorithm::process
> + */
> +void Lsc::process([[maybe_unused]] IPAContext &context,
> +		  [[maybe_unused]] const uint32_t frame,
> +		  IPAFrameContext &frameContext,
> +		  [[maybe_unused]] const mali_c55_stats_buffer *stats,
> +		  ControlList &metadata)
> +{
> +	lscAlgo_.process(frameContext.lsc, metadata);
>   }
>   
>   REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
> diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
> index 3d35fd72bfa8..79ad42094c42 100644
> --- a/src/ipa/mali-c55/algorithms/lsc.h
> +++ b/src/ipa/mali-c55/algorithms/lsc.h
> @@ -5,10 +5,19 @@
>    * Mali-C55 Lens shading correction algorithm
>    */
>   
> -#include <map>
> +#include <vector>
>   #include <tuple>
>   
> +#include <linux/media/arm/mali-c55-config.h>
> +
> +#include "libcamera/internal/value_node.h"
> +
> +#include "libipa/fixedpoint.h"
> +#include "libipa/lsc.h"
> +
>   #include "algorithm.h"
> +#include "ipa_context.h"
> +#include "params.h"
>   
>   namespace libcamera {
>   
> @@ -21,23 +30,30 @@ public:
>   	~Lsc() = 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:
> -	static constexpr unsigned int kRedOffset = 0;
> -	static constexpr unsigned int kGreenOffset = 1024;
> -	static constexpr unsigned int kBlueOffset = 2048;
> -
> -	void fillConfigParamsBlock(MaliC55Params *params) const;
> -	void fillSelectionParamsBlock(MaliC55Params *params,
> -				      uint8_t bank, uint8_t alpha) const;
> +	std::vector<double> segmentsToPosition() const;
> +	void programMeshTables(MaliC55Params *params) const;
> +	void configureMeshBlending(MaliC55Params *params,
> +				   uint8_t bank, uint8_t alpha) const;
>   	std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const;
>   
> -	std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072);
>   	std::vector<uint32_t> colourTemperatures_;
> -	uint32_t meshScale_;
> -	uint32_t meshSize_;
> +	std::vector<uint32_t> mesh_;
> +
> +	std::vector<double> gridPos_;
> +
> +	LscAlgorithm<uint8_t, UQ<2, 6>> lscAlgo_;
>   };
>   
>   } /* namespace ipa::mali_c55::algorithms */
> diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
> index 2f2092677fbf..749309115d84 100644
> --- a/src/ipa/mali-c55/ipa_context.h
> +++ b/src/ipa/mali-c55/ipa_context.h
> @@ -20,6 +20,7 @@
>   #include "libipa/awb.h"
>   #include "libipa/ccm.h"
>   #include "libipa/fixedpoint.h"
> +#include "libipa/lsc.h"
>   
>   namespace libcamera {
>   
> @@ -64,6 +65,8 @@ struct IPAActiveState {
>   	ipa::awb::ActiveState awb;
>   
>   	ipa::ccm::ActiveState ccm;
> +
> +	ipa::lsc::ActiveState lsc;

Very mild preference not to have the blank lines there between the states / contexts

All very minor points:

Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

>   };
>   
>   struct IPAFrameContext : public FrameContext {
> @@ -76,6 +79,8 @@ struct IPAFrameContext : public FrameContext {
>   	ipa::awb::FrameContext awb;
>   
>   	ipa::ccm::FrameContext ccm;
> +
> +	ipa::lsc::FrameContext lsc;
>   };
>   
>   struct IPAContext {
>
Jacopo Mondi June 23, 2026, 12:31 p.m. UTC | #4
On Tue, Jun 23, 2026 at 11:06:25AM +0100, Dan Scally wrote:
> Hi Jacopo
>
> On 15/06/2026 15:05, Jacopo Mondi wrote:
> > Port the Mali-C55 LSC algorithm to use libIPA LscAlgorithm.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > ---
> >   src/ipa/mali-c55/algorithms/lsc.cpp | 163 ++++++++++++++++++++++--------------
> >   src/ipa/mali-c55/algorithms/lsc.h   |  38 ++++++---
> >   src/ipa/mali-c55/ipa_context.h      |   5 ++
> >   3 files changed, 130 insertions(+), 76 deletions(-)
> >
> > diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
> > index 36e50163aecc..624b393b80e5 100644
> > --- a/src/ipa/mali-c55/algorithms/lsc.cpp
> > +++ b/src/ipa/mali-c55/algorithms/lsc.cpp
> > @@ -9,39 +9,74 @@
> >   #include <libcamera/base/utils.h>
> > -#include "libcamera/internal/value_node.h"
> > -
> >   namespace libcamera {
> >   namespace ipa::mali_c55::algorithms {
> >   LOG_DEFINE_CATEGORY(MaliC55Lsc)
> > -int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> > +/* Gain values in [1, 5] range */
> > +static constexpr unsigned int kMeshScale = 6;
>
> The IPU3 lens shading block actually has the concept of a configurable scale
> too, so I wonder if this is something that could be done in the base
> algorithms. It could be done on top though; and 5.0x is probably quite a
> high gain for LSC anyway
>

mmm, configurable scale seems something that belongs to the IPA module
itself, doens't it ? Do you think we can generalize it ? Using which
values ?

> > +
> > +/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */
> > +static constexpr unsigned int kMeshSize = 32;
> > +static constexpr unsigned int kGridSize = kMeshSize * kMeshSize;
> > +
> > +/* Per-colour component page offsets in the mesh table. */
> > +static constexpr unsigned int kRedOffset = 0;
> > +static constexpr unsigned int kGreenOffset = 1024;
> > +static constexpr unsigned int kBlueOffset = 2048;
> > +
> > +/*
> > + * \todo Mali-C55 supports up to 4 colour temperatures.
> > + *
> > + * Unfortunately the uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072)
> > + * gain elements, which correspond to three pages of 1024 (32x32) entries.
> > + */
>
> I think that the 4th colour temperature is not supposed to be usable,
> despite its declaration in the documentation.

Ack, I made the \todo

/*
 * \todo Clarify if Mali-C55 can support up to 4 colour temperatures.
 *
 * The uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072) gain elements,
 * which correspond to three pages of 1024 (32x32) entries.
 */

>
> > +static constexpr unsigned int kMaxColourTemperature = 3;
>
> s/kMaxColourTemperature/kMaxColourTemperatures
>

ack

> > +
> > +/*
> > + * The LSC algorithm implementation only supports 32x32 grids. Create a list of
> > + * positions from the grid size.
> > + */
> > +std::vector<double> Lsc::segmentsToPosition() const
> >   {
> > -	if (!tuningData.contains("meshScale")) {
> > -		LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
> > -		return -EINVAL;
> > -	}
> > +	std::vector<double> positions(kMeshSize);
> > +	for (double i = 0.0; i < kMeshSize; ++i)
> > +		positions[i] = i / (kMeshSize - 1);
> > -	meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
> > +	return positions;
> > +}
> > -	const ValueNode &yamlSets = tuningData["sets"];
> > -	if (!yamlSets.isList()) {
> > -		LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
> > -		return -EINVAL;
> > -	}
> > +int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
>
> context is used, so the decorator can be dropped.
>

Sure, nice catch

> > +{
> > +	gridPos_ = segmentsToPosition();
> > +
> > +	return lscAlgo_.init(tuningData, context.ctrlMap, {
> > +			     .keys = { "r", "g", "b" },
> > +			     .numHCells = kMeshSize,
> > +			     .numVCells = kMeshSize,
> > +			     .sensorSize = context.sensorInfo.activeAreaSize
> > +			});
> > +}
> > -	size_t tableSize = 0;
> > -	const auto &sets = yamlSets.asList();
> > -	for (const auto &yamlSet : sets) {
> > -		uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
> > +int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
> > +{
> > +	int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,
> > +				     gridPos_, gridPos_);
> > +	if (ret)
> > +		return ret;
> > -		if (!ct) {
> > -			LOG(MaliC55Lsc, Error) << "Invalid colour temperature";
> > -			return -EINVAL;
> > -		}
> > +	/* Re-initialize the mesh tables and reserve space for enough entries. */
> > +	mesh_ = std::vector<uint32_t>(kGridSize * kMaxColourTemperature);
> > +	colourTemperatures_.clear();
> > +	/*
> > +	 * Get the lsc tables per colour components and populate mesh_ with
> > +	 * their content.
> > +	 */
> > +	const auto &components = lscAlgo_.getComponents();
> > +	for (auto const &[ct, component] : components) {
> >   		if (std::count(colourTemperatures_.begin(),
> >   			       colourTemperatures_.end(), ct)) {
> >   			LOG(MaliC55Lsc, Error)
> > @@ -49,44 +84,29 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> >   			return -EINVAL;
> >   		}
> > -		std::vector<uint8_t> rTable =
> > -			yamlSet["r"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -		std::vector<uint8_t> gTable =
> > -			yamlSet["g"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -		std::vector<uint8_t> bTable =
> > -			yamlSet["b"].get<std::vector<uint8_t>>().value_or(utils::defopt);
> > -
> > -		/*
> > -		 * Some validation to do; only 16x16 and 32x32 tables of
> > -		 * coefficients are acceptable, and all tables across all of the
> > -		 * sets must be the same size. The first time we encounter a
> > -		 * table we check that it is an acceptable size and if so make
> > -		 * sure all other tables are of equal size.
> > -		 */
> > -		if (!tableSize) {
> > -			if (rTable.size() != 256 && rTable.size() != 1024) {
> > -				LOG(MaliC55Lsc, Error)
> > -					<< "Invalid table size for colour temperature " << ct;
> > -				return -EINVAL;
> > -			}
> > -			tableSize = rTable.size();
> > -		}
> > +		std::vector<uint8_t> rTable = component.at("r");
> > +		std::vector<uint8_t> gTable = component.at("g");
> > +		std::vector<uint8_t> bTable = component.at("b");
> > -		if (rTable.size() != tableSize ||
> > -		    gTable.size() != tableSize ||
> > -		    bTable.size() != tableSize) {
> > +		/* Only 32x32 tables of coefficients are accepted. */
> > +		if (rTable.size() != kGridSize || gTable.size() != kGridSize ||
> > +		    bTable.size() != kGridSize) {
> >   			LOG(MaliC55Lsc, Error)
> > -				<< "Invalid or mismatched table size for colour temperature " << ct;
> > +				<< "Invalid table size for colour temperature " << ct;
> >   			return -EINVAL;
> >   		}
> > -		if (colourTemperatures_.size() >= 3) {
> > +		if (colourTemperatures_.size() >= kMaxColourTemperature) {
> >   			LOG(MaliC55Lsc, Error)
> >   				<< "A maximum of 3 colour temperatures are supported";
> >   			return -EINVAL;
> >   		}
> > -		for (unsigned int i = 0; i < tableSize; i++) {
> > +		/*
> > +		 * Create the mesh table entries by assembling up to 3 gains per
> > +		 * colour temperature in a u32 word.
> > +		 */
> > +		for (unsigned int i = 0; i < kGridSize; i++) {
> >   			mesh_[kRedOffset + i] |=
> >   				(rTable[i] << (colourTemperatures_.size() * 8));
> >   			mesh_[kGreenOffset + i] |=
> > @@ -98,34 +118,35 @@ int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
> >   		colourTemperatures_.push_back(ct);
> >   	}
> > -	/*
> > -	 * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it
> > -	 * is based on the number of values in the tuning data's table.
> > -	 */
> > -	if (tableSize == 256)
> > -		meshSize_ = 15;
> > -	else
> > -		meshSize_ = 31;
> > -
> >   	return 0;
> >   }
> > -void Lsc::fillConfigParamsBlock(MaliC55Params *params) const
> > +/**
> > + * \copydoc libcamera::ipa::Algorithm::queueRequest
> > + */
> > +void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> > +		       IPAFrameContext &frameContext, const ControlList &controls)
> > +{
> > +	lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
> > +			      controls);
> > +}
> > +
> > +void Lsc::programMeshTables(MaliC55Params *params) const
> >   {
> >   	auto block = params->block<MaliC55Blocks::MeshShadingConfig>();
> >   	block->mesh_show = false;
> > -	block->mesh_scale = meshScale_;
> > +	block->mesh_scale = kMeshScale;
> >   	block->mesh_page_r = 0;
> >   	block->mesh_page_g = 1;
> >   	block->mesh_page_b = 2;
> > -	block->mesh_width = meshSize_;
> > -	block->mesh_height = meshSize_;
> > +	block->mesh_width = kMeshSize - 1;
> > +	block->mesh_height = kMeshSize - 1;
> >   	std::copy(mesh_.begin(), mesh_.end(), block->mesh);
> >   }
> > -void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank,
> > +void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank,
> >   				     uint8_t alpha) const
> >   {
> >   	auto block = params->block<MaliC55Blocks::MeshShadingSel>();
> > @@ -187,7 +208,7 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> >   		std::tie(bank, alpha) = findBankAndAlpha(temperatureK);
> >   	}
> > -	fillSelectionParamsBlock(params, bank, alpha);
> > +	configureMeshBlending(params, bank, alpha);
> >   	if (frame > 0)
> >   		return;
> > @@ -196,7 +217,19 @@ void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
> >   	 * If this is the first frame, we need to load the parsed coefficient
> >   	 * tables from tuning data to the ISP.
> >   	 */
> > -	fillConfigParamsBlock(params);
> > +	programMeshTables(params);
> > +}
> > +
> > +/**
> > + * \copydoc libcamera::ipa::Algorithm::process
> > + */
> > +void Lsc::process([[maybe_unused]] IPAContext &context,
> > +		  [[maybe_unused]] const uint32_t frame,
> > +		  IPAFrameContext &frameContext,
> > +		  [[maybe_unused]] const mali_c55_stats_buffer *stats,
> > +		  ControlList &metadata)
> > +{
> > +	lscAlgo_.process(frameContext.lsc, metadata);
> >   }
> >   REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
> > diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
> > index 3d35fd72bfa8..79ad42094c42 100644
> > --- a/src/ipa/mali-c55/algorithms/lsc.h
> > +++ b/src/ipa/mali-c55/algorithms/lsc.h
> > @@ -5,10 +5,19 @@
> >    * Mali-C55 Lens shading correction algorithm
> >    */
> > -#include <map>
> > +#include <vector>
> >   #include <tuple>
> > +#include <linux/media/arm/mali-c55-config.h>
> > +
> > +#include "libcamera/internal/value_node.h"
> > +
> > +#include "libipa/fixedpoint.h"
> > +#include "libipa/lsc.h"
> > +
> >   #include "algorithm.h"
> > +#include "ipa_context.h"
> > +#include "params.h"
> >   namespace libcamera {
> > @@ -21,23 +30,30 @@ public:
> >   	~Lsc() = 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:
> > -	static constexpr unsigned int kRedOffset = 0;
> > -	static constexpr unsigned int kGreenOffset = 1024;
> > -	static constexpr unsigned int kBlueOffset = 2048;
> > -
> > -	void fillConfigParamsBlock(MaliC55Params *params) const;
> > -	void fillSelectionParamsBlock(MaliC55Params *params,
> > -				      uint8_t bank, uint8_t alpha) const;
> > +	std::vector<double> segmentsToPosition() const;
> > +	void programMeshTables(MaliC55Params *params) const;
> > +	void configureMeshBlending(MaliC55Params *params,
> > +				   uint8_t bank, uint8_t alpha) const;
> >   	std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const;
> > -	std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072);
> >   	std::vector<uint32_t> colourTemperatures_;
> > -	uint32_t meshScale_;
> > -	uint32_t meshSize_;
> > +	std::vector<uint32_t> mesh_;
> > +
> > +	std::vector<double> gridPos_;
> > +
> > +	LscAlgorithm<uint8_t, UQ<2, 6>> lscAlgo_;
> >   };
> >   } /* namespace ipa::mali_c55::algorithms */
> > diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
> > index 2f2092677fbf..749309115d84 100644
> > --- a/src/ipa/mali-c55/ipa_context.h
> > +++ b/src/ipa/mali-c55/ipa_context.h
> > @@ -20,6 +20,7 @@
> >   #include "libipa/awb.h"
> >   #include "libipa/ccm.h"
> >   #include "libipa/fixedpoint.h"
> > +#include "libipa/lsc.h"
> >   namespace libcamera {
> > @@ -64,6 +65,8 @@ struct IPAActiveState {
> >   	ipa::awb::ActiveState awb;
> >   	ipa::ccm::ActiveState ccm;
> > +
> > +	ipa::lsc::ActiveState lsc;
>
> Very mild preference not to have the blank lines there between the states / contexts

Ack, I'll remove it (I had actually added a blank line between awb and
ccm already :)


>
> All very minor points:
>
> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>

Thanks!

>
> >   };
> >   struct IPAFrameContext : public FrameContext {
> > @@ -76,6 +79,8 @@ struct IPAFrameContext : public FrameContext {
> >   	ipa::awb::FrameContext awb;
> >   	ipa::ccm::FrameContext ccm;
> > +
> > +	ipa::lsc::FrameContext lsc;
> >   };
> >   struct IPAContext {
> >
>

Patch
diff mbox series

diff --git a/src/ipa/mali-c55/algorithms/lsc.cpp b/src/ipa/mali-c55/algorithms/lsc.cpp
index 36e50163aecc..624b393b80e5 100644
--- a/src/ipa/mali-c55/algorithms/lsc.cpp
+++ b/src/ipa/mali-c55/algorithms/lsc.cpp
@@ -9,39 +9,74 @@ 
 
 #include <libcamera/base/utils.h>
 
-#include "libcamera/internal/value_node.h"
-
 namespace libcamera {
 
 namespace ipa::mali_c55::algorithms {
 
 LOG_DEFINE_CATEGORY(MaliC55Lsc)
 
-int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
+/* Gain values in [1, 5] range */
+static constexpr unsigned int kMeshScale = 6;
+
+/* Mali-C55 hw supports configurable mesh sizes; we fix it to 32. */
+static constexpr unsigned int kMeshSize = 32;
+static constexpr unsigned int kGridSize = kMeshSize * kMeshSize;
+
+/* Per-colour component page offsets in the mesh table. */
+static constexpr unsigned int kRedOffset = 0;
+static constexpr unsigned int kGreenOffset = 1024;
+static constexpr unsigned int kBlueOffset = 2048;
+
+/*
+ * \todo Mali-C55 supports up to 4 colour temperatures.
+ *
+ * Unfortunately the uAPI only expose MALI_C55_NUM_MESH_SHADING_ELEMENTS (3072)
+ * gain elements, which correspond to three pages of 1024 (32x32) entries.
+ */
+static constexpr unsigned int kMaxColourTemperature = 3;
+
+/*
+ * The LSC algorithm implementation only supports 32x32 grids. Create a list of
+ * positions from the grid size.
+ */
+std::vector<double> Lsc::segmentsToPosition() const
 {
-	if (!tuningData.contains("meshScale")) {
-		LOG(MaliC55Lsc, Error) << "meshScale missing from tuningData";
-		return -EINVAL;
-	}
+	std::vector<double> positions(kMeshSize);
+	for (double i = 0.0; i < kMeshSize; ++i)
+		positions[i] = i / (kMeshSize - 1);
 
-	meshScale_ = tuningData["meshScale"].get<uint32_t>(0);
+	return positions;
+}
 
-	const ValueNode &yamlSets = tuningData["sets"];
-	if (!yamlSets.isList()) {
-		LOG(MaliC55Lsc, Error) << "LSC tables missing or invalid";
-		return -EINVAL;
-	}
+int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
+{
+	gridPos_ = segmentsToPosition();
+
+	return lscAlgo_.init(tuningData, context.ctrlMap, {
+			     .keys = { "r", "g", "b" },
+			     .numHCells = kMeshSize,
+			     .numVCells = kMeshSize,
+			     .sensorSize = context.sensorInfo.activeAreaSize
+			});
+}
 
-	size_t tableSize = 0;
-	const auto &sets = yamlSets.asList();
-	for (const auto &yamlSet : sets) {
-		uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+int Lsc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
+{
+	int ret = lscAlgo_.configure(context.activeState.lsc, configInfo.analogCrop,
+				     gridPos_, gridPos_);
+	if (ret)
+		return ret;
 
-		if (!ct) {
-			LOG(MaliC55Lsc, Error) << "Invalid colour temperature";
-			return -EINVAL;
-		}
+	/* Re-initialize the mesh tables and reserve space for enough entries. */
+	mesh_ = std::vector<uint32_t>(kGridSize * kMaxColourTemperature);
+	colourTemperatures_.clear();
 
+	/*
+	 * Get the lsc tables per colour components and populate mesh_ with
+	 * their content.
+	 */
+	const auto &components = lscAlgo_.getComponents();
+	for (auto const &[ct, component] : components) {
 		if (std::count(colourTemperatures_.begin(),
 			       colourTemperatures_.end(), ct)) {
 			LOG(MaliC55Lsc, Error)
@@ -49,44 +84,29 @@  int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 			return -EINVAL;
 		}
 
-		std::vector<uint8_t> rTable =
-			yamlSet["r"].get<std::vector<uint8_t>>().value_or(utils::defopt);
-		std::vector<uint8_t> gTable =
-			yamlSet["g"].get<std::vector<uint8_t>>().value_or(utils::defopt);
-		std::vector<uint8_t> bTable =
-			yamlSet["b"].get<std::vector<uint8_t>>().value_or(utils::defopt);
-
-		/*
-		 * Some validation to do; only 16x16 and 32x32 tables of
-		 * coefficients are acceptable, and all tables across all of the
-		 * sets must be the same size. The first time we encounter a
-		 * table we check that it is an acceptable size and if so make
-		 * sure all other tables are of equal size.
-		 */
-		if (!tableSize) {
-			if (rTable.size() != 256 && rTable.size() != 1024) {
-				LOG(MaliC55Lsc, Error)
-					<< "Invalid table size for colour temperature " << ct;
-				return -EINVAL;
-			}
-			tableSize = rTable.size();
-		}
+		std::vector<uint8_t> rTable = component.at("r");
+		std::vector<uint8_t> gTable = component.at("g");
+		std::vector<uint8_t> bTable = component.at("b");
 
-		if (rTable.size() != tableSize ||
-		    gTable.size() != tableSize ||
-		    bTable.size() != tableSize) {
+		/* Only 32x32 tables of coefficients are accepted. */
+		if (rTable.size() != kGridSize || gTable.size() != kGridSize ||
+		    bTable.size() != kGridSize) {
 			LOG(MaliC55Lsc, Error)
-				<< "Invalid or mismatched table size for colour temperature " << ct;
+				<< "Invalid table size for colour temperature " << ct;
 			return -EINVAL;
 		}
 
-		if (colourTemperatures_.size() >= 3) {
+		if (colourTemperatures_.size() >= kMaxColourTemperature) {
 			LOG(MaliC55Lsc, Error)
 				<< "A maximum of 3 colour temperatures are supported";
 			return -EINVAL;
 		}
 
-		for (unsigned int i = 0; i < tableSize; i++) {
+		/*
+		 * Create the mesh table entries by assembling up to 3 gains per
+		 * colour temperature in a u32 word.
+		 */
+		for (unsigned int i = 0; i < kGridSize; i++) {
 			mesh_[kRedOffset + i] |=
 				(rTable[i] << (colourTemperatures_.size() * 8));
 			mesh_[kGreenOffset + i] |=
@@ -98,34 +118,35 @@  int Lsc::init([[maybe_unused]] IPAContext &context, const ValueNode &tuningData)
 		colourTemperatures_.push_back(ct);
 	}
 
-	/*
-	 * The mesh has either 16x16 or 32x32 nodes, we tell the driver which it
-	 * is based on the number of values in the tuning data's table.
-	 */
-	if (tableSize == 256)
-		meshSize_ = 15;
-	else
-		meshSize_ = 31;
-
 	return 0;
 }
 
-void Lsc::fillConfigParamsBlock(MaliC55Params *params) const
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Lsc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+		       IPAFrameContext &frameContext, const ControlList &controls)
+{
+	lscAlgo_.queueRequest(context.activeState.lsc, frameContext.lsc,
+			      controls);
+}
+
+void Lsc::programMeshTables(MaliC55Params *params) const
 {
 	auto block = params->block<MaliC55Blocks::MeshShadingConfig>();
 
 	block->mesh_show = false;
-	block->mesh_scale = meshScale_;
+	block->mesh_scale = kMeshScale;
 	block->mesh_page_r = 0;
 	block->mesh_page_g = 1;
 	block->mesh_page_b = 2;
-	block->mesh_width = meshSize_;
-	block->mesh_height = meshSize_;
+	block->mesh_width = kMeshSize - 1;
+	block->mesh_height = kMeshSize - 1;
 
 	std::copy(mesh_.begin(), mesh_.end(), block->mesh);
 }
 
-void Lsc::fillSelectionParamsBlock(MaliC55Params *params, uint8_t bank,
+void Lsc::configureMeshBlending(MaliC55Params *params, uint8_t bank,
 				     uint8_t alpha) const
 {
 	auto block = params->block<MaliC55Blocks::MeshShadingSel>();
@@ -187,7 +208,7 @@  void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 		std::tie(bank, alpha) = findBankAndAlpha(temperatureK);
 	}
 
-	fillSelectionParamsBlock(params, bank, alpha);
+	configureMeshBlending(params, bank, alpha);
 
 	if (frame > 0)
 		return;
@@ -196,7 +217,19 @@  void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	 * If this is the first frame, we need to load the parsed coefficient
 	 * tables from tuning data to the ISP.
 	 */
-	fillConfigParamsBlock(params);
+	programMeshTables(params);
+}
+
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void Lsc::process([[maybe_unused]] IPAContext &context,
+		  [[maybe_unused]] const uint32_t frame,
+		  IPAFrameContext &frameContext,
+		  [[maybe_unused]] const mali_c55_stats_buffer *stats,
+		  ControlList &metadata)
+{
+	lscAlgo_.process(frameContext.lsc, metadata);
 }
 
 REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
diff --git a/src/ipa/mali-c55/algorithms/lsc.h b/src/ipa/mali-c55/algorithms/lsc.h
index 3d35fd72bfa8..79ad42094c42 100644
--- a/src/ipa/mali-c55/algorithms/lsc.h
+++ b/src/ipa/mali-c55/algorithms/lsc.h
@@ -5,10 +5,19 @@ 
  * Mali-C55 Lens shading correction algorithm
  */
 
-#include <map>
+#include <vector>
 #include <tuple>
 
+#include <linux/media/arm/mali-c55-config.h>
+
+#include "libcamera/internal/value_node.h"
+
+#include "libipa/fixedpoint.h"
+#include "libipa/lsc.h"
+
 #include "algorithm.h"
+#include "ipa_context.h"
+#include "params.h"
 
 namespace libcamera {
 
@@ -21,23 +30,30 @@  public:
 	~Lsc() = 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:
-	static constexpr unsigned int kRedOffset = 0;
-	static constexpr unsigned int kGreenOffset = 1024;
-	static constexpr unsigned int kBlueOffset = 2048;
-
-	void fillConfigParamsBlock(MaliC55Params *params) const;
-	void fillSelectionParamsBlock(MaliC55Params *params,
-				      uint8_t bank, uint8_t alpha) const;
+	std::vector<double> segmentsToPosition() const;
+	void programMeshTables(MaliC55Params *params) const;
+	void configureMeshBlending(MaliC55Params *params,
+				   uint8_t bank, uint8_t alpha) const;
 	std::tuple<uint8_t, uint8_t> findBankAndAlpha(uint32_t ct) const;
 
-	std::vector<uint32_t> mesh_ = std::vector<uint32_t>(3072);
 	std::vector<uint32_t> colourTemperatures_;
-	uint32_t meshScale_;
-	uint32_t meshSize_;
+	std::vector<uint32_t> mesh_;
+
+	std::vector<double> gridPos_;
+
+	LscAlgorithm<uint8_t, UQ<2, 6>> lscAlgo_;
 };
 
 } /* namespace ipa::mali_c55::algorithms */
diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h
index 2f2092677fbf..749309115d84 100644
--- a/src/ipa/mali-c55/ipa_context.h
+++ b/src/ipa/mali-c55/ipa_context.h
@@ -20,6 +20,7 @@ 
 #include "libipa/awb.h"
 #include "libipa/ccm.h"
 #include "libipa/fixedpoint.h"
+#include "libipa/lsc.h"
 
 namespace libcamera {
 
@@ -64,6 +65,8 @@  struct IPAActiveState {
 	ipa::awb::ActiveState awb;
 
 	ipa::ccm::ActiveState ccm;
+
+	ipa::lsc::ActiveState lsc;
 };
 
 struct IPAFrameContext : public FrameContext {
@@ -76,6 +79,8 @@  struct IPAFrameContext : public FrameContext {
 	ipa::awb::FrameContext awb;
 
 	ipa::ccm::FrameContext ccm;
+
+	ipa::lsc::FrameContext lsc;
 };
 
 struct IPAContext {