diff --git a/include/libcamera/internal/camera_flash.h b/include/libcamera/internal/camera_flash.h
new file mode 100644
index 0000000000000000000000000000000000000000..e41afef2ab84852a340a12a012e3994f00cac27a
--- /dev/null
+++ b/include/libcamera/internal/camera_flash.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, matthias.fend@emfend.at
+ *
+ * Camera flash support
+ */
+#pragma once
+
+#include <memory>
+#include <stdint.h>
+#include <string>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/log.h>
+
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+class MediaEntity;
+class V4L2Subdevice;
+
+class CameraFlash : protected Loggable
+{
+public:
+	enum Mode {
+		None,
+		Flash,
+		Torch,
+	};
+
+	enum StrobeSource {
+		Software,
+		External,
+	};
+
+	explicit CameraFlash(const MediaEntity *entity);
+	~CameraFlash();
+	int init();
+	Mode getMode() const;
+	int setMode(Mode mode);
+	const ControlInfo &getFlashIntensityInfo() const;
+	int32_t getFlashIntensity() const;
+	int setFlashIntensity(int32_t intensity);
+	const ControlInfo &getFlashTimeoutInfo() const;
+	int32_t getFlashTimeout() const;
+	int setFlashTimeout(int32_t timeout_us);
+	StrobeSource getStrobeSource() const;
+	int setStrobeSource(StrobeSource source);
+	int startStrobe();
+	int stopStrobe();
+	const ControlInfo &getTorchIntensityInfo() const;
+	int32_t getTorchIntensity() const;
+	int setTorchIntensity(int32_t intensity);
+
+	const std::string &model() const;
+	const ControlInfoMap &controls() const;
+
+protected:
+	std::string logPrefix() const override;
+
+private:
+	LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraFlash)
+
+	int32_t getSubdevControl(uint32_t id) const;
+	int setSubdevControl(uint32_t id, int32_t value);
+	int validateDriver();
+
+	const MediaEntity *entity_;
+	std::unique_ptr<V4L2Subdevice> subdev_;
+	std::string model_;
+	const ControlInfoMap *controlInfoMap_ = nullptr;
+};
+
+} /* namespace libcamera */
diff --git a/include/libcamera/internal/camera_sensor.h b/include/libcamera/internal/camera_sensor.h
index f6ef4df170d43500bc652762e9a575010a6c1cbd..faeecf244c8d42a43eca52ae04813e0c2f80e516 100644
--- a/include/libcamera/internal/camera_sensor.h
+++ b/include/libcamera/internal/camera_sensor.h
@@ -28,6 +28,7 @@
 
 namespace libcamera {
 
+class CameraFlash;
 class CameraLens;
 class MediaEntity;
 class SensorConfiguration;
@@ -48,6 +49,7 @@ public:
 	virtual V4L2Subdevice *device() = 0;
 
 	virtual CameraLens *focusLens() = 0;
+	virtual CameraFlash *flash() = 0;
 
 	virtual const std::vector<unsigned int> &mbusCodes() const = 0;
 	virtual std::vector<Size> sizes(unsigned int mbusCode) const = 0;
diff --git a/src/libcamera/camera_flash.cpp b/src/libcamera/camera_flash.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4702c590c91e0bcb20d173e7ce03608e1ae6ecfd
--- /dev/null
+++ b/src/libcamera/camera_flash.cpp
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, matthias.fend@emfend.at
+ *
+ * Camera flash support
+ */
+
+#include "libcamera/internal/camera_flash.h"
+
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/v4l2_subdevice.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(CameraFlash)
+
+CameraFlash::CameraFlash(const MediaEntity *entity)
+	: entity_(entity)
+{
+}
+
+CameraFlash::~CameraFlash() = default;
+
+int CameraFlash::init()
+{
+	if (entity_->function() != MEDIA_ENT_F_FLASH) {
+		LOG(CameraFlash, Error)
+			<< "Invalid flash function "
+			<< utils::hex(entity_->function());
+		return -EINVAL;
+	}
+
+	subdev_ = std::make_unique<V4L2Subdevice>(entity_);
+	int ret = subdev_->open();
+	if (ret < 0)
+		return ret;
+
+	controlInfoMap_ = &subdev_->controls();
+
+	ret = validateDriver();
+	if (ret)
+		return ret;
+
+	model_ = subdev_->model();
+
+	return 0;
+}
+
+CameraFlash::Mode CameraFlash::getMode() const
+{
+	Mode m;
+
+	switch (getSubdevControl(V4L2_CID_FLASH_LED_MODE)) {
+	case V4L2_FLASH_LED_MODE_FLASH:
+		m = Flash;
+		break;
+	case V4L2_FLASH_LED_MODE_TORCH:
+		m = Torch;
+		break;
+	case V4L2_FLASH_LED_MODE_NONE:
+	default:
+		m = None;
+		break;
+	}
+
+	return m;
+}
+
+int CameraFlash::setMode(Mode mode)
+{
+	int32_t m;
+
+	switch (mode) {
+	case Flash:
+		m = V4L2_FLASH_LED_MODE_FLASH;
+		break;
+	case Torch:
+		m = V4L2_FLASH_LED_MODE_TORCH;
+		break;
+	case None:
+		m = V4L2_FLASH_LED_MODE_NONE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return setSubdevControl(V4L2_CID_FLASH_LED_MODE, m);
+}
+
+const ControlInfo &CameraFlash::getFlashIntensityInfo() const
+{
+	return controlInfoMap_->find(V4L2_CID_FLASH_INTENSITY)->second;
+}
+
+int32_t CameraFlash::getFlashIntensity() const
+{
+	return getSubdevControl(V4L2_CID_FLASH_INTENSITY);
+}
+
+int CameraFlash::setFlashIntensity(int32_t intensity)
+{
+	return setSubdevControl(V4L2_CID_FLASH_INTENSITY, intensity);
+}
+
+const ControlInfo &CameraFlash::getFlashTimeoutInfo() const
+{
+	return controlInfoMap_->find(V4L2_CID_FLASH_TIMEOUT)->second;
+}
+
+int32_t CameraFlash::getFlashTimeout() const
+{
+	return getSubdevControl(V4L2_CID_FLASH_TIMEOUT);
+}
+
+int CameraFlash::setFlashTimeout(int32_t timeout)
+{
+	return setSubdevControl(V4L2_CID_FLASH_TIMEOUT, timeout);
+}
+
+CameraFlash::StrobeSource CameraFlash::getStrobeSource() const
+{
+	StrobeSource s;
+
+	switch (getSubdevControl(V4L2_CID_FLASH_STROBE_SOURCE)) {
+	case V4L2_FLASH_STROBE_SOURCE_EXTERNAL:
+		s = External;
+		break;
+	case V4L2_FLASH_STROBE_SOURCE_SOFTWARE:
+	default:
+		s = Software;
+		break;
+	}
+
+	return s;
+}
+
+int CameraFlash::setStrobeSource(StrobeSource source)
+{
+	int32_t s;
+
+	switch (source) {
+	case External:
+		s = V4L2_FLASH_STROBE_SOURCE_EXTERNAL;
+		break;
+	case Software:
+		s = V4L2_FLASH_STROBE_SOURCE_SOFTWARE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return setSubdevControl(V4L2_CID_FLASH_STROBE_SOURCE, s);
+}
+
+int CameraFlash::startStrobe()
+{
+	return setSubdevControl(V4L2_CID_FLASH_STROBE, 1);
+}
+
+int CameraFlash::stopStrobe()
+{
+	return setSubdevControl(V4L2_CID_FLASH_STROBE_STOP, 1);
+}
+
+const ControlInfo &CameraFlash::getTorchIntensityInfo() const
+{
+	return controlInfoMap_->find(V4L2_CID_FLASH_TORCH_INTENSITY)->second;
+}
+
+int32_t CameraFlash::getTorchIntensity() const
+{
+	return getSubdevControl(V4L2_CID_FLASH_TORCH_INTENSITY);
+}
+
+int CameraFlash::setTorchIntensity(int32_t intensity)
+{
+	return setSubdevControl(V4L2_CID_FLASH_TORCH_INTENSITY, intensity);
+}
+
+const std::string &CameraFlash::model() const
+{
+	return model_;
+}
+
+const ControlInfoMap &CameraFlash::controls() const
+{
+	return subdev_->controls();
+}
+
+std::string CameraFlash::logPrefix() const
+{
+	return "'" + entity_->name() + "'";
+}
+
+int32_t CameraFlash::getSubdevControl(uint32_t id) const
+{
+	ControlList controlList = subdev_->getControls(std::vector<uint32_t>{ id });
+
+	return controlList.get(id).get<int32_t>();
+}
+
+int CameraFlash::setSubdevControl(uint32_t id, int32_t value)
+{
+	ControlList flashCtrls(subdev_->controls());
+
+	flashCtrls.set(id, value);
+
+	if (subdev_->setControls(&flashCtrls))
+		return -EINVAL;
+
+	return 0;
+}
+
+int CameraFlash::validateDriver()
+{
+	int ret = 0;
+	static constexpr uint32_t mandatoryControls[] = {
+		V4L2_CID_FLASH_LED_MODE,
+		V4L2_CID_FLASH_STROBE_SOURCE,
+		V4L2_CID_FLASH_STROBE,
+		V4L2_CID_FLASH_TIMEOUT,
+		V4L2_CID_FLASH_INTENSITY,
+		V4L2_CID_FLASH_TORCH_INTENSITY,
+	};
+
+	for (uint32_t ctrl : mandatoryControls) {
+		if (!controlInfoMap_->count(ctrl)) {
+			LOG(CameraFlash, Error)
+				<< "Mandatory V4L2 control " << utils::hex(ctrl)
+				<< " not available";
+			ret = -EINVAL;
+		}
+	}
+
+	if (ret) {
+		LOG(CameraFlash, Error)
+			<< "The flash kernel driver needs to be fixed";
+		LOG(CameraFlash, Error)
+			<< "See Documentation/flash_driver_requirements.rst in"
+			<< " the libcamera sources for more information";
+		return ret;
+	}
+
+	return ret;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index b3ca27f217da4ba3a896ef7cbfb5502fa82a4907..0f125661a51e2431c1febc353cef30a1219f9ce7 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -20,6 +20,7 @@ libcamera_internal_sources = files([
     'bayer_format.cpp',
     'byte_stream_buffer.cpp',
     'camera_controls.cpp',
+    'camera_flash.cpp',
     'camera_lens.cpp',
     'clock_recovery.cpp',
     'control_serializer.cpp',
diff --git a/src/libcamera/sensor/camera_sensor_legacy.cpp b/src/libcamera/sensor/camera_sensor_legacy.cpp
index f9e685a9acc499fc91d51ed1d66780a0ad2d2a8f..632b66ea0aa15fcd654e7f0efb50c24cb9b973bf 100644
--- a/src/libcamera/sensor/camera_sensor_legacy.cpp
+++ b/src/libcamera/sensor/camera_sensor_legacy.cpp
@@ -31,6 +31,7 @@
 #include <libcamera/ipa/core_ipa_interface.h>
 
 #include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera_flash.h"
 #include "libcamera/internal/camera_lens.h"
 #include "libcamera/internal/camera_sensor.h"
 #include "libcamera/internal/camera_sensor_properties.h"
@@ -68,6 +69,7 @@ public:
 	V4L2Subdevice *device() override { return subdev_.get(); }
 
 	CameraLens *focusLens() override { return focusLens_.get(); }
+	CameraFlash *flash() override { return flash_.get(); }
 
 	const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }
 	std::vector<Size> sizes(unsigned int mbusCode) const override;
@@ -139,6 +141,7 @@ private:
 	ControlList properties_;
 
 	std::unique_ptr<CameraLens> focusLens_;
+	std::unique_ptr<CameraFlash> flash_;
 };
 
 /**
@@ -665,6 +668,16 @@ int CameraSensorLegacy::discoverAncillaryDevices()
 			}
 			break;
 
+		case MEDIA_ENT_F_FLASH:
+			flash_ = std::make_unique<CameraFlash>(ancillary);
+			ret = flash_->init();
+			if (ret) {
+				LOG(CameraSensor, Error)
+					<< "Flash initialisation failed, flash disabled";
+				flash_.reset();
+			}
+			break;
+
 		default:
 			LOG(CameraSensor, Warning)
 				<< "Unsupported ancillary entity function "
diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp
index 8ea4423698cd8c1eaae43eb5ba8b5d524b94d515..9d533d814b9df453aa4009a87818c1558bcbd665 100644
--- a/src/libcamera/sensor/camera_sensor_raw.cpp
+++ b/src/libcamera/sensor/camera_sensor_raw.cpp
@@ -32,6 +32,7 @@
 #include <libcamera/ipa/core_ipa_interface.h>
 
 #include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera_flash.h"
 #include "libcamera/internal/camera_lens.h"
 #include "libcamera/internal/camera_sensor.h"
 #include "libcamera/internal/camera_sensor_properties.h"
@@ -69,6 +70,7 @@ public:
 	V4L2Subdevice *device() override { return subdev_.get(); }
 
 	CameraLens *focusLens() override { return focusLens_.get(); }
+	CameraFlash *flash() override { return flash_.get(); }
 
 	const std::vector<unsigned int> &mbusCodes() const override { return mbusCodes_; }
 	std::vector<Size> sizes(unsigned int mbusCode) const override;
@@ -150,6 +152,7 @@ private:
 	ControlList properties_;
 
 	std::unique_ptr<CameraLens> focusLens_;
+	std::unique_ptr<CameraFlash> flash_;
 };
 
 /**
@@ -513,6 +516,16 @@ std::optional<int> CameraSensorRaw::init()
 			}
 			break;
 
+		case MEDIA_ENT_F_FLASH:
+			flash_ = std::make_unique<CameraFlash>(ancillary);
+			ret = flash_->init();
+			if (ret) {
+				LOG(CameraSensor, Error)
+					<< "Flash initialisation failed, flash disabled";
+				flash_.reset();
+			}
+			break;
+
 		default:
 			LOG(CameraSensor, Warning)
 				<< "Unsupported ancillary entity function "
