diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 0c8e15030c377ac0797dbdc9d53694ee894cd9b8..9fc275ea9e5b81ce107eabe1982be3c44c01479c 100644
--- a/src/ipa/libipa/agc_mean_luminance.cpp
+++ b/src/ipa/libipa/agc_mean_luminance.cpp
@@ -9,6 +9,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <optional>
 
 #include <libcamera/base/log.h>
 #include <libcamera/control_ids.h>
@@ -171,11 +172,10 @@ static constexpr double kMaxRelativeLuminanceTarget = 0.95;
  * IPA modules that want to use this class to implement their AEGC algorithm
  * should derive it and provide an overriding estimateLuminance() function for
  * this class to use. They must call parseTuningData() in init(), and must also
- * call setLimits() and resetFrameCounter() in configure(). They may then use
- * calculateNewEv() in process(). If the limits passed to setLimits() change for
- * any reason (for example, in response to a FrameDurationLimit control being
- * passed in queueRequest()) then setLimits() must be called again with the new
- * values.
+ * call resetFrameCounter() in configure(). They may then use calculateNewEv()
+ * in process(). To update the algorithm limits for example, in response to a
+ * FrameDurationLimit control being passed in queueRequest()) then
+ * setExposureLimits() must be called with the new values.
  */
 
 AgcMeanLuminance::AgcMeanLuminance()
@@ -459,25 +459,21 @@ int AgcMeanLuminance::parseTuningData(const YamlObject &tuningData)
 
 /**
  * \brief Set the ExposureModeHelper limits for this class
- * \param[in] minExposureTime Minimum exposure time to allow
- * \param[in] maxExposureTime Maximum ewposure time to allow
+ * \param[in] shutterTime The (optional) fixed shutter time
+ * \param[in] gain The (optional) analogue gain value
  * \param[in] maxFrameDuration Maximum frame duration
- * \param[in] minGain Minimum gain to allow
- * \param[in] maxGain Maximum gain to allow
  * \param[in] constraints Additional constraints to apply
  *
- * This function calls \ref ExposureModeHelper::setLimits() for each
+ * This function calls \ref ExposureModeHelper::setExposureLimits() for each
  * ExposureModeHelper that has been created for this class.
  */
-void AgcMeanLuminance::setLimits(utils::Duration minExposureTime,
-				 utils::Duration maxExposureTime,
-				 utils::Duration maxFrameDuration,
-				 double minGain, double maxGain,
-				 std::vector<AgcMeanLuminance::AgcConstraint> constraints)
+void AgcMeanLuminance::setExposureLimits(std::optional<utils::Duration> shutterTime,
+					 std::optional<double> gain,
+					 utils::Duration maxFrameDuration,
+					 std::vector<AgcMeanLuminance::AgcConstraint> constraints)
 {
 	for (auto &[id, helper] : exposureModeHelpers_)
-		helper->setLimits(minExposureTime, maxExposureTime, maxFrameDuration,
-				  minGain, maxGain);
+		helper->setExposureLimits(shutterTime, gain, maxFrameDuration);
 
 	additionalConstraints_ = std::move(constraints);
 }
diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h
index 535f94502dc2649a5f4ba49a7040de12f9f74179..12316ca8bbd7d8b5783a948f5e01d5f0f56bfe3a 100644
--- a/src/ipa/libipa/agc_mean_luminance.h
+++ b/src/ipa/libipa/agc_mean_luminance.h
@@ -61,9 +61,10 @@ public:
 		exposureCompensation_ = gain;
 	}
 
-	void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
-		       utils::Duration maxFrameDuration, double minGain, double maxGain,
-		       std::vector<AgcConstraint> constraints);
+	void setExposureLimits(std::optional<utils::Duration> shutterTime,
+			       std::optional<double> gain,
+			       utils::Duration maxFrameDuration,
+			       std::vector<AgcConstraint> constraints);
 
 	const std::map<int32_t, std::vector<AgcConstraint>> &constraintModes() const
 	{
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp
index fad13ff1521498244224a8a5f375738ee3fc9ff2..e1e36eb1820d4080f2dc295a963a37782a484f02 100644
--- a/src/ipa/libipa/exposure_mode_helper.cpp
+++ b/src/ipa/libipa/exposure_mode_helper.cpp
@@ -109,25 +109,33 @@ ExposureModeHelper::ExposureModeHelper(const Span<std::pair<utils::Duration, dou
 	}
 }
 
