[RFC,v1,5/8] libcamera: pipeline: Set vblank on more platforms
diff mbox series

Message ID 20260827104108.1432632-6-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • ipa: libipa: agc: Take exposure margin into account
Related show

Commit Message

Barnabás Pőcze Aug. 27, 2026, 10:41 a.m. UTC
The common `AgcAlgorithm` provides the desired vblank value the given
frame, so adjust `agc::prepareControls()` accordingly, and propagate
the vblank value to the sensors in the pipeline handlers.

Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
---
 src/ipa/ipu3/ipu3.cpp                        | 3 +--
 src/ipa/libipa/agc.cpp                       | 5 ++---
 src/ipa/libipa/agc.h                         | 9 +++++----
 src/ipa/mali-c55/mali-c55.cpp                | 3 +--
 src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
 src/ipa/softisp/softisp.cpp                  | 3 +--
 src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
 src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
 src/libcamera/pipeline/simple/simple.cpp     | 1 +
 9 files changed, 14 insertions(+), 16 deletions(-)

Comments

Stefan Klug Aug. 27, 2026, 1:44 p.m. UTC | #1
Hi Barnabás,

Quoting Barnabás Pőcze (2026-08-27 12:41:05)
> The common `AgcAlgorithm` provides the desired vblank value the given
> frame, so adjust `agc::prepareControls()` accordingly, and propagate
> the vblank value to the sensors in the pipeline handlers.
> 
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
> ---
>  src/ipa/ipu3/ipu3.cpp                        | 3 +--
>  src/ipa/libipa/agc.cpp                       | 5 ++---
>  src/ipa/libipa/agc.h                         | 9 +++++----
>  src/ipa/mali-c55/mali-c55.cpp                | 3 +--
>  src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
>  src/ipa/softisp/softisp.cpp                  | 3 +--
>  src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
>  src/libcamera/pipeline/simple/simple.cpp     | 1 +
>  9 files changed, 14 insertions(+), 16 deletions(-)
> 
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 031b2e4f4d..f8990e3a4b 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -551,8 +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.cpp b/src/ipa/libipa/agc.cpp
> index 34379c0d61..ce5b2ab6f6 100644
> --- a/src/ipa/libipa/agc.cpp
> +++ b/src/ipa/libipa/agc.cpp
> @@ -56,11 +56,10 @@ namespace agc {
>  
>  /**
>   * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> - *                     uint32_t exposure, double gain)
> + *                     const FrameContext &frameContext)
>   * \param[out] controls The controls list to populate
>   * \param[in] sensor The CameraSensorHelper
> - * \param[in] exposure The exposure (in lines)
> - * \param[in] gain The analogue gain
> + * \param[in] frameContext The agc frame context
>   *
>   * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
>   * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
> index 9f95f23ea8..388693d21d 100644
> --- a/src/ipa/libipa/agc.h
> +++ b/src/ipa/libipa/agc.h
> @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
>  
>  inline void
>  prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> -               uint32_t exposure, double gain)
> +               const FrameContext &frameContext)
>  {
> -       controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
> +       controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
>         controls.set(V4L2_CID_ANALOGUE_GAIN,
>                      static_cast<int32_t>(sensor
> -                                         ? sensor->gainCode(gain)
> -                                         : static_cast<uint32_t>(gain)));
> +                                         ? sensor->gainCode(frameContext.gain)
> +                                         : static_cast<uint32_t>(frameContext.gain)));
> +       controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
>  }

I think the extractControls() and prepareControls() functions have
reached a size that they could be moved to the cpp. Or is there a
specific reason to keep them in the header?

>  
>  } /* namespace agc */
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index 1e7cd1995d..201225128a 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -134,8 +134,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 79ab7338c1..cefaf40aee 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame)
>                 << ", gain " << frameContext.agc.gain << ", vblank " << vblank;

What about moving that log message into prepareControls()? I think that
is relevant debug information on all platforms.

Best regards,
Stefan

> 
>         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/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
> index 8acfcd1a0b..fbb3f3302f 100644
> --- a/src/ipa/softisp/softisp.cpp
> +++ b/src/ipa/softisp/softisp.cpp
> @@ -249,8 +249,7 @@ void IPASoftIsp::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 ca98e74790..4928ef50e9 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);
>  
> -- 
> 2.55.0
>
Jacopo Mondi Aug. 27, 2026, 3:20 p.m. UTC | #2
Hi Dan

On Thu, Aug 27, 2026 at 12:41:05PM +0200, Barnabás Pőcze wrote:
> The common `AgcAlgorithm` provides the desired vblank value the given
> frame, so adjust `agc::prepareControls()` accordingly, and propagate
> the vblank value to the sensors in the pipeline handlers.
>
> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>

Do you happen to be able to re-test this one on IPU3 ?

