diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h
index 03e4d3dbb480557de98a5fa16fd41d4f2e7431c5..6b77dd46f654e4b95db0327e73164719c47a77d9 100644
--- a/include/libcamera/internal/v4l2_device.h
+++ b/include/libcamera/internal/v4l2_device.h
@@ -48,7 +48,7 @@ public:
 
 	bool supportsEvents(v4l2_event_subscription &sub);
 	int setFrameStartEnabled(bool enable);
-	Signal<uint32_t> frameStart;
+	Signal<const v4l2_event &> eventReady;
 
 	void updateControlInfo();
 
diff --git a/src/libcamera/pipeline/ipu3/cio2.h b/src/libcamera/pipeline/ipu3/cio2.h
index 91651a16404e2c524b13829b4909474e579ad217..a3165fcc49c571b10fd104c025f7004d3b70341f 100644
--- a/src/libcamera/pipeline/ipu3/cio2.h
+++ b/src/libcamera/pipeline/ipu3/cio2.h
@@ -16,6 +16,8 @@
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
+struct v4l2_event;
+
 namespace libcamera {
 
 class CameraSensor;
@@ -58,7 +60,7 @@ public:
 	FrameBuffer *queueBuffer(FrameBuffer *rawBuffer);
 	void tryReturnBuffer(FrameBuffer *buffer);
 	Signal<FrameBuffer *> &bufferReady() { return output_->bufferReady; }
-	Signal<uint32_t> &frameStart() { return csi2_->frameStart; }
+	Signal<const v4l2_event &> eventReady() { return csi2_->eventReady; }
 
 	Signal<> bufferAvailable;
 
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 14cab9e5559b809328e1a408368c906b72e6c027..e48fa327055e372f42487ffc09f55441cc8e3871 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include <linux/intel-ipu3.h>
+#include <linux/videodev2.h>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
@@ -63,7 +64,7 @@ public:
 	void statBufferReady(FrameBuffer *buffer);
 	void queuePendingRequests();
 	void cancelPendingRequests();
-	void frameStart(uint32_t sequence);
+	void handleEvent(const v4l2_event &event);
 
 	CIO2Device cio2_;
 	ImgUDevice *imgu_;
@@ -1089,8 +1090,8 @@ int PipelineHandlerIPU3::registerCameras()
 		data->delayedCtrls_ =
 			std::make_unique<DelayedControls>(cio2->sensor()->device(),
 							  params);
-		data->cio2_.frameStart().connect(data.get(),
-						 &IPU3CameraData::frameStart);
+		data->cio2_.eventReady().connect(data.get(),
+						 &IPU3CameraData::handleEvent);
 
 		/* Convert the sensor rotation to a transformation */
 		const auto &rotation = data->properties_.get(properties::Rotation);
@@ -1313,7 +1314,7 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer)
 	 * Record the sensor's timestamp in the request metadata.
 	 *
 	 * \todo The sensor timestamp should be better estimated by connecting
-	 * to the V4L2Device::frameStart signal.
+	 * to the V4L2Device::eventReady signal for frame sync events.
 	 */
 	request->_d()->metadata().set(controls::SensorTimestamp,
 				      buffer->metadata().timestamp);
@@ -1382,9 +1383,14 @@ void IPU3CameraData::statBufferReady(FrameBuffer *buffer)
  * TestPatternMode one. Other controls are handled through the delayed
  * controls class.
  */
