diff --git a/src/ipa/libipa/lsc.cpp b/src/ipa/libipa/lsc.cpp
index 66d33a2dd796..9bc71e3c73ae 100644
--- a/src/ipa/libipa/lsc.cpp
+++ b/src/ipa/libipa/lsc.cpp
@@ -86,16 +86,16 @@ namespace lsc {
 
 /**
  * \param[in] tuningData The tuning data
- * \param[in] sensorSize The physical sensor size
  * \param[in] controls The IPA list of supported controls
+ * \param[in] descriptor The lsc engine descriptor
  *
  * Parse \a tuningData according to the settings specified in \a descriptor to
  * populate the lsc data and registers lsc controls in \a controls.
  *
  * \return 0 on success, a negative error code otherwise
  */
-int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize,
-		       ControlInfoMap::Map &controls)
+int LscAlgorithm::init(const ValueNode &tuningData, ControlInfoMap::Map &controls,
+		       const LscDescriptor &descriptor)
 {
 	polynomial_ = false;
 
@@ -108,7 +108,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize,
 		 * \todo: Most likely the reference frame should be native_size.
 		 * Let's wait how the internal discussions progress.
 		 */
-		impl_ = std::make_unique<LscPolynomial>(sensorSize);
+		impl_ = std::make_unique<LscPolynomial>(descriptor.sensorSize);
 		polynomial_ = true;
 		LOG(Lsc, Debug) << "Using polynomial Lsc";
 	} else {
@@ -123,7 +123,7 @@ int LscAlgorithm::init(const ValueNode &tuningData, const Size &sensorSize,
 		return -EINVAL;
 	}
 
-	int ret = impl_->parseLscData(yamlSets);
+	int ret = impl_->parseLscData(yamlSets, descriptor);
 	if (ret)
 		return ret;
 
diff --git a/src/ipa/libipa/lsc.h b/src/ipa/libipa/lsc.h
index 9a51d32c3464..9fe8ad67ea33 100644
--- a/src/ipa/libipa/lsc.h
+++ b/src/ipa/libipa/lsc.h
@@ -38,8 +38,8 @@ struct FrameContext {
 class LscAlgorithm
 {
 public:
-	int init(const ValueNode &tuningData, const Size &sensorSize,
-		 ControlInfoMap::Map &controls);
+	int init(const ValueNode &tuningData, ControlInfoMap::Map &controls,
+		 const LscDescriptor &descriptor);
 
 	int configure(lsc::ActiveState &state, const Rectangle &analogCrop,
 		      const std::vector<double> &xPos,
diff --git a/src/ipa/libipa/lsc_base.cpp b/src/ipa/libipa/lsc_base.cpp
index 7b0216b689c4..30af8d970a12 100644
--- a/src/ipa/libipa/lsc_base.cpp
+++ b/src/ipa/libipa/lsc_base.cpp
@@ -19,23 +19,20 @@ namespace ipa {
 namespace lsc {
 
 /**
- * \struct Components
+ * \typedef Components
  * \brief Associate a colour components with a list of gains
  *
  * Lsc tables are defined as a list of gain values associated to a colour
  * component.
  *
- * \var Components::r
- * \brief The list of gains for the Red colour component
+ * As different ISP support different colour components (usually 'r', 'gr',
+ * 'gb', 'b' or just 'r', 'g', 'b') this class associates a string
+ * identifier for the colour component to a list of gains.
  *
- * \var Components::gr
- * \brief The list of gains for the Green/Red colour component
+ * Each key name shall match an entry in the tuning file.
  *
- * \var Components::gb
- * \brief The list of gains for the Green/Blue colour component
- *
- * \var Components::b
- * \brief The list of gains for the Blue colour component
+ * The list of keys is provided to the LscAlgorithm class using \a
+ * LscDescriptor::keys.
  */
 
 /**
@@ -57,13 +54,32 @@ void Interpolator<lsc::Components>::
 		    lsc::Components &dest,
 		    double lambda)
 {
-	interpolateVector(a.r, b.r, dest.r, lambda);
-	interpolateVector(a.gr, b.gr, dest.gr, lambda);
-	interpolateVector(a.gb, b.gb, dest.gb, lambda);
-	interpolateVector(a.b, b.b, dest.b, lambda);
+	for (auto const &[k, v] : a)
+		interpolateVector(v, b.at(k), dest[k], lambda);
 }
 #endif
 
+/**
+ * \struct LscDescriptor
+ * \brief Describe the ISP lsc engine
+ *
+ * \var LscDescriptor::keys
+ * \brief The list of colour components to which a list of gains is associated
+ * with in the tuning file. Used for parsing the tuning file
+ *
+ * \var LscDescriptor::numHCells
+ * \brief Number of horizontal cells of the ISP lsc grid. Used for validating
+ * the list of gains parsed from tuning file
+ *
+ * \var LscDescriptor::numVCells
+ * \brief Number of vertical cells of the ISP lsc grid. Used for validating
+ * the list of gains parsed from tuning file
+ *
+ * \var LscDescriptor::sensorSize
+ * \brief The physical sensor size. This is the largest frame size used to
+ * generate the lsc table. Only used by the polynomial lsc algorithm
+ */
+
 /**
  * \class LscImplementation
  * \brief Pure virtual base class for lsc algorithm implementations
@@ -80,6 +96,11 @@ void Interpolator<lsc::Components>::
  * \fn LscImplementation::parseLscData
  * \brief Parse \a tuningData
  * \param[in] tuningData The tuning data
+ * \param[in] descriptor The lsc engine descriptor
+ *
+ * Parse the tuning file using the \a descriptor to identify the colour
+ * components in the tuning data and validate the size of the loaded gains
+ * tables.
  *
  * \return 0 on success, a negative error number otherwise
  */
diff --git a/src/ipa/libipa/lsc_base.h b/src/ipa/libipa/lsc_base.h
index 8d1843a5d4fd..48307455362a 100644
--- a/src/ipa/libipa/lsc_base.h
+++ b/src/ipa/libipa/lsc_base.h
@@ -26,13 +26,7 @@ namespace ipa {
 
 namespace lsc {
 
-struct Components {
-	std::vector<uint16_t> r;
-	std::vector<uint16_t> gr;
-	std::vector<uint16_t> gb;
-	std::vector<uint16_t> b;
-};
-
+using Components = std::map<std::string, std::vector<uint16_t>>;
 using ComponentsMap = std::map<unsigned int, Components>;
 
 } /* namespace lsc */
@@ -56,12 +50,20 @@ void Interpolator<lsc::Components>::
 		    double lambda);
 #endif /* __DOXYGEN__ */
 
+struct LscDescriptor {
+	std::vector<std::string> keys;
+	unsigned int numHCells;
+	unsigned int numVCells;
+	Size sensorSize;
+};
+
 class LscImplementation
 {
 public:
 	virtual ~LscImplementation() {}
 
-	virtual int parseLscData(const ValueNode &tuningData) = 0;
+	virtual int parseLscData(const ValueNode &tuningData,
+				 const LscDescriptor &descriptor) = 0;
 
 	virtual lsc::ComponentsMap
 	sampleForCrop(const Rectangle &cropRectangle,
diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp
index f39122963019..0550229b5152 100644
--- a/src/ipa/libipa/lsc_polynomial.cpp
+++ b/src/ipa/libipa/lsc_polynomial.cpp
@@ -133,44 +133,43 @@ void Polynomial::setReferenceImageSize(const Size &size)
 /**
  * \brief Parse polynomial lsc data
  * \param[in] sets The tuning file content
+ * \param[in] descriptor The lsc engine descriptor
  *
  * Parse the lsc data in polyomial form from the \a sets tuning data.
  *
  * \return 0 on success or a negative error number otherwise
  */
-int LscPolynomial::parseLscData(const ValueNode &sets)
+int LscPolynomial::parseLscData(const ValueNode &sets,
+				const LscDescriptor &descriptor)
 {
 	for (const auto &set : sets.asList()) {
-		std::optional<lsc::Polynomial> pr, pgr, pgb, pb;
 		uint32_t ct = set["ct"].get<uint32_t>(0);
 
-		if (lscData_.count(ct)) {
-			LOG(LscPolynomial, Error)
-				<< "Multiple sets found for "
-				<< "color temperature " << ct;
-			return -EINVAL;
+		PolynomialComponents components;
+		for (auto &k : descriptor.keys) {
+			auto polynomial = set[k.c_str()].get<lsc::Polynomial>();
+			if (!polynomial) {
+				LOG(LscPolynomial, Error)
+					<< "Missing polynomial for component "
+					<< k;
+				return -EINVAL;
+			}
+
+			auto [it, inserted] =
+				components.emplace(std::piecewise_construct,
+						   std::forward_as_tuple(k.c_str()),
+						   std::forward_as_tuple(*polynomial));
+
+			it->second.setReferenceImageSize(descriptor.sensorSize);
 		}
 
-		pr = set["r"].get<lsc::Polynomial>();
-		pgr = set["gr"].get<lsc::Polynomial>();
-		pgb = set["gb"].get<lsc::Polynomial>();
-		pb = set["b"].get<lsc::Polynomial>();
-
-		if (!(pr || pgr || pgb || pb)) {
+		auto [it, inserted] = lscData_.emplace(ct, components);
+		if (!inserted) {
 			LOG(LscPolynomial, Error)
-				<< "Failed to parse polynomial for "
-				<< "colour temperature " << ct;
+				<< "Multiple sets found for "
+				<< "color temperature " << ct;
 			return -EINVAL;
 		}
-
-		pr->setReferenceImageSize(sensorSize_);
-		pgr->setReferenceImageSize(sensorSize_);
-		pgb->setReferenceImageSize(sensorSize_);
-		pb->setReferenceImageSize(sensorSize_);
-
-		lscData_.emplace(std::piecewise_construct,
-				 std::forward_as_tuple(ct),
-				 std::forward_as_tuple(PolynomialComponents{ *pr, *pgr, *pgb, *pb }));
 	}
 
 	if (lscData_.empty()) {
@@ -210,13 +209,17 @@ LscPolynomial::sampleForCrop(const Rectangle &cropRectangle,
 
 	lsc::ComponentsMap components;
 
-	for (const auto &[k, p] : lscData_) {
-		components[k] = {
-			samplePolynomial(p.pr, xPos, yPos, cropRectangle),
-			samplePolynomial(p.pgr, xPos, yPos, cropRectangle),
-			samplePolynomial(p.pgb, xPos, yPos, cropRectangle),
-			samplePolynomial(p.pb, xPos, yPos, cropRectangle)
-		};
+	for (const auto &[t, c] : lscData_) {
+		lsc::Components comp;
+
+		for (const auto &[k, p] : c) {
+			comp.emplace(std::piecewise_construct,
+				     std::forward_as_tuple(k),
+				     std::forward_as_tuple(samplePolynomial(p, xPos, yPos,
+									    cropRectangle)));
+		}
+
+		components[t] = comp;
 	}
 
 	return components;
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
index d2276ecc6f6b..2a26db499d68 100644
--- a/src/ipa/libipa/lsc_polynomial.h
+++ b/src/ipa/libipa/lsc_polynomial.h
@@ -53,12 +53,7 @@ private:
 class LscPolynomial : public LscImplementation
 {
 private:
-	struct PolynomialComponents {
-		lsc::Polynomial pr;
-		lsc::Polynomial pgr;
-		lsc::Polynomial pgb;
-		lsc::Polynomial pb;
-	};
+	using PolynomialComponents = std::map<std::string, lsc::Polynomial>;
 	using PolynomialComponentsMap = std::map<unsigned int, PolynomialComponents>;
 
 public:
@@ -67,7 +62,8 @@ public:
 	{
 	}
 
-	int parseLscData(const ValueNode &sets) override;
+	int parseLscData(const ValueNode &sets,
+			 const LscDescriptor &descriptor) override;
 
 	lsc::ComponentsMap
 	sampleForCrop(const Rectangle &cropRectangle,
diff --git a/src/ipa/libipa/lsc_table.cpp b/src/ipa/libipa/lsc_table.cpp
index 41a862b92e88..415c1cc0ccd4 100644
--- a/src/ipa/libipa/lsc_table.cpp
+++ b/src/ipa/libipa/lsc_table.cpp
@@ -7,9 +7,6 @@
 
 #include "lsc_table.h"
 
-/* \todo Remove RkISP1 from libipa. */
-#include "linux/rkisp1-config.h"
-
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(LscTable)
@@ -29,42 +26,56 @@ namespace ipa {
 /**
  * \brief Parse tabular lsc data
  * \param[in] sets The tuning file content
+ * \param[in] descriptor The lsc engine descriptor
  *
  * Parse the lsc data in tabular form from the \a sets tuning data.
  *
  * \return 0 on success or a negative error number otherwise
  */
-int LscTable::parseLscData(const ValueNode &sets)
+int LscTable::parseLscData(const ValueNode &sets,
+			   const LscDescriptor &descriptor)
 {
 	for (const auto &set : sets.asList()) {
 		uint32_t ct = set["ct"].get<uint32_t>(0);
 
-		if (lscData_.count(ct)) {
-			LOG(LscTable, Error)
-				<< "Multiple sets found for color temperature "
-				<< ct;
-			return -EINVAL;
-		}
+		int ret = parseLscComponent(set, ct, descriptor);
+		if (ret)
+			return ret;
+	}
 
-		lsc::Components components;
-		components.r = parseTable(set, "r");
-		components.gr = parseTable(set, "gr");
-		components.gb = parseTable(set, "gb");
-		components.b = parseTable(set, "b");
+	if (lscData_.empty()) {
+		LOG(LscTable, Error) << "Failed to load any sets";
+		return -EINVAL;
+	}
 
-		if (components.r.empty() || components.gr.empty() ||
-		    components.gb.empty() || components.b.empty()) {
+	return 0;
+}
+
+int LscTable::parseLscComponent(const ValueNode &yamlSet,
+				unsigned int ct, const LscDescriptor &descriptor)
+{
+	lsc::Components component;
+	for (auto &k : descriptor.keys) {
+		auto [it, inserted] = component.emplace(
+			std::piecewise_construct,
+			std::forward_as_tuple(k.c_str()),
+			std::forward_as_tuple(parseTable(yamlSet,
+							 k.c_str(),
+							 descriptor.numHCells,
+							 descriptor.numVCells)));
+		if (!inserted || it->second.empty()) {
 			LOG(LscTable, Error)
-				<< "Set for color temperature " << ct
-				<< " is missing tables";
+				<< "Set " << k << " for color temperature "
+				<< ct << " is missing";
 			return -EINVAL;
 		}
-
-		lscData_.emplace(ct, std::move(components));
 	}
 
-	if (lscData_.empty()) {
-		LOG(LscTable, Error) << "Failed to load any sets";
+	auto [it, inserted] = lscData_.emplace(ct, component);
+	if (!inserted) {
+		LOG(LscTable, Error)
+			<< "Multiple sets found for color temperature "
+			<< ct;
 		return -EINVAL;
 	}
 
@@ -72,17 +83,18 @@ int LscTable::parseLscData(const ValueNode &sets)
 }
 
 std::vector<uint16_t> LscTable::parseTable(const ValueNode &tuningData,
-					   const char *prop)
+					   const char *prop,
+					   unsigned int numHCells,
+					   unsigned int numVCells)
 {
-	static constexpr unsigned int kLscNumSamples =
-		RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+	unsigned int lscNumSamples = numHCells * numVCells;
 
 	std::vector<uint16_t> table =
 		tuningData[prop].get<std::vector<uint16_t>>().value_or(utils::defopt);
-	if (table.size() != kLscNumSamples) {
+	if (table.size() != lscNumSamples) {
 		LOG(LscTable, Error)
 			<< "Invalid '" << prop << "' values: expected "
-			<< kLscNumSamples
+			<< lscNumSamples
 			<< " elements, got " << table.size();
 		return {};
 	}
diff --git a/src/ipa/libipa/lsc_table.h b/src/ipa/libipa/lsc_table.h
index 67c7464ea5b6..3fe52afbad7b 100644
--- a/src/ipa/libipa/lsc_table.h
+++ b/src/ipa/libipa/lsc_table.h
@@ -26,7 +26,8 @@ namespace ipa {
 class LscTable : public LscImplementation
 {
 public:
-	int parseLscData(const ValueNode &sets) override;
+	int parseLscData(const ValueNode &sets,
+			 const LscDescriptor &descriptor) override;
 
 	lsc::ComponentsMap
 	sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,
@@ -39,8 +40,12 @@ public:
 	}
 
 private:
+	int parseLscComponent(const ValueNode &yamlSet,
+			      unsigned int ct, const LscDescriptor &descriptor);
 	std::vector<uint16_t> parseTable(const ValueNode &tuningData,
-					 const char *prop);
+					 const char *prop,
+					 unsigned int numHCells,
+					 unsigned int numVCells);
 
 	lsc::ComponentsMap lscData_;
 };
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 828ab2f32fcc..79d68ac7d6dd 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -67,8 +67,12 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	xPos_ = sizesListToPositions(xSize_);
 	yPos_ = sizesListToPositions(ySize_);
 
-	return lscAlgo_.init(tuningData, context.sensorInfo.activeAreaSize,
-			     context.ctrlMap);
+	return lscAlgo_.init(tuningData,  context.ctrlMap, {
+				.keys = { "r", "gr", "gb", "b" },
+				.numHCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX,
+				.numVCells = RKISP1_CIF_ISP_LSC_SAMPLES_MAX,
+				.sensorSize = context.sensorInfo.activeAreaSize
+			     });
 }
 
 std::vector<double> LensShadingCorrection::parseSizes(const ValueNode &tuningData,
@@ -174,10 +178,14 @@ void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config)
 void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
 				      const lsc::Components &set)
 {
-	std::copy(set.r.begin(), set.r.end(), &config.r_data_tbl[0][0]);
-	std::copy(set.gr.begin(), set.gr.end(), &config.gr_data_tbl[0][0]);
-	std::copy(set.gb.begin(), set.gb.end(), &config.gb_data_tbl[0][0]);
-	std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]);
+	const auto &r = set.at("r");
+	std::copy(r.begin(), r.end(), &config.r_data_tbl[0][0]);
+	const auto &gr = set.at("gr");
+	std::copy(gr.begin(), gr.end(), &config.gr_data_tbl[0][0]);
+	const auto &gb = set.at("gb");
+	std::copy(gb.begin(), gb.end(), &config.gb_data_tbl[0][0]);
+	const auto &b = set.at("b");
+	std::copy(b.begin(), b.end(), &config.b_data_tbl[0][0]);
 }
 
 /**
