diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index de4ab523d0e4fe36..2bebf0eaf3877641 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -9,7 +9,7 @@
 #include <array>
 #include <iomanip>
 #include <memory>
-#include <vector>
+#include <queue>
 
 #include <linux/media-bus-format.h>
 
@@ -34,7 +34,7 @@ class RkISP1CameraData : public CameraData
 {
 public:
 	RkISP1CameraData(PipelineHandler *pipe)
-		: CameraData(pipe), sensor_(nullptr)
+		: CameraData(pipe, 1, 1), sensor_(nullptr)
 	{
 	}
 
@@ -43,8 +43,21 @@ public:
 		delete sensor_;
 	}
 
+	int initIPA() override;
+
 	Stream stream_;
 	CameraSensor *sensor_;
+
+private:
+	void updateSensor(V4L2ControlList controls);
+	void queueRequestHardware(const void *cookie);
+};
+
+class RkISP1RequestData : public RequestData
+{
+public:
+	Buffer *stat;
+	Buffer *param;
 };
 
 class RkISP1CameraConfiguration : public CameraConfiguration
@@ -99,18 +112,69 @@ private:
 			PipelineHandler::cameraData(camera));
 	}
 
+	friend RkISP1CameraData;
+
 	int initLinks();
 	int createCamera(MediaEntity *sensor);
+	void tryCompleteRequest(Request *request);
 	void bufferReady(Buffer *buffer);
+	void statReady(Buffer *buffer);
+	void paramReady(Buffer *buffer);
 
 	MediaDevice *media_;
 	V4L2Subdevice *dphy_;
 	V4L2Subdevice *isp_;
 	V4L2VideoDevice *video_;
+	V4L2VideoDevice *stat_;
+	V4L2VideoDevice *param_;
+
+	BufferPool statPool_;
+	BufferPool paramPool_;
+
+	std::queue<Buffer *> statBuffers_;
+	std::queue<Buffer *> paramBuffers_;
 
 	Camera *activeCamera_;
 };
 
+int RkISP1CameraData::initIPA()
+{
+	ipa_->updateSensor.connect(this,
+				   &RkISP1CameraData::updateSensor);
+	ipa_->queueRequest.connect(this,
+				   &RkISP1CameraData::queueRequestHardware);
+	return 0;
+}
+
+void RkISP1CameraData::updateSensor(V4L2ControlList controls)
+{
+	sensor_->setControls(&controls);
+}
+
+void RkISP1CameraData::queueRequestHardware(const void *cookie)
+{
+	/* Translate cookie to request. */
+	Request *request = reinterpret_cast<Request *>(const_cast<void *>(cookie));
+	PipelineHandlerRkISP1 *pipe =
+		static_cast<PipelineHandlerRkISP1 *>(pipe_);
+	RkISP1RequestData *reqData =
+		static_cast<RkISP1RequestData *>(request->data);
+	Buffer *buffer = request->findBuffer(&stream_);
+	int ret;
+
+	ret = pipe->param_->queueBuffer(reqData->param);
+	if (ret < 0)
+		LOG(RkISP1, Error) << "Failed to queue parameters";
+
+	ret = pipe->stat_->queueBuffer(reqData->stat);
+	if (ret < 0)
+		LOG(RkISP1, Error) << "Failed to queue statistics";
+
+	ret = pipe->video_->queueBuffer(buffer);
+	if (ret < 0)
+		LOG(RkISP1, Error) << "Failed to queue video";
+}
+
 RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera,
 						     RkISP1CameraData *data)
 	: CameraConfiguration()
@@ -202,12 +266,14 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)
 	: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),