-void ExposureModeHelper::setMaxExposure(utils::Duration minExposureTime,
-					utils::Duration maxExposureTime,
-					utils::Duration maxFrameDuration)
+void ExposureModeHelper::setShutterLimits(std::optional<utils::Duration> shutterTime,
+					  utils::Duration maxFrameDuration)
 {
-	minExposureTime_ = minExposureTime;
-
 	/*
-	 * Compute the maximum shutter time.
-	 *
-	 * If maxExposureTime is equal to minExposureTime then we use them
-	 * to fix the exposure time.
+	 * If shutterTime is populated we use it to to fix the shutter time.
 	 *
-	 * Otherwise, if the exposure can range between a min and max delegate
-	 * the maximum shutter time calculation to the sensor helper.
+	 * Otherwise, if the exposure is not fixed delegate the maximum shutter
+	 * time calculation to the sensor helper and restore the min exposure
+	 * time to the sensor's default value.
 	 */
-	maxExposureTime_ = minExposureTime != maxExposureTime
-			 ? sensorHelper_->maxShutterTime(maxFrameDuration,
-							 sensor_.lineDuration_)
-			 : minExposureTime;
+
+	maxExposureTime_ = shutterTime.has_value()
+			 ? shutterTime.value()
+			 : sensorHelper_->maxShutterTime(maxFrameDuration,
+							 sensor_.lineDuration_);
+	minExposureTime_ = shutterTime.has_value()
+			 ? shutterTime.value() : sensor_.minExposureTime_;
+}
+
+void ExposureModeHelper::setGainLimits(std::optional<double> gain)
+{
+	/*
+	 * Use the fixed value as limits if populated, otherwise use the
+	 * sensor's default ones.
+	 */
+	minGain_ = gain.has_value() ? gain.value() : sensor_.minGain_;
+	maxGain_ = gain.has_value() ? gain.value() : sensor_.maxGain_;
 }
 
 /**
@@ -154,42 +162,37 @@ void ExposureModeHelper::configure(const SensorConfiguration &sensorConfig,
 	sensorHelper_ = sensorHelper;
 
 	/* Initialize run-time limits with sensor's default. */
-	minGain_ = sensor_.minGain_;
-	maxGain_ = sensor_.maxGain_;
-
-	setMaxExposure(sensorConfig.minExposureTime_, sensorConfig.maxExposureTime_,
-		       sensorConfig.maxFrameDuration_);
+	setShutterLimits({}, sensorConfig.maxFrameDuration_);
+	setGainLimits({});
 }
 
 /**
  * \brief Set the exposure time and gain limits
- * \param[in] minExposureTime The minimum exposure time supported
- * \param[in] maxExposureTime The maximum exposure time supported
+ * \param[in] shutterTime The (optional) fixed shutter time
+ * \param[in] gain The (optional) fixed gain
  * \param[in] maxFrameDuration The maximum frame duration
- * \param[in] minGain The minimum analogue gain supported
- * \param[in] maxGain The maximum analogue gain supported
  *
- * This function configures the exposure time and analogue gain limits that need
+ * This function configures the shutter time and analogue gain limits that need
  * to be adhered to as the helper divides up exposure. Note that this function
  * *must* be called whenever those limits change and before splitExposure() is
  * used.
  *
- * If the algorithm using the helpers needs to indicate that either exposure time
- * or analogue gain or both should be fixed it can do so by setting both the
- * minima and maxima to the same value.
+ * If the algorithm using the helpers needs to indicate that the shutter time
+ * should be fixed it should populate the optional \a shutterTime argument.
+ * If the shutter time is not fixed, the maximum achievable shutter time is
+ * calculated using \a maxFrameDuration as the upper bound while the minimum
+ * exposure time is reset to the sensor's default.
  *
- * The exposure time limits are calculated using \a maxFrameDuration as the
- * upper bound, the \a maxExposureTime paramter effectivelly only serves
- * to indicate that the caller wants a fixed exposure value.
+ * If the analogue gain should be fixed the optional \a gain argument should
+ * be populated. If the analogue gain is not fixed its min and max values are
+ * reset to the sensor's default.
  */
