diff --git a/src/ipa/simple/agc_simple.cpp b/src/ipa/simple/agc_simple.cpp
new file mode 100644
index 0000000000..0734600105
--- /dev/null
+++ b/src/ipa/simple/agc_simple.cpp
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Exposure and gain
+ */
+
+#include "agc_simple.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include <linux/v4l2-controls.h>
+
+#include <libcamera/base/log.h>
+
+namespace libcamera {
+
+namespace ipa::soft {
+
+LOG_DEFINE_CATEGORY(IPASoftAgcSimple)
+
+
+/*
+ * The number of bins to use for the optimal exposure calculations.
+ */
+static constexpr unsigned int kExposureBinsCount = 5;
+
+/*
+ * The exposure is optimal when the mean sample value of the histogram is
+ * in the middle of the range.
+ */
+static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
+
+/*
+ * This implements the hysteresis for the exposure adjustment.
+ * It is small enough to have the exposure close to the optimal, and is big
+ * enough to prevent the exposure from wobbling around the optimal value.
+ */
+static constexpr float kExposureSatisfactory = 0.2;
+
+/*
+ * Proportional gain for exposure/gain adjustment. Maps the MSV error to a
+ * multiplicative correction factor:
+ *
+ *   factor = 1.0 + kExpProportionalGain * error
+ *
+ * With kExpProportionalGain = 0.04:
+ *   - max error ~2.5 -> factor 1.10 (~10% step, same as before)
+ *   - error 1.0      -> factor 1.04 (~4% step)
+ *   - error 0.3      -> factor 1.012 (~1.2% step)
+ *
+ * This replaces the fixed 10% bang-bang step with a proportional correction
+ * that converges smoothly and avoids overshooting near the target.
+ */
+static constexpr float kExpProportionalGain = 0.04;
+
+/*
+ * Maximum multiplicative step per frame, to bound the correction when the
+ * scene changes dramatically.
+ */
+static constexpr float kExpMaxStep = 0.15;
+
+void AgcSimpleAlgorithm::updateExposure(const Session &session, ActiveState &state, FrameContext &frameContext,
+					const ProcessParams &params, double exposureMSV)
+{
+	double error = kExposureOptimal - exposureMSV;
+	if (std::abs(error) <= kExposureSatisfactory)
+		return;
+
+	utils::Duration exposureDuration = params.exposure * session.lineDuration;
+	int32_t exposure = params.exposure;
+	double again = params.gain;
+
+	/*
+	 * Compute a proportional correction factor. The sign of the error
+	 * determines the direction: positive error means too dark (increase),
+	 * negative means too bright (decrease).
+	 */
+	float step = std::clamp(static_cast<float>(error) * kExpProportionalGain,
+				-kExpMaxStep, kExpMaxStep);
+	float factor = 1.0f + step;
+
+	const auto limits = AgcAlgorithm::calculateLimits(session, frameContext);
+
+	if (factor > 1.0f) {
+		/* Scene too dark: increase exposure first, then gain. */
+		if (exposureDuration < limits.exposure.second) {
+			int32_t next = static_cast<int32_t>(exposure * factor);
+			exposure = std::max(next, exposure + 1);
+		} else {
+			double next = again * factor;
+			if (next - again < session.gainMinStep)
+				again += session.gainMinStep;
+			else
+				again = next;
+		}
+	} else {
+		/* Scene too bright: decrease gain first, then exposure. */
+		if (again > std::max(session.gain10, limits.gain.first)) {
+			double next = again * factor;
+			if (again - next < session.gainMinStep)
+				again -= session.gainMinStep;
+			else
+				again = next;
+		} else {
+			int32_t next = static_cast<int32_t>(exposure * factor);
+			exposure = std::min(next, exposure - 1);
+		}
+	}
+
+	exposureDuration = std::clamp<utils::Duration>(
+		exposure * session.lineDuration,
+		limits.exposure.first, limits.exposure.second);
+	exposure = exposureDuration / session.lineDuration;
+	again = std::clamp(again, limits.gain.first, limits.gain.second);
+
+	state.automatic.exposure = exposure;
+	state.automatic.gain = again;
+
+	LOG(IPASoftAgcSimple, Debug)
+		<< "exposureMSV " << exposureMSV
+		<< " error " << error << " factor " << factor
+		<< " exp " << exposure << " again " << again;
+}
+
+int AgcSimpleAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config)
+{
+	int ret = AgcAlgorithm::configure(session, state, config);
+	if (ret)
+		return ret;
+
+	const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
+	auto defGain = v4l2Gain.def().get<int32_t>();
+
+	if (config.sensor) {
+		session.gain10 = std::max(session.minAnalogueGain, 1.0);
+		session.gainMinStep = (session.maxAnalogueGain - session.minAnalogueGain) / 100.0;
+	} else {
+		session.gain10 = defGain;
+		session.gainMinStep = 1.0;
+	}
+
+	return 0;
+}
+
+void AgcSimpleAlgorithm::process(const Session &session, ActiveState &state,
+				 FrameContext &frameContext, std::optional<ProcessParams> &&params,
+				 ControlList &metadata)
+{
+	utils::Duration newExposureTime = {};
+
+	if (params) {
+		/*
+		* Calculate Mean Sample Value (MSV) according to formula from:
+		* https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+		*/
+		const auto &histogram = params->stats.yHistogram;
+		const unsigned int blackLevelHistIdx = params->blackLevel / (256 / SwIspStats::kYHistogramSize);
+		const unsigned int histogramSize =
+			SwIspStats::kYHistogramSize - blackLevelHistIdx;
+		const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
+		const unsigned int yHistValsPerBinMod =
+			histogramSize / (histogramSize % kExposureBinsCount + 1);
+		int exposureBins[kExposureBinsCount] = {};
+		unsigned int denom = 0;
+		unsigned int num = 0;
+
+		if (yHistValsPerBin == 0) {
+			LOG(IPASoftAgcSimple, Debug)
+				<< "Not adjusting exposure due to insufficient histogram data";
+			return;
+		}
+
+		for (unsigned int i = 0; i < histogramSize; i++) {
+			unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
+			exposureBins[idx] += histogram[blackLevelHistIdx + i];
+		}
+
+		for (unsigned int i = 0; i < kExposureBinsCount; i++) {
+			LOG(IPASoftAgcSimple, Debug) << i << ": " << exposureBins[i];
+			denom += exposureBins[i];
+			num += exposureBins[i] * (i + 1);
+		}
+
+		float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
+		updateExposure(session, state, frameContext, *params, exposureMSV);
+		newExposureTime = state.automatic.exposure * session.lineDuration;
+	}
+
+	AgcAlgorithm::process(session, frameContext, newExposureTime, metadata);
+}
+
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/agc_simple.h b/src/ipa/simple/agc_simple.h
new file mode 100644
index 0000000000..28e414a291
--- /dev/null
+++ b/src/ipa/simple/agc_simple.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Red Hat Inc.
+ *
+ * Exposure and gain
+ */
+
+#pragma once
+
+#include <optional>
+
+#include <libipa/agc.h>
+
+#include "libcamera/internal/software_isp/swisp_stats.h"
+
+namespace libcamera {
+
+namespace ipa::soft {
+
+class AgcSimpleAlgorithm : public AgcAlgorithm
+{
+public:
+	struct Session : AgcAlgorithm::Session {
+		double gain10;
+		double gainMinStep;
+	};
+
+	struct ProcessParams {
+		int32_t exposure;
+		double gain;
+		const SwIspStats &stats;
+		unsigned int blackLevel;
+	};
+
+	int configure(Session &session, ActiveState &state, const ConfigurationParams &config);
+
+	void prepare(ActiveState &state, FrameContext &frameContext)
+	{
+		return AgcAlgorithm::prepare(state, frameContext);
+	}
+
+	void queueRequest(const Session &session, ActiveState &state,
+			  FrameContext &frameContext, const ControlList &controls)
+	{
+		return AgcAlgorithm::queueRequest(session, state, frameContext, controls);
+	}
+
+	void process(const Session &session, ActiveState &state,
+		     FrameContext &frameContext, std::optional<ProcessParams> &&params,
+		     ControlList &metadata);
+
+private:
+	void updateExposure(const Session &session, ActiveState &state, FrameContext &frameContext,
+			    const ProcessParams &params, double exposureMSV);
+};
+
+} /* namepsace ipa::soft */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp
index e9bcb2c032..db4f63eba5 100644
--- a/src/ipa/simple/algorithms/agc.cpp
+++ b/src/ipa/simple/algorithms/agc.cpp
@@ -7,124 +7,46 @@
 
 #include "agc.h"
 
-#include <algorithm>
-#include <cmath>
-#include <stdint.h>
-
 #include <libcamera/base/log.h>
 
-#include "control_ids.h"
-
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(IPASoftExposure)
 
 namespace ipa::soft::algorithms {
 
-/*
- * The number of bins to use for the optimal exposure calculations.
- */
-static constexpr unsigned int kExposureBinsCount = 5;
-
-/*
- * The exposure is optimal when the mean sample value of the histogram is
- * in the middle of the range.
- */
-static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
-
-/*
- * This implements the hysteresis for the exposure adjustment.
- * It is small enough to have the exposure close to the optimal, and is big
- * enough to prevent the exposure from wobbling around the optimal value.
- */
-static constexpr float kExposureSatisfactory = 0.2;
-
-/*
- * Proportional gain for exposure/gain adjustment. Maps the MSV error to a
- * multiplicative correction factor:
- *
- *   factor = 1.0 + kExpProportionalGain * error
- *
- * With kExpProportionalGain = 0.04:
- *   - max error ~2.5 -> factor 1.10 (~10% step, same as before)
- *   - error 1.0      -> factor 1.04 (~4% step)
- *   - error 0.3      -> factor 1.012 (~1.2% step)
- *
- * This replaces the fixed 10% bang-bang step with a proportional correction
- * that converges smoothly and avoids overshooting near the target.
- */
-static constexpr float kExpProportionalGain = 0.04;
-
-/*
- * Maximum multiplicative step per frame, to bound the correction when the
- * scene changes dramatically.
- */
-static constexpr float kExpMaxStep = 0.15;
-
-Agc::Agc()
+int Agc::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData)
 {
+	return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, {
+		.sensor = context.camHelper.get(),
+		.sensorInfo = context.sensorInfo,
+		.sensorControls = context.sensorControls,
+		.ctrlMap = context.ctrlMap,
+		.autoAllowed = true,
+	});
 }
 
-void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV)
+int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo)
 {
-	int32_t exposure = frameContext.sensor.exposure;
-	double again = frameContext.sensor.gain;
-
-	double error = kExposureOptimal - exposureMSV;
-
-	if (std::abs(error) <= kExposureSatisfactory)
-		return;
-
-	/*
-	 * Compute a proportional correction factor. The sign of the error
-	 * determines the direction: positive error means too dark (increase),
-	 * negative means too bright (decrease).
-	 */
-	float step = std::clamp(static_cast<float>(error) * kExpProportionalGain,
-				-kExpMaxStep, kExpMaxStep);
-	float factor = 1.0f + step;
-
-	if (factor > 1.0f) {
-		/* Scene too dark: increase exposure first, then gain. */
-		if (exposure < context.configuration.agc.exposureMax) {
-			int32_t next = static_cast<int32_t>(exposure * factor);
-			exposure = std::max(next, exposure + 1);
-		} else {
-			double next = again * factor;
-			if (next - again < context.configuration.agc.againMinStep)
-				again += context.configuration.agc.againMinStep;
-			else
-				again = next;
-		}
-	} else {
-		/* Scene too bright: decrease gain first, then exposure. */
-		if (again > context.configuration.agc.again10) {
-			double next = again * factor;
-			if (again - next < context.configuration.agc.againMinStep)
-				again -= context.configuration.agc.againMinStep;
-			else
-				again = next;
-		} else {
-			int32_t next = static_cast<int32_t>(exposure * factor);
-			exposure = std::min(next, exposure - 1);
-		}
-	}
-
-	exposure = std::clamp(exposure, context.configuration.agc.exposureMin,
-			      context.configuration.agc.exposureMax);
-	again = std::clamp(again, context.configuration.agc.againMin,
-			   context.configuration.agc.againMax);
-
-	frameContext.agc.exposure = exposure;
-	frameContext.agc.gain = again;
+	return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, {
+		.sensor = context.camHelper.get(),
+		.sensorInfo = context.sensorInfo,
+		.sensorControls = context.sensorControls,
+		.ctrlMap = context.ctrlMap,
+		.autoAllowed = true, // \todo not if raw?
+	});
+}
 
