diff --git a/src/ipa/simple/algorithms/awb.cpp b/src/ipa/simple/algorithms/awb.cpp
index f5c88ea6..0f9964a4 100644
--- a/src/ipa/simple/algorithms/awb.cpp
+++ b/src/ipa/simple/algorithms/awb.cpp
@@ -14,6 +14,8 @@
 
 #include <libcamera/control_ids.h>
 
+#include "libcamera/internal/yaml_parser.h"
+
 #include "libipa/colours.h"
 #include "simple/ipa_context.h"
 
@@ -23,6 +25,21 @@ LOG_DEFINE_CATEGORY(IPASoftAwb)
 
 namespace ipa::soft::algorithms {
 
+int Awb::init([[maybe_unused]] IPAContext &context,
+	      const ValueNode &tuningData)
+{
+	maxGainR_ = tuningData["maxGainR"].get<float>().value_or(4.0f);
+	maxGainB_ = tuningData["maxGainB"].get<float>().value_or(4.0f);
+	speed_ = tuningData["speed"].get<float>().value_or(1.0f);
+
+	LOG(IPASoftAwb, Info)
+		<< "AWB: maxGainR " << maxGainR_
+		<< ", maxGainB " << maxGainB_
+		<< ", speed " << speed_;
+
+	return 0;
+}
+
 int Awb::configure(IPAContext &context,
 		   [[maybe_unused]] const IPAConfigInfo &configInfo)
 {
@@ -84,14 +101,21 @@ void Awb::process(IPAContext &context,
 	const RGB<uint64_t> sum = stats->sum_.max(offset + minValid) - offset;
 
 	/*
-	 * Calculate red and blue gains for AWB.
-	 * Clamp max gain at 4.0, this also avoids 0 division.
+	 * Calculate red and blue gains for AWB. Clamp max gain to avoid
+	 * division by zero and extreme color casts.
 	 */
 	auto &gains = context.activeState.awb.gains;
+	float rawRGain = sum.r() <= sum.g() / maxGainR_ ? maxGainR_ :
+				static_cast<float>(sum.g()) / sum.r();
+	float rawBGain = sum.b() <= sum.g() / maxGainB_ ? maxGainB_ :
+				static_cast<float>(sum.g()) / sum.b();
+
+	/* Apply temporal smoothing to avoid rapid white balance changes. */
+	float alpha = std::clamp(speed_, 0.01f, 1.0f);
 	gains = { {
-		sum.r() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.r(),
-		1.0,
-		sum.b() <= sum.g() / 4 ? 4.0f : static_cast<float>(sum.g()) / sum.b(),
+		gains.r() * (1.0f - alpha) + rawRGain * alpha,
+		1.0f,
+		gains.b() * (1.0f - alpha) + rawBGain * alpha,
 	} };
 
 	RGB<double> rgbGains{ { 1 / gains.r(), 1 / gains.g(), 1 / gains.b() } };
diff --git a/src/ipa/simple/algorithms/awb.h b/src/ipa/simple/algorithms/awb.h
index ad993f39..0aedc1d1 100644
--- a/src/ipa/simple/algorithms/awb.h
+++ b/src/ipa/simple/algorithms/awb.h
@@ -19,6 +19,7 @@ public:
 	Awb() = default;
 	~Awb() = default;
 
+	int init(IPAContext &context, const ValueNode &tuningData) override;
 	int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
 	void prepare(IPAContext &context,
 		     const uint32_t frame,
@@ -29,6 +30,11 @@ public:
 		     IPAFrameContext &frameContext,
 		     const SwIspStats *stats,
 		     ControlList &metadata) override;
+
+private:
+	float maxGainR_;
+	float maxGainB_;
+	float speed_;
 };
 
 } /* namespace ipa::soft::algorithms */
