diff --git a/src/ipa/rpi/controller/decompand_status.h b/src/ipa/rpi/controller/decompand_status.h
new file mode 100644
index 000000000000..2d9888dca4f3
--- /dev/null
+++ b/src/ipa/rpi/controller/decompand_status.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "libipa/pwl.h"
+
+struct DecompandStatus {
+	uint32_t bitdepth;
+	libcamera::ipa::Pwl decompandCurve;
+};
diff --git a/src/ipa/rpi/controller/meson.build b/src/ipa/rpi/controller/meson.build
index 74b74888bbff..c13c48539d10 100644
--- a/src/ipa/rpi/controller/meson.build
+++ b/src/ipa/rpi/controller/meson.build
@@ -14,6 +14,7 @@ rpi_ipa_controller_sources = files([
     'rpi/cac.cpp',
     'rpi/ccm.cpp',
     'rpi/contrast.cpp',
+    'rpi/decompand.cpp',
     'rpi/denoise.cpp',
     'rpi/dpc.cpp',
     'rpi/geq.cpp',
diff --git a/src/ipa/rpi/controller/rpi/decompand.cpp b/src/ipa/rpi/controller/rpi/decompand.cpp
new file mode 100644
index 000000000000..2036750f82f4
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/decompand.cpp
@@ -0,0 +1,58 @@
+#include "decompand.h"
+
+#include <libcamera/base/log.h>
+
+#include "../decompand_status.h"
+#include "../histogram.h"
+
+using namespace RPiController;
+using namespace libcamera;
+
+LOG_DEFINE_CATEGORY(RPiDecompand)
+
+#define NAME "rpi.decompand"
+
+Decompand::Decompand(Controller *controller)
+	: Algorithm(controller)
+{
+}
+
+char const *Decompand::name() const
+{
+	return NAME;
+}
+
+int Decompand::read(const libcamera::YamlObject &params)
+{
+	config_.bitdepth = params["bitdepth"].get<uint32_t>(0);
+	config_.decompandCurve = params["decompand_curve"].get<ipa::Pwl>(ipa::Pwl{});
+	return config_.decompandCurve.empty() ? -EINVAL : 0;
+}
+
+void Decompand::initialise()
+{
+}
+
+void Decompand::switchMode(CameraMode const &cameraMode,
+			   [[maybe_unused]] Metadata *metadata)
+{
+	mode_ = cameraMode;
+}
+
+void Decompand::prepare(Metadata *imageMetadata)
+{
+	DecompandStatus decompandStatus;
+
+	if (config_.bitdepth == 0 || mode_.bitdepth == config_.bitdepth) {
+		decompandStatus.decompandCurve = config_.decompandCurve;
+		imageMetadata->set("decompand.status", decompandStatus);
+	}
+}
+
+/* Register algorithm with the system. */
+static Algorithm *create(Controller *controller)
+{
+	return new Decompand(controller);
+}
+
+static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/rpi/controller/rpi/decompand.h b/src/ipa/rpi/controller/rpi/decompand.h
new file mode 100644
index 000000000000..38b26a21e6d5
--- /dev/null
+++ b/src/ipa/rpi/controller/rpi/decompand.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <libipa/pwl.h>
+
+#include "../decompand_status.h"
+
+#include "algorithm.h"
+
+namespace RPiController {
+
+struct DecompandConfig {
+	uint32_t bitdepth;
+	libcamera::ipa::Pwl decompandCurve;
+};
+
+class Decompand : public Algorithm
+{
+public:
+	Decompand(Controller *controller = nullptr);
+	char const *name() const override;
+	int read(const libcamera::YamlObject &params) override;
+	void initialise() override;
+	void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
+	void prepare(Metadata *imageMetadata) override;
+
+private:
+	CameraMode mode_;
+	DecompandConfig config_;
+};
+
+} /* namespace RPiController */
diff --git a/src/ipa/rpi/pisp/pisp.cpp b/src/ipa/rpi/pisp/pisp.cpp
index 829b91258522..14ece12b0895 100644
--- a/src/ipa/rpi/pisp/pisp.cpp
+++ b/src/ipa/rpi/pisp/pisp.cpp
@@ -32,6 +32,7 @@
 #include "controller/cac_status.h"
 #include "controller/ccm_status.h"
 #include "controller/contrast_status.h"
+#include "controller/decompand_status.h"
 #include "controller/denoise_algorithm.h"
 #include "controller/denoise_status.h"
 #include "controller/dpc_status.h"
@@ -113,6 +114,25 @@ int generateLut(const ipa::Pwl &pwl, uint32_t *lut, std::size_t lutSize,
 	return 0;
 }
 
+int generateDecompandLut(const ipa::Pwl &pwl, Span<uint16_t> lut)
+{
+	if (pwl.empty())
+		return -EINVAL;
+
+	constexpr int step = 1024;
+	for (std::size_t i = 0; i < lut.size(); ++i) {
+		int x = i * step;
+
+		int y = pwl.eval(x);
+		if (y < 0)
+			return -1;
+
+		lut[i] = static_cast<uint16_t>(std::min(y, 65535));
+	}
+
+	return 0;
+}
+
 void packLscLut(uint32_t packed[NumLscVertexes][NumLscVertexes],
 		double const rgb[3][NumLscVertexes][NumLscVertexes])
 {
@@ -236,6 +256,7 @@ private:
 	void applyLensShading(const AlscStatus *alscStatus,
 			      pisp_be_global_config &global);
 	void applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global);
+	void applyDecompand(const DecompandStatus *decompandStatus);
 	void applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global);
 	void applyTdn(const TdnStatus *tdnStatus, const DeviceStatus *deviceStatus,
 		      pisp_be_global_config &global);
@@ -351,6 +372,11 @@ void IpaPiSP::platformPrepareIsp([[maybe_unused]] const PrepareParams &params,
 		if (noiseStatus)
 			applyFocusStats(noiseStatus);
 
+		DecompandStatus *decompandStatus =
+			rpiMetadata.getLocked<DecompandStatus>("decompand.status");
+		if (decompandStatus)
+			applyDecompand(decompandStatus);
+
 		BlackLevelStatus *blackLevelStatus =
 			rpiMetadata.getLocked<BlackLevelStatus>("black_level.status");
 		if (blackLevelStatus)
@@ -702,6 +728,18 @@ void IpaPiSP::applyDPC(const DpcStatus *dpcStatus, pisp_be_global_config &global
 	be_->SetDpc(dpc);
 }
 
+void IpaPiSP::applyDecompand(const DecompandStatus *decompandStatus)
+{
+	pisp_fe_global_config feGlobal;
+	pisp_fe_decompand_config decompand = {};
+
+	if (!generateDecompandLut(decompandStatus->decompandCurve, decompand.lut)) {
+		fe_->SetDecompand(decompand);
+		feGlobal.enables |= PISP_FE_ENABLE_DECOMPAND;
+		fe_->SetGlobal(feGlobal);
+	}
+}
+
 void IpaPiSP::applySdn(const SdnStatus *sdnStatus, pisp_be_global_config &global)
 {
 	pisp_be_sdn_config sdn = {};
