[v2,05/10] mali-c55: Plumb the IPA module in
diff mbox series

Message ID 20240709144950.3277837-6-dan.scally@ideasonboard.com
State Superseded
Headers show
Series
  • Add Mali-C55 IPA Module and Algorithms
Related show

Commit Message

Dan Scally July 9, 2024, 2:49 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.

Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
Changes in v2:

	- None

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

Comments

Kieran Bingham Oct. 9, 2024, 9:25 p.m. UTC | #1
Quoting Daniel Scally (2024-07-09 15:49:45)
> 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.
> 
> Acked-by: Nayden Kanchev  <nayden.kanchev@arm.com>
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
> Changes in v2:
> 
>         - None
> 
>  src/libcamera/pipeline/mali-c55/mali-c55.cpp | 403 +++++++++++++++++--
>  1 file changed, 360 insertions(+), 43 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> index 1e5674fc..dd523d8d 100644
> --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
> @@ -23,13 +23,19 @@
>  #include <libcamera/geometry.h>
>  #include <libcamera/stream.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"
>  
> @@ -70,6 +76,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:
> @@ -79,6 +95,7 @@ public:
>         }
>  
>         int init();
> +       int loadIPA();
>  
>         /* Deflect these functionalities to either TPG or CameraSensor. */
>         const std::vector<Size> sizes(unsigned int mbusCode) const;
> @@ -87,7 +104,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;
> @@ -100,8 +117,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_;
> @@ -166,6 +190,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_)
> @@ -268,7 +297,7 @@ const PixelFormat &MaliC55CameraData::bestRawFormat() const
>         return invalidPixFmt;
>  }
>  
> -void MaliC55CameraData::updateControls()
> +void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls)
>  {
>         if (!sensor_)
>                 return;
> @@ -286,6 +315,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);
>  }
>  
> @@ -339,6 +371,46 @@ 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");
> +       if (ipaTuningFile.empty())
> +               ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");

I think this (ipa_->configurationFile) has since been modified to take
the fallback file path. So this probably needs to be updated with a
rebase to 

	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:
> @@ -348,6 +420,7 @@ public:
>         }
>  
>         Status validate() override;
> +       const Transform &combinedTransform() { return combinedTransform_; }
>  
>         V4L2SubdeviceFormat sensorFormat_;
>  
> @@ -355,6 +428,7 @@ private:
>         static constexpr unsigned int kMaxStreams = 2;
>  
>         const MaliC55CameraData *data_;
> +       Transform combinedTransform_;
>  };
>  
>  CameraConfiguration::Status MaliC55CameraConfiguration::validate()
> @@ -364,6 +438,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);
> @@ -521,7 +608,10 @@ public:
>         int queueRequestDevice(Camera *camera, Request *request) override;
>  
>         void bufferReady(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;
>  
> @@ -565,6 +655,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);
> @@ -574,7 +668,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);
> @@ -590,6 +684,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_;
> @@ -838,6 +934,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)
> @@ -940,7 +1043,34 @@ int PipelineHandlerMaliC55::configure(Camera *camera,
>                 return ret;
>         }
>  
> -       data->updateControls();
> +       if (!data->ipa_)
> +               return 0;
> +
> +       /* 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;
>  }
> @@ -954,8 +1084,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())
> @@ -964,11 +1096,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";

Minor fix could be mentioned in the commit message, no specific need to
pull to it's own patch, but this is independent from the main $COMMIT.

>  
>         return;
>  }
> @@ -976,6 +1115,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;
>  
> @@ -988,27 +1128,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;
> @@ -1018,6 +1182,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;
>                 }
> @@ -1025,6 +1191,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;
>                 }
> @@ -1034,6 +1202,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();
> @@ -1048,6 +1219,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();
>  


I think I've said this on later patches, because I'm going backwards in
time ... but the cleanup helpers might help a lot here. But maybe that's
something to do 'on top' anyway.


>                 for (MaliC55Pipe &pipe : pipes_) {
>                         if (pipe.stream)
> @@ -1058,11 +1231,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;
> @@ -1073,6 +1254,8 @@ void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)
>  
>         stats_->streamOff();
>         params_->streamOff();
> +       if (data->ipa_)
> +               data->ipa_->stop();
>         freeBuffers(camera);
>  }
>  
> @@ -1174,64 +1357,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;
> +       }

All of the 'if (!data->ipa_)' conditions make me wonder if we shouldn't
somehow abstract out and make a distinct 'no op' IPA for TPG which does
things for TPG only.

But the amount of work I could envisage in doing so, or trying to make a
generic no-op IPA with the same interface ... in a way that actually
saves code would probably end up adding more complexity than "if !ipa"
handling...


>  
>         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::bufferReady(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_);
> @@ -1239,6 +1537,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;
>  }
>  
>  /*
> @@ -1264,9 +1564,7 @@ bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
>         if (data->init())
>                 return false;
>  
> -       registerMaliCamera(std::move(data), name);
> -
> -       return true;
> +       return registerMaliCamera(std::move(data), name);
>  }
>  
>  /*
> @@ -1293,9 +1591,27 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
>                         return false;
>  
>                 data->properties_ = data->sensor_->properties();
> -               data->updateControls();
>  
> -               registerMaliCamera(std::move(data), sensor->name());
> +               /*
> +                * \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, DelayedControls::ControlParams> params = {
> +                       { V4L2_CID_ANALOGUE_GAIN, { 1, false } },
> +                       { V4L2_CID_EXPOSURE, { 2, false } },
> +               };

We *REALLY* need to fix this for all the libipa pipeline handlers.

So much so - that I would probably be tempted to say "Hey - lets not
merge more pipeline handlers that hardcode the wrong delays until we fix
the CameraSensorHelper to also report the delays per sensor.

It's "just" a case of adding a delay map in the CameraSensorHelper
instances right?

It shouldn't be hard... but no one is doing it.


If that were done then I'd give this patch my RB tag ;-)



> +
> +               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;
> @@ -1362,6 +1678,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()) {
> -- 
> 2.34.1
>

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 1e5674fc..dd523d8d 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -23,13 +23,19 @@ 
 #include <libcamera/geometry.h>
 #include <libcamera/stream.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"
 
@@ -70,6 +76,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:
@@ -79,6 +95,7 @@  public:
 	}
 
 	int init();
+	int loadIPA();
 
 	/* Deflect these functionalities to either TPG or CameraSensor. */
 	const std::vector<Size> sizes(unsigned int mbusCode) const;
