diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index d0da146c..73f361f8 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -55,9 +55,11 @@ public:
 
 	int loadIPA();
 
+	void tryReturnBuffer(FrameBuffer *buffer);
 	void imguOutputBufferReady(FrameBuffer *buffer);
 	void cio2BufferReady(FrameBuffer *buffer);
 	void paramBufferReady(FrameBuffer *buffer);
+	void captureParamBufferReady(FrameBuffer *buffer);
 	void statBufferReady(FrameBuffer *buffer);
 	void queuePendingRequests();
 	void cancelPendingRequests();
@@ -93,6 +95,8 @@ private:
 	void paramsBufferReady(unsigned int id);
 	void setSensorControls(unsigned int id, const ControlList &sensorControls,
 			       const ControlList &lensControls);
+
+	std::map<FrameBuffer *, int> bufferReturnCounters;
 };
 
 class IPU3CameraConfiguration : public CameraConfiguration
@@ -106,7 +110,8 @@ public:
 	Status validate() override;
 
 	const StreamConfiguration &cio2Format() const { return cio2Configuration_; }
-	const ImgUDevice::PipeConfig imguConfig() const { return pipeConfig_; }
+	const ImgUDevice::PipeConfig imguConfig0() const { return pipeConfig0_; }
+	const ImgUDevice::PipeConfig imguConfig1() const { return pipeConfig1_; }
 
 	/* Cache the combinedTransform_ that will be applied to the sensor */
 	Transform combinedTransform_;
@@ -120,7 +125,8 @@ private:
 	const IPU3CameraData *data_;
 
 	StreamConfiguration cio2Configuration_;
-	ImgUDevice::PipeConfig pipeConfig_;
+	ImgUDevice::PipeConfig pipeConfig0_;
+	ImgUDevice::PipeConfig pipeConfig1_;
 };
 
 class PipelineHandlerIPU3 : public PipelineHandler
@@ -170,6 +176,8 @@ private:
 	MediaDevice *cio2MediaDev_;
 	MediaDevice *imguMediaDev_;
 
+	bool useImgu1_ = false;
+
 	std::vector<IPABuffer> ipaBuffers_;
 };
 
@@ -408,8 +416,8 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
 
 	/* Only compute the ImgU configuration if a YUV stream has been requested. */
 	if (yuvCount) {
-		pipeConfig_ = data_->imgu0_->calculatePipeConfig(&pipe);
-		if (pipeConfig_.isNull()) {
+		pipeConfig0_ = data_->imgu0_->calculatePipeConfig(&pipe);
+		if (pipeConfig0_.isNull()) {
 			LOG(IPU3, Error) << "Failed to calculate pipe configuration: "
 					 << "unsupported resolutions.";
 			return Invalid;
@@ -519,8 +527,13 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 	IPU3CameraData *data = cameraData(camera);
 	Stream *outStream = &data->outStream_;
 	Stream *vfStream = &data->vfStream_;
+	Stream *outCaptureStream = &data->outCaptureStream_;
 	CIO2Device *cio2 = &data->cio2_;
 	V4L2DeviceFormat outputFormat;
+
+	ImgUDevice::PipeConfig imguConfig1 = config->imguConfig1();
+	useImgu1_ = !imguConfig1.isNull();
+
 	int ret;
 
 	/*
@@ -562,6 +575,12 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 	if (ret)
 		return ret;
 
+	if (useImgu1_) {
+		ret = imgu1_.enableLinks(true);
+		if (ret)
+			return ret;
+	}
+
 	/*
 	 * Pass the requested stream size to the CIO2 unit and get back the
 	 * adjusted format to be propagated to the ImgU output devices.
@@ -604,14 +623,20 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 	 * stream has been requested: return here to skip the ImgU configuration
 	 * part.
 	 */
-	ImgUDevice::PipeConfig imguConfig = config->imguConfig();
-	if (imguConfig.isNull())
+	ImgUDevice::PipeConfig imguConfig0 = config->imguConfig0();
+	if (imguConfig0.isNull())
 		return 0;
 
-	ret = imgu0_.configure(imguConfig, &cio2Format);
+	ret = imgu0_.configure(imguConfig0, &cio2Format);
 	if (ret)
 		return ret;
 
+	if (useImgu1_) {
+		ret = imgu1_.configure(imguConfig1, &cio2Format);
+		if (ret)
+			return ret;
+	}
+
 	/* Apply the format to the configured streams output devices. */
 	StreamConfiguration *mainCfg = nullptr;
 	StreamConfiguration *vfCfg = nullptr;
@@ -630,6 +655,15 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 			ret = imgu0_.configureViewfinder(cfg, &outputFormat);
 			if (ret)
 				return ret;
+		} else if (stream == outCaptureStream) {
+			ASSERT(useImgu1_);
+			ret = imgu1_.configureOutput(cfg, &outputFormat);
+			if (ret)
+				return ret;
+
+			ret = imgu1_.configureViewfinder(cfg, &outputFormat);
+			if (ret)
+				return ret;
 		}
 	}
 
@@ -645,22 +679,34 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 	}
 
 	/* Apply the "pipe_mode" control to the ImgU subdevice. */
