diff --git a/src/ipa/simple/algorithms/lsc.cpp b/src/ipa/simple/algorithms/lsc.cpp
new file mode 100644
index 000000000..9588dc95d
--- /dev/null
+++ b/src/ipa/simple/algorithms/lsc.cpp
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#include "lsc.h"
+
+#include <libcamera/base/log.h>
+
+#include "awb.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftLsc)
+
+int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+{
+	int ret_r = lscR.readYaml(tuningData["grids"], "ct", "r");
+	int ret_g = lscG.readYaml(tuningData["grids"], "ct", "g");
+	int ret_b = lscB.readYaml(tuningData["grids"], "ct", "b");
+
+	if (ret_r < 0 || ret_g < 0 || ret_b < 0) {
+		LOG(IPASoftLsc, Error)
+			<< "Failed to parse 'lsc' parameter from tuning file.";
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int Lsc::configure([[maybe_unused]] IPAContext &context,
+		   [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+	return 0;
+}
+
+void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+		  [[maybe_unused]] IPAFrameContext &frameContext, DebayerParams *params)
+{
+	unsigned int ct =
+		context.activeState.awb.temperatureK.value_or(kDefaultTemperature);
+
+	const LscMatrix matrixR = lscR.getInterpolated(ct);
+	const LscMatrix matrixG = lscG.getInterpolated(ct);
+	const LscMatrix matrixB = lscB.getInterpolated(ct);
+
+	DebayerParams::LscLookupTable lut;
+	constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
+	for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) {
+		lut[j++] = matrixR.data()[i];
+		lut[j++] = matrixG.data()[i];
+		lut[j++] = matrixB.data()[i];
+	}
+	params->lscLut = lut;
+}
+
+REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/lsc.h b/src/ipa/simple/algorithms/lsc.h
new file mode 100644
index 000000000..9afdac0de
--- /dev/null
+++ b/src/ipa/simple/algorithms/lsc.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#pragma once
+
+#include "libcamera/internal/matrix.h"
+
+#include <libipa/interpolator.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Lsc : public Algorithm
+{
+public:
+	Lsc() = default;
+	~Lsc() = default;
+
+	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int configure(IPAContext &context,
+		      const IPAConfigInfo &configInfo) override;
+	void prepare(IPAContext &context,
+		     const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     DebayerParams *params) override;
+
+private:
+	using LscMatrix = Matrix<uint8_t, DebayerParams::kLscGridSize, DebayerParams::kLscGridSize>;
+	Interpolator<LscMatrix> lscR;
+	Interpolator<LscMatrix> lscG;
+	Interpolator<LscMatrix> lscB;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
index 73c637220..c9f6e5590 100644
--- a/src/ipa/simple/algorithms/meson.build
+++ b/src/ipa/simple/algorithms/meson.build
@@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([
     'agc.cpp',
     'blc.cpp',
     'ccm.cpp',
+    'lsc.cpp',
 ])
