diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index db4f63eba5..7d035618e8 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -7,7 +7,12 @@
 
 #include "agc.h"
 
+#include <variant>
+
 #include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libipa/histogram.h>
 
 namespace libcamera {
 
@@ -15,38 +20,119 @@ LOG_DEFINE_CATEGORY(IPASoftExposure)
 
 namespace ipa::soft::algorithms {
 
-int Agc::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData)
+namespace {
+
+class AgcTraits : public AgcMeanLuminance::Traits
 {
-	return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, {
+public:
+	AgcTraits(const SwIspStats &stats)
+		: stats_(stats)
+	{
+	}
+
+	double estimateLuminance(double gain) const override
+	{
+		double sum = 0;
+		double count = 0;
+
+		for (const auto &[i, cnt] : utils::enumerate(stats_.yHistogram)) {
+			sum += std::min(1.0, gain * i / stats_.yHistogram.size()) * cnt;
+			count += cnt;
+		}
+
+		return sum / count;
+	}
+
+private:
+	const SwIspStats &stats_;
+};
+
+}
+
+int Agc::init(IPAContext &context, const ValueNode &tuningData)
+{
+	const AgcAlgorithm::ConfigurationParams config = {
 		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
 		.autoAllowed = true,
-	});
+	};
+
+	if (config.sensor)
+		agc_.emplace<AgcMeanLuminanceAlgorithm>();
+	else
+		agc_.emplace<AgcSimpleAlgorithm>();
+
+	return std::visit(utils::overloaded {
+		[&](AgcSimpleAlgorithm &impl) {
+			return impl.configure(context.configuration.agc.simple,
+					      context.activeState.agc.simple,
+					      config);
+		},
+		[&](AgcMeanLuminanceAlgorithm &impl) {
+			int ret = impl.init(tuningData);
+			if (ret)
+				return ret;
+
+			return impl.configure(context.configuration.agc.ml,
+					      context.activeState.agc.ml,
+					      config);
+		},
+	}, agc_);
 }
 
 int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo)
 {
-	return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, {
+	const AgcAlgorithm::ConfigurationParams config = {
 		.sensor = context.camHelper.get(),
 		.sensorInfo = context.sensorInfo,
 		.sensorControls = context.sensorControls,
 		.ctrlMap = context.ctrlMap,
-		.autoAllowed = true, // \todo not if raw?
-	});
+		.autoAllowed = true, // \todo if not raw?
+	};
+
+	return std::visit(utils::overloaded {
+		[&](AgcSimpleAlgorithm &impl) {
+			return impl.configure(context.configuration.agc.simple,
+					      context.activeState.agc.simple,
+					      config);
+		},
+		[&](AgcMeanLuminanceAlgorithm &impl) {
+			return impl.configure(context.configuration.agc.ml,
+					      context.activeState.agc.ml,
+					      config);
+		},
+	}, agc_);
 }
 
 void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls)
 {
-	agc_.queueRequest(context.configuration.agc.simple, context.activeState.agc.simple,
-			  frameContext.agc.simple, controls);
+	std::visit(utils::overloaded {
+		[&](AgcSimpleAlgorithm &impl) {
+			impl.queueRequest(context.configuration.agc.simple,
+					  context.activeState.agc.simple,
+					  frameContext.agc.simple, controls);
+		},
+		[&](AgcMeanLuminanceAlgorithm &impl) {
+			impl.queueRequest(context.configuration.agc.ml,
+					  context.activeState.agc.ml,
+					  frameContext.agc.ml, controls);
+		},
+	}, agc_);
 }
 
 void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 		  IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)
 {
-	agc_.prepare(context.activeState.agc.simple, frameContext.agc.simple);
+	std::visit(utils::overloaded {
+		[&](AgcSimpleAlgorithm &impl) {
+			impl.prepare(context.activeState.agc.simple, frameContext.agc.simple);
+		},
+		[&](AgcMeanLuminanceAlgorithm &impl) {
+			impl.prepare(context.activeState.agc.ml, frameContext.agc.ml);
+		},
+	}, agc_);
 }
 
 void Agc::process(IPAContext &context,
@@ -55,20 +141,43 @@ void Agc::process(IPAContext &context,
 		  const SwIspStats *stats,
 		  ControlList &metadata)
 {
-	if (stats->valid) {
-		agc_.process(context.configuration.agc.simple, context.activeState.agc.simple, frameContext.agc.simple, {{
-			.exposure = frameContext.sensor.exposure,
-			.gain = frameContext.sensor.gain,
-			.stats = *stats,
-			.blackLevel = context.activeState.blc.level,
-		}}, metadata);
-	} else {
-		agc_.process(context.configuration.agc.simple, context.activeState.agc.simple,
-			     frameContext.agc.simple, {}, metadata);
-	}
+	std::visit(utils::overloaded {
+		[&](AgcSimpleAlgorithm &impl) {
+			if (stats->valid) {
+				impl.process(context.configuration.agc.simple, context.activeState.agc.simple, frameContext.agc.simple, {{
+					.exposure = frameContext.sensor.exposure,
+					.gain = frameContext.sensor.gain,
+					.stats = *stats,
+					.blackLevel = context.activeState.blc.level,
+				}}, metadata);
+			} else {
+				impl.process(context.configuration.agc.simple, context.activeState.agc.simple,
+					     frameContext.agc.simple, {}, metadata);
+			}
+
+			frameContext.agc.exposure = frameContext.agc.simple.exposure;
+			frameContext.agc.gain = frameContext.agc.simple.gain;
+		},
+		[&](AgcMeanLuminanceAlgorithm &impl) {
+			if (stats->valid) {
+				Histogram hist(stats->yHistogram);
+
+				impl.process(context.configuration.agc.ml, context.activeState.agc.ml, frameContext.agc.ml, {{
+					.traits = AgcTraits(*stats),
+					.hist = hist,
+					.exposure = uint32_t(frameContext.sensor.exposure),
+					.gain = frameContext.sensor.gain,
+				}}, metadata);
+
+			} else {
+				impl.process(context.configuration.agc.ml, context.activeState.agc.ml,
+					     frameContext.agc.ml, {}, metadata);
+			}
 
-	frameContext.agc.exposure = frameContext.agc.simple.exposure;
-	frameContext.agc.gain = frameContext.agc.simple.gain;
+			frameContext.agc.exposure = frameContext.agc.ml.exposure;
+			frameContext.agc.gain = frameContext.agc.ml.gain;
+		},
+	}, agc_);
 }
 
 REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
index 869817fc95..5dac17ab1d 100644
--- a/src/ipa/simple/algorithms/agc.h
+++ b/src/ipa/simple/algorithms/agc.h
@@ -7,6 +7,10 @@
 
 #pragma once
 
+#include <variant>
+
+#include <libipa/agc_mean_luminance.h>
+
 #include "algorithm.h"
 #include "agc_simple.h"
 
@@ -30,7 +34,10 @@ public:
 		     ControlList &metadata) override;
 
 private:
