diff --git a/src/ipa/raspberrypi/controller/agc_algorithm.h b/src/ipa/raspberrypi/controller/agc_algorithm.h
index 3a91444c3a61..bf9c501db553 100644
--- a/src/ipa/raspberrypi/controller/agc_algorithm.h
+++ b/src/ipa/raspberrypi/controller/agc_algorithm.h
@@ -17,6 +17,12 @@ class AgcAlgorithm : public Algorithm
 public:
 	AgcAlgorithm(Controller *controller) : Algorithm(controller) {}
 	/* An AGC algorithm must provide the following: */
+	virtual void pauseShutter();
+	virtual void resumeShutter();
+	virtual bool isShutterPaused() const;
+	virtual void pauseGain();
+	virtual void resumeGain();
+	virtual bool isGainPaused() const;
 	virtual unsigned int getConvergenceFrames() const = 0;
 	virtual void setEv(double ev) = 0;
 	virtual void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) = 0;
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp b/src/ipa/raspberrypi/controller/rpi/agc.cpp
index bd54a639d637..3d04724349f7 100644
--- a/src/ipa/raspberrypi/controller/rpi/agc.cpp
+++ b/src/ipa/raspberrypi/controller/rpi/agc.cpp
@@ -275,18 +275,36 @@ bool Agc::isPaused() const
 	return false;
 }
 
-void Agc::pause()
+void Agc::pauseShutter()
 {
 	fixedShutter_ = status_.shutterTime;
-	fixedAnalogueGain_ = status_.analogueGain;
 }
 
-void Agc::resume()
+void Agc::resumeShutter()
 {
 	fixedShutter_ = 0s;
+}
+
+bool Agc::isShutterPaused() const
+{
+	return fixedShutter_ != 0s;
+}
+
+void Agc::pauseGain()
+{
+	fixedAnalogueGain_ = status_.analogueGain;
+}
+
+void Agc::resumeGain()
+{
 	fixedAnalogueGain_ = 0;
 }
 
+bool Agc::isGainPaused() const
+{
+	return fixedAnalogueGain_ != 0;
+}
+
 unsigned int Agc::getConvergenceFrames() const
 {
 	/*
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.h b/src/ipa/raspberrypi/controller/rpi/agc.h
index 6d6b0e5ad857..cfb57f41848b 100644
--- a/src/ipa/raspberrypi/controller/rpi/agc.h
+++ b/src/ipa/raspberrypi/controller/rpi/agc.h
@@ -77,8 +77,12 @@ public:
 	int read(const libcamera::YamlObject &params) override;
 	/* AGC handles "pausing" for itself. */
 	bool isPaused() const override;
-	void pause() override;
-	void resume() override;
+	void pauseShutter() override;
+	void resumeShutter() override;
+	bool isShutterPaused() const override;
+	void pauseGain() override;
+	void resumeGain() override;
+	bool isGainPaused() const override;
 	unsigned int getConvergenceFrames() const override;
 	void setEv(double ev) override;
 	void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) override;
diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp
index 69c73f8c780a..c041aac008eb 100644
--- a/src/ipa/raspberrypi/raspberrypi.cpp
+++ b/src/ipa/raspberrypi/raspberrypi.cpp
@@ -74,8 +74,13 @@ 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::ExposureTimeMode,
+	  ControlInfo(static_cast<int32_t>(controls::ExposureTimeModeAuto),
+		      static_cast<int32_t>(controls::ExposureTimeModeManual)) },
 	{ &controls::ExposureTime, ControlInfo(0, 66666) },
+	{ &controls::AnalogueGainMode,
+	  ControlInfo(static_cast<int32_t>(controls::AnalogueGainModeAuto),
+		      static_cast<int32_t>(controls::AnalogueGainModeManual)) },
 	{ &controls::AnalogueGain, ControlInfo(1.0f, 16.0f) },
 	{ &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },
 	{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
@@ -556,9 +561,18 @@ void IPARPi::reportMetadata()
 	}
 
 	AgcStatus *agcStatus = rpiMetadata_.getLocked<AgcStatus>("agc.status");
