@@ -14,11 +14,14 @@ 
 #include <libcamera/camera.h>
 #include <libcamera/control_ids.h>
 #include <libcamera/formats.h>
+#include <libcamera/ipa/ipu3.h>
 #include <libcamera/request.h>
 #include <libcamera/stream.h>
 
 #include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/delayed_controls.h"
 #include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_manager.h"
 #include "libcamera/internal/log.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/pipeline_handler.h"
@@ -54,6 +57,8 @@  public:
 	{
 	}
 
+	int loadIPA();
+
 	void imguOutputBufferReady(FrameBuffer *buffer);
 	void cio2BufferReady(FrameBuffer *buffer);
 
@@ -65,6 +70,10 @@  public:
 	Stream rawStream_;
 
 	uint32_t exposureTime_;
+	std::unique_ptr<DelayedControls> delayedCtrls_;
+
+private:
+	void queueFrameAction(unsigned int id, const IPAOperationData &op);
 };
 
 class IPU3CameraConfiguration : public CameraConfiguration
@@ -594,6 +603,13 @@  int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] ControlList *con
 	IPU3CameraData *data = cameraData(camera);
 	CIO2Device *cio2 = &data->cio2_;
 	ImgUDevice *imgu = data->imgu_;
+
+	CameraSensorInfo sensorInfo = {};
+	std::map<unsigned int, IPAStream> streamConfig;
+	std::map<unsigned int, const ControlInfoMap &> entityControls;
+	IPAOperationData ipaConfig;
+	IPAOperationData result = {};
+
 	int ret;
 
 	/* Allocate buffers for internal pipeline usage. */
@@ -601,6 +617,11 @@  int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] ControlList *con
 	if (ret)
 		return ret;
 
+	IPAOperationData ipaData = {};
+	ret = data->ipa_->start(ipaData, nullptr);
+	if (ret)
+		goto error;
+
 	/*
 	 * Start the ImgU video devices, buffers will be queued to the
 	 * ImgU output and viewfinder when requests will be queued.
@@ -613,11 +634,31 @@  int PipelineHandlerIPU3::start(Camera *camera, [[maybe_unused]] ControlList *con
 	if (ret)
 		goto error;
 
+	/* Inform IPA of stream configuration and sensor controls. */
+	ret = data->cio2_.sensor()->sensorInfo(&sensorInfo);
+	if (ret)
+		goto error;
+
+	streamConfig[0] = {
+		.pixelFormat = data->outStream_.configuration().pixelFormat,
+		.size = data->outStream_.configuration().size,
+	};
+	streamConfig[1] = {
+		.pixelFormat = data->vfStream_.configuration().pixelFormat,
+		.size = data->vfStream_.configuration().size,
+	};
+
+	entityControls.emplace(0, data->cio2_.sensor()->controls());
+
+	data->ipa_->configure(sensorInfo, streamConfig, entityControls,
+			      ipaConfig, &result);
+
 	return 0;
 
 error:
 	imgu->stop();
 	cio2->stop();
+	data->ipa_->stop();
 	freeBuffers(camera);
 	LOG(IPU3, Error) << "Failed to start camera " << camera->id();
 
@@ -634,6 +675,8 @@  void PipelineHandlerIPU3::stop(Camera *camera)
 	if (ret)
 		LOG(IPU3, Warning) << "Failed to stop camera " << camera->id();
 
+	data->ipa_->stop();
+
 	freeBuffers(camera);
 }
 
@@ -883,6 +926,10 @@  int PipelineHandlerIPU3::registerCameras()
 		if (ret)
 			continue;
 
+		ret = data->loadIPA();
+		if (ret)
+			continue;
+
 		/* Initialize the camera properties. */
 		data->properties_ = cio2->sensor()->properties();
 
@@ -890,6 +937,22 @@  int PipelineHandlerIPU3::registerCameras()
 		if (ret)
 			continue;
 
+		/*
+		 * \todo Read delay values from the sensor itself or from a
+		 * a sensor database. For now use generic values taken from
+		 * the Raspberry Pi and listed as 'generic values'.
+		 */
+		std::unordered_map<uint32_t, unsigned int> delays = {
+			{ V4L2_CID_ANALOGUE_GAIN, 1 },
+			{ V4L2_CID_EXPOSURE, 2 },
+		};
+
+		data->delayedCtrls_ =
+			std::make_unique<DelayedControls>(cio2->sensor()->device(),
+							  delays);
+		data->cio2_.frameStart().connect(data->delayedCtrls_.get(),
+						 &DelayedControls::applyControls);
+
 		/**
 		 * \todo Dynamically assign ImgU and output devices to each
 		 * stream and camera; as of now, limit support to two cameras
@@ -933,6 +996,34 @@  int PipelineHandlerIPU3::registerCameras()
 	return numCameras ? 0 : -ENODEV;
 }
 
+int IPU3CameraData::loadIPA()
+{
+	ipa_ = IPAManager::createIPA(pipe_, 1, 1);
+	if (!ipa_)
+		return -ENOENT;
+
+	ipa_->queueFrameAction.connect(this, &IPU3CameraData::queueFrameAction);
+
+	ipa_->init(IPASettings{});
+
+	return 0;
+}
+
+void IPU3CameraData::queueFrameAction([[maybe_unused]] unsigned int id,
+				      const IPAOperationData &action)
+{
+	switch (action.operation) {
+	case IPU3_IPA_ACTION_SET_SENSOR_CONTROLS: {
+		const ControlList &controls = action.controls[0];
+		delayedCtrls_->push(controls);
+		break;
+	}
+	default:
+		LOG(IPU3, Error) << "Unknown action " << action.operation;
+		break;
+	}
+}
+
 /* -----------------------------------------------------------------------------
  * Buffer Ready slots
  */