[libcamera-devel,2/3] pipeline: ipa: raspberrypi: Correctly report available control limits
diff mbox series

Message ID 20220610122533.11888-2-naush@raspberrypi.com
State Superseded
Headers show
Series
  • [libcamera-devel,1/3] pipeline: ipa: raspberrypi: Move ControlInfoMap to the IPA
Related show

Commit Message

Naushir Patuck June 10, 2022, 12:25 p.m. UTC
The ipa currently advertises a static ControlInfoMap to specify the available
controls and their limits. Fix this limitation by having the IPA populate a new
ControlInfoMap with updated limits for ExposureTime, AnalogueGain, and
FrameDurationLimits controls based on the current sensor mode. This new
ControlInfoMap is then returned back to the pipeline handler and available to
the application after a successful Camera::Configure() call.

Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
---
 include/libcamera/ipa/raspberrypi.mojom       |  1 +
 src/ipa/raspberrypi/raspberrypi.cpp           | 24 ++++++++++++++++---
 .../pipeline/raspberrypi/raspberrypi.cpp      |  3 +++
 3 files changed, 25 insertions(+), 3 deletions(-)

Comments

Kieran Bingham June 21, 2022, 12:15 p.m. UTC | #1
Quoting Naushir Patuck via libcamera-devel (2022-06-10 13:25:32)
> The ipa currently advertises a static ControlInfoMap to specify the available
> controls and their limits. Fix this limitation by having the IPA populate a new
> ControlInfoMap with updated limits for ExposureTime, AnalogueGain, and
> FrameDurationLimits controls based on the current sensor mode. This new
> ControlInfoMap is then returned back to the pipeline handler and available to
> the application after a successful Camera::Configure() call.
> 
> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>  include/libcamera/ipa/raspberrypi.mojom       |  1 +
>  src/ipa/raspberrypi/raspberrypi.cpp           | 24 ++++++++++++++++---
>  .../pipeline/raspberrypi/raspberrypi.cpp      |  3 +++
>  3 files changed, 25 insertions(+), 3 deletions(-)
> 
> diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
> index 77f52c282b0f..c0de435b7b33 100644
> --- a/include/libcamera/ipa/raspberrypi.mojom
> +++ b/include/libcamera/ipa/raspberrypi.mojom
> @@ -45,6 +45,7 @@ struct IPAConfig {
>  
>  struct IPAConfigResult {

Aha, I see the precedent for "Result" now. so perhaps it's more suitable
in the previous patch than I realised.


>         float modeSensitivity;
> +       libcamera.ControlInfoMap controlInfo;
>  };
>  
>  struct StartConfig {
> diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
> index 089528f5e126..295f6b735dc0 100644
> --- a/src/ipa/raspberrypi/raspberrypi.cpp
> +++ b/src/ipa/raspberrypi/raspberrypi.cpp
> @@ -74,8 +74,6 @@ constexpr Duration controllerMinFrameDuration = 1.0s / 30.0;
>  /* List of controls handled by the Raspberry Pi IPA */
>  static const ControlInfoMap::Map ipaControls{
>         { &controls::AeEnable, ControlInfo(false, true) },
> -       { &controls::ExposureTime, ControlInfo(0, 999999) },
> -       { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
>         { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
>         { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
>         { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
> @@ -89,7 +87,6 @@ static const ControlInfoMap::Map ipaControls{
>         { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
>         { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },
>         { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
> -       { &controls::FrameDurationLimits, ControlInfo(INT64_C(1000), INT64_C(1000000000)) },
>         { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }
>  };
>  
> @@ -446,6 +443,27 @@ int IPARPi::configure(const IPACameraSensorInfo &sensorInfo,
>         ASSERT(controls);
>         *controls = std::move(ctrls);
>  
> +       /*
> +        * Apply the correct limits to the exposure, gain and frame duration controls
> +        * based on the current sensor mode.
> +        */
> +       ControlInfoMap::Map ctrlMap = ipaControls;
> +       const Duration minSensorFrameDuration = mode_.min_frame_length * mode_.line_length;
> +       const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
> +       ctrlMap.emplace(&controls::FrameDurationLimits,
> +                       ControlInfo(static_cast<int64_t>(minSensorFrameDuration.get<std::micro>()),
> +                                   static_cast<int64_t>(maxSensorFrameDuration.get<std::micro>())));
> +
> +       ctrlMap.emplace(&controls::AnalogueGain,
> +                       ControlInfo(1.0f, static_cast<float>(helper_->Gain(maxSensorGainCode_))));
> +
> +       const uint32_t exposureMin = sensorCtrls_.at(V4L2_CID_EXPOSURE).min().get<int32_t>();
> +       const uint32_t exposureMax = sensorCtrls_.at(V4L2_CID_EXPOSURE).max().get<int32_t>();
> +       ctrlMap.emplace(&controls::ExposureTime,
> +                       ControlInfo(static_cast<int32_t>(helper_->Exposure(exposureMin).get<std::micro>()),
> +                                   static_cast<int32_t>(helper_->Exposure(exposureMax).get<std::micro>())));
> +
> +       result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);


aha, this means applications can't read the exposure time / gain
controls before the camera is configured.

I think that sounds 'correct' - but I wonder if we need to make that
clear in some documentation somewhere, or somehow in an extension of
'simple-cam' showing how to manage camera controls. (Not to block this
patch of course).


We should make sure applications realise they have to recheck controls
after a configuration operation. (Maybe that's obvious .. I'm not sure).

I was dwelling on if we should only send the updates to the
ControlInfoMap over the IPC mechanism, but I don't think this is
critical path, and it's simpler to send the whole updated map - so:


Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

>         return 0;
>  }
>  
> diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> index d980c1a71dd8..4596f2babcea 100644
> --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> @@ -940,6 +940,9 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
>         /* Store the mode sensitivity for the application. */
>         data->properties_.set(properties::SensorSensitivity, result.modeSensitivity);
>  
> +       /* Update the controls that the Raspberry Pi IPA can handle. */
> +       data->controlInfo_ = result.controlInfo;
> +
>         /* Setup the Video Mux/Bridge entities. */
>         for (auto &[device, link] : data->bridgeDevices_) {
>                 /*
> -- 
> 2.25.1
>
Naushir Patuck June 21, 2022, 12:36 p.m. UTC | #2
Hi Kieran,

Thank you for your feedback.

On Tue, 21 Jun 2022 at 13:15, Kieran Bingham <
kieran.bingham@ideasonboard.com> wrote:

> Quoting Naushir Patuck via libcamera-devel (2022-06-10 13:25:32)
> > The ipa currently advertises a static ControlInfoMap to specify the
> available
> > controls and their limits. Fix this limitation by having the IPA
> populate a new
> > ControlInfoMap with updated limits for ExposureTime, AnalogueGain, and
> > FrameDurationLimits controls based on the current sensor mode. This new
> > ControlInfoMap is then returned back to the pipeline handler and
> available to
> > the application after a successful Camera::Configure() call.
> >
> > Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
> > ---
> >  include/libcamera/ipa/raspberrypi.mojom       |  1 +
> >  src/ipa/raspberrypi/raspberrypi.cpp           | 24 ++++++++++++++++---
> >  .../pipeline/raspberrypi/raspberrypi.cpp      |  3 +++
> >  3 files changed, 25 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/libcamera/ipa/raspberrypi.mojom
> b/include/libcamera/ipa/raspberrypi.mojom
> > index 77f52c282b0f..c0de435b7b33 100644
> > --- a/include/libcamera/ipa/raspberrypi.mojom
> > +++ b/include/libcamera/ipa/raspberrypi.mojom
> > @@ -45,6 +45,7 @@ struct IPAConfig {
> >
> >  struct IPAConfigResult {
>
> Aha, I see the precedent for "Result" now. so perhaps it's more suitable
> in the previous patch than I realised.
>
>
> >         float modeSensitivity;
> > +       libcamera.ControlInfoMap controlInfo;
> >  };
> >
> >  struct StartConfig {
> > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp
> b/src/ipa/raspberrypi/raspberrypi.cpp
> > index 089528f5e126..295f6b735dc0 100644
> > --- a/src/ipa/raspberrypi/raspberrypi.cpp
> > +++ b/src/ipa/raspberrypi/raspberrypi.cpp
> > @@ -74,8 +74,6 @@ constexpr Duration controllerMinFrameDuration = 1.0s /
> 30.0;
> >  /* List of controls handled by the Raspberry Pi IPA */
> >  static const ControlInfoMap::Map ipaControls{
> >         { &controls::AeEnable, ControlInfo(false, true) },
> > -       { &controls::ExposureTime, ControlInfo(0, 999999) },
> > -       { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
> >         { &controls::AeMeteringMode,
> ControlInfo(controls::AeMeteringModeValues) },
> >         { &controls::AeConstraintMode,
> ControlInfo(controls::AeConstraintModeValues) },
> >         { &controls::AeExposureMode,
> ControlInfo(controls::AeExposureModeValues) },
> > @@ -89,7 +87,6 @@ static const ControlInfoMap::Map ipaControls{
> >         { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
> >         { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f)
> },
> >         { &controls::ScalerCrop, ControlInfo(Rectangle{},
> Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
> > -       { &controls::FrameDurationLimits, ControlInfo(INT64_C(1000),
> INT64_C(1000000000)) },
> >         { &controls::draft::NoiseReductionMode,
> ControlInfo(controls::draft::NoiseReductionModeValues) }
> >  };
> >
> > @@ -446,6 +443,27 @@ int IPARPi::configure(const IPACameraSensorInfo
> &sensorInfo,
> >         ASSERT(controls);
> >         *controls = std::move(ctrls);
> >
> > +       /*
> > +        * Apply the correct limits to the exposure, gain and frame
> duration controls
> > +        * based on the current sensor mode.
> > +        */
> > +       ControlInfoMap::Map ctrlMap = ipaControls;
> > +       const Duration minSensorFrameDuration = mode_.min_frame_length *
> mode_.line_length;
> > +       const Duration maxSensorFrameDuration = mode_.max_frame_length *
> mode_.line_length;
> > +       ctrlMap.emplace(&controls::FrameDurationLimits,
> > +
>  ControlInfo(static_cast<int64_t>(minSensorFrameDuration.get<std::micro>()),
> > +
>  static_cast<int64_t>(maxSensorFrameDuration.get<std::micro>())));
> > +
> > +       ctrlMap.emplace(&controls::AnalogueGain,
> > +                       ControlInfo(1.0f,
> static_cast<float>(helper_->Gain(maxSensorGainCode_))));
> > +
> > +       const uint32_t exposureMin =
> sensorCtrls_.at(V4L2_CID_EXPOSURE).min().get<int32_t>();
> > +       const uint32_t exposureMax =
> sensorCtrls_.at(V4L2_CID_EXPOSURE).max().get<int32_t>();
> > +       ctrlMap.emplace(&controls::ExposureTime,
> > +
>  ControlInfo(static_cast<int32_t>(helper_->Exposure(exposureMin).get<std::micro>()),
> > +
>  static_cast<int32_t>(helper_->Exposure(exposureMax).get<std::micro>())));
> > +
> > +       result->controlInfo = ControlInfoMap(std::move(ctrlMap),
> controls::controls);
>
>
> aha, this means applications can't read the exposure time / gain
> controls before the camera is configured.
>
> I think that sounds 'correct' - but I wonder if we need to make that
> clear in some documentation somewhere, or somehow in an extension of
> 'simple-cam' showing how to manage camera controls. (Not to block this
> patch of course).
>

I did stop to think what to do here.  We could add ControlInfo for
ExposureTime and AnalogueGain for the "default" sensor mode, but
getting to the sensor mode is a bit more difficult in ipa::init() as things
stand.  Additionally, these values would likely have to be ignored by
the application.  It has to do a camera::Configure() call before starting,
so the values might have changed, rendering the previous values as
invalid.


>
>
> We should make sure applications realise they have to recheck controls
> after a configuration operation. (Maybe that's obvious .. I'm not sure).
>

Documenting this is probably a good thing so as to avoid application writers
from tripping up!

Regards,
Naush


>
> I was dwelling on if we should only send the updates to the
> ControlInfoMap over the IPC mechanism, but I don't think this is
> critical path, and it's simpler to send the whole updated map - so:
>
>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>
> >         return 0;
> >  }
> >
> > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > index d980c1a71dd8..4596f2babcea 100644
> > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > @@ -940,6 +940,9 @@ int PipelineHandlerRPi::configure(Camera *camera,
> CameraConfiguration *config)
> >         /* Store the mode sensitivity for the application. */
> >         data->properties_.set(properties::SensorSensitivity,
> result.modeSensitivity);
> >
> > +       /* Update the controls that the Raspberry Pi IPA can handle. */
> > +       data->controlInfo_ = result.controlInfo;
> > +
> >         /* Setup the Video Mux/Bridge entities. */
> >         for (auto &[device, link] : data->bridgeDevices_) {
> >                 /*
> > --
> > 2.25.1
> >
>
Laurent Pinchart June 21, 2022, 12:47 p.m. UTC | #3
Hi Naush,

Thank you for the patch.

On Tue, Jun 21, 2022 at 01:36:09PM +0100, Naushir Patuck via libcamera-devel wrote:
> On Tue, 21 Jun 2022 at 13:15, Kieran Bingham wrote:
> > Quoting Naushir Patuck via libcamera-devel (2022-06-10 13:25:32)
> > > The ipa currently advertises a static ControlInfoMap to specify the available
> > > controls and their limits. Fix this limitation by having the IPA populate a new
> > > ControlInfoMap with updated limits for ExposureTime, AnalogueGain, and
> > > FrameDurationLimits controls based on the current sensor mode. This new
> > > ControlInfoMap is then returned back to the pipeline handler and available to
> > > the application after a successful Camera::Configure() call.

s/Configure/configure/

> > >
> > > Signed-off-by: Naushir Patuck <naush@raspberrypi.com>
> > > ---
> > >  include/libcamera/ipa/raspberrypi.mojom       |  1 +
> > >  src/ipa/raspberrypi/raspberrypi.cpp           | 24 ++++++++++++++++---
> > >  .../pipeline/raspberrypi/raspberrypi.cpp      |  3 +++
> > >  3 files changed, 25 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
> > > index 77f52c282b0f..c0de435b7b33 100644
> > > --- a/include/libcamera/ipa/raspberrypi.mojom
> > > +++ b/include/libcamera/ipa/raspberrypi.mojom
> > > @@ -45,6 +45,7 @@ struct IPAConfig {
> > >
> > >  struct IPAConfigResult {
> >
> > Aha, I see the precedent for "Result" now. so perhaps it's more suitable
> > in the previous patch than I realised.
> >
> > >         float modeSensitivity;
> > > +       libcamera.ControlInfoMap controlInfo;
> > >  };
> > >
> > >  struct StartConfig {
> > > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
> > > index 089528f5e126..295f6b735dc0 100644
> > > --- a/src/ipa/raspberrypi/raspberrypi.cpp
> > > +++ b/src/ipa/raspberrypi/raspberrypi.cpp
> > > @@ -74,8 +74,6 @@ constexpr Duration controllerMinFrameDuration = 1.0s / 30.0;
> > >  /* List of controls handled by the Raspberry Pi IPA */
> > >  static const ControlInfoMap::Map ipaControls{
> > >         { &controls::AeEnable, ControlInfo(false, true) },
> > > -       { &controls::ExposureTime, ControlInfo(0, 999999) },
> > > -       { &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
> > >         { &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
> > >         { &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
> > >         { &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
> > > @@ -89,7 +87,6 @@ static const ControlInfoMap::Map ipaControls{
> > >         { &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
> > >         { &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },
> > >         { &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
> > > -       { &controls::FrameDurationLimits, ControlInfo(INT64_C(1000), INT64_C(1000000000)) },
> > >         { &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }
> > >  };
> > >
> > > @@ -446,6 +443,27 @@ int IPARPi::configure(const IPACameraSensorInfo &sensorInfo,
> > >         ASSERT(controls);
> > >         *controls = std::move(ctrls);
> > >
> > > +       /*
> > > +        * Apply the correct limits to the exposure, gain and frame duration controls
> > > +        * based on the current sensor mode.
> > > +        */
> > > +       ControlInfoMap::Map ctrlMap = ipaControls;
> > > +       const Duration minSensorFrameDuration = mode_.min_frame_length * mode_.line_length;
> > > +       const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
> > > +       ctrlMap.emplace(&controls::FrameDurationLimits,
> > > +  ControlInfo(static_cast<int64_t>(minSensorFrameDuration.get<std::micro>()),
> > > +  static_cast<int64_t>(maxSensorFrameDuration.get<std::micro>())));
> > > +
> > > +       ctrlMap.emplace(&controls::AnalogueGain,
> > > +                       ControlInfo(1.0f, static_cast<float>(helper_->Gain(maxSensorGainCode_))));
> > > +
> > > +       const uint32_t exposureMin = sensorCtrls_.at(V4L2_CID_EXPOSURE).min().get<int32_t>();
> > > +       const uint32_t exposureMax = sensorCtrls_.at(V4L2_CID_EXPOSURE).max().get<int32_t>();
> > > +       ctrlMap.emplace(&controls::ExposureTime,
> > > +  ControlInfo(static_cast<int32_t>(helper_->Exposure(exposureMin).get<std::micro>()),
> > > +  static_cast<int32_t>(helper_->Exposure(exposureMax).get<std::micro>())));
> > > +
> > > +       result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
> >
> > aha, this means applications can't read the exposure time / gain
> > controls before the camera is configured.
> >
> > I think that sounds 'correct' - but I wonder if we need to make that
> > clear in some documentation somewhere, or somehow in an extension of
> > 'simple-cam' showing how to manage camera controls. (Not to block this
> > patch of course).
> 
> I did stop to think what to do here.  We could add ControlInfo for
> ExposureTime and AnalogueGain for the "default" sensor mode, but
> getting to the sensor mode is a bit more difficult in ipa::init() as things
> stand.  Additionally, these values would likely have to be ignored by
> the application.  It has to do a camera::Configure() call before starting,
> so the values might have changed, rendering the previous values as
> invalid.
> 
> > We should make sure applications realise they have to recheck controls
> > after a configuration operation. (Maybe that's obvious .. I'm not sure).
> 
> Documenting this is probably a good thing so as to avoid application writers
> from tripping up!

This API is way too fragile, even with documentation. That's fine, we'll
fix it later, but for now I'd like to report *something* even before the
first configure() call. Let's make it as meaningful as reasonable
possible, that will be good enough.

> > I was dwelling on if we should only send the updates to the
> > ControlInfoMap over the IPC mechanism, but I don't think this is
> > critical path, and it's simpler to send the whole updated map - so:
> >
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >
> > >         return 0;
> > >  }
> > >
> > > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > index d980c1a71dd8..4596f2babcea 100644
> > > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > @@ -940,6 +940,9 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
> > >         /* Store the mode sensitivity for the application. */
> > >         data->properties_.set(properties::SensorSensitivity, result.modeSensitivity);
> > >
> > > +       /* Update the controls that the Raspberry Pi IPA can handle. */
> > > +       data->controlInfo_ = result.controlInfo;
> > > +
> > >         /* Setup the Video Mux/Bridge entities. */
> > >         for (auto &[device, link] : data->bridgeDevices_) {
> > >                 /*

Patch
diff mbox series

diff --git a/include/libcamera/ipa/raspberrypi.mojom b/include/libcamera/ipa/raspberrypi.mojom
index 77f52c282b0f..c0de435b7b33 100644
--- a/include/libcamera/ipa/raspberrypi.mojom
+++ b/include/libcamera/ipa/raspberrypi.mojom
@@ -45,6 +45,7 @@  struct IPAConfig {
 
 struct IPAConfigResult {
        float modeSensitivity;
+       libcamera.ControlInfoMap controlInfo;
 };
 
 struct StartConfig {
diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
index 089528f5e126..295f6b735dc0 100644
--- a/src/ipa/raspberrypi/raspberrypi.cpp
+++ b/src/ipa/raspberrypi/raspberrypi.cpp
@@ -74,8 +74,6 @@  constexpr Duration controllerMinFrameDuration = 1.0s / 30.0;
 /* List of controls handled by the Raspberry Pi IPA */
 static const ControlInfoMap::Map ipaControls{
 	{ &controls::AeEnable, ControlInfo(false, true) },
-	{ &controls::ExposureTime, ControlInfo(0, 999999) },
-	{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },
 	{ &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
 	{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
 	{ &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
@@ -89,7 +87,6 @@  static const ControlInfoMap::Map ipaControls{
 	{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
 	{ &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },
 	{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },
-	{ &controls::FrameDurationLimits, ControlInfo(INT64_C(1000), INT64_C(1000000000)) },
 	{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }
 };
 
@@ -446,6 +443,27 @@  int IPARPi::configure(const IPACameraSensorInfo &sensorInfo,
 	ASSERT(controls);
 	*controls = std::move(ctrls);
 
+	/*
+	 * Apply the correct limits to the exposure, gain and frame duration controls
+	 * based on the current sensor mode.
+	 */
+	ControlInfoMap::Map ctrlMap = ipaControls;
+	const Duration minSensorFrameDuration = mode_.min_frame_length * mode_.line_length;
+	const Duration maxSensorFrameDuration = mode_.max_frame_length * mode_.line_length;
+	ctrlMap.emplace(&controls::FrameDurationLimits,
+			ControlInfo(static_cast<int64_t>(minSensorFrameDuration.get<std::micro>()),
+				    static_cast<int64_t>(maxSensorFrameDuration.get<std::micro>())));
+
+	ctrlMap.emplace(&controls::AnalogueGain,
+			ControlInfo(1.0f, static_cast<float>(helper_->Gain(maxSensorGainCode_))));
+
+	const uint32_t exposureMin = sensorCtrls_.at(V4L2_CID_EXPOSURE).min().get<int32_t>();
+	const uint32_t exposureMax = sensorCtrls_.at(V4L2_CID_EXPOSURE).max().get<int32_t>();
+	ctrlMap.emplace(&controls::ExposureTime,
+			ControlInfo(static_cast<int32_t>(helper_->Exposure(exposureMin).get<std::micro>()),
+				    static_cast<int32_t>(helper_->Exposure(exposureMax).get<std::micro>())));
+
+	result->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);
 	return 0;
 }
 
diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
index d980c1a71dd8..4596f2babcea 100644
--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
@@ -940,6 +940,9 @@  int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
 	/* Store the mode sensitivity for the application. */
 	data->properties_.set(properties::SensorSensitivity, result.modeSensitivity);
 
+	/* Update the controls that the Raspberry Pi IPA can handle. */
+	data->controlInfo_ = result.controlInfo;
+
 	/* Setup the Video Mux/Bridge entities. */
 	for (auto &[device, link] : data->bridgeDevices_) {
 		/*