-	if (agcStatus) {
-		libcameraMetadata_.set(controls::AeLocked, agcStatus->locked);
+	if (agcStatus)
 		libcameraMetadata_.set(controls::DigitalGain, agcStatus->digitalGain);
+
+	RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+		controller_.getAlgorithm("agc"));
+	if (agc) {
+		if (agc->isShutterPaused() && agc->isGainPaused())
+			libcameraMetadata_.set(controls::AeState, controls::AeStateIdle);
+		else if (agcStatus)
+			libcameraMetadata_.set(controls::AeState,
+					       agcStatus->locked ? controls::AeStateConverged
+								 : controls::AeStateSearching);
 	}
 
 	LuxStatus *luxStatus = rpiMetadata_.getLocked<LuxStatus>("lux.status");
@@ -703,20 +717,22 @@ void IPARPi::queueRequest(const ControlList &controls)
 				   << " = " << ctrl.second.toString();
 
 		switch (ctrl.first) {
-		case controls::AE_ENABLE: {
-			RPiController::Algorithm *agc = controller_.getAlgorithm("agc");
+		case controls::EXPOSURE_TIME_MODE: {
+			RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+				controller_.getAlgorithm("agc"));
 			if (!agc) {
 				LOG(IPARPI, Warning)
-					<< "Could not set AE_ENABLE - no AGC algorithm";
+					<< "Could not set EXPOSURE_TIME_MODE - no AGC algorithm";
 				break;
 			}
 
-			if (ctrl.second.get<bool>() == false)
-				agc->pause();
+			if (ctrl.second.get<int32_t>() == controls::ExposureTimeModeManual)
+				agc->pauseShutter();
 			else
-				agc->resume();
+				agc->resumeShutter();
 
-			libcameraMetadata_.set(controls::AeEnable, ctrl.second.get<bool>());
+			libcameraMetadata_.set(controls::ExposureTimeMode,
+					       ctrl.second.get<int32_t>());
 			break;
 		}
 
@@ -729,6 +745,13 @@ void IPARPi::queueRequest(const ControlList &controls)
 				break;
 			}
 
+			/*
+			 * Ignore manual exposure time when the auto exposure
+			 * algorithm is running.
+			 */
+			if (!agc->isShutterPaused())
+				break;
+
 			/* The control provides units of microseconds. */
 			agc->setFixedShutter(ctrl.second.get<int32_t>() * 1.0us);
 
@@ -736,6 +759,25 @@ void IPARPi::queueRequest(const ControlList &controls)
 			break;
 		}
 
+		case controls::ANALOGUE_GAIN_MODE: {
+			RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+				controller_.getAlgorithm("agc"));
+			if (!agc) {
+				LOG(IPARPI, Warning)
+					<< "Could not set ANALOGUE_GAIN_MODE - no AGC algorithm";
+				break;
+			}
+
+			if (ctrl.second.get<int32_t>() == controls::AnalogueGainModeManual)
+				agc->pauseGain();
+			else
+				agc->resumeGain();
+
+			libcameraMetadata_.set(controls::AnalogueGainMode,
+					       ctrl.second.get<int32_t>());
+			break;
+		}
+
 		case controls::ANALOGUE_GAIN: {
 			RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
 				controller_.getAlgorithm("agc"));
@@ -745,6 +787,13 @@ void IPARPi::queueRequest(const ControlList &controls)
 				break;
 			}
 
+			/*
+			 * Ignore manual analogue gain value when the auto gain
+			 * algorithm is running.
+			 */
+			if (!agc->isGainPaused())
+				break;
+
 			agc->setFixedAnalogueGain(ctrl.second.get<float>());
 
 			libcameraMetadata_.set(controls::AnalogueGain,
@@ -801,6 +850,13 @@ void IPARPi::queueRequest(const ControlList &controls)
 				break;
 			}
 
+			/*
+			 * Ignore AE_EXPOSURE_MODE if the shutter or the gain
+			 * are in auto mode.
+			 */
+			if (!agc->isShutterPaused() || !agc->isGainPaused())
+				break;
+
 			int32_t idx = ctrl.second.get<int32_t>();
 			if (ExposureModeTable.count(idx)) {
 				agc->setExposureMode(ExposureModeTable.at(idx));
