[v4,06/11] mali-c55: Plumb the IPA module in
diff mbox series

Message ID 20241115122540.478103-7-dan.scally@ideasonboard.com
State New
Headers show
Series
  • Add Mali-C55 IPA Module and Algorithms
Related show

Commit Message

Dan Scally Nov. 15, 2024, 12:25 p.m. UTC
From: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Plumb the Pipeline-IPA loop in.

Load the IPA module at camera creation time and create the loop between
the pipeline and the IPA.

When a new Request is queued the IPA is asked to prepare the parameters
buffer, once ready it notifies the pipeline which queues the parameters
to the ISP along with a buffer for statistics and frames,

Once statistics are ready they get passed to the IPA which upates its
settings for the next frame.

Driveby fix an error message in the Pipeline Handler's ::freeBuffers()
function which reported a problem with the wrong video device in an
error path.

Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
---
Changes in v4:

	- None

Changes in v3:

	- Corrected the call to ipa_->configurationFile() to include a fallback
	  file name.
	- Used the new CameraSensor::getSensorDelays() to fetch sensor delay
	  values from CameraSensorProperties.
Changes in v2:

	- None

 src/libcamera/pipeline/mali-c55/mali-c55.cpp | 421 +++++++++++++++++--
 1 file changed, 378 insertions(+), 43 deletions(-)

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index e451204b..43ef0572 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -21,17 +21,23 @@ 
 #include <libcamera/camera.h>
 #include <libcamera/formats.h>
 #include <libcamera/geometry.h>
+#include <libcamera/property_ids.h>
 #include <libcamera/stream.h>
 
 #include <libcamera/ipa/core_ipa_interface.h>
+#include <libcamera/ipa/mali-c55_ipa_interface.h>
+#include <libcamera/ipa/mali-c55_ipa_proxy.h>
 
 #include "libcamera/internal/bayer_format.h"
 #include "libcamera/internal/camera.h"
 #include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/delayed_controls.h"
 #include "libcamera/internal/device_enumerator.h"
 #include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/ipa_manager.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/request.h"
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
@@ -72,6 +78,16 @@  constexpr Size kMaliC55MinSize = { 128, 128 };
 constexpr Size kMaliC55MaxSize = { 8192, 8192 };
 constexpr unsigned int kMaliC55ISPInternalFormat = MEDIA_BUS_FMT_RGB121212_1X36;
 
+struct MaliC55FrameInfo {
+	Request *request;
+
+	FrameBuffer *paramBuffer;
+	FrameBuffer *statBuffer;
+
+	bool paramsDone;
+	bool statsDone;
+};
+
 class MaliC55CameraData : public Camera::Private
 {
 public:
@@ -81,6 +97,7 @@  public:
 	}
 
 	int init();
+	int loadIPA();
 
 	/* Deflect these functionalities to either TPG or CameraSensor. */
 	const std::vector<Size> sizes(unsigned int mbusCode) const;
@@ -89,7 +106,7 @@  public:
 	int pixfmtToMbusCode(const PixelFormat &pixFmt) const;
 	const PixelFormat &bestRawFormat() const;
 
-	void updateControls();
+	void updateControls(const ControlInfoMap &ipaControls);
 
 	PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;
 	Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;
@@ -102,8 +119,15 @@  public:
 	Stream frStream_;
 	Stream dsStream_;
 
+	std::unique_ptr<ipa::mali_c55::IPAProxyMaliC55> ipa_;
+	std::vector<IPABuffer> ipaStatBuffers_;
+	std::vector<IPABuffer> ipaParamBuffers_;
+
+	std::unique_ptr<DelayedControls> delayedCtrls_;
+
 private:
 	void initTPGData();
+	void setSensorControls(const ControlList &sensorControls);
 
 	std::string id_;
 	std::vector<unsigned int> tpgCodes_;
@@ -167,6 +191,11 @@  void MaliC55CameraData::initTPGData()
 	tpgResolution_ = tpgSizes_.back();
 }
 
+void MaliC55CameraData::setSensorControls(const ControlList &sensorControls)
+{
+	delayedCtrls_->push(sensorControls);
+}
+
 const std::vector<Size> MaliC55CameraData::sizes(unsigned int mbusCode) const
 {
 	if (sensor_)
@@ -272,7 +301,7 @@  const PixelFormat &MaliC55CameraData::bestRawFormat() const
 	return invalidPixFmt;
 }
 
