diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index 60c918f..fd5a20c 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -204,7 +204,7 @@ DeviceEnumerator::~DeviceEnumerator()
  *
  * \return 0 on success or a negative error code otherwise
  */
-int DeviceEnumerator::addDevice(const std::string &deviceNode)
+std::shared_ptr<MediaDevice> DeviceEnumerator::createDevice(const std::string &deviceNode)
 {
 	std::shared_ptr<MediaDevice> media = std::make_shared<MediaDevice>(deviceNode);
 
@@ -213,33 +213,22 @@ int DeviceEnumerator::addDevice(const std::string &deviceNode)
 		LOG(DeviceEnumerator, Info)
 			<< "Unable to populate media device " << deviceNode
 			<< " (" << strerror(-ret) << "), skipping";
-		return ret;
+		return nullptr;
 	}
 
 	LOG(DeviceEnumerator, Debug)
 		<< "New media device \"" << media->driver()
 		<< "\" created from " << deviceNode;
 
-	/* Associate entities to device node paths. */
-	for (MediaEntity *entity : media->entities()) {
-		if (entity->deviceMajor() == 0 && entity->deviceMinor() == 0)
-			continue;
-
-		std::string deviceNode = lookupDeviceNode(entity->deviceMajor(),
-							  entity->deviceMinor());
-		if (deviceNode.empty())
-			return -EINVAL;
-
-		ret = entity->setDeviceNode(deviceNode);
-		if (ret)
-			return ret;
-	}
+	return media;
+}
 
+int DeviceEnumerator::addDevice(std::shared_ptr<MediaDevice> media)
+{
 	LOG(DeviceEnumerator, Debug)
-		<< "Added device " << deviceNode << ": " << media->driver();
-
-	devices_.push_back(std::move(media));
+		<< "Added device " << media->deviceNode() << ": " << media->driver();
 
+	devices_.push_back(media);
 	return 0;
 }
 
diff --git a/src/libcamera/device_enumerator_sysfs.cpp b/src/libcamera/device_enumerator_sysfs.cpp
index f3054d5..3b5bc90 100644
--- a/src/libcamera/device_enumerator_sysfs.cpp
+++ b/src/libcamera/device_enumerator_sysfs.cpp
@@ -71,7 +71,18 @@ int DeviceEnumeratorSysfs::enumerate()
 			continue;
 		}
 
-		addDevice(devnode);
+		std::shared_ptr<MediaDevice> media = createDevice(devnode);
+		if (!media) {
+			closedir(dir);
+			return -ENODEV;
+		}
+
+		if (populateMediaDevice(media) < 0) {
+			closedir(dir);
+			return -ENODEV;
+		}
+
+		addDevice(media);
 	}
 
 	closedir(dir);
@@ -79,6 +90,26 @@ int DeviceEnumeratorSysfs::enumerate()
 	return 0;
 }
 