-	context.activeState.agc.exposure = exposure;
-	context.activeState.agc.again = again;
+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);
+}
 
-	LOG(IPASoftExposure, Debug)
-		<< "exposureMSV " << exposureMSV
-		<< " error " << error << " factor " << factor
-		<< " exp " << exposure << " again " << again;
+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);
 }
 
 void Agc::process(IPAContext &context,
@@ -133,66 +55,20 @@ void Agc::process(IPAContext &context,
 		  const SwIspStats *stats,
 		  ControlList &metadata)
 {
-	utils::Duration exposureTime =
-		context.configuration.agc.lineDuration * frameContext.sensor.exposure;
-	metadata.set(controls::ExposureTime, exposureTime.get<std::micro>());
-	metadata.set(controls::AnalogueGain, frameContext.sensor.gain);
-
-	if (!context.activeState.agc.valid) {
-		/*
-		 * Init active-state from sensor values in case updateExposure()
-		 * does not run for the first frame.
-		 */
-		context.activeState.agc.exposure = frameContext.sensor.exposure;
-		context.activeState.agc.again = frameContext.sensor.gain;
-		context.activeState.agc.valid = true;
-	}
-
-	if (!stats->valid) {
-		/*
-		 * Use the new exposure and gain values calculated the last time
-		 * there were valid stats.
-		 */
-		frameContext.agc.exposure = context.activeState.agc.exposure;
-		frameContext.agc.gain = context.activeState.agc.again;
-		return;
-	}
-
-	/*
-	 * Calculate Mean Sample Value (MSV) according to formula from:
-	 * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
-	 */
-	const auto &histogram = stats->yHistogram;
-	const unsigned int blackLevelHistIdx =
-		context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize);
-	const unsigned int histogramSize =
-		SwIspStats::kYHistogramSize - blackLevelHistIdx;
-	const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
-	const unsigned int yHistValsPerBinMod =
-		histogramSize / (histogramSize % kExposureBinsCount + 1);
-	int exposureBins[kExposureBinsCount] = {};
-	unsigned int denom = 0;
-	unsigned int num = 0;
-
-	if (yHistValsPerBin == 0) {
-		LOG(IPASoftExposure, Debug)
-			<< "Not adjusting exposure due to insufficient histogram data";
-		return;
-	}
-
-	for (unsigned int i = 0; i < histogramSize; i++) {
-		unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
-		exposureBins[idx] += histogram[blackLevelHistIdx + i];
-	}
-
-	for (unsigned int i = 0; i < kExposureBinsCount; i++) {
-		LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i];
-		denom += exposureBins[i];
-		num += exposureBins[i] * (i + 1);
+	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);
 	}
 