-void MaliC55CameraData::updateControls()
+void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls)
 {
 	if (!sensor_)
 		return;
@@ -290,6 +319,9 @@  void MaliC55CameraData::updateControls()
 		ControlInfo(ispMinCrop, sensorInfo.analogCrop,
 			    sensorInfo.analogCrop);
 
+	for (auto const &c : ipaControls)
+		controls.emplace(c.first, c.second);
+
 	controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
 }
 
@@ -343,6 +375,45 @@  Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &si
 	return bestSize;
 }
 
+int MaliC55CameraData::loadIPA()
+{
+	int ret;
+
+	/* Do not initialize IPA for TPG. */
+	if (!sensor_)
+		return 0;
+
+	ipa_ = IPAManager::createIPA<ipa::mali_c55::IPAProxyMaliC55>(pipe(), 1, 1);
+	if (!ipa_)
+		return -ENOENT;
+
+	ipa_->setSensorControls.connect(this, &MaliC55CameraData::setSensorControls);
+
+	std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml",
+							    "uncalibrated.yaml");
+
+	/* We need to inform the IPA of the sensor configuration */
+	ipa::mali_c55::IPAConfigInfo ipaConfig{};
+
+	ret = sensor_->sensorInfo(&ipaConfig.sensorInfo);
+	if (ret)
+		return ret;
+
+	ipaConfig.sensorControls = sensor_->controls();
+
+	ControlInfoMap ipaControls;
+	ret = ipa_->init({ ipaTuningFile, sensor_->model() }, ipaConfig,
+			 &ipaControls);
+	if (ret) {
+		LOG(MaliC55, Error) << "Failed to initialise the Mali-C55 IPA";
+		return ret;
+	}
+
+	updateControls(ipaControls);
+
+	return 0;
+}
+
 class MaliC55CameraConfiguration : public CameraConfiguration
 {
 public:
@@ -352,6 +423,7 @@  public:
 	}
 
 	Status validate() override;
+	const Transform &combinedTransform() { return combinedTransform_; }
 
 	V4L2SubdeviceFormat sensorFormat_;
 
@@ -359,6 +431,7 @@  private:
 	static constexpr unsigned int kMaxStreams = 2;
 
 	const MaliC55CameraData *data_;
+	Transform combinedTransform_;
 };
 
 CameraConfiguration::Status MaliC55CameraConfiguration::validate()
@@ -368,6 +441,19 @@  CameraConfiguration::Status MaliC55CameraConfiguration::validate()
 	if (config_.empty())
 		return Invalid;
 
+	/*
+	 * The TPG doesn't support flips, so we only need to calculate a
+	 * transform if we have a sensor.
+	 */
+	if (data_->sensor_) {
+		Orientation requestedOrientation = orientation;
+		combinedTransform_ = data_->sensor_->computeTransform(&orientation);
+		if (orientation != requestedOrientation)
+			status = Adjusted;
+	} else {
+		combinedTransform_ = Transform::Rot0;
+	}
+
 	/* Only 2 streams available. */
 	if (config_.size() > kMaxStreams) {
 		config_.resize(kMaxStreams);
@@ -531,7 +617,10 @@  public:
 	int queueRequestDevice(Camera *camera, Request *request) override;
 
 	void imageBufferReady(FrameBuffer *buffer);
+	void paramsBufferReady(FrameBuffer *buffer);
 	void statsBufferReady(FrameBuffer *buffer);
+	void paramsComputed(unsigned int requestId);
+	void statsProcessed(unsigned int requestId, const ControlList &metadata);
 
 	bool match(DeviceEnumerator *enumerator) override;
 
@@ -576,6 +665,10 @@  private:
 			pipe.stream = nullptr;
 	}
 
+	MaliC55FrameInfo *findFrameInfo(FrameBuffer *buffer);
+	MaliC55FrameInfo *findFrameInfo(Request *request);
+	void tryComplete(MaliC55FrameInfo *info);
+
 	int configureRawStream(MaliC55CameraData *data,
 			       const StreamConfiguration &config,
 			       V4L2SubdeviceFormat &subdevFormat);
@@ -585,7 +678,7 @@  private:
 
 	void applyScalerCrop(Camera *camera, const ControlList &controls);
 
-	void registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
+	bool registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
 				const std::string &name);
 	bool registerTPGCamera(MediaLink *link);
 	bool registerSensorCamera(MediaLink *link);
@@ -601,6 +694,8 @@  private:
 	std::vector<std::unique_ptr<FrameBuffer>> paramsBuffers_;
 	std::queue<FrameBuffer *> availableParamsBuffers_;
 
