diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 0935050a33..7db7270480 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -551,10 +551,7 @@ void IPAIPU3::setControls(unsigned int frame)
 	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(
-		ctrls, context_.camHelper.get(),
-		frameContext.agc.exposure, frameContext.agc.gain
-	);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	ControlList lensCtrls(lensCtrls_);
 	lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE,
diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
index 4f8038ae41..97153541fc 100644
--- a/src/ipa/libipa/agc.h
+++ b/src/ipa/libipa/agc.h
@@ -29,27 +29,6 @@ namespace ipa {
 
 namespace agc {
 
-[[nodiscard]]
-inline std::pair<uint32_t, double>
-extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
-{
-	auto exposure = controls.get(V4L2_CID_EXPOSURE).get<int32_t>();
-	auto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
-
-	return {
-		uint32_t(exposure),
-		sensor ? sensor->gain(gainCode) : gainCode,
-	};
-}
-
-inline void
-prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
-		int32_t exposure, double gain)
-{
-	controls.set(V4L2_CID_EXPOSURE, exposure);
-	controls.set(V4L2_CID_ANALOGUE_GAIN, int32_t(sensor ? sensor->gainCode(gain) : gain));
-}
-
 struct Session {
 	utils::Duration minExposureTime;
 	utils::Duration maxExposureTime;
@@ -108,6 +87,33 @@ struct FrameContext {
 	bool autoGainModeChange;
 };
 
+
+[[nodiscard]]
+inline std::pair<uint32_t, double>
+extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
+{
+	auto exposure = controls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+	auto gainCode = controls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
+
+	return {
+		uint32_t(exposure),
+		sensor ? sensor->gain(gainCode) : gainCode,
+	};
+}
+
+inline void
+prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
+		const FrameContext &frameContext)
+{
+	uint32_t gain = sensor
+		? sensor->gainCode(frameContext.gain)
+		: uint32_t(frameContext.gain);
+
+	controls.set(V4L2_CID_EXPOSURE, int32_t(frameContext.exposure));
+	controls.set(V4L2_CID_ANALOGUE_GAIN, int32_t(gain));
+	controls.set(V4L2_CID_VBLANK, int32_t(frameContext.vblank));
+}
+
 } /* namespace agc */
 
 class AgcAlgorithm
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index c0c2c83dca..160f7ac88a 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -133,7 +133,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig
 void IPAMaliC55::setControls(const IPAFrameContext &frameContext)
 {
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	setSensorControls.emit(ctrls);
 }
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 6425d8f1e8..033e69f9f0 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -367,8 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
 		<< ", gain " << frameContext.agc.gain << ", vblank " << vblank;
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(), exposure, frameContext.agc.gain);
-	ctrls.set(V4L2_CID_VBLANK, static_cast<int32_t>(vblank));
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 
 	setSensorControls.emit(frame, ctrls);
 }
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index f35e269deb..5d1e0deb87 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -249,7 +249,7 @@ void IPASoftSimple::processStats(const uint32_t frame,
 	metadataReady.emit(frame, metadata);
 
 	ControlList ctrls(context_.sensorControls);
-	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc.exposure, frameContext.agc.gain);
+	agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc);
 	setSensorControls.emit(ctrls);
 }
 
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 0f3e169d3b..4147f9475a 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras()
 		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 			{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 			{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+			{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 		};
 
 		data->delayedCtrls_ =
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 599ff88b59..ef156d421b 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
 	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 	};
 
 	data->delayedCtrls_ =
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 3917aa897e..968aab6740 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe,
 	std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
 		{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },
 		{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },
+		{ V4L2_CID_VBLANK, { delays.vblankDelay, true } },
 	};
 	delayedCtrls_ = std::make_unique<DelayedControls>(sensor_->device(), params);
 
