diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp
index 29afb121..aff86803 100644
--- a/src/libcamera/pipeline/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo.cpp
@@ -245,9 +245,22 @@ int PipelineHandlerUVC::processControls(UVCCameraData *data, Request *request)
 			controls.set(V4L2_CID_SATURATION, value);
 		} else if (id == controls::ManualExposure) {
 			controls.set(V4L2_CID_EXPOSURE_AUTO, static_cast<int32_t>(1));
-			controls.set(V4L2_CID_EXPOSURE_ABSOLUTE, value);
+			/*
+			 * controls::ManualExposure is in units of 1 us, and UVC
+			 * expects V4L2_CID_EXPOSURE_ABSOLUTE in units of 100 us.
+			 * So divide by 100 when setting the control.
+			 */
+			controls.set(V4L2_CID_EXPOSURE_ABSOLUTE, value.get<int32_t>() / 100);
 		} else if (id == controls::ManualGain) {
-			controls.set(V4L2_CID_GAIN, value);
+			/*
+			 * controls::ManualGain is specified as an absolute float value.
+			 * Map this in a linear way such that 1.0 -> default gain
+			 * of the V4L2_CID_GAIN control.
+			 */
+			ControlRange gainInfo = controls.infoMap()->at(V4L2_CID_GAIN);
+			float requestGain = value.get<float>();
+			int32_t gain = requestGain * gainInfo.def().get<int32_t>();
+			controls.set(V4L2_CID_GAIN, gain);
 		}
 	}
 