+	std::map<unsigned int, MaliC55FrameInfo> frameInfoMap_;
+
 	std::array<MaliC55Pipe, MaliC55NumPipes> pipes_;
 
 	bool dsFitted_;
@@ -849,6 +944,13 @@  int PipelineHandlerMaliC55::configure(Camera *camera,
 	if (ret)
 		return ret;
 
+	if (data->sensor_) {
+		ret = data->sensor_->setFormat(&subdevFormat,
+					       maliConfig->combinedTransform());
+		if (ret)
+			return ret;
+	}
+
 	if (data->csi_) {
 		ret = data->csi_->setFormat(0, &subdevFormat);
 		if (ret)
@@ -930,7 +1032,55 @@  int PipelineHandlerMaliC55::configure(Camera *camera,
 		pipe->stream = stream;
 	}
 
-	data->updateControls();
+	if (!data->ipa_)
+		return 0;
+
+	/*
+	 * Enable the media link between the ISP subdevice and the statistics
+	 * video device.
+	 */
+	const MediaEntity *ispEntity = isp_->entity();
+	ret = ispEntity->getPadByIndex(3)->links()[0]->setEnabled(true);
+	if (ret) {
+		LOG(MaliC55, Error) << "Couldn't enable statistics link";
+		return ret;
+	}
+
+	/*
+	 * Enable the media link between the ISP subdevice and the parameters
+	 * video device.
+	 */
+	ret = ispEntity->getPadByIndex(4)->links()[0]->setEnabled(true);
+	if (ret) {
+		LOG(MaliC55, Error) << "Couldn't enable parameters link";
+		return ret;
+	}
+
+	/* We need to inform the IPA of the sensor configuration */
+	ipa::mali_c55::IPAConfigInfo ipaConfig{};
+
+	ret = data->sensor_->sensorInfo(&ipaConfig.sensorInfo);
+	if (ret)
+		return ret;
+
+	ipaConfig.sensorControls = data->sensor_->controls();
+
+	/*
+	 * And we also need to tell the IPA the bayerOrder of the data (as
+	 * affected by any flips that we've configured)
+	 */
+	const Transform &combinedTransform = maliConfig->combinedTransform();
+	BayerFormat::Order bayerOrder = data->sensor_->bayerOrder(combinedTransform);
+
+	ControlInfoMap ipaControls;
+	ret = data->ipa_->configure(ipaConfig, utils::to_underlying(bayerOrder),
+				    &ipaControls);
+	if (ret) {
+		LOG(MaliC55, Error) << "Failed to configure IPA";
+		return ret;
+	}
+
+	data->updateControls(ipaControls);
 
 	return 0;
 }
@@ -944,8 +1094,10 @@  int PipelineHandlerMaliC55::exportFrameBuffers(Camera *camera, Stream *stream,
 	return pipe->cap->exportBuffers(count, buffers);
 }
 
-void PipelineHandlerMaliC55::freeBuffers([[maybe_unused]] Camera *camera)
+void PipelineHandlerMaliC55::freeBuffers(Camera *camera)
 {
+	MaliC55CameraData *data = cameraData(camera);
+
 	while (!availableStatsBuffers_.empty())
 		availableStatsBuffers_.pop();
 	while (!availableParamsBuffers_.empty())
@@ -954,11 +1106,18 @@  void PipelineHandlerMaliC55::freeBuffers([[maybe_unused]] Camera *camera)
 	statsBuffers_.clear();
 	paramsBuffers_.clear();
 
+	if (data->ipa_) {
+		data->ipa_->unmapBuffers(data->ipaStatBuffers_);
+		data->ipa_->unmapBuffers(data->ipaParamBuffers_);
+	}
+	data->ipaStatBuffers_.clear();
+	data->ipaParamBuffers_.clear();
+
 	if (stats_->releaseBuffers())
 		LOG(MaliC55, Error) << "Failed to release stats buffers";
 
 	if (params_->releaseBuffers())
-		LOG(MaliC55, Error) << "Failed to release stats buffers";
+		LOG(MaliC55, Error) << "Failed to release params buffers";
 
 	return;
 }
@@ -966,6 +1125,7 @@  void PipelineHandlerMaliC55::freeBuffers([[maybe_unused]] Camera *camera)
 int PipelineHandlerMaliC55::allocateBuffers(Camera *camera)
 {
 	MaliC55CameraData *data = cameraData(camera);
+	unsigned int ipaBufferId = 1;
 	unsigned int bufferCount;
 	int ret;
 
@@ -978,27 +1138,51 @@  int PipelineHandlerMaliC55::allocateBuffers(Camera *camera)
 	if (ret < 0)
 		return ret;
 
-	for (std::unique_ptr<FrameBuffer> &buffer : statsBuffers_)
+	for (std::unique_ptr<FrameBuffer> &buffer : statsBuffers_) {
+		buffer->setCookie(ipaBufferId++);
+		data->ipaStatBuffers_.emplace_back(buffer->cookie(),
+						   buffer->planes());
 		availableStatsBuffers_.push(buffer.get());
+	}
 
 	ret = params_->allocateBuffers(bufferCount, &paramsBuffers_);
 	if (ret < 0)
 		return ret;
 
-	for (std::unique_ptr<FrameBuffer> &buffer : paramsBuffers_)
+	for (std::unique_ptr<FrameBuffer> &buffer : paramsBuffers_) {
+		buffer->setCookie(ipaBufferId++);
+		data->ipaParamBuffers_.emplace_back(buffer->cookie(),
+						    buffer->planes());
 		availableParamsBuffers_.push(buffer.get());
+	}
+
+	if (data->ipa_) {
+		data->ipa_->mapBuffers(data->ipaStatBuffers_, true);
+		data->ipa_->mapBuffers(data->ipaParamBuffers_, false);
+	}
 
 	return 0;
 }
 
 int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
 {
+	MaliC55CameraData *data = cameraData(camera);
 	int ret;
 
 	ret = allocateBuffers(camera);
 	if (ret)
 		return ret;
 
+	if (data->ipa_) {
+		ret = data->ipa_->start();
+		if (ret) {
+			LOG(MaliC55, Error)
+				<< "Failed to start IPA" << camera->id();
+			freeBuffers(camera);
+			return ret;
+		}
+	}
+
 	for (MaliC55Pipe &pipe : pipes_) {
 		if (!pipe.stream)
 			continue;
@@ -1008,6 +1192,8 @@  int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control
 		ret = pipe.cap->importBuffers(stream->configuration().bufferCount);
 		if (ret) {
 			LOG(MaliC55, Error) << "Failed to import buffers";
+			if (data->ipa_)
+				data->ipa_->stop();
 			freeBuffers(camera);
 			return ret;
 		}
@@ -1015,6 +1201,8 @@  int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control
 		ret = pipe.cap->streamOn();
 		if (ret) {
 			LOG(MaliC55, Error) << "Failed to start stream";
+			if (data->ipa_)
+				data->ipa_->stop();
 			freeBuffers(camera);
 			return ret;
 		}
@@ -1024,6 +1212,9 @@  int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control
 	if (ret) {
 		LOG(MaliC55, Error) << "Failed to start stats stream";
 
+		if (data->ipa_)
+			data->ipa_->stop();
+
 		for (MaliC55Pipe &pipe : pipes_) {
 			if (pipe.stream)
 				pipe.cap->streamOff();
@@ -1038,6 +1229,8 @@  int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control
 		LOG(MaliC55, Error) << "Failed to start params stream";
 
 		stats_->streamOff();
+		if (data->ipa_)
+			data->ipa_->stop();
 
 		for (MaliC55Pipe &pipe : pipes_) {
 			if (pipe.stream)
@@ -1048,11 +1241,19 @@  int PipelineHandlerMaliC55::start(Camera *camera, [[maybe_unused]] const Control
 		return ret;
 	}
 
+	ret = isp_->setFrameStartEnabled(true);
+	if (ret)
+		LOG(MaliC55, Error) << "Failed to enable frame start events";
+
 	return 0;
 }
 
-void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)
+void PipelineHandlerMaliC55::stopDevice(Camera *camera)
 {
+	MaliC55CameraData *data = cameraData(camera);
+
+	isp_->setFrameStartEnabled(false);
+
 	for (MaliC55Pipe &pipe : pipes_) {
 		if (!pipe.stream)
 			continue;
@@ -1063,6 +1264,8 @@  void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)
 
 	stats_->streamOff();
 	params_->streamOff();
+	if (data->ipa_)
+		data->ipa_->stop();
 	freeBuffers(camera);
 }
 
@@ -1164,64 +1367,179 @@  void PipelineHandlerMaliC55::applyScalerCrop(Camera *camera,
 
 int PipelineHandlerMaliC55::queueRequestDevice(Camera *camera, Request *request)
 {
-	FrameBuffer *statsBuffer;
-	int ret;
+	MaliC55CameraData *data = cameraData(camera);
+
+	/* Do not run the IPA if the TPG is in use. */
+	if (!data->ipa_) {
+		MaliC55FrameInfo frameInfo;
+		frameInfo.request = request;
+		frameInfo.statBuffer = nullptr;
+		frameInfo.paramBuffer = nullptr;
+		frameInfo.paramsDone = true;
+		frameInfo.statsDone = true;
+
+		frameInfoMap_[request->sequence()] = frameInfo;
+
+		for (auto &[stream, buffer] : request->buffers()) {
+			MaliC55Pipe *pipe = pipeFromStream(data, stream);
+
+			pipe->cap->queueBuffer(buffer);
+		}
+
+		return 0;
+	}
 
 	if (availableStatsBuffers_.empty()) {
 		LOG(MaliC55, Error) << "Stats buffer underrun";
 		return -ENOENT;
 	}
 
-	statsBuffer = availableStatsBuffers_.front();
+	if (availableParamsBuffers_.empty()) {
+		LOG(MaliC55, Error) << "Params buffer underrun";
+		return -ENOENT;
+	}
+
+	MaliC55FrameInfo frameInfo;
+	frameInfo.request = request;
+
+	frameInfo.statBuffer = availableStatsBuffers_.front();
 	availableStatsBuffers_.pop();
+	frameInfo.paramBuffer = availableParamsBuffers_.front();
+	availableParamsBuffers_.pop();
 
-	/*
-	 * We need to associate the Request to this buffer even though it's a
-	 * purely internal one because we will need to use request->sequence()
-	 * later.
-	 */
-	statsBuffer->_d()->setRequest(request);
+	frameInfo.paramsDone = false;
+	frameInfo.statsDone = false;
 
-	for (auto &[stream, buffer] : request->buffers()) {
-		MaliC55Pipe *pipe = pipeFromStream(cameraData(camera), stream);
+	frameInfoMap_[request->sequence()] = frameInfo;
 
-		ret = pipe->cap->queueBuffer(buffer);
-		if (ret)
-			return ret;
+	data->ipa_->queueRequest(request->sequence(), request->controls());
+	data->ipa_->fillParams(request->sequence(),
+			       frameInfo.paramBuffer->cookie());
+
+	return 0;
+}
+
+MaliC55FrameInfo *PipelineHandlerMaliC55::findFrameInfo(Request *request)
+{
+	for (auto &[sequence, info] : frameInfoMap_) {
+		if (info.request == request)
+			return &info;
 	}
 
-	/*
-	 * Some controls need to be applied immediately, as in example,
-	 * the ScalerCrop one.
-	 *
-	 * \todo Move it buffer queue time (likely after the IPA has filled in
-	 * the parameters buffer) once we have plumbed the IPA loop in.
-	 */
-	applyScalerCrop(camera, request->controls());
+	return nullptr;
+}
 
-	ret = stats_->queueBuffer(statsBuffer);
-	if (ret)
-		return ret;
+MaliC55FrameInfo *PipelineHandlerMaliC55::findFrameInfo(FrameBuffer *buffer)
+{
+	for (auto &[sequence, info] : frameInfoMap_) {
+		if (info.paramBuffer == buffer ||
+		    info.statBuffer == buffer)
+			return &info;
+	}
 
-	return 0;
+	return nullptr;
+}
+
+void PipelineHandlerMaliC55::tryComplete(MaliC55FrameInfo *info)
+{
+	if (!info->paramsDone)
+		return;
+	if (!info->statsDone)
+		return;
+
+	Request *request = info->request;
+	if (request->hasPendingBuffers())
+		return;
+
+	if (info->statBuffer)
+		availableStatsBuffers_.push(info->statBuffer);
+	if (info->paramBuffer)
+		availableParamsBuffers_.push(info->paramBuffer);
+
+	frameInfoMap_.erase(request->sequence());
+
+	completeRequest(request);
 }
 
 void PipelineHandlerMaliC55::imageBufferReady(FrameBuffer *buffer)
 {
 	Request *request = buffer->request();
+	MaliC55FrameInfo *info = findFrameInfo(request);
+	ASSERT(info);
 
 	if (completeBuffer(request, buffer))
-		completeRequest(request);
+		tryComplete(info);
+}
+
+void PipelineHandlerMaliC55::paramsBufferReady(FrameBuffer *buffer)
+{
+	MaliC55FrameInfo *info = findFrameInfo(buffer);
+	ASSERT(info);
+
+	info->paramsDone = true;
+
+	tryComplete(info);
 }
 
 void PipelineHandlerMaliC55::statsBufferReady(FrameBuffer *buffer)
 {
-	availableStatsBuffers_.push(buffer);
+	MaliC55FrameInfo *info = findFrameInfo(buffer);
+	ASSERT(info);
+
+	Request *request = info->request;
+	MaliC55CameraData *data = cameraData(request->_d()->camera());
+
+	ControlList sensorControls = data->delayedCtrls_->get(buffer->metadata().sequence);
+
+	data->ipa_->processStats(request->sequence(), buffer->cookie(),
+				 sensorControls);
 }
 
-void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
+void PipelineHandlerMaliC55::paramsComputed(unsigned int requestId)
+{
+	MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId];
+	Request *request = frameInfo.request;
+	MaliC55CameraData *data = cameraData(request->_d()->camera());
+
+	/*
+	 * Queue buffers for stats and params, then queue buffers to the capture
+	 * video devices.
+	 */
+
+	frameInfo.paramBuffer->_d()->metadata().planes()[0].bytesused =
+		sizeof(struct mali_c55_params_buffer);
+	params_->queueBuffer(frameInfo.paramBuffer);
+	stats_->queueBuffer(frameInfo.statBuffer);
+
+	for (auto &[stream, buffer] : request->buffers()) {
+		MaliC55Pipe *pipe = pipeFromStream(data, stream);
+
+		pipe->cap->queueBuffer(buffer);
+	}
+}
+
+void PipelineHandlerMaliC55::statsProcessed(unsigned int requestId,
+					    const ControlList &metadata)
+{
+	MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId];
+
+	frameInfo.statsDone = true;
+	frameInfo.request->metadata().merge(metadata);
+
+	tryComplete(&frameInfo);
+}
+
+bool PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraData> data,
 						const std::string &name)
 {
+	if (data->loadIPA())
+		return false;
+
+	if (data->ipa_) {
+		data->ipa_->statsProcessed.connect(this, &PipelineHandlerMaliC55::statsProcessed);
+		data->ipa_->paramsComputed.connect(this, &PipelineHandlerMaliC55::paramsComputed);
+	}
+
 	std::set<Stream *> streams{ &data->frStream_ };
 	if (dsFitted_)
 		streams.insert(&data->dsStream_);
@@ -1229,6 +1547,8 @@  void PipelineHandlerMaliC55::registerMaliCamera(std::unique_ptr<MaliC55CameraDat
 	std::shared_ptr<Camera> camera = Camera::create(std::move(data),
 							name, streams);
 	registerCamera(std::move(camera));
+
+	return true;
 }
 
 /*
@@ -1254,9 +1574,7 @@  bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
 	if (data->init())
 		return false;
 
-	registerMaliCamera(std::move(data), name);
-
-	return true;
+	return registerMaliCamera(std::move(data), name);
 }
 
 /*
@@ -1283,9 +1601,25 @@  bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 			return false;
 
 		data->properties_ = data->sensor_->properties();
-		data->updateControls();
 
-		registerMaliCamera(std::move(data), sensor->name());
+		uint8_t exposureDelay, gainDelay, vblankDelay, hblankDelay;
+		data->sensor_->getSensorDelays(exposureDelay, gainDelay,
+					       vblankDelay, hblankDelay);
+		std::unordered_map<uint32_t, DelayedControls::ControlParams> params = {
+			{ V4L2_CID_ANALOGUE_GAIN, { gainDelay, false } },
+			{ V4L2_CID_EXPOSURE, { exposureDelay, false } },
+		};
+
+		data->delayedCtrls_ =
+			std::make_unique<DelayedControls>(data->sensor_->device(),
+							  params);
+		isp_->frameStart.connect(data->delayedCtrls_.get(),
+					 &DelayedControls::applyControls);
+
+		/* \todo: Init properties. */
+
+		if (!registerMaliCamera(std::move(data), sensor->name()))
+			return false;
 	}
 
 	return true;
@@ -1365,6 +1699,7 @@  bool PipelineHandlerMaliC55::match(DeviceEnumerator *enumerator)
 	}
 
 	stats_->bufferReady.connect(this, &PipelineHandlerMaliC55::statsBufferReady);
+	params_->bufferReady.connect(this, &PipelineHandlerMaliC55::paramsBufferReady);
 
 	ispSink = isp_->entity()->getPadByIndex(0);
 	if (!ispSink || ispSink->links().empty()) {