-void IPU3CameraData::frameStart(uint32_t sequence)
+
+void IPU3CameraData::handleEvent(const v4l2_event &event)
 {
-	delayedCtrls_->applyControls(sequence);
+	if (event.type != V4L2_EVENT_FRAME_SYNC)
+		return;
+
+	auto frameSyncEvent = &event.u.frame_sync;
+	delayedCtrls_->applyControls(frameSyncEvent->frame_sequence);
 
 	if (processingRequests_.empty())
 		return;
diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
index 73a03373c8334ea41478d18047c90c97aeb52c2e..da3153ad7fafc133cdde6accb5e3bd54f80520c9 100644
--- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp
+++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp
@@ -17,6 +17,7 @@
 #include <linux/media-bus-format.h>
 #include <linux/media.h>
 #include <linux/media/arm/mali-c55-config.h>
+#include <linux/videodev2.h>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
@@ -198,6 +199,8 @@ public:
 	PixelFormat adjustRawFormat(const PixelFormat &pixFmt) const;
 	Size adjustRawSizes(const PixelFormat &pixFmt, const Size &rawSize) const;
 
+	void handleEvent(const v4l2_event &event);
+
 	Stream frStream_;
 	Stream dsStream_;
 
@@ -451,6 +454,15 @@ Size MaliC55CameraData::adjustRawSizes(const PixelFormat &rawFmt, const Size &si
 	return bestSize;
 }
 
+void MaliC55CameraData::handleEvent(const v4l2_event &event)
+{
+	if (event.type != V4L2_EVENT_FRAME_SYNC)
+		return;
+
+	auto frameSyncEvent = &event.u.frame_sync;
+	delayedCtrls_->applyControls(frameSyncEvent->frame_sequence);
+}
+
 int MaliC55CameraData::loadIPA()
 {
 	int ret;
@@ -1849,8 +1861,7 @@ bool PipelineHandlerMaliC55::registerSensorCamera(MediaLink *ispLink)
 		V4L2Subdevice *sensorSubdev = in->sensor_->device();
 		data->delayedCtrls_ = std::make_unique<DelayedControls>(sensorSubdev,
 									params);
-		isp_->frameStart.connect(data->delayedCtrls_.get(),
-					 &DelayedControls::applyControls);
+		isp_->eventReady.connect(data.get(), &MaliC55CameraData::handleEvent);
 
 		/* \todo Init properties. */
 
@@ -1913,8 +1924,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link)
 
 	data->delayedCtrls_ =
 		std::make_unique<DelayedControls>(sensor->device(), params);
-	isp_->frameStart.connect(data->delayedCtrls_.get(),
-				 &DelayedControls::applyControls);
+	isp_->eventReady.connect(data.get(), &MaliC55CameraData::handleEvent);
 
 	ivc_->bufferReady.connect(mem->cru_.get(), &RZG2LCRU::returnBuffer);
 
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 96382c93a4273297e439891d9cea0287fd84fdad..e49dd546ee0a5207c75a364564d5673fd8463b30 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -106,6 +106,8 @@ public:
 	const PipelineHandlerRkISP1 *pipe() const;
 	int loadIPA(unsigned int hwRevision, uint32_t supportedBlocks);
 
+	void handleEvent(const v4l2_event &event);
+
 	Stream mainPathStream_;
 	Stream selfPathStream_;
 	std::unique_ptr<CameraSensor> sensor_;
@@ -430,6 +432,15 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks)
 	return 0;
 }
 
