[libcamera-devel,v4,4/9] ipa: rkisp1: Add support for manual gain and exposure
diff mbox series

Message ID 20221124025133.17875-5-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • Add Bayer format support for RkISP1
Related show

Commit Message

Laurent Pinchart Nov. 24, 2022, 2:51 a.m. UTC
From: Paul Elder <paul.elder@ideasonboard.com>

Add support for manual gain and exposure in the rkisp1 IPA.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v3:

- Convert exposure and gain V4L2 controls limits to libcamera values
- Convert exposure control from duration to lines in AGC
- Initialize lineDuration in init()
- Small fixes in debug messages
- Drop unnecessary curly braces
- Use std::piecewise_construct
---
 src/ipa/rkisp1/algorithms/agc.cpp | 63 +++++++++++++++++++++++++++----
 src/ipa/rkisp1/algorithms/agc.h   |  4 ++
 src/ipa/rkisp1/ipa_context.h      | 13 ++++++-
 src/ipa/rkisp1/rkisp1.cpp         | 25 ++++++++++++
 4 files changed, 96 insertions(+), 9 deletions(-)

Comments

Jacopo Mondi Nov. 24, 2022, 12:23 p.m. UTC | #1
Hi Laurent

On Thu, Nov 24, 2022 at 04:51:28AM +0200, Laurent Pinchart via libcamera-devel wrote:
> From: Paul Elder <paul.elder@ideasonboard.com>
>
> Add support for manual gain and exposure in the rkisp1 IPA.
>
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Looks good!
Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>

Thanks
  j