-void ExposureModeHelper::setLimits(utils::Duration minExposureTime,
-				   utils::Duration maxExposureTime,
-				   utils::Duration maxFrameDuration,
-				   double minGain, double maxGain)
+void ExposureModeHelper::setExposureLimits(std::optional<utils::Duration> shutterTime,
+					   std::optional<double> gain,
+					    utils::Duration maxFrameDuration)
 {
-	minGain_ = minGain;
-	maxGain_ = maxGain;
-	setMaxExposure(minExposureTime, maxExposureTime, maxFrameDuration);
+	setShutterLimits(shutterTime, maxFrameDuration);
+	setGainLimits(gain);
 }
 
 utils::Duration ExposureModeHelper::clampExposureTime(utils::Duration exposureTime,
diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h
index 36791c99face056835b0bb2d28b533380e8d9b95..8831ed0751c75d60b61b608c2b0cccfaf1d59726 100644
--- a/src/ipa/libipa/exposure_mode_helper.h
+++ b/src/ipa/libipa/exposure_mode_helper.h
@@ -7,6 +7,7 @@
 
 #pragma once
 
+#include <optional>
 #include <tuple>
 #include <utility>
 #include <vector>
@@ -40,16 +41,18 @@ public:
 
 	void configure(const SensorConfiguration &sensorConfig,
 		       const CameraSensorHelper *sensorHelper);
-	void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime,
-		       utils::Duration maxFrameDuration, double minGain, double maxGain);
+	void setExposureLimits(std::optional<utils::Duration> shutterTime,
+			       std::optional<double> gain,
+			       utils::Duration maxFrameDuration);
 
 	std::tuple<utils::Duration, double, double, double>
 	splitExposure(utils::Duration exposure) const;
 
 private:
-	void setMaxExposure(utils::Duration minExposureTime,
-			    utils::Duration maxExposureTime,
-			    utils::Duration maxFrameDuration);
+	void setShutterLimits(std::optional<utils::Duration> shutterTime,
+			      utils::Duration maxFrameDuration);
+	void setGainLimits(std::optional<double> gain);
+
 	utils::Duration clampExposureTime(utils::Duration exposureTime,
 					  double *quantizationGain = nullptr) const;
 	double clampGain(double gain, double *quantizationGain = nullptr) const;
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index db318a2c49f2fbd9b00222ec699a657eed131595..c8210e175186a282faf586378c5a0a761612047c 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -573,34 +573,23 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 	 * Set the AGC limits using the fixed exposure time and/or gain in
 	 * manual mode, or the sensor limits in auto mode.
 	 */
-	utils::Duration minExposureTime;
-	utils::Duration maxExposureTime;
-	double minAnalogueGain;
-	double maxAnalogueGain;
+	std::optional<utils::Duration> shutterTime;
+	std::optional<double> gain;
 
-	if (frameContext.agc.autoExposureEnabled) {
-		minExposureTime = context.configuration.sensor.minExposureTime;
-		maxExposureTime = context.configuration.sensor.maxExposureTime;
-	} else {
-		minExposureTime = context.configuration.sensor.lineDuration
-				* frameContext.agc.exposure;
-		maxExposureTime = minExposureTime;
+	if (!frameContext.agc.autoExposureEnabled) {
+		shutterTime = context.configuration.sensor.lineDuration
+			    * frameContext.agc.exposure;
 	}
 
-	if (frameContext.agc.autoGainEnabled) {
-		minAnalogueGain = context.configuration.sensor.minAnalogueGain;
-		maxAnalogueGain = context.configuration.sensor.maxAnalogueGain;
-	} else {
-		minAnalogueGain = frameContext.agc.gain;
-		maxAnalogueGain = frameContext.agc.gain;
-	}
+	if (!frameContext.agc.autoGainEnabled)
+		gain = frameContext.agc.gain;
 
 	std::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints;
 	if (context.activeState.wdr.mode != controls::WdrOff)
 		additionalConstraints.push_back(context.activeState.wdr.constraint);
 
-	setLimits(minExposureTime, maxExposureTime, frameContext.agc.maxFrameDuration,
-		  minAnalogueGain, maxAnalogueGain, std::move(additionalConstraints));
+	setExposureLimits(shutterTime, gain, frameContext.agc.maxFrameDuration,
+			  std::move(additionalConstraints));
 
 	/*
 	 * The Agc algorithm needs to know the effective exposure value that was