@@ -87,7 +104,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;
@@ -100,8 +117,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_;
@@ -166,6 +190,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_)
@@ -268,7 +297,7 @@  const PixelFormat &MaliC55CameraData::bestRawFormat() const
 	return invalidPixFmt;
 }
 
-void MaliC55CameraData::updateControls()
+void MaliC55CameraData::updateControls(const ControlInfoMap &ipaControls)
 {
 	if (!sensor_)
 		return;
@@ -286,6 +315,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);
 }
 
@@ -339,6 +371,46 @@  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");
+	if (ipaTuningFile.empty())
+		ipaTuningFile = ipa_->configurationFile("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:
@@ -348,6 +420,7 @@  public:
 	}
 
 	Status validate() override;
+	const Transform &combinedTransform() { return combinedTransform_; }
 
 	V4L2SubdeviceFormat sensorFormat_;
 
@@ -355,6 +428,7 @@  private:
 	static constexpr unsigned int kMaxStreams = 2;
 
 	const MaliC55CameraData *data_;
+	Transform combinedTransform_;
 };
 
 CameraConfiguration::Status MaliC55CameraConfiguration::validate()
@@ -364,6 +438,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);
@@ -521,7 +608,10 @@  public:
 	int queueRequestDevice(Camera *camera, Request *request) override;
 
 	void bufferReady(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;
 
@@ -565,6 +655,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);
@@ -574,7 +668,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);
@@ -590,6 +684,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_;
@@ -838,6 +934,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)
@@ -940,7 +1043,34 @@  int PipelineHandlerMaliC55::configure(Camera *camera,
 		return ret;
 	}
 
-	data->updateControls();
+	if (!data->ipa_)
+		return 0;
+
+	/* 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;
 }
@@ -954,8 +1084,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())
@@ -964,11 +1096,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;
 }
@@ -976,6 +1115,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;
 
@@ -988,27 +1128,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;
@@ -1018,6 +1182,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;
 		}
@@ -1025,6 +1191,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;
 		}
@@ -1034,6 +1202,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();
@@ -1048,6 +1219,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)
@@ -1058,11 +1231,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;
@@ -1073,6 +1254,8 @@  void PipelineHandlerMaliC55::stopDevice([[maybe_unused]] Camera *camera)
 
 	stats_->streamOff();
 	params_->streamOff();
+	if (data->ipa_)
+		data->ipa_->stop();
 	freeBuffers(camera);
 }
 
@@ -1174,64 +1357,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::bufferReady(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_);
@@ -1239,6 +1537,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;
 }
 
 /*
@@ -1264,9 +1564,7 @@  bool PipelineHandlerMaliC55::registerTPGCamera(MediaLink *link)
 	if (data->init())
 		return false;
 
-	registerMaliCamera(std::move(data), name);
-
-	return true;
+	return registerMaliCamera(std::move(data), name);
 }
 
 /*
@@ -1293,9 +1591,27 @@  bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 			return false;
 
 		data->properties_ = data->sensor_->properties();
-		data->updateControls();
 
-		registerMaliCamera(std::move(data), sensor->name());
+		/*
+		 * \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, DelayedControls::ControlParams> params = {
+			{ V4L2_CID_ANALOGUE_GAIN, { 1, false } },
+			{ V4L2_CID_EXPOSURE, { 2, 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;
@@ -1362,6 +1678,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()) {