diff --git a/include/libcamera/internal/camera_sensor.h b/include/libcamera/internal/camera_sensor.h
index 878f3c28a3c9..bea52badaff7 100644
--- a/include/libcamera/internal/camera_sensor.h
+++ b/include/libcamera/internal/camera_sensor.h
@@ -29,6 +29,8 @@ class BayerFormat;
 class CameraLens;
 class MediaEntity;
 
+enum class Transform;
+
 struct CameraSensorProperties;
 
 class CameraSensor : protected Loggable
@@ -68,6 +70,8 @@ public:
 
 	CameraLens *focusLens() { return focusLens_.get(); }
 
+	Transform validateTransform(Transform *transform) const;
+
 protected:
 	std::string logPrefix() const override;
 
diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp
index 3afcbc482095..a4ad0ba9a099 100644
--- a/src/libcamera/camera_sensor.cpp
+++ b/src/libcamera/camera_sensor.cpp
@@ -982,6 +982,65 @@ void CameraSensor::updateControlInfo()
  * connected to the sensor
  */
 
+/**
+ * \brief Validate a transform request against the sensor capabilities
+ * \param[inout] transform The requested transformation, updated to match
+ * the sensor capabilities
+ *
+ * The requested \a transform is adjusted against the sensor rotation and its
+ * capabilities.
+ *
+ * In example, if the requested \a transform is Transform::Identity and the
+ * sensor rotation is 180 degrees, the resulting transform returned by the
+ * function is Transform::Rot180 to automatically correct the image, but only if
+ * the sensor can actually apply horizontal and vertical flips.
+ *
+ * \return A Transform instance that represents how \a transform is applied to
+ * the camera sensor
+ */
+Transform CameraSensor::validateTransform(Transform *transform) const
+{
+	/* Adjust the requested transform to the sensor rotation. */
+	int32_t rotation = properties().get(properties::Rotation).value_or(0);
+	bool success;
+
+	Transform rotationTransform = transformFromRotation(rotation, &success);
+	if (!success)
+		LOG(CameraSensor, Warning) << "Invalid rotation of " << rotation
+					   << " degrees - ignoring";
+
+	Transform combined = *transform * rotationTransform;
+
+	/*
+	 * The camera sensor cannot do Transpose. Adjust any combined result
+	 * that includes a transpose by flipping the transpose bit to notify
+	 * applications they either get the transform they requested, or have
+	 * to do a simple transpose themselves (they don't have to worry about
+	 * the other possible cases).
+	 */
+	if (!!(combined & Transform::Transpose)) {
+		/*
+		 * Flipping the transpose bit in "transform" flips it in the
+		 * combined result too (as it's the last thing that happens),
+		 * which is of course clearing it.
+		 */
+		*transform ^= Transform::Transpose;
+		combined &= ~Transform::Transpose;
+	}
+
+	/*
+	 * If the sensor can do no transforms, then combined must be changed to
+	 * the identity and the sensor rotation must be cleared from the
+	 * requested "transform".
+	 */
+	if (!supportFlips_ && !!combined) {
+		*transform = -rotationTransform;
+		combined = Transform::Identity;
+	}
+
+	return combined;
+}
+
 std::string CameraSensor::logPrefix() const
 {
 	return "'" + entity_->name() + "'";
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index e4d79ea44aed..a424ac914859 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -184,48 +184,15 @@ CameraConfiguration::Status IPU3CameraConfiguration::validate()
 	if (config_.empty())
 		return Invalid;
 
-	Transform combined = transform * data_->rotationTransform_;
-
-	/*
-	 * We combine the platform and user transform, but must "adjust away"
-	 * any combined result that includes a transposition, as we can't do
-	 * those. In this case, flipping only the transpose bit is helpful to
-	 * applications - they either get the transform they requested, or have
-	 * to do a simple transpose themselves (they don't have to worry about
-	 * the other possible cases).
-	 */
-	if (!!(combined & Transform::Transpose)) {
-		/*
-		 * Flipping the transpose bit in "transform" flips it in the
-		 * combined result too (as it's the last thing that happens),
-		 * which is of course clearing it.
-		 */
-		transform ^= Transform::Transpose;
-		combined &= ~Transform::Transpose;
-		status = Adjusted;
-	}
-
 	/*
-	 * We also check if the sensor doesn't do h/vflips at all, in which
-	 * case we clear them, and the application will have to do everything.
+	 * Validate the requested transform against the sensor capabilities and
+	 * rotation and store the final combined transform that configure() will
+	 * need to apply to the sensor to save us working it out again.
 	 */
-	if (!data_->supportsFlips_ && !!combined) {
-		/*
-		 * If the sensor can do no transforms, then combined must be
-		 * changed to the identity. The only user transform that gives
-		 * rise to this is the inverse of the rotation. (Recall that
-		 * combined = transform * rotationTransform.)
-		 */
-		transform = -data_->rotationTransform_;
-		combined = Transform::Identity;
+	Transform requestedTransform = transform;
+	combinedTransform_ = data_->cio2_.sensor()->validateTransform(&transform);
+	if (transform != requestedTransform)
 		status = Adjusted;
-	}
-
-	/*
-	 * Store the final combined transform that configure() will need to
-	 * apply to the sensor to save us working it out again.
-	 */
-	combinedTransform_ = combined;
 
 	/* Cap the number of entries to the available streams. */
 	if (config_.size() > kMaxStreams) {
diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
index 087c71b65700..e83984985bce 100644
--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
@@ -366,59 +366,14 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
 	status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
 
 	/*
-	 * What if the platform has a non-90 degree rotation? We can't even
-	 * "adjust" the configuration and carry on. Alternatively, raising an
-	 * error means the platform can never run. Let's just print a warning
-	 * and continue regardless; the rotation is effectively set to zero.
+	 * Validate the requested transform against the sensor capabilities and
+	 * rotation and store the final combined transform that configure() will
+	 * need to apply to the sensor to save us working it out again.
 	 */
-	int32_t rotation = data_->sensor_->properties().get(properties::Rotation).value_or(0);
-	bool success;
-	Transform rotationTransform = transformFromRotation(rotation, &success);
-	if (!success)
-		LOG(RPI, Warning) << "Invalid rotation of " << rotation
-				  << " degrees - ignoring";
-	Transform combined = transform * rotationTransform;
-
-	/*
-	 * We combine the platform and user transform, but must "adjust away"
-	 * any combined result that includes a transform, as we can't do those.
-	 * In this case, flipping only the transpose bit is helpful to
-	 * applications - they either get the transform they requested, or have
-	 * to do a simple transpose themselves (they don't have to worry about
-	 * the other possible cases).
-	 */
-	if (!!(combined & Transform::Transpose)) {
-		/*
-		 * Flipping the transpose bit in "transform" flips it in the
-		 * combined result too (as it's the last thing that happens),
-		 * which is of course clearing it.
-		 */
-		transform ^= Transform::Transpose;
-		combined &= ~Transform::Transpose;
-		status = Adjusted;
-	}
-
-	/*
-	 * We also check if the sensor doesn't do h/vflips at all, in which
-	 * case we clear them, and the application will have to do everything.
-	 */
-	if (!data_->supportsFlips_ && !!combined) {
-		/*
-		 * If the sensor can do no transforms, then combined must be
-		 * changed to the identity. The only user transform that gives
-		 * rise to this the inverse of the rotation. (Recall that
-		 * combined = transform * rotationTransform.)
-		 */
-		transform = -rotationTransform;
-		combined = Transform::Identity;
+	Transform requestedTransform = transform;
+	combinedTransform_ = data_->sensor_->validateTransform(&transform);
+	if (transform != requestedTransform)
 		status = Adjusted;
-	}
-
-	/*
-	 * Store the final combined transform that configure() will need to
-	 * apply to the sensor to save us working it out again.
-	 */
-	combinedTransform_ = combined;
 
 	unsigned int rawCount = 0, outCount = 0, count = 0, maxIndex = 0;
 	std::pair<int, Size> outSize[2];
@@ -453,7 +408,7 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
 			if (data_->flipsAlterBayerOrder_) {
 				BayerFormat bayer = BayerFormat::fromV4L2PixelFormat(fourcc);
 				bayer.order = data_->nativeBayerOrder_;
-				bayer = bayer.transform(combined);
+				bayer = bayer.transform(combinedTransform_);
 				fourcc = bayer.toV4L2PixelFormat();
 			}
 