-	ControlList ctrls(imgu0_.imgu_->controls());
+	ControlList ctrls0(imgu0_.imgu_->controls());
 	/*
 	 * Set the ImgU pipe mode to 'Video' unconditionally to have statistics
 	 * generated.
-	 *
-	 * \todo Figure out what the 'Still Capture' mode is meant for, and use
-	 * it accordingly.
 	 */
-	ctrls.set(V4L2_CID_IPU3_PIPE_MODE,
-		  static_cast<int32_t>(IPU3PipeModeVideo));
-	ret = imgu0_.imgu_->setControls(&ctrls);
+	ctrls0.set(V4L2_CID_IPU3_PIPE_MODE,
+		   static_cast<int32_t>(IPU3PipeModeVideo));
+	ret = imgu0_.imgu_->setControls(&ctrls0);
 	if (ret) {
 		LOG(IPU3, Error) << "Unable to set pipe_mode control";
 		return ret;
 	}
 
+	if (useImgu1_) {
+		ControlList ctrls1(imgu1_.imgu_->controls());
+		/*
+		 * \todo Figure out what the 'Still Capture' mode is meant for,
+		 * and use it accordingly.
+		 */
+		ctrls1.set(V4L2_CID_IPU3_PIPE_MODE,
+			   static_cast<int32_t>(IPU3PipeModeStillCapture));
+		ret = imgu1_.imgu_->setControls(&ctrls1);
+		if (ret) {
+			LOG(IPU3, Error) << "Unable to set pipe_mode control";
+			return ret;
+		}
+	}
+
 	ipa::ipu3::IPAConfigInfo configInfo;
 	configInfo.sensorControls = data->cio2_.sensor()->controls();
 
@@ -669,8 +715,8 @@ int PipelineHandlerIPU3::configure(Camera *camera, CameraConfiguration *c)
 		configInfo.lensControls = lens->controls();
 
 	configInfo.sensorInfo = sensorInfo;
-	configInfo.bdsOutputSize = config->imguConfig().bds;
-	configInfo.iif = config->imguConfig().iif;
+	configInfo.bdsOutputSize = config->imguConfig0().bds;
+	configInfo.iif = config->imguConfig0().iif;
 
 	ret = data->ipa_->configure(configInfo, &data->ipaControls_);
 	if (ret) {
@@ -798,8 +844,8 @@ int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] const ControlLis
 	CIO2Device *cio2 = &data->cio2_;
 	int ret;
 
-	imgu0_.input_->bufferReady.connect(&data->cio2_,
-					   &CIO2Device::tryReturnBuffer);
+	imgu0_.input_->bufferReady.connect(data,
+					   &IPU3CameraData::tryReturnBuffer);
 	imgu0_.output_->bufferReady.connect(data,
 					    &IPU3CameraData::imguOutputBufferReady);
 	imgu0_.viewfinder_->bufferReady.connect(data,
@@ -808,6 +854,16 @@ int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] const ControlLis
 					   &IPU3CameraData::paramBufferReady);
 	imgu0_.stat_->bufferReady.connect(data,
 					  &IPU3CameraData::statBufferReady);
+
+	if (useImgu1_) {
+		imgu1_.input_->bufferReady.connect(data,
+				&IPU3CameraData::tryReturnBuffer);
+		imgu1_.output_->bufferReady.connect(data,
+				&IPU3CameraData::imguOutputBufferReady);
+		imgu1_.param_->bufferReady.connect(data,
+				&IPU3CameraData::captureParamBufferReady);
+	}
+
 	/* Disable test pattern mode on the sensor, if any. */
 	ret = cio2->sensor()->setTestPatternMode(
 		controls::draft::TestPatternModeEnum::TestPatternModeOff);
@@ -837,16 +893,26 @@ int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] const ControlLis
 	if (ret)
 		goto error;
 
+	if (useImgu1_) {
+		ret = imgu1_.start();
+		if (ret)
+			goto error;
+	}
+
 	return 0;
 
 error:
 	imgu0_.stop();
