diff --git a/src/ipa/raspberrypi/cam_helper_imx290.cpp b/src/ipa/raspberrypi/cam_helper_imx290.cpp
new file mode 100644
index 00000000..6f412e40
--- /dev/null
+++ b/src/ipa/raspberrypi/cam_helper_imx290.cpp
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2021, Raspberry Pi (Trading) Limited
+ *
+ * cam_helper_imx290.cpp - camera helper for imx290 sensor
+ */
+
+#include <math.h>
+
+#include "cam_helper.hpp"
+
+using namespace RPiController;
+
+class CamHelperImx290 : public CamHelper
+{
+public:
+	CamHelperImx290();
+	uint32_t GainCode(double gain) const override;
+	double Gain(uint32_t gain_code) const override;
+	void GetDelays(int &exposure_delay, int &gain_delay,
+		       int &vblank_delay) const override;
+	unsigned int HideFramesModeSwitch() const override;
+
+private:
+	/*
+	 * Smallest difference between the frame length and integration time,
+	 * in units of lines.
+	 */
+	static constexpr int frameIntegrationDiff = 2;
+};
+
+CamHelperImx290::CamHelperImx290()
+	: CamHelper(nullptr, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperImx290::GainCode(double gain) const
+{
+	int code = 66.6667 * log10(gain);
+	return std::max(0, std::min(code, 0xf0));
+}
+
+double CamHelperImx290::Gain(uint32_t gain_code) const
+{
+	return pow(10, 0.015 * gain_code);
+}
+
+void CamHelperImx290::GetDelays(int &exposure_delay, int &gain_delay,
+				int &vblank_delay) const
+{
+	exposure_delay = 2;
+	gain_delay = 2;
+	vblank_delay = 2;
+}
+
+unsigned int CamHelperImx290::HideFramesModeSwitch() const
+{
+	/* After a mode switch, we seem to get 1 bad frame. */
+	return 1;
+}
+
+static CamHelper *Create()
+{
+	return new CamHelperImx290();
+}
+
+static RegisterCamHelper reg("imx290", &Create);
diff --git a/src/ipa/raspberrypi/data/imx290.json b/src/ipa/raspberrypi/data/imx290.json
new file mode 100644
index 00000000..6fb92cc4
--- /dev/null
+++ b/src/ipa/raspberrypi/data/imx290.json
@@ -0,0 +1,165 @@
+{
+    "rpi.black_level":
+    {
+        "black_level": 3840
+    },
+    "rpi.dpc":
+    {
+    },
+    "rpi.lux":
+    {
+        "reference_shutter_speed": 6813,
+        "reference_gain": 1.0,
+        "reference_aperture": 1.0,
+        "reference_lux": 890,
+        "reference_Y": 12900
+    },
+    "rpi.noise":
+    {
+        "reference_constant": 0,
+        "reference_slope": 2.67
+    },
+    "rpi.geq":
+    {
+        "offset": 187,
+        "slope": 0.00842
+    },
+    "rpi.sdn":
+    {
+    },
+    "rpi.awb":
+    {
+	"bayes": 0
+    },
+    "rpi.agc":
+    {
+	"speed": 0.2,
+        "metering_modes":
+        {
+            "matrix":
+            {
+                "weights":
+                [
+                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+                ]
+            },
+            "centre-weighted":
+            {
+                "weights":
+                [
+                    3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
+                ]
+            },
+            "spot":
+            {
+                "weights":
+                [
+                    2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+                ]
+            }
+        },
+        "exposure_modes":
+        {
+            "normal":
+            {
+                "shutter":
+                [
+                    10, 30000, 60000
+                ],
+                "gain":
+                [
+                    1.0,  2.0,   8.0
+                ]
+            },
+            "sport":
+            {
+                "shutter":
+                [
+                    10, 5000, 10000, 20000, 120000
+                ],
+                "gain":
+                [
+                    1.0, 2.0, 4.0, 6.0, 6.0
+                ]
+            }
+        },
+        "constraint_modes":
+        {
+            "normal":
+            [
+            ],
+            "highlight":
+            [
+                {
+                    "bound": "LOWER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
+                    [
+                        0, 0.5, 1000, 0.5
+                    ]
+                },
+                {
+                    "bound": "UPPER", "q_lo": 0.98, "q_hi": 1.0, "y_target":
+                    [
+                        0, 0.8, 1000, 0.8
+                    ]
+                }
+            ]
+        },
+        "y_target":
+        [
+            0, 0.16, 1000, 0.16, 10000, 0.16
+        ]
+    },
+    "rpi.alsc":
+    {
+        "omega": 1.3,
+        "n_iter": 100,
+        "luminance_strength": 0.7,
+        "luminance_lut":
+        [
+            2.844, 2.349, 2.018, 1.775, 1.599, 1.466, 1.371, 1.321, 1.306, 1.316, 1.357, 1.439, 1.552, 1.705, 1.915, 2.221,
+            2.576, 2.151, 1.851, 1.639, 1.478, 1.358, 1.272, 1.231, 1.218, 1.226, 1.262, 1.335, 1.438, 1.571, 1.766, 2.067,
+            2.381, 2.005, 1.739, 1.545, 1.389, 1.278, 1.204, 1.166, 1.153, 1.161, 1.194, 1.263, 1.356, 1.489, 1.671, 1.943,
+            2.242, 1.899, 1.658, 1.481, 1.329, 1.225, 1.156, 1.113, 1.096, 1.107, 1.143, 1.201, 1.289, 1.423, 1.607, 1.861,
+            2.152, 1.831, 1.602, 1.436, 1.291, 1.193, 1.121, 1.069, 1.047, 1.062, 1.107, 1.166, 1.249, 1.384, 1.562, 1.801,
+            2.104, 1.795, 1.572, 1.407, 1.269, 1.174, 1.099, 1.041, 1.008, 1.029, 1.083, 1.146, 1.232, 1.364, 1.547, 1.766,
+            2.104, 1.796, 1.572, 1.403, 1.264, 1.171, 1.097, 1.036, 1.001, 1.025, 1.077, 1.142, 1.231, 1.363, 1.549, 1.766,
+            2.148, 1.827, 1.594, 1.413, 1.276, 1.184, 1.114, 1.062, 1.033, 1.049, 1.092, 1.153, 1.242, 1.383, 1.577, 1.795,
+            2.211, 1.881, 1.636, 1.455, 1.309, 1.214, 1.149, 1.104, 1.081, 1.089, 1.125, 1.184, 1.273, 1.423, 1.622, 1.846,
+            2.319, 1.958, 1.698, 1.516, 1.362, 1.262, 1.203, 1.156, 1.137, 1.142, 1.171, 1.229, 1.331, 1.484, 1.682, 1.933,
+            2.459, 2.072, 1.789, 1.594, 1.441, 1.331, 1.261, 1.219, 1.199, 1.205, 1.232, 1.301, 1.414, 1.571, 1.773, 2.052,
+            2.645, 2.206, 1.928, 1.728, 1.559, 1.451, 1.352, 1.301, 1.282, 1.289, 1.319, 1.395, 1.519, 1.685, 1.904, 2.227
+        ],
+        "sigma": 0.005,
+        "sigma_Cb": 0.005
+    },
+    "rpi.contrast":
+    {
+        "ce_enable": 1,
+        "gamma_curve":
+        [
+            0, 0, 1024, 5040, 2048, 9338, 3072, 12356, 4096, 15312, 5120, 18051, 6144, 20790, 7168, 23193,
+            8192, 25744, 9216, 27942, 10240, 30035, 11264, 32005, 12288, 33975, 13312, 35815, 14336, 37600, 15360, 39168,
+            16384, 40642, 18432, 43379, 20480, 45749, 22528, 47753, 24576, 49621, 26624, 51253, 28672, 52698, 30720, 53796,
+            32768, 54876, 36864, 57012, 40960, 58656, 45056, 59954, 49152, 61183, 53248, 62355, 57344, 63419, 61440, 64476,
+            65535, 65535
+        ]
+    },
+    "rpi.sharpen":
+    {
+    },
+    "rpi.ccm":
+    {
+        "ccms":
+	[
+            {
+		"ct": 3900, "ccm":
+		[
+		    1.54659, -0.17707, -0.36953, -0.51471, 1.72733, -0.21262, 0.06667, -0.92279, 1.85612
+		]
+	    }
+	]
+    },
+    "rpi.focus":
+    {
+    }
+}
diff --git a/src/ipa/raspberrypi/data/meson.build b/src/ipa/raspberrypi/data/meson.build
index 5236bf1e..509ad58b 100644
--- a/src/ipa/raspberrypi/data/meson.build
+++ b/src/ipa/raspberrypi/data/meson.build
@@ -3,6 +3,7 @@
 conf_files = files([
     'imx219.json',
     'imx477.json',
+    'imx290.json',
     'ov5647.json',
     'uncalibrated.json',
 ])
diff --git a/src/ipa/raspberrypi/meson.build b/src/ipa/raspberrypi/meson.build
index 59e49686..7e88f8e0 100644
--- a/src/ipa/raspberrypi/meson.build
+++ b/src/ipa/raspberrypi/meson.build
@@ -21,6 +21,7 @@ rpi_ipa_sources = files([
     'cam_helper_ov5647.cpp',
     'cam_helper_imx219.cpp',
     'cam_helper_imx477.cpp',
+    'cam_helper_imx290.cpp',
     'controller/controller.cpp',
     'controller/histogram.cpp',
     'controller/algorithm.cpp',