-	float exposureMSV = (denom == 0 ? 0 : static_cast<float>(num) / denom);
-	updateExposure(context, frameContext, exposureMSV);
+	frameContext.agc.exposure = frameContext.agc.simple.exposure;
+	frameContext.agc.gain = frameContext.agc.simple.gain;
 }
 
 REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h
index 112d9f5a19..869817fc95 100644
--- a/src/ipa/simple/algorithms/agc.h
+++ b/src/ipa/simple/algorithms/agc.h
@@ -8,6 +8,7 @@
 #pragma once
 
 #include "algorithm.h"
+#include "agc_simple.h"
 
 namespace libcamera {
 
@@ -16,16 +17,20 @@ namespace ipa::soft::algorithms {
 class Agc : public Algorithm
 {
 public:
-	Agc();
+	Agc() = default;
 	~Agc() = default;
 
+	int init(IPAContext &context, const ValueNode &tuningData) override;
+	int configure(IPAContext &context, const IPAConfigInfo &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, DebayerParams *params) override;
 	void process(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     const SwIspStats *stats,
 		     ControlList &metadata) override;
 
 private:
-	void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV);
+	AgcSimpleAlgorithm agc_;
 };
 
 } /* namespace ipa::soft::algorithms */
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index d35fb1e91d..36e1d8bba8 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -7,7 +7,6 @@
 
 #pragma once
 