-	  video_(nullptr)
+	  video_(nullptr), stat_(nullptr), param_(nullptr)
 {
 }
 
 PipelineHandlerRkISP1::~PipelineHandlerRkISP1()
 {
+	delete param_;
+	delete stat_;
 	delete video_;
 	delete isp_;
 	delete dphy_;
@@ -317,6 +383,20 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
 	if (ret)
 		return ret;
 
+	V4L2DeviceFormat statFormat = {};
+	statFormat.fourcc = V4L2_META_FMT_RK_ISP1_STAT_3A;
+
+	ret = stat_->setFormat(&statFormat);
+	if (ret)
+		return ret;
+
+	V4L2DeviceFormat paramFormat = {};
+	paramFormat.fourcc = V4L2_META_FMT_RK_ISP1_PARAMS;
+
+	ret = param_->setFormat(&paramFormat);
+	if (ret)
+		return ret;
+
 	if (outputFormat.size != cfg.size ||
 	    outputFormat.fourcc != cfg.pixelFormat) {
 		LOG(RkISP1, Error)
@@ -333,30 +413,92 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera,
 					   const std::set<Stream *> &streams)
 {
 	Stream *stream = *streams.begin();
+	int ret;
 
 	if (stream->memoryType() == InternalMemory)
-		return video_->exportBuffers(&stream->bufferPool());
+		ret = video_->exportBuffers(&stream->bufferPool());
 	else
-		return video_->importBuffers(&stream->bufferPool());
+		ret = video_->importBuffers(&stream->bufferPool());
+
+	if (ret)
+		return ret;
+
+	statPool_.createBuffers(stream->configuration().bufferCount);
+	ret = stat_->exportBuffers(&statPool_);
+	if (ret) {
+		video_->releaseBuffers();
+		return ret;
+	}
+
+	paramPool_.createBuffers(stream->configuration().bufferCount);
+	ret = param_->exportBuffers(&paramPool_);
+	if (ret) {
+		stat_->releaseBuffers();
+		video_->releaseBuffers();
+		return ret;
+	}
+
+	for (unsigned int i = 0; i < stream->configuration().bufferCount; i++) {
+		statBuffers_.push(new Buffer(i));
+		paramBuffers_.push(new Buffer(i));
+	}
+
+	return ret;
 }
 
 int PipelineHandlerRkISP1::freeBuffers(Camera *camera,
 				       const std::set<Stream *> &streams)
 {
+	while (!paramBuffers_.empty())
+		paramBuffers_.pop();
+
+	while (!statBuffers_.empty())
+		statBuffers_.pop();
+
+	if (param_->releaseBuffers())
+		LOG(RkISP1, Error) << "Failed to release parameters buffers";
+
+	if (stat_->releaseBuffers())
+		LOG(RkISP1, Error) << "Failed to release stat buffers";
+
 	if (video_->releaseBuffers())
-		LOG(RkISP1, Error) << "Failed to release buffers";
+		LOG(RkISP1, Error) << "Failed to release video buffers";
 
 	return 0;
 }
 
 int PipelineHandlerRkISP1::start(Camera *camera)
 {
+	RkISP1CameraData *data = cameraData(camera);
 	int ret;
 
+	ret = data->ipa_->initSensor(data->sensor_->controls());
+	if (ret)
+		return ret;
+
+	ret = param_->streamOn();
+	if (ret) {
+		LOG(RkISP1, Error)
+			<< "Failed to start parameters " << camera->name();
+		return ret;
+	}
+
+	ret = stat_->streamOn();
+	if (ret) {
+		param_->streamOff();
+		LOG(RkISP1, Error)
+			<< "Failed to start statistics " << camera->name();
+		return ret;
+	}
+
 	ret = video_->streamOn();
-	if (ret)
+	if (ret) {
+		param_->streamOff();
+		stat_->streamOff();
+
 		LOG(RkISP1, Error)
 			<< "Failed to start camera " << camera->name();
+	}
 
 	activeCamera_ = camera;
 
@@ -372,6 +514,16 @@ void PipelineHandlerRkISP1::stop(Camera *camera)
 		LOG(RkISP1, Warning)
 			<< "Failed to stop camera " << camera->name();
 
+	ret = stat_->streamOff();
+	if (ret)
+		LOG(RkISP1, Warning)
+			<< "Failed to stop statistics " << camera->name();
+
+	ret = param_->streamOff();
+	if (ret)
+		LOG(RkISP1, Warning)
+			<< "Failed to stop parameters " << camera->name();
+
 	activeCamera_ = nullptr;
 }
 
@@ -380,6 +532,16 @@ int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)
 	RkISP1CameraData *data = cameraData(camera);
 	Stream *stream = &data->stream_;
 