+void RkISP1CameraData::handleEvent(const v4l2_event &event)
+{
+	if (event.type != V4L2_EVENT_FRAME_SYNC)
+		return;
+
+	auto frameSyncEvent = &event.u.frame_sync;
+	delayedCtrls_->applyControls(frameSyncEvent->frame_sequence);
+}
+
 int RkISP1CameraData::loadTuningFile(const std::string &path)
 {
 	int ret;
@@ -1472,8 +1483,7 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
 	data->delayedCtrls_ =
 		std::make_unique<DelayedControls>(data->sensor_->device(),
 						  params);
-	isp_->frameStart.connect(data->delayedCtrls_.get(),
-				 &DelayedControls::applyControls);
+	isp_->eventReady.connect(data.get(), &RkISP1CameraData::handleEvent);
 
 	uint32_t supportedBlocks = kDefaultExtParamsBlocks;
 
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 71f3f26a4ffeb933176665677231549269782369..2315a1c47dc26ed88fa149106a25dcf1ffbc3ec0 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -873,7 +873,7 @@ int PipelineHandlerBase::registerCamera(std::unique_ptr<RPi::CameraData> &camera
 
 	/* Setup the general IPA signal handlers. */
 	data->frontendDevice()->dequeueTimeout.connect(data, &RPi::CameraData::cameraTimeout);
-	data->frontendDevice()->frameStart.connect(data, &RPi::CameraData::frameStarted);
+	data->frontendDevice()->eventReady.connect(data, &RPi::CameraData::handleEvent);
 	data->ipa_->setDelayedControls.connect(data, &CameraData::setDelayedControls);
 	data->ipa_->setLensControls.connect(data, &CameraData::setLensControls);
 	data->ipa_->metadataReady.connect(data, &CameraData::metadataReady);
@@ -1392,12 +1392,17 @@ void CameraData::cameraTimeout()
 	clearIncompleteRequests();
 }
 
-void CameraData::frameStarted(uint32_t sequence)
+void CameraData::handleEvent(const v4l2_event &event)
 {
-	LOG(RPI, Debug) << "Frame start " << sequence;
+	if (event.type != V4L2_EVENT_FRAME_SYNC)
+		return;
+
+	auto frameSyncEvent = &event.u.frame_sync;
+
+	LOG(RPI, Debug) << "Frame start " << frameSyncEvent->frame_sequence;
 
 	/* Write any controls for the next frame as soon as we can. */
-	delayedCtrls_->applyControls(sequence);
+	delayedCtrls_->applyControls(frameSyncEvent->frame_sequence);
 }
 
 void CameraData::clearIncompleteRequests()
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
index ca12a4875faf4f1b8b8ad1d637a20414857fe2ff..f076721c659330150b01ff4079a83a7ac719e9d3 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.h
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h
@@ -36,6 +36,8 @@
 
 using namespace std::chrono_literals;
 
+struct v4l2_event;
+
 namespace libcamera {
 
 namespace RPi {
@@ -86,7 +88,7 @@ public:
 	virtual void platformSetIspCrop(unsigned int index, const Rectangle &ispCrop) = 0;
 
 	void cameraTimeout();
-	void frameStarted(uint32_t sequence);
+	void handleEvent(const v4l2_event &event);
 
 	void clearIncompleteRequests();
 	void handleStreamBuffer(FrameBuffer *buffer, Stream *stream);
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index 4631d5450b4ac4c47a78a631917d8b226a5269b0..75e068bf516d80af6c8aeeb94bf00ba265744fdd 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -299,6 +299,8 @@ public:
 		return stream - &streams_.front();
 	}
 
+	void handleEvent(const v4l2_event &event);
+
 	struct Entity {
 		/* The media entity, always valid. */
 		MediaEntity *entity;
@@ -988,6 +990,15 @@ void SimpleCameraData::clearIncompleteRequests()
 	}
 }
 
+void SimpleCameraData::handleEvent(const v4l2_event &event)
+{
+	if (event.type != V4L2_EVENT_FRAME_SYNC)
+		return;
+
+	auto frameSyncEvent = &event.u.frame_sync;
+	delayedCtrls_->applyControls(frameSyncEvent->frame_sequence);
+}
+
 void SimpleCameraData::tryCompleteRequest(Request *request)
 {
 	if (request->hasPendingBuffers())
@@ -1676,8 +1687,8 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
 			stop(camera);
 			return ret;
 		}
-		frameStartEmitter->frameStart.connect(data->delayedCtrls_.get(),
-						      &DelayedControls::applyControls);
+		frameStartEmitter->eventReady.connect(data,
+						      &SimpleCameraData::handleEvent);
 	}
 
 	ret = video->streamOn();
@@ -1716,8 +1727,8 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
 
 	if (frameStartEmitter) {
 		frameStartEmitter->setFrameStartEnabled(false);
-		frameStartEmitter->frameStart.disconnect(data->delayedCtrls_.get(),
-							 &DelayedControls::applyControls);
+		frameStartEmitter->eventReady.connect(data,
+						      &SimpleCameraData::handleEvent);
 	}
 
 	if (data->useConversion_) {
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 225b938649dcfe86f5528c3a82ed1b5ca0c94a5a..192edd802a22d3edc7055132eb84ca00e095dd26 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -493,7 +493,7 @@ bool V4L2Device::supportsEvents(v4l2_event_subscription &sub)
  * \param[in] enable True to enable frame start events, false to disable them
  *
  * This function enables or disables generation of frame start events. Once
- * enabled, the events are signalled through the frameStart signal.
+ * enabled, the events are signalled through the eventReady signal.
  *
  * \return 0 on success, a negative error code otherwise
  */
@@ -518,8 +518,8 @@ int V4L2Device::setFrameStartEnabled(bool enable)
 }
 
 /**
- * \var V4L2Device::frameStart
- * \brief A Signal emitted when capture of a frame has started
+ * \var V4L2Device::eventReady
+ * \brief A Signal emitted when a V4L2 event is received
  */
 
 /**
@@ -857,15 +857,7 @@ void V4L2Device::eventAvailable()
 		return;
 	}
 
-	if (event.type != V4L2_EVENT_FRAME_SYNC) {
-		LOG(V4L2, Error)
-			<< "Spurious event (" << event.type
-			<< "), disabling event notifier";
-		fdEventNotifier_->setEnabled(false);
-		return;
-	}
-
-	frameStart.emit(event.u.frame_sync.frame_sequence);
+	eventReady.emit(event);
 }
 
 static const std::map<uint32_t, ColorSpace> v4l2ToColorSpace = {