+	imgu1_.stop();
 	cio2->stop();
 	data->ipa_->stop();
 	freeBuffers(camera);
 	LOG(IPU3, Error) << "Failed to start camera " << camera->id();
 
 	imgu0_.disconnectSignals();
+	imgu1_.disconnectSignals();
+
+	useImgu1_ = false;
 
 	return ret;
 }
@@ -861,6 +927,7 @@ void PipelineHandlerIPU3::stopDevice(Camera *camera)
 	data->ipa_->stop();
 
 	ret |= imgu0_.stop();
+	ret |= imgu1_.stop();
 	ret |= data->cio2_.stop();
 	if (ret)
 		LOG(IPU3, Warning) << "Failed to stop camera " << camera->id();
@@ -868,6 +935,9 @@ void PipelineHandlerIPU3::stopDevice(Camera *camera)
 	freeBuffers(camera);
 
 	imgu0_.disconnectSignals();
+	imgu1_.disconnectSignals();
+
+	useImgu1_ = false;
 }
 
 void IPU3CameraData::cancelPendingRequests()
@@ -1312,22 +1382,45 @@ void IPU3CameraData::paramsBufferReady(unsigned int id)
 	if (!info)
 		return;
 
+	bool hasYuv = false;
+	bool hasCapture = false;
 	/* Queue all buffers from the request aimed for the ImgU. */
 	for (auto it : info->request->buffers()) {
 		const Stream *stream = it.first;
 		FrameBuffer *outbuffer = it.second;
 
-		if (stream == &outStream_)
+		if (stream == &outStream_) {
+			hasYuv = true;
 			imgu0_->output_->queueBuffer(outbuffer);
-		else if (stream == &vfStream_)
+		} else if (stream == &vfStream_) {
+			hasYuv = true;
 			imgu0_->viewfinder_->queueBuffer(outbuffer);
+		} else if (stream == &outCaptureStream_) {
+			hasCapture = true;
+
+			imgu1_->output_->queueBuffer(outbuffer);
+		}
 	}
 
-	imgu0_->param_->queueBuffer(info->paramBuffer);
-	imgu0_->stat_->queueBuffer(info->statBuffer);
-	imgu0_->input_->queueBuffer(info->rawBuffer);
+	if (hasYuv) {
+		bufferReturnCounters[info->rawBuffer] += 1;
 
-	info->captureParamDequeued = true;
+		imgu0_->param_->queueBuffer(info->paramBuffer);
+		imgu0_->stat_->queueBuffer(info->statBuffer);
+		imgu0_->input_->queueBuffer(info->rawBuffer);
+	} else {
+		info->paramDequeued = true;
+		info->metadataProcessed = true;
+	}
+
+	if (hasCapture) {
+		bufferReturnCounters[info->rawBuffer] += 1;
+
+		imgu1_->param_->queueBuffer(info->captureParamBuffer);
+		imgu1_->input_->queueBuffer(info->rawBuffer);
+	} else {
+		info->captureParamDequeued = true;
+	}
 }
 
 void IPU3CameraData::metadataReady(unsigned int id, const ControlList &metadata)
@@ -1348,6 +1441,15 @@ void IPU3CameraData::metadataReady(unsigned int id, const ControlList &metadata)
  * Buffer Ready slots
  */
 
+void IPU3CameraData::tryReturnBuffer(FrameBuffer *buffer)
+{
+	if (--bufferReturnCounters[buffer] > 0)
+		return;
+
+	bufferReturnCounters.erase(buffer);
+	cio2_.tryReturnBuffer(buffer);
+}
+
 /**
  * \brief Handle buffers completion at the ImgU output
  * \param[in] buffer The completed buffer
@@ -1440,6 +1542,26 @@ void IPU3CameraData::paramBufferReady(FrameBuffer *buffer)
 		pipe()->completeRequest(request);
 }
 
+void IPU3CameraData::captureParamBufferReady(FrameBuffer *buffer)
+{
+	IPU3Frames::Info *info = frameInfos_.find(buffer);
+	if (!info)
+		return;
+
+	info->captureParamDequeued = true;
+
+	/*
+	 * tryComplete() will delete info if it completes the IPU3Frame.
+	 * In that event, we must have obtained the Request before hand.
+	 *
+	 * \todo Improve the FrameInfo API to avoid this type of issue
+	 */
+	Request *request = info->request;
+
+	if (frameInfos_.tryComplete(info))
+		pipe()->completeRequest(request);
+}
+
 void IPU3CameraData::statBufferReady(FrameBuffer *buffer)
 {
 	IPU3Frames::Info *info = frameInfos_.find(buffer);
