diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index d7df95e4519a..09176b4e0d17 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -253,9 +253,22 @@ int PipelineHandlerUVC::processControls(UVCCameraData *data, Request *request)
 			controls.set(V4L2_CID_SATURATION, value);
 		} else if (id == controls::ExposureTime) {
 			controls.set(V4L2_CID_EXPOSURE_AUTO, static_cast<int32_t>(1));
-			controls.set(V4L2_CID_EXPOSURE_ABSOLUTE, value);
+			/*
+			 * controls::ExposureTime is in units of 1 us, and UVC
+			 * expects V4L2_CID_EXPOSURE_ABSOLUTE in units of 100 us.
+			 */
+			controls.set(V4L2_CID_EXPOSURE_ABSOLUTE,
+				     value.get<int32_t>() / 100);
 		} else if (id == controls::AnalogueGain) {
-			controls.set(V4L2_CID_GAIN, value);
+			/*
+			 * controls::AnalogueGain is specified as an absolute
+			 * multiplier for all RGB samples. Map this multiplier
+			 * in a linear way such that 1.0 -> default gain
+			 * of the V4L2_CID_GAIN control on the uvcvideo device.
+			 */
+			const ControlInfo &gainInfo = controls.infoMap()->at(V4L2_CID_GAIN);
+			int32_t gain = lroundf(value.get<float>() * gainInfo.def().get<int32_t>());
+			controls.set(V4L2_CID_GAIN, gain);
 		}
 	}
 
@@ -350,7 +363,7 @@ int UVCCameraData::init(MediaEntity *entity)
 	ControlInfoMap::Map ctrls;
 
 	for (const auto &ctrl : controls) {
-		const ControlInfo &info = ctrl.second;
+		ControlInfo info = ctrl.second;
 		const ControlId *id;
 
 		switch (ctrl.first->id()) {
@@ -368,6 +381,15 @@ int UVCCameraData::init(MediaEntity *entity)
 			break;
 		case V4L2_CID_GAIN:
 			id = &controls::AnalogueGain;
+			/*
+			 * AnalogueGain is a float control, convert the type of
+			 * the range.
+			 */
+			info = ControlInfo{
+				{ static_cast<float>(info.min().get<int32_t>()) },
+				{ static_cast<float>(info.max().get<int32_t>()) },
+				{ static_cast<float>(info.def().get<int32_t>()) }
+			};
 			break;
 		default:
 			continue;