+int DeviceEnumeratorSysfs::populateMediaDevice(std::shared_ptr<MediaDevice> media)
+{
+	/* Associate entities to device node paths. */
+	for (MediaEntity *entity : media->entities()) {
+		if (entity->deviceMajor() == 0 && entity->deviceMinor() == 0)
+			continue;
+
+		std::string deviceNode = lookupDeviceNode(entity->deviceMajor(),
+							  entity->deviceMinor());
+		if (deviceNode.empty())
+			return -EINVAL;
+
+		int ret = entity->setDeviceNode(deviceNode);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 std::string DeviceEnumeratorSysfs::lookupDeviceNode(int major, int minor)
 {
 	std::string deviceNode;
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
index 86f6ca1..ef9a31d 100644
--- a/src/libcamera/device_enumerator_udev.cpp
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -7,6 +7,10 @@
 
 #include "device_enumerator_udev.h"
 
+#include <algorithm>
+#include <list>
+#include <map>
+
 #include <fcntl.h>
 #include <libudev.h>
 #include <string.h>
@@ -57,6 +61,11 @@ int DeviceEnumeratorUdev::init()
 	if (ret < 0)
 		return ret;
 
+	ret = udev_monitor_filter_add_match_subsystem_devtype(monitor_, "video4linux",
+							      nullptr);
+	if (ret < 0)
+		return ret;
+
 	return 0;
 }
 
@@ -74,6 +83,14 @@ int DeviceEnumeratorUdev::enumerate()
 	if (ret < 0)
 		goto done;
 
+	ret = udev_enumerate_add_match_subsystem(udev_enum, "video4linux");
+	if (ret < 0)
+		goto done;
+
+	ret = udev_enumerate_add_match_is_initialized(udev_enum);
+	if (ret < 0)
+		goto done;
+
 	ret = udev_enumerate_scan_devices(udev_enum);
 	if (ret < 0)
 		goto done;
@@ -85,6 +102,7 @@ int DeviceEnumeratorUdev::enumerate()
 	udev_list_entry_foreach(ent, ents) {
 		struct udev_device *dev;
 		const char *devnode;
+		const char *subsystem;
 		const char *syspath = udev_list_entry_get_name(ent);
 
 		dev = udev_device_new_from_syspath(udev_, syspath);
@@ -96,16 +114,36 @@ int DeviceEnumeratorUdev::enumerate()
 		}
 
 		devnode = udev_device_get_devnode(dev);
-		if (!devnode) {
-			udev_device_unref(dev);
-			ret = -ENODEV;
-			goto done;
+		if (!devnode)
+			goto unref;
+
+		subsystem = udev_device_get_subsystem(dev);
+		if (!subsystem)
+			goto unref;
+
+		if (!strcmp(subsystem, "media")) {
+			std::shared_ptr<MediaDevice> media = createDevice(devnode);
+			if (!media)
+				goto unref;
+
+			ret = populateMediaDevice(media);
+			if (ret == 0)
+				addDevice(media);
+		} else if (!strcmp(subsystem, "video4linux")) {
+			addV4L2Device(udev_device_get_devnum(dev));
+		} else {
+			goto unref;
 		}
 
-		addDevice(devnode);
+		udev_device_unref(dev);
+		continue;
 
+	unref:
 		udev_device_unref(dev);
+		ret = -ENODEV;
+		goto done;
 	}
+
 done:
 	udev_enumerate_unref(udev_enum);
 	if (ret < 0)
@@ -122,6 +160,49 @@ done:
 	return 0;
 }
 
+int DeviceEnumeratorUdev::populateMediaDevice(std::shared_ptr<MediaDevice> media)
+{
+	/* number of pending devnodes */
+	int count = 0;
+	int ret;
+
+	/* Associate entities to device node paths. */
+	for (MediaEntity *entity : media->entities()) {
+		if (entity->deviceMajor() == 0 && entity->deviceMinor() == 0)
+			continue;
+
+		std::string deviceNode = lookupDeviceNode(entity->deviceMajor(),
+							  entity->deviceMinor());
+		dev_t devnum = makedev(entity->deviceMajor(),
+				       entity->deviceMinor());
+
+		/* take device from orphan list first, if it is in the list */
+		if (std::find(orphans_.begin(), orphans_.end(), devnum) != orphans_.end()) {
+			if (deviceNode.empty())
+				return -EINVAL;
+
+			ret = entity->setDeviceNode(deviceNode);
+			if (ret)
+				return ret;
+
+			orphans_.remove(devnum);
+			continue;
+		}
+
+		if (deviceNode.empty() || (ret = ::access(deviceNode.c_str(), R_OK | W_OK)) < 0) {
+			map_[media].push_back(devnum);
+			revMap_[devnum] = media;
+			devnumToEntity_[devnum] = entity;
+			count++;
+		}
+
+		if (!ret)
+			entity->setDeviceNode(deviceNode);
+	}
+
+	return count;
+}
+
 std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor)
 {
 	struct udev_device *device;
@@ -143,17 +224,66 @@ std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor)
 	return deviceNode;
 }
 