> ---
>  src/ipa/ipu3/ipu3.cpp                        | 3 +--
>  src/ipa/libipa/agc.cpp                       | 5 ++---
>  src/ipa/libipa/agc.h                         | 9 +++++----
>  src/ipa/mali-c55/mali-c55.cpp                | 3 +--
>  src/ipa/rkisp1/rkisp1.cpp                    | 4 +---
>  src/ipa/softisp/softisp.cpp                  | 3 +--
>  src/libcamera/pipeline/ipu3/ipu3.cpp         | 1 +
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 +
>  src/libcamera/pipeline/simple/simple.cpp     | 1 +
>  9 files changed, 14 insertions(+), 16 deletions(-)
>
> diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
> index 031b2e4f4d..f8990e3a4b 100644
> --- a/src/ipa/ipu3/ipu3.cpp
> +++ b/src/ipa/ipu3/ipu3.cpp
> @@ -551,8 +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.cpp b/src/ipa/libipa/agc.cpp
> index 34379c0d61..ce5b2ab6f6 100644
> --- a/src/ipa/libipa/agc.cpp
> +++ b/src/ipa/libipa/agc.cpp
> @@ -56,11 +56,10 @@ namespace agc {
>
>  /**
>   * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> - *                     uint32_t exposure, double gain)
> + *                     const FrameContext &frameContext)
>   * \param[out] controls The controls list to populate
>   * \param[in] sensor The CameraSensorHelper
> - * \param[in] exposure The exposure (in lines)
> - * \param[in] gain The analogue gain
> + * \param[in] frameContext The agc frame context
>   *
>   * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
>   * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
> diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
> index 9f95f23ea8..388693d21d 100644
> --- a/src/ipa/libipa/agc.h
> +++ b/src/ipa/libipa/agc.h
> @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
>
>  inline void
>  prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
> -		uint32_t exposure, double gain)
> +		const FrameContext &frameContext)
>  {
> -	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
> +	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
>  	controls.set(V4L2_CID_ANALOGUE_GAIN,
>  		     static_cast<int32_t>(sensor
> -					  ? sensor->gainCode(gain)
> -					  : static_cast<uint32_t>(gain)));
> +					  ? sensor->gainCode(frameContext.gain)
> +					  : static_cast<uint32_t>(frameContext.gain)));
> +	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
>  }
>
>  } /* namespace agc */
> diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
> index 1e7cd1995d..201225128a 100644
> --- a/src/ipa/mali-c55/mali-c55.cpp
> +++ b/src/ipa/mali-c55/mali-c55.cpp
> @@ -134,8 +134,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 79ab7338c1..cefaf40aee 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -367,9 +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/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
> index 8acfcd1a0b..fbb3f3302f 100644
> --- a/src/ipa/softisp/softisp.cpp
> +++ b/src/ipa/softisp/softisp.cpp
> @@ -249,8 +249,7 @@ void IPASoftIsp::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 ca98e74790..4928ef50e9 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);
>
> --
> 2.55.0
>

Patch
diff mbox series

diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 031b2e4f4d..f8990e3a4b 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -551,8 +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.cpp b/src/ipa/libipa/agc.cpp
index 34379c0d61..ce5b2ab6f6 100644
--- a/src/ipa/libipa/agc.cpp
+++ b/src/ipa/libipa/agc.cpp
@@ -56,11 +56,10 @@  namespace agc {
 
 /**
  * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
- *                     uint32_t exposure, double gain)
+ *                     const FrameContext &frameContext)
  * \param[out] controls The controls list to populate
  * \param[in] sensor The CameraSensorHelper
- * \param[in] exposure The exposure (in lines)
- * \param[in] gain The analogue gain
+ * \param[in] frameContext The agc frame context
  *
  * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN
  * in \a controls. The gain is mapped to the gain code if \a sensor is provided,
diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h
index 9f95f23ea8..388693d21d 100644
--- a/src/ipa/libipa/agc.h
+++ b/src/ipa/libipa/agc.h
@@ -107,13 +107,14 @@  extractControls(const ControlList &controls, const CameraSensorHelper *sensor)
 
 inline void
 prepareControls(ControlList &controls, const CameraSensorHelper *sensor,
-		uint32_t exposure, double gain)
+		const FrameContext &frameContext)
 {
-	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
+	controls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(frameContext.exposure));
 	controls.set(V4L2_CID_ANALOGUE_GAIN,
 		     static_cast<int32_t>(sensor
-					  ? sensor->gainCode(gain)
-					  : static_cast<uint32_t>(gain)));
+					  ? sensor->gainCode(frameContext.gain)
+					  : static_cast<uint32_t>(frameContext.gain)));
+	controls.set(V4L2_CID_VBLANK, static_cast<int32_t>(frameContext.vblank));
 }
 
 } /* namespace agc */
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index 1e7cd1995d..201225128a 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -134,8 +134,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 79ab7338c1..cefaf40aee 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -367,9 +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/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp
index 8acfcd1a0b..fbb3f3302f 100644
--- a/src/ipa/softisp/softisp.cpp
+++ b/src/ipa/softisp/softisp.cpp
@@ -249,8 +249,7 @@  void IPASoftIsp::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 ca98e74790..4928ef50e9 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);