diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
index 0bdfa727..5d1c5e64 100644
--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
@@ -48,6 +48,12 @@ LOG_DEFINE_CATEGORY(RPI)
 
 namespace {
 
+constexpr int PREFERRED_BIT_DEPTH = 12;
+
+constexpr double PENALTY_AR = 1500.0;
+constexpr double PENALTY_BITDEPTH = 500.0;
+constexpr double PENALTY_UNPACKED = 50.0;
+
 bool isRaw(PixelFormat &pixFmt)
 {
 	/*
@@ -61,58 +67,91 @@ bool isRaw(PixelFormat &pixFmt)
 	return info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
 }
 
-double scoreFormat(double desired, double actual)
+struct Interval {
+	static Interval XRange(const SizeRange &s)
+	{
+		return { static_cast<int>(s.min.width), static_cast<int>(s.max.width) };
+	}
+	static Interval YRange(const SizeRange &s)
+	{
+		return { static_cast<int>(s.min.height), static_cast<int>(s.max.height) };
+	}
+	int min, max;
+	int separation(const Interval &other) const
+	{
+		/*
+		 * The separation between two intervals is positive if "this" interval lies
+		 * wholly above "other", zero if they overlap and otherwise negative.
+		 */
+		if (min > other.max)
+			return min - other.max;
+		else if (other.min > max)
+			return max - other.min;
+		return 0;
+	}
+	int clamp(int value) const
+	{
+		return std::clamp(value, min, max);
+	}
+};
+
+double sizeAr(const Size &s)
+{
+	return static_cast<double>(s.width) / s.height;
+}
+
+double scoreSeparation(int separation)
 {
-	double score = desired - actual;
+	double score = separation;
 	/* Smaller desired dimensions are preferred. */
-	if (score < 0.0)
+	if (score < 0)
 		score = (-score) / 8;
 	/* Penalise non-exact matches. */
-	if (actual != desired)
+	if (separation != 0)
 		score *= 2;
 
 	return score;
 }
 
 V4L2DeviceFormat findBestMode(V4L2VideoDevice::Formats &formatsMap,
-			      const Size &req)
+			      const SensorMode &hintMode)
 {
 	double bestScore = std::numeric_limits<double>::max(), score;
 	V4L2DeviceFormat bestMode;
 
-#define PENALTY_AR		1500.0
-#define PENALTY_8BIT		2000.0
-#define PENALTY_10BIT		1000.0
-#define PENALTY_12BIT		   0.0
-#define PENALTY_UNPACKED	 500.0
+	LOG(RPI, Info) << "Hinted mode: " << hintMode.toString();
+
+	Interval hintX = Interval::XRange(hintMode.sizeRange);
+	Interval hintY = Interval::YRange(hintMode.sizeRange);
+	double hintAr = sizeAr(hintMode.sizeRange.max);
 
-	/* Calculate the closest/best mode from the user requested size. */
+	/* Calculate the closest/best mode from the hinted sensor mode. */
 	for (const auto &iter : formatsMap) {
 		V4L2PixelFormat v4l2Format = iter.first;
 		const PixelFormatInfo &info = PixelFormatInfo::info(v4l2Format);
 
 		for (const SizeRange &sz : iter.second) {
-			double modeWidth = sz.contains(req) ? req.width : sz.max.width;
-			double modeHeight = sz.contains(req) ? req.height : sz.max.height;
-			double reqAr = static_cast<double>(req.width) / req.height;
-			double modeAr = modeWidth / modeHeight;
+			Interval sensorX = Interval::XRange(sz);
+			Interval sensorY = Interval::YRange(sz);
+			double sensorAr = sizeAr(sz.max);
+
+			double widthSeparation = hintX.separation(sensorX);
+			double heightSeparation = hintY.separation(sensorY);
 
 			/* Score the dimensions for closeness. */
-			score = scoreFormat(req.width, modeWidth);
-			score += scoreFormat(req.height, modeHeight);
-			score += PENALTY_AR * scoreFormat(reqAr, modeAr);
+			score = scoreSeparation(widthSeparation);
+			score += scoreSeparation(heightSeparation);
+			score += PENALTY_AR * std::abs(hintAr - sensorAr);
 
 			/* Add any penalties... this is not an exact science! */
 			if (!info.packed)
 				score += PENALTY_UNPACKED;
 
-			if (info.bitsPerPixel == 12)
-				score += PENALTY_12BIT;
-			else if (info.bitsPerPixel == 10)
-				score += PENALTY_10BIT;
-			else if (info.bitsPerPixel == 8)
-				score += PENALTY_8BIT;
+			int bitDifference = info.bitsPerPixel - hintMode.bitDepth;
+			score += std::abs(bitDifference) * PENALTY_BITDEPTH;
 
+			int modeWidth = sensorX.clamp(hintMode.sizeRange.max.width);
+			int modeHeight = sensorY.clamp(hintMode.sizeRange.max.height);
 			if (score <= bestScore) {
 				bestScore = score;
 				bestMode.fourcc = v4l2Format;
@@ -353,7 +392,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
 			 * the user request.
 			 */
 			V4L2VideoDevice::Formats fmts = data_->unicam_[Unicam::Image].dev()->formats();
-			V4L2DeviceFormat sensorFormat = findBestMode(fmts, cfg.size);
+			SensorMode mode(PREFERRED_BIT_DEPTH, SizeRange(cfg.size));
+			V4L2DeviceFormat sensorFormat = findBestMode(fmts, mode);
 			int ret = data_->unicam_[Unicam::Image].dev()->tryFormat(&sensorFormat);
 			if (ret)
 				return Invalid;
@@ -484,11 +524,13 @@ CameraConfiguration *PipelineHandlerRPi::generateConfiguration(Camera *camera,
 	unsigned int rawCount = 0;
 	unsigned int outCount = 0;
 	for (const StreamRole role : roles) {
+		SensorMode mode;
 		switch (role) {
 		case StreamRole::Raw:
 			size = data->sensor_->resolution();
 			fmts = data->unicam_[Unicam::Image].dev()->formats();
-			sensorFormat = findBestMode(fmts, size);
+			mode = SensorMode(PREFERRED_BIT_DEPTH, SizeRange(size));
+			sensorFormat = findBestMode(fmts, mode);
 			pixelFormat = sensorFormat.fourcc.toPixelFormat();
 			ASSERT(pixelFormat.isValid());
 			bufferCount = 2;
@@ -598,9 +640,18 @@ int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config)
 		}
 	}
 
+	/*
+	 * If any parts of the hinted sensor mode are unset, fill them in now.
+	 */
+	if (config->sensorMode.bitDepth == 0)
+		config->sensorMode.bitDepth = PREFERRED_BIT_DEPTH;
+
+	if (config->sensorMode.sizeRange.max == Size(0, 0))
+		config->sensorMode.sizeRange = SizeRange(rawStream ? sensorSize : maxSize);
+
 	/* First calculate the best sensor mode we can use based on the user request. */
 	V4L2VideoDevice::Formats fmts = data->unicam_[Unicam::Image].dev()->formats();
-	V4L2DeviceFormat sensorFormat = findBestMode(fmts, rawStream ? sensorSize : maxSize);
+	V4L2DeviceFormat sensorFormat = findBestMode(fmts, config->sensorMode);
 
 	/*
 	 * Unicam image output format. The ISP input format gets set at start,
