diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 92f5bd072134..ed4ea8d31866 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -172,6 +172,8 @@ private:
 
 	void setControls(unsigned int frame);
 	void calculateBdsGrid(const Size &bdsOutputSize);
+	void initializeFrameContext(IPAFrameContext &frameContext,
+				    const ControlList &controls);
 
 	std::map<unsigned int, MappedFrameBuffer> buffers_;
 
@@ -190,6 +192,10 @@ private:
 IPAIPU3::IPAIPU3()
 	: context_(kMaxFrameContexts)
 {
+	context_.frameContexts.setInitCallback(
+		[this](IPAFrameContext &fc, const ControlList &c) {
+			this->initializeFrameContext(fc, c);
+		});
 }
 
 std::string IPAIPU3::logPrefix() const
@@ -561,7 +567,7 @@ void IPAIPU3::computeParams(const uint32_t frame, const uint32_t bufferId)
 	 */
 	params->use = {};
 
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 
 	for (const auto &algo : algorithms())
 		algo->prepare(context_, frame, frameContext, params);
@@ -594,7 +600,7 @@ void IPAIPU3::processStats(const uint32_t frame,
 	const ipu3_uapi_stats_3a *stats =
 		reinterpret_cast<ipu3_uapi_stats_3a *>(mem.data());
 
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 
 	frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
 	frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
@@ -627,10 +633,14 @@ void IPAIPU3::processStats(const uint32_t frame,
  */
 void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
+	context_.frameContexts.getOrInitContext(frame, controls);
+}
 
+void IPAIPU3::initializeFrameContext(IPAFrameContext &frameContext,
+				     const ControlList &controls)
+{
 	for (const auto &algo : algorithms())
-		algo->queueRequest(context_, frame, frameContext, controls);
+		algo->queueRequest(context_, frameContext.frame(), frameContext, controls);
 }
 
 /**
diff --git a/src/ipa/libipa/fc_queue.h b/src/ipa/libipa/fc_queue.h
index 812022c496ed..633bf646d88d 100644
--- a/src/ipa/libipa/fc_queue.h
+++ b/src/ipa/libipa/fc_queue.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include <libcamera/base/log.h>
+#include <libcamera/controls.h>
 
 namespace libcamera {
 
@@ -27,52 +28,33 @@ struct FrameContext {
 private:
 	template<typename T> friend class FCQueue;
 	uint32_t frame_;
-	bool initialised_ = false;
 };
 
 template<typename FC>
 class FCQueue
 {
 public:
+	using InitCallback = std::function<void(FC &, const ControlList &)>;
+
 	FCQueue(unsigned int size)
 		: contexts_(size)
 	{
 	}
 
+	void setInitCallback(const InitCallback &cb)
+	{
+		initCallback_ = cb;
+	}
+
 	void clear()
 	{
 		for (FC &ctx : contexts_) {
-			ctx.initialised_ = false;
 			ctx.frame_ = 0;
 		}
+		initialized_ = false;
 	}
 
-	FC &alloc(const uint32_t frame)
-	{
-		FC &fc = contexts_[frame % contexts_.size()];
-		FrameContext &frameContext = fc;
-
-		/*
-		 * Do not re-initialise if a get() call has already fetched this
-		 * frame context to preseve the context.
-		 *
-		 * \todo If the the sequence number of the context to initialise
-		 * is smaller than the sequence number of the queue slot to use,
-		 * it means that we had a serious request underrun and more
-		 * frames than the queue size has been produced since the last
-		 * time the application has queued a request. Does this deserve
-		 * an error condition ?
-		 */
-		if (frame != 0 && frame <= frameContext.frame_)
-			LOG(FCQueue, Warning)
-				<< "Frame " << frame << " already initialised";
-		else
-			init(fc, frame);
-
-		return fc;
-	}
-
-	FC &get(uint32_t frame)
+	FC &getOrInitContext(unsigned int frame, const ControlList &controls = {})
 	{
 		FC &fc = contexts_[frame % contexts_.size()];
 		FrameContext &frameContext = fc;
@@ -90,51 +72,42 @@ public:
 					    << " has been overwritten by "
 					    << frameContext.frame_;
 
-		if (frame == 0 && !frameContext.initialised_) {
-			/*
-			 * If the IPA calls get() at start() time it will get an
-			 * un-intialized FrameContext as the below "frame ==
-			 * frameContext.frame_" check will return success
-			 * because FrameContexts are zeroed at creation time.
-			 *
-			 * Make sure the FrameContext gets initialised if get()
-			 * is called before alloc() by the IPA for frame#0.
-			 */
-			init(fc, frame);
-
+		if (initialized_ && frame == frameContext.frame_) {
+			if (!controls.empty()) {
+				/* Too late to apply the controls. Store them for later. */
+				LOG(FCQueue, Warning)
+					<< "Request underrun. Controls for frame "
+					<< frame << " are delayed ";
+				controlsToApply_.merge(controls,
+						       ControlList::MergePolicy::OverwriteExisting);
+			}
+			LOG(FCQueue, Debug) << "Got " << frame;
 			return fc;
 		}
 
-		if (frame == frameContext.frame_)
-			return fc;
+		const ControlList *controls2 = &controls;
+		if (!controlsToApply_.empty()) {
+			LOG(FCQueue, Debug) << "Applied late controls on frame" << frame;
+			controlsToApply_.merge(controls, ControlList::MergePolicy::OverwriteExisting);
+			controls2 = &controlsToApply_;
+		}
 
-		/*
-		 * The frame context has been retrieved before it was
-		 * initialised through the initialise() call. This indicates an
-		 * algorithm attempted to access a Frame context before it was
-		 * queued to the IPA. Controls applied for this request may be
-		 * left unhandled.
-		 *
-		 * \todo Set an error flag for per-frame control errors.
-		 */
-		LOG(FCQueue, Warning)
-			<< "Obtained an uninitialised FrameContext for " << frame;
+		LOG(FCQueue, Debug) << "Init " << frame;
 
-		init(fc, frame);
+		fc = {};
+		frameContext.frame_ = frame;
+		initCallback_(fc, *controls2);
+		initialized_ = true;
+		controlsToApply_.clear();
 
 		return fc;
 	}
 
 private:
-	void init(FC &fc, const uint32_t frame)
-	{
-		fc = {};
-		FrameContext &frameContext = fc;
-		frameContext.frame_ = frame;
-		frameContext.initialised_ = true;
-	}
-
 	std::vector<FC> contexts_;
+	InitCallback initCallback_;
+	ControlList controlsToApply_;
+	bool initialized_;
 };
 
 } /* namespace ipa */
diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp
index fd5c9563d6c3..5bd112b5f30a 100644
--- a/src/ipa/mali-c55/mali-c55.cpp
+++ b/src/ipa/mali-c55/mali-c55.cpp
@@ -71,6 +71,8 @@ private:
 	void updateControls(const IPACameraSensorInfo &sensorInfo,
 			    const ControlInfoMap &sensorControls,
 			    ControlInfoMap *ipaControls);
+	void initializeFrameContext(IPAFrameContext &frameContext,
+				    const ControlList &controls);
 	void setControls();
 
 	std::map<unsigned int, MappedFrameBuffer> buffers_;
@@ -91,6 +93,10 @@ namespace {
 IPAMaliC55::IPAMaliC55()
 	: context_(kMaxFrameContexts)
 {
+	context_.frameContexts.setInitCallback(
+		[this](IPAFrameContext &fc, const ControlList &c) {
+			this->initializeFrameContext(fc, c);
+		});
 }
 
 std::string IPAMaliC55::logPrefix() const
@@ -320,21 +326,25 @@ void IPAMaliC55::unmapBuffers(const std::vector<IPABuffer> &buffers)
 	}
 }
 
-void IPAMaliC55::queueRequest(const uint32_t request, const ControlList &controls)
+void IPAMaliC55::queueRequest(const uint32_t frame, const ControlList &controls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.alloc(request);
+	context_.frameContexts.getOrInitContext(frame, controls);
+}
 
+void IPAMaliC55::initializeFrameContext(IPAFrameContext &frameContext,
+					const ControlList &controls)
+{
 	for (const auto &a : algorithms()) {
 		Algorithm *algo = static_cast<Algorithm *>(a.get());
 
-		algo->queueRequest(context_, request, frameContext, controls);
+		algo->queueRequest(context_, frameContext.frame(), frameContext, controls);
 	}
 }
 
 void IPAMaliC55::fillParams(unsigned int request,
 			    [[maybe_unused]] uint32_t bufferId)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.get(request);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(request);
 	MaliC55Params params(buffers_.at(bufferId).planes()[0]);
 
 	for (const auto &algo : algorithms())