+	if (paramBuffers_.empty()) {
+		LOG(RkISP1, Error) << "Parameters buffer underrun";
+		return -ENOENT;
+	}
+
+	if (statBuffers_.empty()) {
+		LOG(RkISP1, Error) << "Statistic buffer underrun";
+		return -ENOENT;
+	}
+
 	Buffer *buffer = request->findBuffer(stream);
 	if (!buffer) {
 		LOG(RkISP1, Error)
@@ -387,12 +549,24 @@ int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)
 		return -ENOENT;
 	}
 
-	int ret = video_->queueBuffer(buffer);
-	if (ret < 0)
-		return ret;
+	RkISP1RequestData *reqData = new RkISP1RequestData();
+	request->data = reqData;
+	reqData->param = paramBuffers_.front();
+	reqData->stat = statBuffers_.front();
+
+	prepareInternalBuffer(reqData->param, request,
+			      &paramPool_.buffers()[reqData->param->index()]);
+	prepareInternalBuffer(reqData->stat, request,
+			      &statPool_.buffers()[reqData->stat->index()]);
+
+	paramBuffers_.pop();
+	statBuffers_.pop();
 
 	PipelineHandler::queueRequest(camera, request);
 
+	data->ipa_->processRequest(request, request->controls(),
+				   *reqData->param);
+
 	return 0;
 }
 
@@ -435,6 +609,10 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
 	std::unique_ptr<RkISP1CameraData> data =
 		utils::make_unique<RkISP1CameraData>(this);
 
+	data->controlInfo_.emplace(std::piecewise_construct,
+				   std::forward_as_tuple(AeEnable),
+				   std::forward_as_tuple(AeEnable, false, true));
+
 	data->sensor_ = new CameraSensor(sensor);
 	ret = data->sensor_->init();
 	if (ret)
@@ -478,7 +656,17 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
 	if (video_->open() < 0)
 		return false;
 
+	stat_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1-statistics");
+	if (stat_->open() < 0)
+		return false;
+
+	param_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1-input-params");
+	if (param_->open() < 0)
+		return false;
+
 	video_->bufferReady.connect(this, &PipelineHandlerRkISP1::bufferReady);
+	stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statReady);
+	param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramReady);
 
 	/* Configure default links. */
 	if (initLinks() < 0) {
@@ -504,13 +692,66 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
  * Buffer Handling
  */
 
+void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
+{
+	RkISP1RequestData *reqData =
+		static_cast<RkISP1RequestData *>(request->data);
+
+	if (reqData->param)
+		return;
+
+	if (reqData->stat)
+		return;
+
+	if (request->hasPendingBuffers())
+		return;
+
+	delete reqData;
+	request->data = nullptr;
+
+	completeRequest(activeCamera_, request);
+}
+
 void PipelineHandlerRkISP1::bufferReady(Buffer *buffer)
 {
 	ASSERT(activeCamera_);
 	Request *request = buffer->request();
 
 	completeBuffer(activeCamera_, request, buffer);
-	completeRequest(activeCamera_, request);
+	tryCompleteRequest(request);
+}
+
+void PipelineHandlerRkISP1::statReady(Buffer *buffer)
+{
+	ASSERT(activeCamera_);
+	RkISP1CameraData *data = cameraData(activeCamera_);
+	Request *request = buffer->request();
+	RkISP1RequestData *reqData =
+		static_cast<RkISP1RequestData *>(request->data);
+
+	data->ipa_->updateStatistics(request, *buffer);
+
+	/* TODO: Fetch libcamera status controls from IPA */
+
+	reqData->stat = nullptr;
+
+	statBuffers_.push(buffer);
+
+	tryCompleteRequest(request);
+}
+
+void PipelineHandlerRkISP1::paramReady(Buffer *buffer)
+{
+	ASSERT(activeCamera_);
+	Request *request = buffer->request();
+	RkISP1RequestData *reqData =
+		static_cast<RkISP1RequestData *>(request->data);
+
+	reqData->param = nullptr;
+
+	paramBuffers_.push(buffer);
+
+	tryCompleteRequest(request);
 }
 
 REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1);
