diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp
index 1ade717c17..7acfebf52f 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 <limits>
 #include <optional>
 
 #include <libcamera/base/log.h>
@@ -488,8 +489,14 @@ AgcMeanLuminance::AgcMeanLuminance(Configuration &&config)
 	 * possible before touching gain.
 	 */
 	if (exposureModeHelpers_.empty())
-		exposureModeHelpers_.try_emplace(controls::AeExposureModeNormal,
-						 std::vector<ExposureModeHelper::Stage>{});
+		exposureModeHelpers_.try_emplace(
+			controls::AeExposureModeNormal,
+			std::vector<ExposureModeHelper::Stage>{
+				{
+					.exposureTime = utils::Duration::max(),
+					.gain = std::numeric_limits<double>::max(),
+				},
+			});
 }
 
 AgcMeanLuminance::~AgcMeanLuminance() = default;
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp
index 4ec31f54c3..5c4242519b 100644
--- a/src/ipa/libipa/exposure_mode_helper.cpp
+++ b/src/ipa/libipa/exposure_mode_helper.cpp
@@ -84,6 +84,7 @@ ExposureModeHelper::ExposureModeHelper(std::vector<Stage> &&stages)
 	: stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us),
 	  maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr)
 {
+	ASSERT(!stages_.empty());
 }
 
 /**
@@ -203,8 +204,8 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons
  * value.
  *
  * If no combination of exposure time and gain limits is found that meets the
- * required exposure, the helper falls-back to simply maximising the exposure
- * time first, followed by analogue gain, followed by digital gain.
+ * required exposure, then the exposure and gain of the last stage will be used,
+ * and the digital gain will be increased as necessary.
  *
  * During the calculations the gain missed due to quantization is recorded and
  * returned as quantization gain. The quantization gain is not included in the
@@ -219,6 +220,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 {
 	ASSERT(maxExposureTime_);
 	ASSERT(maxGain_);
+	ASSERT(!stages_.empty());
 
 	utils::Duration exposureTime;
 	double gain;
@@ -240,13 +242,13 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 			 exposure / (exposureTime * gain * quantGain) };
 	}
 
-	double stageGain = clampGain(1.0);
-	double lastStageGain = stageGain;
+	utils::Duration stageExposureTime = {};
+	double lastStageGain = clampGain(1.0);
+	double stageGain = {};
 
 	for (const auto &stage : stages_) {
-		utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime,
-								      &quantGain);
-		stageGain = clampGain(stage.gain);
+		stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain);
+		stageGain = clampGain(stage.gain, &quantGain2);
 
 		/*
 		 * We perform the clamping on both exposure time and gain in
@@ -280,14 +282,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
 	}
 
 	/*
-	 * From here on all we can do is max out the exposure time, followed by
-	 * the analogue gain. If we still haven't achieved the target we send
-	 * the rest of the exposure time to digital gain. If we were given no
-	 * stages to use then the default stageGain of 1.0 is used so that
-	 * exposure time is maxed before gain is touched at all.
+	 * If no stages could satisfy the desired exposure, then use the last
+	 * stage's exposure time and gain, which is the maximum allowed.
 	 */
-	exposureTime = clampExposureTime(exposure / stageGain, &quantGain);
-	gain = clampGain(exposure / exposureTime, &quantGain2);
+	exposureTime = stageExposureTime;
+	gain = stageGain;
 	quantGain *= quantGain2;
 
 	return { exposureTime, gain, quantGain,