+// add V4L2 device to the media device if it exists, otherwise to the orphan list
+// if adding to media device, and it's the last one, then send up the media device
+int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum)
+{
+	MediaEntity *entity = devnumToEntity_[devnum];
+	if (!entity) {
+		orphans_.push_back(devnum);
+		return 0;
+	}
+
+	std::string deviceNode = lookupDeviceNode(entity->deviceMajor(),
+						  entity->deviceMinor());
+	if (deviceNode.empty())
+		return -EINVAL;
+
+	int ret = entity->setDeviceNode(deviceNode);
+	if (ret)
+		return ret;
+
+	std::shared_ptr<MediaDevice> media = revMap_[devnum];
+	map_[media].remove(devnum);
+	revMap_.erase(devnum);
+	devnumToEntity_.erase(devnum);
+
+	if (map_[media].empty())
+		addDevice(media);
+
+	return 0;
+}
+
 void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier)
 {
 	struct udev_device *dev = udev_monitor_receive_device(monitor_);
 	std::string action(udev_device_get_action(dev));
 	std::string deviceNode(udev_device_get_devnode(dev));
+	dev_t deviceNum(udev_device_get_devnum(dev));
 
 	LOG(DeviceEnumerator, Debug)
 		<< action << " device " << udev_device_get_devnode(dev);
 
 	if (action == "add") {
-		addDevice(deviceNode);
+		const char *subsystem = udev_device_get_subsystem(dev);
+		if (!subsystem) {
+			udev_device_unref(dev);
+			return;
+		}
+
+		if (!strcmp(subsystem, "media")) {
+			std::shared_ptr<MediaDevice> media = createDevice(deviceNode);
+			if (!media) {
+				udev_device_unref(dev);
+				return;
+			}
+
+			int ret = populateMediaDevice(media);
+			if (ret == 0)
+				addDevice(media);
+		} else if (!strcmp(subsystem, "video4linux")) {
+			addV4L2Device(deviceNum);
+		}
 	} else if (action == "remove") {
 		removeDevice(deviceNode);
 	}
diff --git a/src/libcamera/include/device_enumerator.h b/src/libcamera/include/device_enumerator.h
index 02aec3b..4c3e500 100644
--- a/src/libcamera/include/device_enumerator.h
+++ b/src/libcamera/include/device_enumerator.h
@@ -44,12 +44,14 @@ public:
 	std::shared_ptr<MediaDevice> search(const DeviceMatch &dm);
 
 protected:
-	int addDevice(const std::string &deviceNode);
+	std::shared_ptr<MediaDevice> createDevice(const std::string &deviceNode);
+	int addDevice(std::shared_ptr<MediaDevice> media);
 	void removeDevice(const std::string &deviceNode);
 
 private:
 	std::vector<std::shared_ptr<MediaDevice>> devices_;
 
+	virtual int populateMediaDevice(std::shared_ptr<MediaDevice>) = 0;
 	virtual std::string lookupDeviceNode(int major, int minor) = 0;
 };
 
diff --git a/src/libcamera/include/device_enumerator_sysfs.h b/src/libcamera/include/device_enumerator_sysfs.h
index 8d3adc9..d14bbf8 100644
--- a/src/libcamera/include/device_enumerator_sysfs.h
+++ b/src/libcamera/include/device_enumerator_sysfs.h
@@ -9,6 +9,8 @@
 
 #include <string>
 
+#include "media_device.h"
+
 #include "device_enumerator.h"
 
 namespace libcamera {
@@ -20,7 +22,8 @@ public:
 	int enumerate();
 
 private:
-	std::string lookupDeviceNode(int major, int minor);
+	int populateMediaDevice(std::shared_ptr<MediaDevice> media) final;
+	std::string lookupDeviceNode(int major, int minor) final;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/include/device_enumerator_udev.h b/src/libcamera/include/device_enumerator_udev.h
index 80f9372..b3bbcb0 100644
--- a/src/libcamera/include/device_enumerator_udev.h
+++ b/src/libcamera/include/device_enumerator_udev.h
@@ -7,8 +7,14 @@
 #ifndef __LIBCAMERA_DEVICE_ENUMERATOR_UDEV_H__
 #define __LIBCAMERA_DEVICE_ENUMERATOR_UDEV_H__
 
+#include <list>
+#include <map>
 #include <string>
 
+#include <sys/sysmacros.h>
+
+#include "media_device.h"
+
 #include "device_enumerator.h"
 
 struct udev;
@@ -32,8 +38,16 @@ private:
 	struct udev_monitor *monitor_;
 	EventNotifier *notifier_;
 
+	std::map<std::shared_ptr<MediaDevice>, std::list<dev_t>> map_;
+	std::map<dev_t, std::shared_ptr<MediaDevice>> revMap_;
+	std::map<dev_t, MediaEntity *> devnumToEntity_;
+
+	std::list<dev_t> orphans_;
+
+	int populateMediaDevice(std::shared_ptr<MediaDevice> media) final;
 	std::string lookupDeviceNode(int major, int minor) final;
 
+	int addV4L2Device(dev_t devnum);
 	void udevNotify(EventNotifier *notifier);
 };
 