@@ -346,7 +356,7 @@ void IPAMaliC55::fillParams(unsigned int request,
 void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,
 			      const ControlList &sensorControls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.get(request);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(request);
 	const mali_c55_stats_buffer *stats = nullptr;
 
 	stats = reinterpret_cast<mali_c55_stats_buffer *>(
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index f83da545be85..f2b387e09ac7 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -174,6 +174,8 @@ void Awb::queueRequest(IPAContext &context,
 	awbAlgo_->handleControls(controls);
 
 	frameContext.awb.autoEnabled = awb.autoEnabled;
+	frameContext.awb.gains = awb.automatic.gains;
+	frameContext.awb.temperatureK = awb.automatic.temperatureK;
 
 	if (awb.autoEnabled)
 		return;
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 88cf6afc219d..d0c753976dd4 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -8,6 +8,7 @@
 #include <algorithm>
 #include <array>
 #include <chrono>
+#include <functional>
 #include <stdint.h>
 #include <string.h>
 
@@ -67,8 +68,7 @@ public:
 
 	void queueRequest(const uint32_t frame, const ControlList &controls) override;
 	void computeParams(const uint32_t frame, const uint32_t bufferId) override;
-	void initializeFrameContext(const uint32_t frame,
-				    IPAFrameContext &frameContext,
+	void initializeFrameContext(IPAFrameContext &frameContext,
 				    const ControlList &controls);
 	void processStats(const uint32_t frame, const uint32_t bufferId,
 			  const ControlList &sensorControls) override;
@@ -131,6 +131,10 @@ const ControlInfoMap::Map rkisp1Controls{
 IPARkISP1::IPARkISP1()
 	: context_(kMaxFrameContexts)
 {
+	context_.frameContexts.setInitCallback(
+		[this](IPAFrameContext &fc, const ControlList &c) {
+			this->initializeFrameContext(fc, c);
+		});
 }
 
 std::string IPARkISP1::logPrefix() const
@@ -217,8 +221,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
 
 void IPARkISP1::start(const ControlList &controls, StartResult *result)
 {
-	IPAFrameContext frameContext = {};
-	initializeFrameContext(0, frameContext, controls);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(0, controls);
 	result->controls = getSensorControls(frameContext);
 	result->code = 0;
 }
@@ -336,27 +339,23 @@ void IPARkISP1::unmapBuffers(const std::vector<unsigned int> &ids)
 
 void IPARkISP1::queueRequest(const uint32_t frame, const ControlList &controls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
 	context_.debugMetadata.enableByControl(controls);
-
-	initializeFrameContext(frame, frameContext, controls);
+	context_.frameContexts.getOrInitContext(frame, controls);
 }
 
-void IPARkISP1::initializeFrameContext(const uint32_t frame,
-				       IPAFrameContext &frameContext,
-				       const ControlList &controls)
+void IPARkISP1::initializeFrameContext(IPAFrameContext &fc, const ControlList &controls)
 {
 	for (const auto &a : algorithms()) {
 		Algorithm *algo = static_cast<Algorithm *>(a.get());
 		if (algo->disabled_)
 			continue;
-		algo->queueRequest(context_, frame, frameContext, controls);
+		algo->queueRequest(context_, fc.frame(), fc, controls);
 	}
 }
 
 void IPARkISP1::computeParams(const uint32_t frame, const uint32_t bufferId)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 
 	/*
 	 * \todo: This needs discussion. In raw mode, computeParams is
@@ -385,7 +384,7 @@ void IPARkISP1::computeParams(const uint32_t frame, const uint32_t bufferId)
 void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId,
 			     const ControlList &sensorControls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 
 	/*
 	 * In raw capture mode, the ISP is bypassed and no statistics buffer is
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 7d25bdd26017..394107606713 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -47,6 +47,10 @@ public:
 	IPASoftSimple()
 		: context_(kMaxFrameContexts)
 	{
+		context_.frameContexts.setInitCallback(
+			[this](IPAFrameContext &fc, const ControlList &c) {
+				this->initializeFrameContext(fc, c);
+			});
 	}
 
 	~IPASoftSimple();
@@ -73,6 +77,8 @@ protected:
 
 private:
 	void updateExposure(double exposureMSV);
+	void initializeFrameContext(IPAFrameContext &frameContext,
+				    const ControlList &controls);
 
 	DebayerParams *params_;
 	SwIspStats *stats_;
@@ -277,17 +283,21 @@ void IPASoftSimple::stop()
 
 void IPASoftSimple::queueRequest(const uint32_t frame, const ControlList &controls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
+	context_.frameContexts.getOrInitContext(frame, controls);
+}
 
+void IPASoftSimple::initializeFrameContext(IPAFrameContext &frameContext,
+					   const ControlList &controls)
+{
 	for (const auto &algo : algorithms())
-		algo->queueRequest(context_, frame, frameContext, controls);
+		algo->queueRequest(context_, frameContext.frame(), frameContext, controls);
 }
 
 void IPASoftSimple::computeParams(const uint32_t frame)
 {
 	context_.activeState.combinedMatrix = Matrix<float, 3, 3>::identity();
 
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 	for (const auto &algo : algorithms())
 		algo->prepare(context_, frame, frameContext, params_);
 	params_->combinedMatrix = context_.activeState.combinedMatrix;
@@ -299,7 +309,7 @@ void IPASoftSimple::processStats(const uint32_t frame,
 				 [[maybe_unused]] const uint32_t bufferId,
 				 const ControlList &sensorControls)
 {
-	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+	IPAFrameContext &frameContext = context_.frameContexts.getOrInitContext(frame);
 
 	frameContext.sensor.exposure =
 		sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