-#include <array>
 #include <optional>
 #include <stdint.h>
 
@@ -16,19 +15,21 @@
 #include "libcamera/internal/matrix.h"
 #include "libcamera/internal/vector.h"
 
+#include <libipa/camera_sensor_helper.h>
 #include <libipa/fc_queue.h>
 
 #include "core_ipa_interface.h"
 
+#include "agc_simple.h"
+
 namespace libcamera {
 
 namespace ipa::soft {
 
 struct IPASessionConfiguration {
 	struct {
-		int32_t exposureMin, exposureMax;
-		double againMin, againMax, again10, againMinStep;
-		utils::Duration lineDuration;
+		AgcSimpleAlgorithm::Session simple;
+		double again10, againMinStep;
 	} agc;
 	struct {
 		std::optional<uint8_t> level;
@@ -37,9 +38,7 @@ struct IPASessionConfiguration {
 
 struct IPAActiveState {
 	struct {
-		int32_t exposure;
-		double again;
-		bool valid;
+		AgcSimpleAlgorithm::ActiveState simple;
 	} agc;
 
 	struct {
@@ -67,6 +66,7 @@ struct IPAFrameContext : public FrameContext {
 	Matrix<float, 3, 3> ccm;
 
 	struct {
+		AgcSimpleAlgorithm::FrameContext simple;
 		int32_t exposure;
 		double gain;
 	} agc;
@@ -90,10 +90,12 @@ struct IPAContext {
 	}
 
 	IPACameraSensorInfo sensorInfo;
+	ControlInfoMap sensorControls;
 	IPASessionConfiguration configuration;
 	IPAActiveState activeState;
 	FCQueue<IPAFrameContext> frameContexts;
 	ControlInfoMap::Map ctrlMap;
+	std::unique_ptr<CameraSensorHelper> camHelper;
 	bool ccmEnabled = false;
 };
 
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
index 2f9f15f4aa..3033e91763 100644
--- a/src/ipa/simple/meson.build
+++ b/src/ipa/simple/meson.build
@@ -6,6 +6,7 @@ subdir('data')
 ipa_name = 'ipa_soft_simple'
 
 soft_simple_sources = files([
+    'agc_simple.cpp',
     'ipa_context.cpp',
     'soft_simple.cpp',
 ])
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 22702638bb..5c85b3986f 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -5,7 +5,6 @@
  * Simple Software Image Processing Algorithm module
  */
 
-#include <chrono>
 #include <stdint.h>
 #include <sys/mman.h>
 
@@ -34,8 +33,6 @@
 namespace libcamera {
 LOG_DEFINE_CATEGORY(IPASoft)
 
-using namespace std::literals::chrono_literals;
-
 namespace ipa::soft {
 
 /* Maximum number of frame contexts to be held */
@@ -76,8 +73,6 @@ private:
 
 	DebayerParams *params_;
 	SwIspStats *stats_;
-	std::unique_ptr<CameraSensorHelper> camHelper_;
-	ControlInfoMap sensorInfoMap_;
 
 	/* Local parameter storage */
 	struct IPAContext context_;
@@ -99,14 +94,15 @@ int IPASoftSimple::init(const IPASettings &settings,
 			ControlInfoMap *ipaControls,
 			bool *ccmEnabled)
 {
-	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
-	if (!camHelper_) {
+	context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+	if (!context_.camHelper) {
 		LOG(IPASoft, Warning)
 			<< "Failed to create camera sensor helper for "
 			<< settings.sensorModel;
 	}
 
 	context_.sensorInfo = sensorInfo;
+	context_.sensorControls = sensorControls;
 
 	/* Load the tuning data file */
 	File file(settings.configurationFile);
@@ -201,38 +197,15 @@ int IPASoftSimple::init(const IPASettings &settings,
 
 int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
 {
-	sensorInfoMap_ = configInfo.sensorControls;
-
-	const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;
-	const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;
+	context_.sensorControls = configInfo.sensorControls;
 
 	/* Clear the IPA context before the streaming session. */
 	context_.configuration = {};
 	context_.activeState = {};
 	context_.frameContexts.clear();
 
-	context_.configuration.agc.lineDuration =
-		context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate;
-	context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();
-	context_.configuration.agc.exposureMax = exposureInfo.max().get<int32_t>();
-	if (!context_.configuration.agc.exposureMin) {
-		LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
-		context_.configuration.agc.exposureMin = 1;
-	}
-
-	int32_t againMin = gainInfo.min().get<int32_t>();
-	int32_t againMax = gainInfo.max().get<int32_t>();
-	int32_t againDef = gainInfo.def().get<int32_t>();
-
-	if (camHelper_) {
-		context_.configuration.agc.againMin = camHelper_->gain(againMin);
-		context_.configuration.agc.againMax = camHelper_->gain(againMax);
-		context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0);
-		context_.configuration.agc.againMinStep =
-			(context_.configuration.agc.againMax -
-			 context_.configuration.agc.againMin) /
-			100.0;
-		if (camHelper_->blackLevel().has_value()) {
+	if (context_.camHelper) {
+		if (context_.camHelper->blackLevel().has_value()) {
 			/*
 			 * The black level from camHelper_ is a 16 bit value, software ISP
 			 * works with 8 bit pixel values, both regardless of the actual
@@ -240,13 +213,8 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
 			 * by dividing the value from the helper by 256.
 			 */
 			context_.configuration.black.level =
-				camHelper_->blackLevel().value() / 256;
+				context_.camHelper->blackLevel().value() / 256;
 		}
-	} else {
-		context_.configuration.agc.againMax = againMax;
-		context_.configuration.agc.again10 = againDef;
-		context_.configuration.agc.againMin = againMin;
-		context_.configuration.agc.againMinStep = 1.0;
 	}
 
 	for (const auto &algo : algorithms()) {
@@ -255,13 +223,6 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
 			return ret;
 	}
 
-	LOG(IPASoft, Info)
-		<< "Exposure " << context_.configuration.agc.exposureMin << "-"
-		<< context_.configuration.agc.exposureMax
-		<< ", gain " << context_.configuration.agc.againMin << "-"
-		<< context_.configuration.agc.againMax
-		<< " (" << context_.configuration.agc.againMinStep << ")";
-
 	return 0;
 }
 
@@ -311,17 +272,17 @@ void IPASoftSimple::processStats(const uint32_t frame,
 	frameContext.sensor.exposure =
 		sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
 	int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
-	frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again;
+	frameContext.sensor.gain = context_.camHelper ? context_.camHelper->gain(again) : again;
 
 	ControlList metadata(controls::controls);
 	for (const auto &algo : algorithms())
 		algo->process(context_, frame, frameContext, stats_, metadata);
 	metadataReady.emit(frame, metadata);
 
-	ControlList ctrls(sensorInfoMap_);
+	ControlList ctrls(context_.sensorControls);
 
-	int32_t againNew = camHelper_
-		? camHelper_->gainCode(frameContext.agc.gain)
+	int32_t againNew = context_.camHelper
+		? context_.camHelper->gainCode(frameContext.agc.gain)
 		: static_cast<int32_t>(frameContext.agc.gain);
 	ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure);
 	ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew);
