drivers: video: introduction of the stm32 venc driver

The STM32 video encoder (VENC) peripheral is a hardware
accelerator allowing to compress RGB/YUV frames into
H264 video bitstream chunks.

Signed-off-by: Hugues Fruchet <hugues.fruchet@foss.st.com>
diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt
index 7a03156..2f6a324 100644
--- a/drivers/video/CMakeLists.txt
+++ b/drivers/video/CMakeLists.txt
@@ -15,6 +15,7 @@
 zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640	ov2640.c)
 zephyr_library_sources_ifdef(CONFIG_VIDEO_GC2145	gc2145.c)
 zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMI	video_stm32_dcmi.c)
+zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_VENC	video_stm32_venc.c)
 zephyr_library_sources_ifdef(CONFIG_VIDEO_OV5640	ov5640.c)
 zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7670	ov7670.c)
 zephyr_library_sources_ifdef(CONFIG_VIDEO_OV9655	ov9655.c)
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 7b1f57a..9d7fa84 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -76,6 +76,8 @@
 
 source "drivers/video/Kconfig.stm32_dcmi"
 
+source "drivers/video/Kconfig.stm32_venc"
+
 source "drivers/video/Kconfig.ov5640"
 
 source "drivers/video/Kconfig.ov7670"
diff --git a/drivers/video/Kconfig.stm32_venc b/drivers/video/Kconfig.stm32_venc
new file mode 100644
index 0000000..c0e1957
--- /dev/null
+++ b/drivers/video/Kconfig.stm32_venc
@@ -0,0 +1,24 @@
+# STM32 VENC driver configuration options
+
+# Copyright (c) 2025 STMicroelectronics.
+# SPDX-License-Identifier: Apache-2.0
+
+config VIDEO_STM32_VENC
+	bool "STM32 video encoder (VENC) driver"
+	default y
+	depends on DT_HAS_ST_STM32_VENC_ENABLED
+	select HAS_STM32LIB
+	select USE_STM32_LL_VENC
+	select USE_STM32_HAL_RIF if SOC_SERIES_STM32N6X
+	select RESET
+	help
+	  Enable driver for STM32 video encoder peripheral.
+
+if VIDEO_STM32_VENC
+
+# See hal_stm32/lib/vc8000nanoe/inc/encdebug.h
+module = VC8000NANOE
+module-str = vc8000nanoe
+source "subsys/logging/Kconfig.template.log_config"
+
+endif
diff --git a/drivers/video/video_stm32_venc.c b/drivers/video/video_stm32_venc.c
new file mode 100644
index 0000000..4f26977
--- /dev/null
+++ b/drivers/video/video_stm32_venc.c
@@ -0,0 +1,914 @@
+/*
+ * Copyright (c) 2025 STMicroelectronics.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT st_stm32_venc
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <zephyr/drivers/video.h>
+#include <zephyr/drivers/clock_control.h>
+#include <zephyr/drivers/clock_control/stm32_clock_control.h>
+#include <zephyr/drivers/reset.h>
+#include <zephyr/irq.h>
+#include <zephyr/kernel.h>
+#include <zephyr/logging/log.h>
+#include <zephyr/multi_heap/shared_multi_heap.h>
+
+#include <ewl.h>
+#include <h264encapi.h>
+#include <reg_offset_v7.h>
+
+LOG_MODULE_REGISTER(stm32_venc, CONFIG_VIDEO_LOG_LEVEL);
+
+#define VENC_DEFAULT_WIDTH               320
+#define VENC_DEFAULT_HEIGHT              240
+#define VENC_DEFAULT_IN_FMT              VIDEO_PIX_FMT_NV12
+#define VENC_DEFAULT_OUT_FMT             VIDEO_PIX_FMT_H264
+#define VENC_DEFAULT_FRAMERATE           30
+#define VENC_DEFAULT_LEVEL               H264ENC_LEVEL_4
+#define VENC_DEFAULT_QP                  25
+#define VENC_ESTIMATED_COMPRESSION_RATIO 10
+
+#define ALIGNMENT_INCR 8UL
+
+#define EWL_HEAP_ALIGNED_ALLOC(size)\
+	shared_multi_heap_aligned_alloc(CONFIG_VIDEO_BUFFER_SMH_ATTRIBUTE, ALIGNMENT_INCR, (size))
+#define EWL_HEAP_ALIGNED_FREE(block) shared_multi_heap_free(block)
+
+#define EWL_TIMEOUT 100UL
+
+#define MEM_CHUNKS 32
+
+#define NUM_SLICES_READY_MASK GENMASK(23, 16)
+#define LOW_LATENCY_HW_ITF_EN 29
+
+typedef void (*irq_config_func_t)(const struct device *dev);
+
+struct stm32_venc_config {
+	mm_reg_t reg;
+	const struct stm32_pclken pclken;
+	const struct reset_dt_spec reset;
+	irq_config_func_t irq_config;
+};
+
+struct stm32_venc_ewl {
+	uint32_t client_type;
+	uint32_t *chunks[MEM_CHUNKS];
+	uint32_t *aligned_chunks[MEM_CHUNKS];
+	uint32_t total_chunks;
+	const struct stm32_venc_config *config;
+	struct k_sem complete;
+	uint32_t irq_status;
+	uint32_t irq_cnt;
+	uint32_t mem_cnt;
+};
+
+static struct stm32_venc_ewl ewl_instance;
+
+struct stm32_venc_data {
+	const struct device *dev;
+	struct k_mutex lock;
+	struct video_format in_fmt;
+	struct video_format out_fmt;
+	struct k_fifo in_fifo_in;
+	struct k_fifo in_fifo_out;
+	struct k_fifo out_fifo_in;
+	struct k_fifo out_fifo_out;
+	struct video_buffer *vbuf;
+	H264EncInst encoder;
+	uint32_t frame_nb;
+	bool resync;
+};
+
+static H264EncPictureType to_h264pixfmt(uint32_t pixelformat)
+{
+	switch (pixelformat) {
+	case VIDEO_PIX_FMT_NV12:
+		return H264ENC_YUV420_SEMIPLANAR;
+	case VIDEO_PIX_FMT_RGB565:
+		return H264ENC_RGB565;
+	default:
+		__ASSERT_NO_MSG(false);
+		return H264ENC_YUV420_SEMIPLANAR;
+	}
+}
+
+u32 EWLReadAsicID(void)
+{
+	const struct stm32_venc_config *config = ewl_instance.config;
+
+	return sys_read32(config->reg + BASE_HEncASIC);
+}
+
+EWLHwConfig_t EWLReadAsicConfig(void)
+{
+	const struct stm32_venc_config *config = ewl_instance.config;
+	EWLHwConfig_t cfg_info;
+	uint32_t cfgval, cfgval2;
+
+	cfgval = sys_read32(config->reg + BASE_HEncSynth);
+	cfgval2 = sys_read32(config->reg + BASE_HEncSynth1);
+
+	cfg_info = EWLBuildHwConfig(cfgval, cfgval2);
+
+	LOG_DBG("maxEncodedWidth   = %d", cfg_info.maxEncodedWidth);
+	LOG_DBG("h264Enabled       = %d", cfg_info.h264Enabled);
+	LOG_DBG("jpegEnabled       = %d", cfg_info.jpegEnabled);
+	LOG_DBG("vp8Enabled        = %d", cfg_info.vp8Enabled);
+	LOG_DBG("vsEnabled         = %d", cfg_info.vsEnabled);
+	LOG_DBG("rgbEnabled        = %d", cfg_info.rgbEnabled);
+	LOG_DBG("searchAreaSmall   = %d", cfg_info.searchAreaSmall);
+	LOG_DBG("scalingEnabled    = %d", cfg_info.scalingEnabled);
+	LOG_DBG("address64bits     = %d", cfg_info.addr64Support);
+	LOG_DBG("denoiseEnabled    = %d", cfg_info.dnfSupport);
+	LOG_DBG("rfcEnabled        = %d", cfg_info.rfcSupport);
+	LOG_DBG("instanctEnabled   = %d", cfg_info.instantSupport);
+	LOG_DBG("busType           = %d", cfg_info.busType);
+	LOG_DBG("synthesisLanguage = %d", cfg_info.synthesisLanguage);
+	LOG_DBG("busWidth          = %d", cfg_info.busWidth * 32);
+
+	return cfg_info;
+}
+
+const void *EWLInit(EWLInitParam_t *param)
+{
+	__ASSERT_NO_MSG(param != NULL);
+	__ASSERT_NO_MSG(param->clientType == EWL_CLIENT_TYPE_H264_ENC);
+
+	/* sync */
+	k_sem_init(&ewl_instance.complete, 0, 1);
+
+	/* set client type */
+	ewl_instance.client_type = param->clientType;
+	ewl_instance.irq_cnt = 0;
+
+	return (void *)&ewl_instance;
+}
+
+i32 EWLRelease(const void *inst)
+{
+	ARG_UNUSED(inst);
+
+	return EWL_OK;
+}
+
+void EWLWriteReg(const void *instance, uint32_t offset, uint32_t val)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+	const struct stm32_venc_config *config = inst->config;
+
+	sys_write32(val, config->reg + offset);
+}
+
+void EWLEnableHW(const void *instance, uint32_t offset, uint32_t val)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+	const struct stm32_venc_config *config = inst->config;
+
+	sys_write32(val, config->reg + offset);
+}
+
+void EWLDisableHW(const void *instance, uint32_t offset, uint32_t val)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+	const struct stm32_venc_config *config = inst->config;
+
+	sys_write32(val, config->reg + offset);
+}
+
+uint32_t EWLReadReg(const void *instance, uint32_t offset)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+	const struct stm32_venc_config *config = inst->config;
+
+	return sys_read32(config->reg + offset);
+}
+
+i32 EWLMallocRefFrm(const void *instance, uint32_t size, EWLLinearMem_t *info)
+{
+	return EWLMallocLinear(instance, size, info);
+}
+
+void EWLFreeRefFrm(const void *instance, EWLLinearMem_t *info)
+{
+	EWLFreeLinear(instance, info);
+}
+
+i32 EWLMallocLinear(const void *instance, uint32_t size, EWLLinearMem_t *info)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+
+	__ASSERT_NO_MSG(inst != NULL);
+	__ASSERT_NO_MSG(info != NULL);
+
+	/* align size */
+	uint32_t size_aligned = ROUND_UP(size, ALIGNMENT_INCR);
+
+	info->size = size_aligned;
+
+	/* allocate */
+	inst->chunks[inst->total_chunks] = (uint32_t *)EWL_HEAP_ALIGNED_ALLOC(size_aligned);
+	if (inst->chunks[inst->total_chunks] == NULL) {
+		LOG_DBG("unable to allocate %8d bytes", size_aligned);
+		return EWL_ERROR;
+	}
+
+	/* align given allocated buffer */
+	inst->aligned_chunks[inst->total_chunks] =
+		(uint32_t *)ROUND_UP((uint32_t)inst->chunks[inst->total_chunks], ALIGNMENT_INCR);
+	/* put the aligned pointer in the return structure */
+	info->virtualAddress = inst->aligned_chunks[inst->total_chunks];
+	if (info->virtualAddress == NULL) {
+		LOG_DBG("unable to get chunk for %8d bytes", size_aligned);
+		EWL_HEAP_ALIGNED_FREE(inst->chunks[inst->total_chunks]);
+		return EWL_ERROR;
+	}
+	inst->total_chunks++;
+
+	/* bus address is the same as virtual address because no MMU */
+	info->busAddress = (ptr_t)info->virtualAddress;
+
+	inst->mem_cnt += size;
+	LOG_DBG("allocated %8d bytes --> %p / 0x%x. Total : %d", size_aligned,
+		(void *)info->virtualAddress, info->busAddress, inst->mem_cnt);
+
+	return EWL_OK;
+}
+
+void EWLFreeLinear(const void *instance, EWLLinearMem_t *info)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+
+	__ASSERT_NO_MSG(inst != NULL);
+	__ASSERT_NO_MSG(info != NULL);
+
+	/* find the pointer corresponding to the aligned buffer */
+	for (uint32_t i = 0; i < inst->total_chunks; i++) {
+		if (inst->aligned_chunks[i] == info->virtualAddress) {
+			if (inst->chunks[i] != NULL) {
+				EWL_HEAP_ALIGNED_FREE(inst->chunks[i]);
+			}
+			break;
+		}
+	}
+	info->virtualAddress = NULL;
+	info->busAddress = 0;
+	info->size = 0;
+}
+
+i32 EWLReserveHw(const void *inst)
+{
+	__ASSERT_NO_MSG(inst != NULL);
+
+	return EWL_OK;
+}
+
+void EWLReleaseHw(const void *inst)
+{
+	__ASSERT_NO_MSG(inst != NULL);
+}
+
+void *EWLmalloc(uint32_t n)
+{
+	struct stm32_venc_ewl *inst = &ewl_instance;
+	void *p = NULL;
+
+	p = EWL_HEAP_ALIGNED_ALLOC(n);
+	if (p == NULL) {
+		LOG_ERR("alloc failed for size=%d", n);
+		return NULL;
+	}
+
+	inst->mem_cnt += n;
+	LOG_DBG("%8d bytes --> %p, total : %d", n, p, inst->mem_cnt);
+
+	return p;
+}
+
+void EWLfree(void *p)
+{
+	if (p != NULL) {
+		EWL_HEAP_ALIGNED_FREE(p);
+	}
+}
+
+void *EWLcalloc(uint32_t n, uint32_t s)
+{
+	void *p = EWLmalloc(n * s);
+
+	if (p != NULL) {
+		EWLmemset(p, 0, n * s);
+	}
+
+	return p;
+}
+
+void *EWLmemcpy(void *d, const void *s, uint32_t n)
+{
+	return memcpy(d, s, (size_t)n);
+}
+
+void *EWLmemset(void *d, i32 c, uint32_t n)
+{
+	return memset(d, c, (size_t)n);
+}
+
+int EWLmemcmp(const void *s1, const void *s2, uint32_t n)
+{
+	return memcmp(s1, s2, n);
+}
+
+i32 EWLWaitHwRdy(const void *instance, uint32_t *slicesReady)
+{
+	struct stm32_venc_ewl *inst = (struct stm32_venc_ewl *)instance;
+	const struct stm32_venc_config *config = inst->config;
+	uint32_t ret = EWL_HW_WAIT_TIMEOUT;
+	volatile uint32_t irq_stats;
+	uint32_t prevSlicesReady = 0;
+	k_timepoint_t timeout = sys_timepoint_calc(K_MSEC(EWL_TIMEOUT));
+	uint32_t start = sys_clock_tick_get_32();
+
+	__ASSERT_NO_MSG(inst != NULL);
+
+	/* check how to clear IRQ flags for VENC */
+	uint32_t clrByWrite1 = EWLReadReg(inst, BASE_HWFuse2) & HWCFGIrqClearSupport;
+
+	do {
+		irq_stats = sys_read32(config->reg + BASE_HEncIRQ);
+		/* get the number of completed slices from ASIC registers. */
+		if (slicesReady != NULL && *slicesReady > prevSlicesReady) {
+			*slicesReady = FIELD_GET(NUM_SLICES_READY_MASK,
+						 sys_read32(config->reg + BASE_HEncControl7));
+		}
+
+		LOG_DBG("IRQ stat = %08x", irq_stats);
+
+		uint32_t hw_handshake_status = IS_BIT_SET(
+			sys_read32(config->reg + BASE_HEncInstantInput), LOW_LATENCY_HW_ITF_EN);
+
+		/* ignore the irq status of input line buffer in hw handshake mode */
+		if ((irq_stats == ASIC_STATUS_LINE_BUFFER_DONE) && (hw_handshake_status != 0UL)) {
+			sys_write32(ASIC_STATUS_FUSE, config->reg + BASE_HEncIRQ);
+			continue;
+		}
+
+		if ((irq_stats & ASIC_STATUS_ALL) != 0UL) {
+			/* clear IRQ and slice ready status */
+			uint32_t clr_stats;
+
+			irq_stats &= ~(ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE);
+
+			if (clrByWrite1 != 0UL) {
+				clr_stats = ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE;
+			} else {
+				clr_stats = irq_stats;
+			}
+
+			sys_write32(clr_stats, config->reg + BASE_HEncIRQ);
+			ret = EWL_OK;
+			break;
+		}
+
+		if (slicesReady != NULL && *slicesReady > prevSlicesReady) {
+			ret = EWL_OK;
+			break;
+		}
+
+	} while (!sys_timepoint_expired(timeout));
+
+	LOG_DBG("encoding = %d ms", k_ticks_to_ms_ceil32(sys_clock_tick_get_32() - start));
+
+	if (slicesReady != NULL) {
+		LOG_DBG("slicesReady = %d", *slicesReady);
+	}
+
+	return ret;
+}
+
+void EWLassert(bool expr, const char *str_expr, const char *file, unsigned int line)
+{
+	__ASSERT(expr, "ASSERTION FAIL [%s] @ %s:%d", str_expr, file, line);
+}
+
+/* Set CONFIG_VC8000NANOE_LOG_LEVEL_DBG to enable library tracing */
+void EWLtrace(const char *s)
+{
+	printk("%s\n", s);
+}
+
+void EWLtraceparam(const char *fmt, const char *param, unsigned int val)
+{
+	printk(fmt, param, val);
+}
+
+static int stm32_venc_enable_clock(const struct device *dev)
+{
+	const struct stm32_venc_config *config = dev->config;
+	const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
+
+	if (!device_is_ready(clk)) {
+		LOG_ERR("clock control device not ready");
+		return -ENODEV;
+	}
+
+	if (clock_control_on(clk, (clock_control_subsys_t)&config->pclken) != 0) {
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int stm32_venc_set_fmt(const struct device *dev, struct video_format *fmt)
+{
+	struct stm32_venc_data *data = dev->data;
+
+	if (fmt->type == VIDEO_BUF_TYPE_INPUT) {
+		if ((fmt->pixelformat != VIDEO_PIX_FMT_NV12) &&
+		    (fmt->pixelformat != VIDEO_PIX_FMT_RGB565)) {
+			LOG_ERR("invalid input pixel format");
+			return -EINVAL;
+		}
+
+		fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE;
+		data->in_fmt = *fmt;
+	} else {
+		if (fmt->pixelformat != VIDEO_PIX_FMT_H264) {
+			LOG_ERR("invalid output pixel format");
+			return -EINVAL;
+		}
+
+		fmt->size = ROUND_UP(fmt->width * fmt->height / VENC_ESTIMATED_COMPRESSION_RATIO,
+				     ALIGNMENT_INCR);
+
+		data->out_fmt = *fmt;
+	}
+
+	return 0;
+}
+
+static int stm32_venc_get_fmt(const struct device *dev, struct video_format *fmt)
+{
+	struct stm32_venc_data *data = dev->data;
+
+	if (fmt->type == VIDEO_BUF_TYPE_INPUT) {
+		*fmt = data->in_fmt;
+	} else {
+		*fmt = data->out_fmt;
+	}
+
+	return 0;
+}
+
+static int encoder_prepare(struct stm32_venc_data *data)
+{
+	H264EncRet ret;
+	H264EncConfig cfg = {0};
+	H264EncPreProcessingCfg preproc_cfg = {0};
+	H264EncRateCtrl ratectrl_cfg = {0};
+	H264EncCodingCtrl codingctrl_cfg = {0};
+
+	data->frame_nb = 0;
+
+	/* set config to 1 reference frame */
+	cfg.refFrameAmount = 1;
+	/* frame rate */
+	cfg.frameRateDenom = 1;
+	cfg.frameRateNum = VENC_DEFAULT_FRAMERATE;
+	/* image resolution */
+	cfg.width = data->out_fmt.width;
+	cfg.height = data->out_fmt.height;
+	/* stream type */
+	cfg.streamType = H264ENC_BYTE_STREAM;
+
+	/* encoding level*/
+	cfg.level = VENC_DEFAULT_LEVEL;
+	cfg.svctLevel = 0;
+	cfg.viewMode = H264ENC_BASE_VIEW_SINGLE_BUFFER;
+
+	ret = H264EncInit(&cfg, &data->encoder);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncInit error=%d", ret);
+		return -EIO;
+	}
+
+	/* set format conversion for preprocessing */
+	ret = H264EncGetPreProcessing(data->encoder, &preproc_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncGetPreProcessing error=%d", ret);
+		return -EIO;
+	}
+	preproc_cfg.inputType = to_h264pixfmt(data->in_fmt.pixelformat);
+	ret = H264EncSetPreProcessing(data->encoder, &preproc_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncSetPreProcessing error=%d", ret);
+		return -EIO;
+	}
+
+	/* setup coding ctrl */
+	ret = H264EncGetCodingCtrl(data->encoder, &codingctrl_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncGetCodingCtrl error=%d", ret);
+		return -EIO;
+	}
+
+	ret = H264EncSetCodingCtrl(data->encoder, &codingctrl_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncSetCodingCtrl error=%d", ret);
+		return -EIO;
+	}
+
+	/* set bit rate configuration */
+	ret = H264EncGetRateCtrl(data->encoder, &ratectrl_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncGetRateCtrl error=%d", ret);
+		return -EIO;
+	}
+
+	/* Constant bitrate */
+	ratectrl_cfg.pictureRc = 0;
+	ratectrl_cfg.mbRc = 0;
+	ratectrl_cfg.pictureSkip = 0;
+	ratectrl_cfg.hrd = 0;
+	ratectrl_cfg.qpHdr = VENC_DEFAULT_QP;
+	ratectrl_cfg.qpMin = ratectrl_cfg.qpHdr;
+	ratectrl_cfg.qpMax = ratectrl_cfg.qpHdr;
+
+	ret = H264EncSetRateCtrl(data->encoder, &ratectrl_cfg);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncSetRateCtrl error=%d", ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int encoder_start(struct stm32_venc_data *data, struct video_buffer *output)
+{
+	H264EncRet ret;
+	H264EncIn encIn = {0};
+	H264EncOut encOut = {0};
+
+	encIn.pOutBuf = (uint32_t *)output->buffer;
+	encIn.busOutBuf = (uint32_t)encIn.pOutBuf;
+	encIn.outBufSize = output->size;
+
+	/* create stream */
+	ret = H264EncStrmStart(data->encoder, &encIn, &encOut);
+	if (ret != H264ENC_OK) {
+		LOG_ERR("H264EncStrmStart error=%d", ret);
+		return -EIO;
+	}
+
+	output->bytesused = encOut.streamSize;
+	LOG_DBG("SPS/PPS generated, size= %d", output->bytesused);
+
+	data->resync = true;
+
+	return 0;
+}
+
+static int encoder_end(struct stm32_venc_data *data)
+{
+	H264EncIn encIn = {0};
+	H264EncOut encOut = {0};
+
+	if (data->encoder != NULL) {
+		H264EncStrmEnd(data->encoder, &encIn, &encOut);
+		data->encoder = NULL;
+	}
+
+	return 0;
+}
+
+static int encode_frame(struct stm32_venc_data *data)
+{
+	int ret = H264ENC_FRAME_READY;
+	struct video_buffer *input;
+	struct video_buffer *output;
+	H264EncIn encIn = {0};
+	H264EncOut encOut = {0};
+
+	if (k_fifo_is_empty(&data->in_fifo_in) || k_fifo_is_empty(&data->out_fifo_in)) {
+		/* Encoding deferred to next buffer queueing */
+		return 0;
+	}
+
+	input = k_fifo_get(&data->in_fifo_in, K_NO_WAIT);
+	output = k_fifo_get(&data->out_fifo_in, K_NO_WAIT);
+
+	if (data->encoder == NULL) {
+		ret = encoder_prepare(data);
+		if (ret) {
+			goto out;
+		}
+
+		ret = encoder_start(data, output);
+
+		/*
+		 * Output buffer is now filled with SPS/PPS header, return it to application.
+		 * Input buffer is dropped to not introduce latency.
+		 */
+		goto out;
+	}
+
+	/* one key frame every seconds */
+	if ((data->frame_nb % VENC_DEFAULT_FRAMERATE) == 0 || data->resync) {
+		/* if frame is the first or resync needed: set as intra coded */
+		encIn.codingType = H264ENC_INTRA_FRAME;
+	} else {
+		/* if there was a frame previously, set as predicted */
+		encIn.timeIncrement = 1;
+		encIn.codingType = H264ENC_PREDICTED_FRAME;
+	}
+
+	encIn.ipf = H264ENC_REFERENCE_AND_REFRESH;
+	encIn.ltrf = H264ENC_REFERENCE;
+
+	/* set input buffers to structures */
+	encIn.busLuma = (ptr_t)input->buffer;
+	encIn.busChromaU = (ptr_t)encIn.busLuma + data->in_fmt.width * data->in_fmt.height;
+
+	encIn.pOutBuf = (uint32_t *)output->buffer;
+	encIn.busOutBuf = (uint32_t)encIn.pOutBuf;
+	encIn.outBufSize = output->size;
+	encOut.streamSize = 0;
+
+	ret = H264EncStrmEncode(data->encoder, &encIn, &encOut, NULL, NULL, NULL);
+	output->bytesused = encOut.streamSize;
+	LOG_DBG("output=%p, encOut.streamSize=%d", output, encOut.streamSize);
+
+	switch (ret) {
+	case H264ENC_FRAME_READY:
+		/* save stream */
+		if (encOut.streamSize == 0) {
+			/* Nothing encoded */
+			data->resync = true;
+			goto out;
+		}
+		output->bytesused = encOut.streamSize;
+		break;
+	case H264ENC_FUSE_ERROR:
+		LOG_ERR("H264EncStrmEncode error=%d", ret);
+
+		LOG_ERR("DCMIPP and VENC desync at frame %d, restart the video", data->frame_nb);
+		encoder_end(data);
+
+		ret = encoder_start(data, output);
+		if (ret) {
+			goto out;
+		}
+		break;
+	default:
+		LOG_ERR("H264EncStrmEncode error=%d", ret);
+		LOG_ERR("error encoding frame %d", data->frame_nb);
+
+		encoder_end(data);
+
+		ret = encoder_start(data, output);
+		if (ret) {
+			goto out;
+		}
+
+		data->resync = true;
+
+		ret = -EIO;
+		goto out;
+	}
+
+	data->frame_nb++;
+
+out:
+	k_fifo_put(&data->in_fifo_out, input);
+	k_fifo_put(&data->out_fifo_out, output);
+
+	return ret;
+}
+
+static int stm32_venc_set_stream(const struct device *dev, bool enable, enum video_buf_type type)
+{
+	struct stm32_venc_data *data = dev->data;
+
+	ARG_UNUSED(type);
+
+	if (!enable) {
+		/* Stop VENC */
+		encoder_end(data);
+	}
+
+	return 0;
+}
+
+static int stm32_venc_enqueue(const struct device *dev, struct video_buffer *vbuf)
+{
+	struct stm32_venc_data *data = dev->data;
+	int ret = 0;
+
+	k_mutex_lock(&data->lock, K_FOREVER);
+
+	if (vbuf->type == VIDEO_BUF_TYPE_INPUT) {
+		k_fifo_put(&data->in_fifo_in, vbuf);
+	} else {
+		k_fifo_put(&data->out_fifo_in, vbuf);
+	}
+
+	ret = encode_frame(data);
+
+	k_mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int stm32_venc_dequeue(const struct device *dev, struct video_buffer **vbuf,
+			      k_timeout_t timeout)
+{
+	struct stm32_venc_data *data = dev->data;
+	int ret = 0;
+
+	k_mutex_lock(&data->lock, K_FOREVER);
+
+	if ((*vbuf)->type == VIDEO_BUF_TYPE_INPUT) {
+		*vbuf = k_fifo_get(&data->in_fifo_out, timeout);
+	} else {
+		*vbuf = k_fifo_get(&data->out_fifo_out, timeout);
+	}
+
+	if (*vbuf == NULL) {
+		ret = -EAGAIN;
+		goto out;
+	}
+
+out:
+	k_mutex_unlock(&data->lock);
+	return ret;
+}
+
+ISR_DIRECT_DECLARE(stm32_venc_isr)
+{
+	struct stm32_venc_ewl *inst = &ewl_instance;
+	const struct stm32_venc_config *config = inst->config;
+	uint32_t hw_handshake_status =
+		IS_BIT_SET(sys_read32(config->reg + BASE_HEncInstantInput), LOW_LATENCY_HW_ITF_EN);
+	uint32_t irq_status = sys_read32(config->reg + BASE_HEncIRQ);
+
+	inst->irq_status = irq_status;
+	inst->irq_cnt++;
+
+	if (hw_handshake_status == 0 && (irq_status & ASIC_STATUS_FUSE) != 0) {
+		sys_write32(ASIC_STATUS_FUSE | ASIC_IRQ_LINE, config->reg + BASE_HEncIRQ);
+		/* read back the IRQ status to update its value */
+		irq_status = sys_read32(config->reg + BASE_HEncIRQ);
+	}
+
+	if (irq_status != 0U) {
+		/* status flag is raised,
+		 * clear the ones that the IRQ needs to clear
+		 * and signal to EWLWaitHwRdy
+		 */
+		sys_write32(ASIC_STATUS_SLICE_READY | ASIC_IRQ_LINE, config->reg + BASE_HEncIRQ);
+	}
+
+	k_sem_give(&inst->complete);
+
+	return 0;
+}
+
+#define VENC_FORMAT_CAP(pixfmt)                \
+	{                                      \
+		.pixelformat = pixfmt,         \
+		.width_min = 48,               \
+		.width_max = 1920,             \
+		.height_min = 48,              \
+		.height_max = 1088,            \
+		.width_step = 16,              \
+		.height_step = 16,             \
+	}
+
+static const struct video_format_cap in_fmts[] = {
+	VENC_FORMAT_CAP(VIDEO_PIX_FMT_NV12),
+	VENC_FORMAT_CAP(VIDEO_PIX_FMT_RGB565),
+	{0},
+};
+
+static const struct video_format_cap out_fmts[] = {
+	VENC_FORMAT_CAP(VIDEO_PIX_FMT_H264),
+	{0},
+};
+
+static int stm32_venc_get_caps(const struct device *dev, struct video_caps *caps)
+{
+	if (caps->type == VIDEO_BUF_TYPE_INPUT) {
+		caps->format_caps = in_fmts;
+	} else {
+		caps->format_caps = out_fmts;
+	}
+
+	/* VENC produces full frames */
+	caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT;
+	caps->min_vbuf_count = 1;
+
+	return 0;
+}
+
+static DEVICE_API(video, stm32_venc_driver_api) = {
+	.set_format = stm32_venc_set_fmt,
+	.get_format = stm32_venc_get_fmt,
+	.set_stream = stm32_venc_set_stream,
+	.enqueue = stm32_venc_enqueue,
+	.dequeue = stm32_venc_dequeue,
+	.get_caps = stm32_venc_get_caps,
+};
+
+static void stm32_venc_irq_config_func(const struct device *dev)
+{
+	IRQ_DIRECT_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), stm32_venc_isr, 0);
+	irq_enable(DT_INST_IRQN(0));
+}
+
+static struct stm32_venc_data stm32_venc_data_0 = {};
+
+static const struct stm32_venc_config stm32_venc_config_0 = {
+	.reg = DT_INST_REG_ADDR(0),
+	.pclken = {.bus = DT_INST_CLOCKS_CELL(0, bus), .enr = DT_INST_CLOCKS_CELL(0, bits)},
+	.reset = RESET_DT_SPEC_INST_GET_BY_IDX(0, 0),
+	.irq_config = stm32_venc_irq_config_func,
+};
+
+static void RISAF_Config(void)
+{
+	/* Define and initialize the master configuration structure */
+	RIMC_MasterConfig_t RIMC_master = {0};
+
+	/* Enable the clock for the RIFSC (RIF Security Controller) */
+	__HAL_RCC_RIFSC_CLK_ENABLE();
+
+	RIMC_master.MasterCID = RIF_CID_1;
+	RIMC_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV;
+
+	/* Configure the master attributes for the video encoder peripheral (VENC) */
+	HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_VENC, &RIMC_master);
+
+	/* Set the secure and privileged attributes for the VENC as a slave */
+	HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_VENC,
+					      RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV);
+}
+
+static int stm32_venc_init(const struct device *dev)
+{
+	const struct stm32_venc_config *config = dev->config;
+	struct stm32_venc_data *data = dev->data;
+	int err;
+
+	/* Enable VENC clock */
+	err = stm32_venc_enable_clock(dev);
+	if (err < 0) {
+		LOG_ERR("clock enabling failed.");
+		return err;
+	}
+
+	/* Reset VENC */
+	if (!device_is_ready(config->reset.dev)) {
+		LOG_ERR("reset controller not ready");
+		return -ENODEV;
+	}
+	reset_line_toggle_dt(&config->reset);
+
+	data->dev = dev;
+	k_mutex_init(&data->lock);
+	k_fifo_init(&data->in_fifo_in);
+	k_fifo_init(&data->in_fifo_out);
+	k_fifo_init(&data->out_fifo_in);
+	k_fifo_init(&data->out_fifo_out);
+
+	/* Run IRQ init */
+	config->irq_config(dev);
+
+	RISAF_Config();
+
+	LOG_DBG("CPU frequency    : %d", HAL_RCC_GetCpuClockFreq() / 1000000);
+	LOG_DBG("sysclk frequency : %d", HAL_RCC_GetSysClockFreq() / 1000000);
+	LOG_DBG("pclk5 frequency  : %d", HAL_RCC_GetPCLK5Freq() / 1000000);
+
+	/* default input */
+	data->in_fmt.width = VENC_DEFAULT_WIDTH;
+	data->in_fmt.height = VENC_DEFAULT_HEIGHT;
+	data->in_fmt.pixelformat = VENC_DEFAULT_IN_FMT;
+	data->in_fmt.pitch = data->in_fmt.width;
+
+	/* default output */
+	data->out_fmt.width = VENC_DEFAULT_WIDTH;
+	data->out_fmt.height = VENC_DEFAULT_HEIGHT;
+	data->out_fmt.pixelformat = VENC_DEFAULT_OUT_FMT;
+
+	/* store config for register accesses */
+	ewl_instance.config = config;
+
+	LOG_DBG("%s inited", dev->name);
+
+	return 0;
+}
+
+DEVICE_DT_INST_DEFINE(0, &stm32_venc_init, NULL, &stm32_venc_data_0, &stm32_venc_config_0,
+		      POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &stm32_venc_driver_api);
diff --git a/tests/drivers/build_all/video/testcase.yaml b/tests/drivers/build_all/video/testcase.yaml
index 30430a7..dc8300b 100644
--- a/tests/drivers/build_all/video/testcase.yaml
+++ b/tests/drivers/build_all/video/testcase.yaml
@@ -41,3 +41,6 @@
       - ek_ra8d1/r7fa8d1bhecbd
     extra_args:
       - platform:ek_ra81/r7fa8d1bhecbd:SHIELD="dvp_20pin_ov7670"
+  drivers.video.stm32_venc.build:
+    platform_allow:
+      - stm32n6570_dk/stm32n657xx/sb