diff --git a/src/libcamera/include/pipeline_handler.h b/src/libcamera/include/pipeline_handler.h
index 7da6df1ab2a0..4e306d964bff 100644
--- a/src/libcamera/include/pipeline_handler.h
+++ b/src/libcamera/include/pipeline_handler.h
@@ -72,6 +72,9 @@ public:
 	virtual int start(Camera *camera) = 0;
 	virtual void stop(Camera *camera);
 
+	virtual int readControls(Camera *camera, Request *request) = 0;
+	virtual int writeControls(Camera *camera, Request *request) = 0;
+
 	virtual int queueRequest(Camera *camera, Request *request);
 
 	bool completeBuffer(Camera *camera, Request *request, Buffer *buffer);
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 11e25e276c81..811a7c85bfc8 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -213,6 +213,9 @@ public:
 	int start(Camera *camera) override;
 	void stop(Camera *camera) override;
 
+	int readControls(Camera *camera, Request *request) override;
+	int writeControls(Camera *camera, Request *request) override;
+
 	int queueRequest(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -812,6 +815,22 @@ void PipelineHandlerIPU3::stop(Camera *camera)
 	PipelineHandler::stop(camera);
 }
 
+int PipelineHandlerIPU3::readControls(Camera *camera, Request *request)
+{
+	LOG(IPU3, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
+int PipelineHandlerIPU3::writeControls(Camera *camera, Request *request)
+{
+	LOG(IPU3, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
 int PipelineHandlerIPU3::queueRequest(Camera *camera, Request *request)
 {
 	int error = 0;
diff --git a/src/libcamera/pipeline/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi.cpp
index d6749eaae759..337554501c82 100644
--- a/src/libcamera/pipeline/raspberrypi.cpp
+++ b/src/libcamera/pipeline/raspberrypi.cpp
@@ -15,6 +15,7 @@
 #include "media_device.h"
 #include "pipeline_handler.h"
 #include "utils.h"
+#include "v4l2_controls.h"
 #include "v4l2_device.h"
 
 namespace libcamera {
@@ -77,6 +78,9 @@ public:
 	int start(Camera *camera) override;
 	void stop(Camera *camera) override;
 
+	int writeControls(Camera *camera, Request *request);
+	int readControls(Camera *camera, Request *request);
+
 	int queueRequest(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -293,6 +297,94 @@ void PipelineHandlerRPi::stop(Camera *camera)
 	PipelineHandler::stop(camera);
 }
 
+int PipelineHandlerRPi::writeControls(Camera *camera, Request *request)
+{
+	RPiCameraData *data = cameraData(camera);
+
+	std::vector<V4L2Control *> controls;
+
+	for (Control c : request->controls()) {
+		if (c.value().isWrite()) {
+			switch (c.id()) {
+			case ManualGain:
+				break;
+			case ManualExposure:
+				break;
+			default:
+				LOG(RPI, Error)
+					<< "Unhandled write control";
+				break;
+			}
+		}
+	}
+
+	/* Perhaps setControls could accept an empty vector/list as success? */
+	if (!controls.empty())
+		return data->unicam_->setControls(controls);
+
+	return 0;
+}
+
+/* This is becoming horrible. How can we improve this
+ * - Iterate reqeust controls to find controls to read
+ * - Add those to a new list
+ * - Query the device for that list to get controls
+ * - iterate returned list
+ * 	- For each control, search for corresponding control in request
+ * 	- Set control value to return value.
+ * 	- Return list.
+ * 	- Check for any values that were not updated?
+ */
+int PipelineHandlerRPi::readControls(Camera *camera, Request *request)
+{
+	RPiCameraData *data = cameraData(camera);
+	std::vector<unsigned int> controlIDs;
+	std::vector<V4L2Control *> controls;
+
+	for (Control c : request->controls()) {
+		if (c.value().isRead()) {
+			LOG(RPI, Error) << "Read Control: " << c;
+
+			switch (c.id()) {
+			case ManualGain:
+				controlIDs.push_back(V4L2_CID_ANALOGUE_GAIN);
+				break;
+			case ManualExposure:
+				controlIDs.push_back(V4L2_CID_EXPOSURE);
+				break;
+			default:
+				LOG(RPI, Error)
+					<< "Unhandled write control";
+				break;
+			}
+		}
+	}
+
+	/* Perhaps setControls could accept an empty vector/list as success? */
+	if (controlIDs.empty())
+		return 0;
+
+	controls = data->unicam_->getControls(controlIDs);
+	if (controls.empty())
+		return -ENODATA;
+
+	for (V4L2Control *ctrl : controls) {
+		switch(ctrl->id()) {
+		case V4L2_CID_ANALOGUE_GAIN:
+			//Control gain(ManualGain); //= request->controls().find(ManualGain);
+			auto it = request->controls().find(ManualGain);
+			Control gain = *it;
+
+			//V4L2IntControl *c = static_cast<V4L2IntControl *>(ctrl);
+			gain.value().set(0x88);
+
+			break;
+		}
+	}
+
+	return 0;
+}
+
 int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request)
 {
 	RPiCameraData *data = cameraData(camera);
@@ -304,7 +396,15 @@ int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request)
 		return -ENOENT;
 	}
 
-	int ret = data->isp_.capture->queueBuffer(buffer);
+	/*
+	 * Handle 'set' controls first
+	 * Todo: Validate ordering and timing.
+	 */
+	int ret = writeControls(camera, request);
+	if (ret < 0)
+		return ret;
+
+	ret = data->isp_.capture->queueBuffer(buffer);
 	if (ret < 0)
 		return ret;
 
@@ -394,6 +494,12 @@ void RPiCameraData::ispCaptureReady(Buffer *buffer)
 	Request *request = queuedRequests_.front();
 
 	pipe_->completeBuffer(camera_, request, buffer);
+
+	int ret = pipe_->readControls(camera_, request);
+	if (ret < 0)
+		LOG(RPI, Error)
+			<< "Failed to read controls. No way to pass error";
+
 	pipe_->completeRequest(camera_, request);
 }
 
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 9b3eea2f6dd3..d618d9c34a22 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -88,6 +88,9 @@ public:
 	int start(Camera *camera) override;
 	void stop(Camera *camera) override;
 
+	int readControls(Camera *camera, Request *request) override;
+	int writeControls(Camera *camera, Request *request) override;
+
 	int queueRequest(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -359,6 +362,22 @@ void PipelineHandlerRkISP1::stop(Camera *camera)
 	activeCamera_ = nullptr;
 }
 
+int PipelineHandlerRkISP1::readControls(Camera *camera, Request *request)
+{
+	LOG(RkISP1, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
+int PipelineHandlerRkISP1::writeControls(Camera *camera, Request *request)
+{
+	LOG(RkISP1, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
 int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)
 {
 	RkISP1CameraData *data = cameraData(camera);
diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp
index e2d02c0dafb8..216fbe237827 100644
--- a/src/libcamera/pipeline/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo.cpp
@@ -14,6 +14,7 @@
 #include "media_device.h"
 #include "pipeline_handler.h"
 #include "utils.h"
+#include "v4l2_controls.h"
 #include "v4l2_device.h"
 
 namespace libcamera {
@@ -73,6 +74,9 @@ public:
 	int start(Camera *camera) override;
 	void stop(Camera *camera) override;
 
+	int readControls(Camera *camera, Request *request) override;
+	int writeControls(Camera *camera, Request *request) override;
+
 	int queueRequest(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -252,6 +256,117 @@ void PipelineHandlerUVC::stop(Camera *camera)
 	PipelineHandler::stop(camera);
 }
 
+int PipelineHandlerUVC::writeControls(Camera *camera, Request *request)
+{
+	UVCCameraData *data = cameraData(camera);
+
+	std::vector<V4L2Control *> controls;
+
+	for (Control c : request->controls()) {
+		if (c.value().isWrite()) {
+			switch (c.id()) {
+			case ManualBrightness:
+				controls.emplace_back(new V4L2IntControl(V4L2_CID_BRIGHTNESS, c.value().getInt()));
+				break;
+
+			case IpaAwbEnable:
+			case ManualGain:
+			case ManualExposure:
+				break;
+			default:
+				LOG(UVC, Error)
+					<< "Unhandled write control";
+				break;
+			}
+		}
+	}
+
+	/* Perhaps setControls could accept an empty vector/list as success? */
+	if (!controls.empty()) {
+		int ret = data->video_->setControls(controls);
+
+		/* It would be nice to avoid the need to manually delete
+		 * the controls */
+		for (V4L2Control *c : controls)
+			delete c;
+
+		return ret;
+	}
+
+
+	return 0;
+}
+
+int PipelineHandlerUVC::readControls(Camera *camera, Request *request)
+{
+	UVCCameraData *data = cameraData(camera);
+	std::vector<unsigned int> controlIDs;
+	std::vector<V4L2Control *> controls;
+
+	for (Control c : request->controls()) {
+		if (c.value().isRead()) {
+			LOG(UVC, Error) << "Read Control: " << c;
+
+			switch (c.id()) {
+			case ManualBrightness:
+				controlIDs.push_back(V4L2_CID_BRIGHTNESS);
+				break;
+			case ManualGain:
+				controlIDs.push_back(V4L2_CID_ANALOGUE_GAIN);
+				break;
+			case ManualExposure:
+				controlIDs.push_back(V4L2_CID_EXPOSURE);
+				break;
+			default:
+				LOG(UVC, Error)
+					<< "Unhandled write control";
+				break;
+			}
+		}
+	}
+
+	/* Perhaps setControls could accept an empty vector/list as success? */
+	if (controlIDs.empty())
+		return 0;
+
+	controls = data->video_->getControls(controlIDs);
+	if (controls.empty())
+		return -ENODATA;
+
+	for (V4L2Control *ctrl : controls) {
+		switch (ctrl->id()) {
+		case V4L2_CID_BRIGHTNESS: {
+			Control bright = *request->controls().find(ManualBrightness);
+			/* RFC:
+			 * If the iterator doesn't find the control ^
+			 * then it causes a segfault, so this is really nasty...
+			 * and where just a map might be better - (ornot?) as it
+			 * will create the object at that key.
+			 * Now , that *shouldn't* happen (we should only look
+			 * for controls that were given to us in this
+			 * function ... but
+			 */
+			V4L2IntControl *vc = static_cast<V4L2IntControl *>(ctrl);
+			bright.value().set(vc->value());
+
+			LOG(UVC, Debug) << "Setting Brightness: " << bright;
+
+			break;
+		}
+		case V4L2_CID_ANALOGUE_GAIN: {
+			Control gain = *request->controls().find(ManualGain);
+			V4L2IntControl *vc = static_cast<V4L2IntControl *>(ctrl);
+			gain.value().set(vc->value());
+			break;
+		}
+		default:
+			LOG(UVC, Warning) << "Unhandled V4L2 Control ID";
+		}
+	}
+
+	return 0;
+}
+
 int PipelineHandlerUVC::queueRequest(Camera *camera, Request *request)
 {
 	UVCCameraData *data = cameraData(camera);
@@ -263,7 +378,11 @@ int PipelineHandlerUVC::queueRequest(Camera *camera, Request *request)
 		return -ENOENT;
 	}
 
-	int ret = data->video_->queueBuffer(buffer);
+	int ret = writeControls(camera, request);
+	if (ret < 0)
+		return ret;
+
+	ret = data->video_->queueBuffer(buffer);
 	if (ret < 0)
 		return ret;
 
@@ -317,6 +436,12 @@ void UVCCameraData::bufferReady(Buffer *buffer)
 	Request *request = queuedRequests_.front();
 
 	pipe_->completeBuffer(camera_, request, buffer);
+
+	int ret = pipe_->readControls(camera_, request);
+	if (ret < 0)
+		LOG(UVC, Error)
+			<< "Failed to read controls. No way to pass error";
+
 	pipe_->completeRequest(camera_, request);
 }
 
diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp
index 0e4eede351d8..9ecce48c0b12 100644
--- a/src/libcamera/pipeline/vimc.cpp
+++ b/src/libcamera/pipeline/vimc.cpp
@@ -67,6 +67,9 @@ public:
 	int start(Camera *camera) override;
 	void stop(Camera *camera) override;
 
+	int readControls(Camera *camera, Request *request) override;
+	int writeControls(Camera *camera, Request *request) override;
+
 	int queueRequest(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -210,6 +213,22 @@ void PipelineHandlerVimc::stop(Camera *camera)
 	PipelineHandler::stop(camera);
 }
 
+int PipelineHandlerVimc::readControls(Camera *camera, Request *request)
+{
+	LOG(VIMC, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
+int PipelineHandlerVimc::writeControls(Camera *camera, Request *request)
+{
+	LOG(VIMC, Error)
+		<< "NOT IMPLEMENTED";
+
+	return -EINVAL;
+}
+
 int PipelineHandlerVimc::queueRequest(Camera *camera, Request *request)
 {
 	VimcCameraData *data = cameraData(camera);
