diff --git a/test/ipa/camera_sensor_helper.cpp b/test/ipa/camera_sensor_helper.cpp
new file mode 100644
index 000000000000..d8a8c6850a04
--- /dev/null
+++ b/test/ipa/camera_sensor_helper.cpp
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy.
+ */
+
+#include "libipa/camera_sensor_helper.h"
+
+#include <iostream>
+#include <string.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+using namespace libcamera::ipa;
+
+/*
+ * Helper function to compute the m parameter of the exponential gain model
+ * when the gain code is expressed in dB.
+ */
+static constexpr double expGainDb(double step)
+{
+	constexpr double log2_10 = 3.321928094887362;
+
+	/*
+         * The gain code is expressed in step * dB (e.g. in 0.1 dB steps):
+         *
+         * G_code = G_dB/step = 20/step*log10(G_linear)
+         *
+         * Inverting the formula, we get
+         *
+         * G_linear = 10^(step/20*G_code) = 2^(log2(10)*step/20*G_code)
+         */
+	return log2_10 * step / 20;
+}
+
+class CameraSensorHelperExponential : public CameraSensorHelper
+{
+public:
+	CameraSensorHelperExponential()
+	{
+		gainType_ = AnalogueGainExponential;
+		gainConstants_.exp = { 1.0, expGainDb(0.3) };
+	}
+};
+
+class CameraSensorHelperLinear : public CameraSensorHelper
+{
+public:
+	CameraSensorHelperLinear()
+	{
+		gainType_ = AnalogueGainLinear;
+		gainConstants_.linear = { 0, 1024, -1, 1024 };
+	}
+};
+
+class CameraSensorHelperTest : public Test
+{
+protected:
+	int testGainModel(CameraSensorHelper &helper)
+	{
+		int ret = TestPass;
+
+		/*
+		 * Arbitrarily test 255 code positions assuming 8 bit fields
+		 * are the normal maximum for a gain code register.
+		 */
+		for (unsigned int i = 0; i < 255; i++) {
+			float gain = helper.gain(i);
+			uint32_t gainCode = helper.gainCode(gain);
+
+			if (i != gainCode) {
+				std::cout << "Gain conversions failed: "
+					  << i << " : " << gain << " : "
+					  << gainCode << std::endl;
+
+				ret = TestFail;
+			}
+		};
+
+		return ret;
+	}
+
+	int run() override
+	{
+		unsigned int failures = 0;
+
+		CameraSensorHelperExponential exponential;
+		CameraSensorHelperLinear linear;
+
+		if (testGainModel(exponential) == TestFail)
+			failures++;
+
+		if (testGainModel(linear) == TestFail)
+			failures++;
+
+		return failures ? TestFail : TestPass;
+	}
+};
+
+TEST_REGISTER(CameraSensorHelperTest)
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index 180b0da0a51a..abc2456fc341 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: CC0-1.0
 
 ipa_test = [
+    {'name': 'camera_sensor_helper', 'sources': ['camera_sensor_helper.cpp'],
+     'should_fail': true},
     {'name': 'ipa_module_test', 'sources': ['ipa_module_test.cpp']},
     {'name': 'ipa_interface_test', 'sources': ['ipa_interface_test.cpp']},
 ]
@@ -11,5 +13,6 @@ foreach test : ipa_test
                      link_with : [libipa, test_libraries],
                      include_directories : [libipa_includes, test_includes_internal])
 
-    test(test['name'], exe, suite : 'ipa')
+    test(test['name'], exe, suite : 'ipa',
+         should_fail : test.get('should_fail', false))
 endforeach