> ---
> Changes since v3:
>
> - Convert exposure and gain V4L2 controls limits to libcamera values
> - Convert exposure control from duration to lines in AGC
> - Initialize lineDuration in init()
> - Small fixes in debug messages
> - Drop unnecessary curly braces
> - Use std::piecewise_construct
> ---
>  src/ipa/rkisp1/algorithms/agc.cpp | 63 +++++++++++++++++++++++++++----
>  src/ipa/rkisp1/algorithms/agc.h   |  4 ++
>  src/ipa/rkisp1/ipa_context.h      | 13 ++++++-
>  src/ipa/rkisp1/rkisp1.cpp         | 25 ++++++++++++
>  4 files changed, 96 insertions(+), 9 deletions(-)
>
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index 169afe372803..ccbcd4a9583d 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -74,9 +74,14 @@ Agc::Agc()
>  int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
>  {
>  	/* Configure the default exposure and gain. */
> -	context.activeState.agc.gain = std::max(context.configuration.sensor.minAnalogueGain,
> -						kMinAnalogueGain);
> -	context.activeState.agc.exposure = 10ms / context.configuration.sensor.lineDuration;
> +	context.activeState.agc.automatic.gain =
> +		std::max(context.configuration.sensor.minAnalogueGain,
> +			 kMinAnalogueGain);
> +	context.activeState.agc.automatic.exposure =
> +		10ms / context.configuration.sensor.lineDuration;
> +	context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> +	context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> +	context.activeState.agc.autoEnabled = true;
>
>  	/*
>  	 * According to the RkISP1 documentation:
> @@ -108,14 +113,58 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
>  	return 0;
>  }
>
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void Agc::queueRequest(IPAContext &context,
> +		       [[maybe_unused]] const uint32_t frame,
> +		       IPAFrameContext &frameContext,
> +		       const ControlList &controls)
> +{
> +	auto &agc = context.activeState.agc;
> +
> +	const auto &agcEnable = controls.get(controls::AeEnable);
> +	if (agcEnable && *agcEnable != agc.autoEnabled) {
> +		agc.autoEnabled = *agcEnable;
> +
> +		LOG(RkISP1Agc, Debug)
> +			<< (agc.autoEnabled ? "Enabling" : "Disabling") << " AGC";
> +	}
> +
> +	const auto &exposure = controls.get(controls::ExposureTime);
> +	if (exposure && !agc.autoEnabled) {
> +		agc.manual.exposure = *exposure * 1.0us
> +				    / context.configuration.sensor.lineDuration;
> +
> +		LOG(RkISP1Agc, Debug)
> +			<< "Set exposure to " << agc.manual.exposure;
> +	}
> +
> +	const auto &gain = controls.get(controls::AnalogueGain);
> +	if (gain && !agc.autoEnabled) {
> +		agc.manual.gain = *gain;
> +
> +		LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain;
> +	}
> +
> +	frameContext.agc.autoEnabled = agc.autoEnabled;
> +
> +	if (!frameContext.agc.autoEnabled) {
> +		frameContext.agc.exposure = agc.manual.exposure;
> +		frameContext.agc.gain = agc.manual.gain;
> +	}
> +}
> +
>  /**
>   * \copydoc libcamera::ipa::Algorithm::prepare
>   */
>  void Agc::prepare(IPAContext &context, const uint32_t frame,
>  		  IPAFrameContext &frameContext, rkisp1_params_cfg *params)
>  {
> -	frameContext.agc.exposure = context.activeState.agc.exposure;
> -	frameContext.agc.gain = context.activeState.agc.gain;
> +	if (frameContext.agc.autoEnabled) {
> +		frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
> +		frameContext.agc.gain = context.activeState.agc.automatic.gain;
> +	}
>
>  	if (frame > 0)
>  		return;
> @@ -263,8 +312,8 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext,
>  			      << stepGain;
>
>  	/* Update the estimated exposure and gain. */
> -	activeState.agc.exposure = shutterTime / configuration.sensor.lineDuration;
> -	activeState.agc.gain = stepGain;
> +	activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;
> +	activeState.agc.automatic.gain = stepGain;
>  }
>
>  /**
> diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
> index da4d2d4e8359..a228d0c37768 100644
> --- a/src/ipa/rkisp1/algorithms/agc.h
> +++ b/src/ipa/rkisp1/algorithms/agc.h
> @@ -26,6 +26,10 @@ public:
>  	~Agc() = default;
>
>  	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
> +	void queueRequest(IPAContext &context,
> +			  const uint32_t frame,
> +			  IPAFrameContext &frameContext,
> +			  const ControlList &controls) override;
>  	void prepare(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext,
>  		     rkisp1_params_cfg *params) override;
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index 3e47ac663c58..b9b2065328d6 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -54,8 +54,16 @@ struct IPASessionConfiguration {
>
>  struct IPAActiveState {
>  	struct {
> -		uint32_t exposure;
> -		double gain;
> +		struct {
> +			uint32_t exposure;
> +			double gain;
> +		} manual;
> +		struct {
> +			uint32_t exposure;
> +			double gain;
> +		} automatic;
> +
> +		bool autoEnabled;
>  	} agc;
>
>  	struct {
> @@ -96,6 +104,7 @@ struct IPAFrameContext : public FrameContext {
>  	struct {
>  		uint32_t exposure;
>  		double gain;
> +		bool autoEnabled;
>  	} agc;
>
>  	struct {
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index 6ac29df8ec8d..76e818d2d11e 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -158,6 +158,9 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>  		return -ENODEV;
>  	}
>
> +	context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
> +						   * 1.0s / sensorInfo.pixelRate;
> +
>  	/* Load the tuning data file. */
>  	File file(settings.configurationFile);
>  	if (!file.open(File::OpenModeFlag::ReadOnly)) {
> @@ -377,6 +380,28 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
>  {
>  	ControlInfoMap::Map ctrlMap = rkisp1Controls;
>
> +	/*
> +	 * Compute exposure time limits from the V4L2_CID_EXPOSURE control
> +	 * limits and the line duration.
> +	 */
> +	double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
> +	const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
> +	int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
> +	int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
> +	int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
> +	ctrlMap.emplace(std::piecewise_construct,
> +			std::forward_as_tuple(&controls::ExposureTime),
> +			std::forward_as_tuple(minExposure, maxExposure, defExposure));
> +
> +	/* Compute the analogue gain limits. */
> +	const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
> +	float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
> +	float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
> +	float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
> +	ctrlMap.emplace(std::piecewise_construct,
> +			std::forward_as_tuple(&controls::AnalogueGain),
> +			std::forward_as_tuple(minGain, maxGain, defGain));
> +
>  	/*
>  	 * Compute the frame duration limits.
>  	 *
> --
> Regards,
>
> Laurent Pinchart
>
Paul Elder Nov. 25, 2022, 7:17 a.m. UTC | #2
On Thu, Nov 24, 2022 at 04:51:28AM +0200, Laurent Pinchart wrote:
> From: Paul Elder <paul.elder@ideasonboard.com>
> 
> Add support for manual gain and exposure in the rkisp1 IPA.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>

> ---
> Changes since v3:
> 
> - Convert exposure and gain V4L2 controls limits to libcamera values
> - Convert exposure control from duration to lines in AGC
> - Initialize lineDuration in init()
> - Small fixes in debug messages
> - Drop unnecessary curly braces
> - Use std::piecewise_construct
> ---
>  src/ipa/rkisp1/algorithms/agc.cpp | 63 +++++++++++++++++++++++++++----
>  src/ipa/rkisp1/algorithms/agc.h   |  4 ++
>  src/ipa/rkisp1/ipa_context.h      | 13 ++++++-
>  src/ipa/rkisp1/rkisp1.cpp         | 25 ++++++++++++
>  4 files changed, 96 insertions(+), 9 deletions(-)
> 
> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
> index 169afe372803..ccbcd4a9583d 100644
> --- a/src/ipa/rkisp1/algorithms/agc.cpp
> +++ b/src/ipa/rkisp1/algorithms/agc.cpp
> @@ -74,9 +74,14 @@ Agc::Agc()
>  int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
>  {
>  	/* Configure the default exposure and gain. */
> -	context.activeState.agc.gain = std::max(context.configuration.sensor.minAnalogueGain,
> -						kMinAnalogueGain);
> -	context.activeState.agc.exposure = 10ms / context.configuration.sensor.lineDuration;
> +	context.activeState.agc.automatic.gain =
> +		std::max(context.configuration.sensor.minAnalogueGain,
> +			 kMinAnalogueGain);
> +	context.activeState.agc.automatic.exposure =
> +		10ms / context.configuration.sensor.lineDuration;
> +	context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
> +	context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
> +	context.activeState.agc.autoEnabled = true;
>  
>  	/*
>  	 * According to the RkISP1 documentation:
> @@ -108,14 +113,58 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
>  	return 0;
>  }
>  
> +/**
> + * \copydoc libcamera::ipa::Algorithm::queueRequest
> + */
> +void Agc::queueRequest(IPAContext &context,
> +		       [[maybe_unused]] const uint32_t frame,
> +		       IPAFrameContext &frameContext,
> +		       const ControlList &controls)
> +{
> +	auto &agc = context.activeState.agc;
> +
> +	const auto &agcEnable = controls.get(controls::AeEnable);
> +	if (agcEnable && *agcEnable != agc.autoEnabled) {
> +		agc.autoEnabled = *agcEnable;
> +
> +		LOG(RkISP1Agc, Debug)
> +			<< (agc.autoEnabled ? "Enabling" : "Disabling") << " AGC";
> +	}
> +
> +	const auto &exposure = controls.get(controls::ExposureTime);
> +	if (exposure && !agc.autoEnabled) {
> +		agc.manual.exposure = *exposure * 1.0us
> +				    / context.configuration.sensor.lineDuration;
> +
> +		LOG(RkISP1Agc, Debug)
> +			<< "Set exposure to " << agc.manual.exposure;
> +	}
> +
> +	const auto &gain = controls.get(controls::AnalogueGain);
> +	if (gain && !agc.autoEnabled) {
> +		agc.manual.gain = *gain;
> +
> +		LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain;
> +	}
> +
> +	frameContext.agc.autoEnabled = agc.autoEnabled;
> +
> +	if (!frameContext.agc.autoEnabled) {
> +		frameContext.agc.exposure = agc.manual.exposure;
> +		frameContext.agc.gain = agc.manual.gain;
> +	}
> +}
> +
>  /**
>   * \copydoc libcamera::ipa::Algorithm::prepare
>   */
>  void Agc::prepare(IPAContext &context, const uint32_t frame,
>  		  IPAFrameContext &frameContext, rkisp1_params_cfg *params)
>  {
> -	frameContext.agc.exposure = context.activeState.agc.exposure;
> -	frameContext.agc.gain = context.activeState.agc.gain;
> +	if (frameContext.agc.autoEnabled) {
> +		frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
> +		frameContext.agc.gain = context.activeState.agc.automatic.gain;
> +	}
>  
>  	if (frame > 0)
>  		return;
> @@ -263,8 +312,8 @@ void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext,
>  			      << stepGain;
>  
>  	/* Update the estimated exposure and gain. */
> -	activeState.agc.exposure = shutterTime / configuration.sensor.lineDuration;
> -	activeState.agc.gain = stepGain;
> +	activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;
> +	activeState.agc.automatic.gain = stepGain;
>  }
>  
>  /**
> diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
> index da4d2d4e8359..a228d0c37768 100644
> --- a/src/ipa/rkisp1/algorithms/agc.h
> +++ b/src/ipa/rkisp1/algorithms/agc.h
> @@ -26,6 +26,10 @@ public:
>  	~Agc() = default;
>  
>  	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
> +	void queueRequest(IPAContext &context,
> +			  const uint32_t frame,
> +			  IPAFrameContext &frameContext,
> +			  const ControlList &controls) override;
>  	void prepare(IPAContext &context, const uint32_t frame,
>  		     IPAFrameContext &frameContext,
>  		     rkisp1_params_cfg *params) override;
> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
> index 3e47ac663c58..b9b2065328d6 100644
> --- a/src/ipa/rkisp1/ipa_context.h
> +++ b/src/ipa/rkisp1/ipa_context.h
> @@ -54,8 +54,16 @@ struct IPASessionConfiguration {
>  
>  struct IPAActiveState {
>  	struct {
> -		uint32_t exposure;
> -		double gain;
> +		struct {
> +			uint32_t exposure;
> +			double gain;
> +		} manual;
> +		struct {
> +			uint32_t exposure;
> +			double gain;
> +		} automatic;
> +
> +		bool autoEnabled;
>  	} agc;
>  
>  	struct {
> @@ -96,6 +104,7 @@ struct IPAFrameContext : public FrameContext {
>  	struct {
>  		uint32_t exposure;
>  		double gain;
> +		bool autoEnabled;
>  	} agc;
>  
>  	struct {
> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
> index 6ac29df8ec8d..76e818d2d11e 100644
> --- a/src/ipa/rkisp1/rkisp1.cpp
> +++ b/src/ipa/rkisp1/rkisp1.cpp
> @@ -158,6 +158,9 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
>  		return -ENODEV;
>  	}
>  
> +	context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
> +						   * 1.0s / sensorInfo.pixelRate;
> +
>  	/* Load the tuning data file. */
>  	File file(settings.configurationFile);
>  	if (!file.open(File::OpenModeFlag::ReadOnly)) {
> @@ -377,6 +380,28 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
>  {
>  	ControlInfoMap::Map ctrlMap = rkisp1Controls;
>  
> +	/*
> +	 * Compute exposure time limits from the V4L2_CID_EXPOSURE control
> +	 * limits and the line duration.
> +	 */
> +	double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
> +	const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
> +	int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
> +	int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
> +	int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
> +	ctrlMap.emplace(std::piecewise_construct,
> +			std::forward_as_tuple(&controls::ExposureTime),
> +			std::forward_as_tuple(minExposure, maxExposure, defExposure));
> +
> +	/* Compute the analogue gain limits. */
> +	const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
> +	float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
> +	float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
> +	float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
> +	ctrlMap.emplace(std::piecewise_construct,
> +			std::forward_as_tuple(&controls::AnalogueGain),
> +			std::forward_as_tuple(minGain, maxGain, defGain));
> +
>  	/*
>  	 * Compute the frame duration limits.

Patch
diff mbox series

diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index 169afe372803..ccbcd4a9583d 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -74,9 +74,14 @@  Agc::Agc()
 int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
 {
 	/* Configure the default exposure and gain. */
-	context.activeState.agc.gain = std::max(context.configuration.sensor.minAnalogueGain,
-						kMinAnalogueGain);
-	context.activeState.agc.exposure = 10ms / context.configuration.sensor.lineDuration;
+	context.activeState.agc.automatic.gain =
+		std::max(context.configuration.sensor.minAnalogueGain,
+			 kMinAnalogueGain);
+	context.activeState.agc.automatic.exposure =
+		10ms / context.configuration.sensor.lineDuration;
+	context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;
+	context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;
+	context.activeState.agc.autoEnabled = true;
 
 	/*
 	 * According to the RkISP1 documentation:
@@ -108,14 +113,58 @@  int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)
 	return 0;
 }
 
+/**
+ * \copydoc libcamera::ipa::Algorithm::queueRequest
+ */
+void Agc::queueRequest(IPAContext &context,
+		       [[maybe_unused]] const uint32_t frame,
+		       IPAFrameContext &frameContext,
+		       const ControlList &controls)
+{
+	auto &agc = context.activeState.agc;
+
+	const auto &agcEnable = controls.get(controls::AeEnable);
+	if (agcEnable && *agcEnable != agc.autoEnabled) {
+		agc.autoEnabled = *agcEnable;
+
+		LOG(RkISP1Agc, Debug)
+			<< (agc.autoEnabled ? "Enabling" : "Disabling") << " AGC";
+	}
+
+	const auto &exposure = controls.get(controls::ExposureTime);
+	if (exposure && !agc.autoEnabled) {
+		agc.manual.exposure = *exposure * 1.0us
+				    / context.configuration.sensor.lineDuration;
+
+		LOG(RkISP1Agc, Debug)
+			<< "Set exposure to " << agc.manual.exposure;
+	}
+
+	const auto &gain = controls.get(controls::AnalogueGain);
+	if (gain && !agc.autoEnabled) {
+		agc.manual.gain = *gain;
+
+		LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain;
+	}
+
+	frameContext.agc.autoEnabled = agc.autoEnabled;
+
+	if (!frameContext.agc.autoEnabled) {
+		frameContext.agc.exposure = agc.manual.exposure;
+		frameContext.agc.gain = agc.manual.gain;
+	}
+}
+
 /**
  * \copydoc libcamera::ipa::Algorithm::prepare
  */
 void Agc::prepare(IPAContext &context, const uint32_t frame,
 		  IPAFrameContext &frameContext, rkisp1_params_cfg *params)
 {
-	frameContext.agc.exposure = context.activeState.agc.exposure;
-	frameContext.agc.gain = context.activeState.agc.gain;
+	if (frameContext.agc.autoEnabled) {
+		frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
+		frameContext.agc.gain = context.activeState.agc.automatic.gain;
+	}
 
 	if (frame > 0)
 		return;
@@ -263,8 +312,8 @@  void Agc::computeExposure(IPAContext &context, IPAFrameContext &frameContext,
 			      << stepGain;
 
 	/* Update the estimated exposure and gain. */
-	activeState.agc.exposure = shutterTime / configuration.sensor.lineDuration;
-	activeState.agc.gain = stepGain;
+	activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration;
+	activeState.agc.automatic.gain = stepGain;
 }
 
 /**
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index da4d2d4e8359..a228d0c37768 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -26,6 +26,10 @@  public:
 	~Agc() = default;
 
 	int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
+	void queueRequest(IPAContext &context,
+			  const uint32_t frame,
+			  IPAFrameContext &frameContext,
+			  const ControlList &controls) override;
 	void prepare(IPAContext &context, const uint32_t frame,
 		     IPAFrameContext &frameContext,
 		     rkisp1_params_cfg *params) override;
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index 3e47ac663c58..b9b2065328d6 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -54,8 +54,16 @@  struct IPASessionConfiguration {
 
 struct IPAActiveState {
 	struct {
-		uint32_t exposure;
-		double gain;
+		struct {
+			uint32_t exposure;
+			double gain;
+		} manual;
+		struct {
+			uint32_t exposure;
+			double gain;
+		} automatic;
+
+		bool autoEnabled;
 	} agc;
 
 	struct {
@@ -96,6 +104,7 @@  struct IPAFrameContext : public FrameContext {
 	struct {
 		uint32_t exposure;
 		double gain;
+		bool autoEnabled;
 	} agc;
 
 	struct {
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 6ac29df8ec8d..76e818d2d11e 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -158,6 +158,9 @@  int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
 		return -ENODEV;
 	}
 
+	context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
+						   * 1.0s / sensorInfo.pixelRate;
+
 	/* Load the tuning data file. */
 	File file(settings.configurationFile);
 	if (!file.open(File::OpenModeFlag::ReadOnly)) {
@@ -377,6 +380,28 @@  void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
 {
 	ControlInfoMap::Map ctrlMap = rkisp1Controls;
 
+	/*
+	 * Compute exposure time limits from the V4L2_CID_EXPOSURE control
+	 * limits and the line duration.
+	 */
+	double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
+	const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
+	int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
+	int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
+	int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
+	ctrlMap.emplace(std::piecewise_construct,
+			std::forward_as_tuple(&controls::ExposureTime),
+			std::forward_as_tuple(minExposure, maxExposure, defExposure));
+
+	/* Compute the analogue gain limits. */
+	const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
+	float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
+	float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
+	float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
+	ctrlMap.emplace(std::piecewise_construct,
+			std::forward_as_tuple(&controls::AnalogueGain),
+			std::forward_as_tuple(minGain, maxGain, defGain));
+
 	/*
 	 * Compute the frame duration limits.
 	 *