-	AgcSimpleAlgorithm agc_;
+	std::variant<
+		AgcSimpleAlgorithm,
+		AgcMeanLuminanceAlgorithm
+	> agc_;
 };
 
 } /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index 36e1d8bba8..b4a11b04cd 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -15,6 +15,7 @@
 #include "libcamera/internal/matrix.h"
 #include "libcamera/internal/vector.h"
 
+#include <libipa/agc_mean_luminance.h>
 #include <libipa/camera_sensor_helper.h>
 #include <libipa/fc_queue.h>
 
@@ -29,6 +30,7 @@ namespace ipa::soft {
 struct IPASessionConfiguration {
 	struct {
 		AgcSimpleAlgorithm::Session simple;
+		AgcMeanLuminanceAlgorithm::Session ml;
 		double again10, againMinStep;
 	} agc;
 	struct {
@@ -39,6 +41,7 @@ struct IPASessionConfiguration {
 struct IPAActiveState {
 	struct {
 		AgcSimpleAlgorithm::ActiveState simple;
+		AgcMeanLuminanceAlgorithm::ActiveState ml;
 	} agc;
 
 	struct {
@@ -67,6 +70,7 @@ struct IPAFrameContext : public FrameContext {
 
 	struct {
 		AgcSimpleAlgorithm::FrameContext simple;
+		AgcMeanLuminanceAlgorithm::FrameContext ml;
 		int32_t exposure;
 		double gain;
 	} agc;
