[PATCH 2/3] media: allegro-dvt: Add Gen 3 IP stateful decoder driver

Yassine Ouaissa yassine.ouaissa at allegrodvt.com
Sun May 11 07:47:33 PDT 2025


This commit introduces a new allegro-dvt V4L2 stateful decoder driver
for the Gen 3 IP with support for:
- AVC (H.264), HEVC (H.265), and JPEG decoding
- Output formats: NV12, NV16, I420, and P010 for capture

The allegrodvt is added to the vendor-prefixes.yaml.
Update the MAINTAINERS file by adding the new driver directory.

Signed-off-by: Yassine Ouaissa <yassine.ouaissa at allegrodvt.com>
---
 .../devicetree/bindings/vendor-prefixes.yaml  |    2 +-
 MAINTAINERS                                   |    2 +
 drivers/media/platform/allegro-dvt/Kconfig    |    1 +
 drivers/media/platform/allegro-dvt/Makefile   |    1 +
 .../media/platform/allegro-dvt/al300/Kconfig  |   12 +
 .../media/platform/allegro-dvt/al300/Makefile |    6 +
 .../allegro-dvt/al300/al_codec_common.c       |  781 +++++++++
 .../allegro-dvt/al300/al_codec_common.h       |  226 +++
 .../allegro-dvt/al300/al_codec_util.c         |  179 ++
 .../allegro-dvt/al300/al_codec_util.h         |  186 ++
 .../platform/allegro-dvt/al300/al_vdec_drv.c  | 1536 +++++++++++++++++
 .../platform/allegro-dvt/al300/al_vdec_drv.h  |   94 +
 12 files changed, 3025 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/platform/allegro-dvt/al300/Kconfig
 create mode 100644 drivers/media/platform/allegro-dvt/al300/Makefile
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_codec_common.c
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_codec_common.h
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_codec_util.c
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_codec_util.h
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_vdec_drv.c
 create mode 100644 drivers/media/platform/allegro-dvt/al300/al_vdec_drv.h

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 86f6a19b28ae..90bc1ec721ed 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -81,7 +81,7 @@ patternProperties:
     description: Aldec, Inc.
   "^alfa-network,.*":
     description: ALFA Network Inc.
-  "^allegro,.*":
+  "^(allegro|allegrodvt),.*":
     description: Allegro DVT
   "^allegromicro,.*":
     description: Allegro MicroSystems, Inc.
diff --git a/MAINTAINERS b/MAINTAINERS
index d81d2756cb2e..313d08cf90e0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -802,10 +802,12 @@ F:	drivers/platform/x86/dell/alienware-wmi*
 
 ALLEGRO DVT VIDEO IP CORE DRIVER
 M:	Michael Tretter <m.tretter at pengutronix.de>
+M:	Yassine OUAISSA <yassine.ouaissa at allegrodvt.com>
 R:	Pengutronix Kernel Team <kernel at pengutronix.de>
 L:	linux-media at vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/allegro,al5e.yaml
+F:	drivers/media/platform/allegro-dvt/al300
 F:	drivers/media/platform/allegro-dvt/zynqmp
 
 ALLIED VISION ALVIUM CAMERA DRIVER
diff --git a/drivers/media/platform/allegro-dvt/Kconfig b/drivers/media/platform/allegro-dvt/Kconfig
index e9008614c27b..0d01ed0ad08a 100644
--- a/drivers/media/platform/allegro-dvt/Kconfig
+++ b/drivers/media/platform/allegro-dvt/Kconfig
@@ -2,4 +2,5 @@
 
 comment "Allegro DVT media platform drivers"
 
+source "drivers/media/platform/allegro-dvt/al300/Kconfig"
 source "drivers/media/platform/allegro-dvt/zynqmp/Kconfig"
diff --git a/drivers/media/platform/allegro-dvt/Makefile b/drivers/media/platform/allegro-dvt/Makefile
index d2aa6875edcf..c70ca19a47fb 100644
--- a/drivers/media/platform/allegro-dvt/Makefile
+++ b/drivers/media/platform/allegro-dvt/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 
+obj-y += al300/
 obj-y += zynqmp/
diff --git a/drivers/media/platform/allegro-dvt/al300/Kconfig b/drivers/media/platform/allegro-dvt/al300/Kconfig
new file mode 100644
index 000000000000..74bafb286250
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_ALLEGRO_DVT_D300
+	tristate "Allegro DVT Video IP Decode Gen 3"
+	depends on V4L_MEM2MEM_DRIVERS
+	depends on VIDEO_DEV && OF && HAS_DMA
+	select V4L2_MEM2MEM_DEV
+	select VIDEOBUF2_DMA_CONTIG
+	select REGMAP_MMIO
+	help
+	  This is a video4linux2 driver for the Allegro DVT IP Decode Gen 3,
+	  that support codecs : AVC (H.264), HEVC (H.265), and JPEG.
diff --git a/drivers/media/platform/allegro-dvt/al300/Makefile b/drivers/media/platform/allegro-dvt/al300/Makefile
new file mode 100644
index 000000000000..3c50caccb731
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+al300-vdec-objs := al_codec_common.o al_codec_util.o
+al300-vdec-objs += al_vdec_drv.o
+
+obj-$(CONFIG_VIDEO_ALLEGRO_DVT_D300) += al300-vdec.o
diff --git a/drivers/media/platform/allegro-dvt/al300/al_codec_common.c b/drivers/media/platform/allegro-dvt/al300/al_codec_common.c
new file mode 100644
index 000000000000..9c137b1da113
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_codec_common.c
@@ -0,0 +1,781 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Core MCU functionality including firmware loading,
+ * memory allocation, and general MCU interaction interfaces
+ *
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ */
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "al_codec_common.h"
+
+#define AL_CODEC_UID 0x0000
+#define AL_CODEC_UID_ID 0x30AB6E51
+#define AL_CODEC_RESET 0x0010
+#define AL_CODEC_RESET_CMD BIT(0)
+#define AL_CODEC_COMM_MASK BIT(0)
+#define AL_CODEC_IRQ_MASK 0x0014
+#define AL_CODEC_IRQ_STATUS_CLEAR 0x0018
+#define AL_CODEC_IRQ_MCU_2_CPU BIT(30)
+#define AL_CODEC_MCU_CLK 0x0400
+#define AL_CODEC_MCU_CLK_ENABLE BIT(0)
+#define AL_CODEC_MCU_CLK_DISABLE 0
+#define AL_CODEC_MCU_RST 0x0404
+#define AL_CODEC_MCU_RST_ENABLE BIT(0)
+#define AL_CODEC_MCU_RST_DISABLE 0
+#define AL_CODEC_MCU_IRQ 0x040C
+#define AL_CODEC_MCU_BOOT_ADDR 0x0410
+#define AL_CODEC_MCU_APB_ADDR 0x0418
+#define AL_CODEC_MCU_PERIPHERAL_ADDR 0x0428
+#define AL_CODEC_MCU_IP_INTERRUPT_MASK 0x0440
+#define AL_CODEC_INSTRUCTION_DATA_OFFSET 0x0450
+#define AL_CODEC_IP_OFFSET GENMASK(26, 25)
+#define AL_CODEC_APB_MASK GENMASK(26, 0)
+#define AL_CODEC_MAX_ADDR GENMASK_ULL(38, 0)
+
+#define AL_CODEC_MCU_BOOT_RESET_WAIT 2000 /* in us */
+
+/*
+ * struct codec_dma_buf - Allocated dma buffer
+ *
+ * @list: list head for buffer queue
+ * @paddr: physical address of the allcated DMA buffer
+ * @vaddr: virtual address of the allocated DMA buffer
+ * @size: Size of allocated dma memory
+ */
+struct codec_dma_buf {
+	void *vaddr;
+	dma_addr_t paddr;
+	u32 size;
+	struct list_head list;
+};
+
+struct mb_header {
+	u64 start;
+	u64 end;
+} __packed;
+
+struct boot_header {
+	u32 bh_version;
+	u32 fw_version;
+	char model[16];
+	u64 vaddr_start;
+	u64 vaddr_end;
+	u64 vaddr_boot;
+	struct mb_header h2m;
+	struct mb_header m2h;
+	u64 machine_id;
+	/* fill by driver before fw boot */
+	u64 ip_start;
+	u64 ip_end;
+	u64 mcu_clk_rate;
+} __packed;
+
+/* Regmap config */
+static const struct regmap_config al_regmap_config = {
+	.name = "regs",
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = 0xfff,
+	.fast_io = true,
+	.cache_type = REGCACHE_NONE,
+	.can_multi_write = true,
+};
+
+static inline int al_common_read(struct al_common_dev *dev, u32 offset,
+				 u32 *val)
+{
+	return regmap_read(dev->regmap, offset, val);
+}
+
+static inline int al_common_write(struct al_common_dev *dev, u32 offset,
+				  u32 val)
+{
+	return regmap_write(dev->regmap, offset, val);
+}
+
+static inline int al_common_write_multiple(struct al_common_dev *dev,
+					   u32 offset, const void *val,
+					   u32 num_regs)
+{
+	return regmap_bulk_write(dev->regmap, offset, val, num_regs);
+}
+
+static inline int common_update_bits(struct al_common_dev *dev, u32 offset,
+				     u32 mask, u32 val)
+{
+	return regmap_update_bits(dev->regmap, offset, mask, val);
+}
+
+static void common_trigger_mcu_irq(void *arg)
+{
+	struct al_common_dev *dev = arg;
+
+	common_update_bits(dev, AL_CODEC_MCU_IRQ, AL_CODEC_COMM_MASK, BIT(0));
+}
+
+static void common_reset(struct al_common_dev *dev)
+{
+	/* reset ip */
+	common_update_bits(dev, AL_CODEC_RESET, AL_CODEC_COMM_MASK,
+			   AL_CODEC_RESET_CMD);
+
+	/* reset and stop mcu */
+	al_common_write(dev, AL_CODEC_MCU_CLK, AL_CODEC_MCU_CLK_ENABLE);
+	al_common_write(dev, AL_CODEC_MCU_RST, AL_CODEC_MCU_RST_ENABLE);
+	/* time to reset the mct */
+	udelay(AL_CODEC_MCU_BOOT_RESET_WAIT);
+	al_common_write(dev, AL_CODEC_MCU_CLK, AL_CODEC_MCU_CLK_DISABLE);
+
+	common_update_bits(dev, AL_CODEC_MCU_IRQ, AL_CODEC_COMM_MASK, 0);
+	al_common_write(dev, AL_CODEC_MCU_IP_INTERRUPT_MASK, 0);
+
+	udelay(AL_CODEC_MCU_BOOT_RESET_WAIT * 5);
+	common_update_bits(dev, AL_CODEC_MCU_RST, AL_CODEC_COMM_MASK,
+			   AL_CODEC_MCU_RST_DISABLE);
+}
+
+static int common_probe_check_and_setup_hw(struct al_common_dev *dev)
+{
+	unsigned int id;
+	int ret;
+
+	/* Check regmap */
+	if (WARN_ON(!dev->regmap))
+		return -EINVAL;
+
+	ret = al_common_read(dev, AL_CODEC_UID, &id);
+
+	if (ret)
+		return ret;
+
+	if (id != AL_CODEC_UID_ID) {
+		al_codec_err(dev, "bad device id, expected 0x%08x, got 0x%08x",
+			     AL_CODEC_UID_ID, id);
+		return -ENODEV;
+	}
+
+	common_reset(dev);
+	al_common_write(dev, AL_CODEC_IRQ_MASK, AL_CODEC_IRQ_MCU_2_CPU);
+
+	return 0;
+}
+
+static void common_dma_buf_insert(struct al_common_dev *dev,
+				  struct codec_dma_buf *buf)
+{
+	mutex_lock(&dev->buf_lock);
+	list_add(&buf->list, &dev->alloc_buffers);
+	mutex_unlock(&dev->buf_lock);
+}
+
+static void common_dma_buf_remove(struct al_common_dev *dev,
+				  struct codec_dma_buf *buf)
+{
+	mutex_lock(&dev->buf_lock);
+	list_del(&buf->list);
+	mutex_unlock(&dev->buf_lock);
+}
+
+static struct codec_dma_buf *common_dma_buf_lookup(struct al_common_dev *dev,
+						   dma_addr_t buf_paddr)
+{
+	struct codec_dma_buf *buf = NULL;
+
+	mutex_lock(&dev->buf_lock);
+	list_for_each_entry(buf, &dev->alloc_buffers, list)
+		if (likely(buf->paddr == buf_paddr))
+			break;
+
+	mutex_unlock(&dev->buf_lock);
+
+	return list_entry_is_head(buf, &dev->alloc_buffers, list) ? NULL : buf;
+}
+
+static void common_dma_buf_cleanup(struct al_common_dev *dev)
+{
+	struct codec_dma_buf *buf, *tmp;
+
+	mutex_lock(&dev->buf_lock);
+	list_for_each_entry_safe(buf, tmp, &dev->alloc_buffers, list) {
+		dma_free_coherent(&dev->pdev->dev, buf->size, buf->vaddr,
+				  buf->paddr);
+		list_del(&buf->list);
+		kfree(buf);
+	}
+	mutex_unlock(&dev->buf_lock);
+}
+
+static void *common_dma_alloc(struct al_common_dev *dev, size_t size,
+			      dma_addr_t *paddr, gfp_t flag)
+{
+	void *vaddr;
+
+	vaddr = dma_alloc_coherent(&dev->pdev->dev, size, paddr, flag);
+
+	if (!vaddr)
+		return NULL;
+
+	/* PADDR <= (2^39 - 1) (39-bit MCU PADDR) */
+	if ((*paddr + size) > AL_CODEC_MAX_ADDR) {
+		al_codec_err(dev, "mem check failed for 0x%16llx of size %zu",
+			     *paddr, size);
+		dma_free_coherent(&dev->pdev->dev, size, vaddr, *paddr);
+		return NULL;
+	}
+
+	return vaddr;
+}
+
+void al_common_remove(struct al_common_dev *dev)
+{
+	common_dma_buf_cleanup(dev);
+	if (dev->fw_cpu_mem)
+		dma_free_coherent(&dev->pdev->dev, dev->fw_size,
+				  dev->fw_cpu_mem, dev->fw_phys_addr);
+
+	/* reset device */
+	common_reset(dev);
+}
+
+static void handle_alloc_memory_req(struct al_common_dev *dev,
+				    struct msg_itf_header *hdr)
+{
+	struct msg_itf_alloc_mem_reply_full reply;
+	struct msg_itf_alloc_mem_req req;
+	struct codec_dma_buf *buf;
+	int ret;
+
+	reply.reply.phyAddr = 0;
+	ret = al_common_get_data(dev, (char *)&req, hdr->payload_len);
+	if (ret) {
+		al_codec_err(dev, "Unable to get cma req");
+		return;
+	}
+
+	buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+	if (!buf)
+		goto send_reply;
+
+	buf->size = req.uSize;
+	buf->vaddr = common_dma_alloc(dev, req.uSize, &buf->paddr, GFP_KERNEL);
+	if (!buf->vaddr)
+		goto send_reply;
+
+	reply.reply.phyAddr = (u64)buf->paddr;
+	common_dma_buf_insert(dev, buf);
+
+send_reply:
+	reply.hdr.type = MSG_ITF_TYPE_ALLOC_MEM_REPLY;
+	/* both fields embed info need to finish request */
+	reply.hdr.drv_ctx_hdl = hdr->drv_ctx_hdl;
+	reply.hdr.drv_cmd_hdl = hdr->drv_cmd_hdl;
+	reply.hdr.payload_len = sizeof(reply.reply);
+
+	ret = al_common_send(dev, &reply.hdr);
+	if (ret) {
+		al_codec_err(dev, "Unable to reply to cma alloc");
+		common_dma_buf_remove(dev, buf);
+	}
+}
+
+static void handle_free_memory_req(struct al_common_dev *dev,
+				   struct msg_itf_header *hdr)
+{
+	struct msg_itf_free_mem_reply_full reply;
+	struct msg_itf_free_mem_req req;
+	struct codec_dma_buf *buf;
+	int ret;
+
+	reply.reply.ret = -1;
+	ret = al_common_get_data(dev, (char *)&req, hdr->payload_len);
+	if (ret) {
+		al_codec_err(dev, "Unable to put cma req");
+		return;
+	}
+
+	buf = common_dma_buf_lookup(dev, req.phyAddr);
+	al_codec_dbg(3, "req.phyAddr = %p => %p, Size %d",
+		     (void *)(long)req.phyAddr, buf, buf->size);
+	if (!buf) {
+		al_codec_err(dev, "Unable to get dma handle for %p",
+			     (void *)(long)req.phyAddr);
+		reply.reply.ret = -EINVAL;
+		goto send_reply;
+	}
+
+	dma_free_coherent(&dev->pdev->dev, buf->size, buf->vaddr, buf->paddr);
+	common_dma_buf_remove(dev, buf);
+	reply.reply.ret = 0;
+
+send_reply:
+	reply.hdr.type = MSG_ITF_TYPE_FREE_MEM_REPLY;
+	/* both fields embed info need to hinish request */
+	reply.hdr.drv_ctx_hdl = hdr->drv_ctx_hdl;
+	reply.hdr.drv_cmd_hdl = hdr->drv_cmd_hdl;
+	reply.hdr.payload_len = sizeof(reply.reply);
+
+	ret = al_common_send(dev, &reply.hdr);
+	if (ret)
+		al_codec_err(dev, "Unable to reply to cma free");
+}
+
+static void handle_mcu_console_print(struct al_common_dev *dev,
+				     struct msg_itf_header *hdr)
+{
+#if defined(DEBUG)
+	struct msg_itf_write_req *req;
+	int ret;
+
+	/* one more byte to be sure to have a zero terminated string */
+	req = kzalloc(hdr->payload_len + 1, GFP_KERNEL);
+	if (!req) {
+		al_common_skip_data(dev, hdr->payload_len);
+		al_codec_err(dev, "Unable to alloc memory");
+		return;
+	}
+
+	ret = al_codec_msg_get_data(&dev->mb_m2h, (char *)req,
+				    hdr->payload_len);
+	if (ret) {
+		al_codec_err(dev, "Unable to get request");
+		kfree(req);
+		return;
+	}
+
+	al_mcu_dbg("%s", (char *)(req + 1));
+	kfree(req);
+#else
+	al_common_skip_data(dev, hdr->payload_len);
+#endif
+}
+
+static void process_one_message(struct al_common_dev *dev,
+				struct msg_itf_header *hdr)
+{
+	if (hdr->type == MSG_ITF_TYPE_ALLOC_MEM_REQ)
+		handle_alloc_memory_req(dev, hdr);
+	else if (hdr->type == MSG_ITF_TYPE_FREE_MEM_REQ)
+		handle_free_memory_req(dev, hdr);
+	else if (hdr->type == MSG_ITF_TYPE_WRITE_REQ)
+		handle_mcu_console_print(dev, hdr);
+	else if (hdr->type == MSG_ITF_TYPE_MCU_ALIVE)
+		complete(&dev->completion);
+	else
+		dev->process_msg_cb(dev->cb_arg, hdr);
+}
+
+static void common_reply_handle(struct al_common_dev *dev)
+{
+	struct msg_itf_header hdr;
+	int ret;
+
+	while (1) {
+		ret = al_codec_msg_get_header(&dev->mb_m2h, &hdr);
+		if (ret)
+			break;
+
+		process_one_message(dev, &hdr);
+	}
+}
+
+static irqreturn_t common_irq_handler(int irq, void *data)
+{
+	struct al_common_dev *dev = data;
+
+	/* poll all messages */
+	common_reply_handle(dev);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t common_hardirq_handler(int irq, void *data)
+{
+	struct al_common_dev *dev = data;
+	u32 irq_status;
+	int ret;
+
+	ret = al_common_read(dev, AL_CODEC_IRQ_STATUS_CLEAR, &irq_status);
+	if (ret || !irq_status)
+		return IRQ_NONE;
+
+	al_common_write(dev, AL_CODEC_IRQ_STATUS_CLEAR, AL_CODEC_IRQ_MCU_2_CPU);
+
+	return IRQ_WAKE_THREAD;
+}
+
+static inline u64 get_machine_boot_addr(struct al_common_dev *dev,
+					struct boot_header *bh)
+{
+	return dev->fw_phys_addr + bh->vaddr_boot - bh->vaddr_start;
+}
+
+static int common_start_fw(struct al_common_dev *dev, struct boot_header *bh)
+{
+	u64 boot_addr;
+	u32 regbuf[2] = { 0 };
+	int ret;
+
+	boot_addr = get_machine_boot_addr(dev, bh);
+	regbuf[0] = upper_32_bits(boot_addr);
+	regbuf[1] = lower_32_bits(boot_addr);
+
+	ret = al_common_write_multiple(dev, AL_CODEC_MCU_BOOT_ADDR, regbuf,
+				       ARRAY_SIZE(regbuf));
+	if (ret) {
+		al_codec_err(dev, "Unable to set the MCU boot address");
+		return ret;
+	}
+
+	al_codec_dbg(3, "boot_addr = %pad\n", &boot_addr);
+
+	/* Enable the MCU clock */
+	ret = common_update_bits(dev, AL_CODEC_MCU_CLK, AL_CODEC_COMM_MASK,
+				 AL_CODEC_MCU_CLK_ENABLE);
+
+	if (ret) {
+		al_codec_err(dev, " failed to enable the MCU clock");
+		return ret;
+	}
+
+	return !wait_for_completion_timeout(&dev->completion, HZ);
+}
+
+static inline u64 common_get_periph_addr(struct al_common_dev *dev)
+{
+	struct resource *res;
+
+	res = platform_get_resource_byname(dev->pdev, IORESOURCE_MEM, "apb");
+	if (!res) {
+		al_codec_err(dev, "Unable to find APB start address");
+		return 0;
+	}
+
+	if (res->start & AL_CODEC_APB_MASK) {
+		al_codec_err(dev, "APB start address is invalid");
+		return 0;
+	}
+
+	return res->start;
+}
+
+static int common_alloc_and_setup_fw_memory(struct al_common_dev *dev,
+					    struct boot_header *bh)
+{
+	u64 periph_addr;
+	u32 regbuf[4] = { 0 };
+	int ret;
+
+	dev->fw_cpu_mem = common_dma_alloc(dev, dev->fw_size,
+					   &dev->fw_phys_addr, GFP_KERNEL);
+	if (!dev->fw_cpu_mem)
+		return -ENOMEM;
+
+	ret = al_common_write_multiple(dev, AL_CODEC_INSTRUCTION_DATA_OFFSET,
+				       regbuf, ARRAY_SIZE(regbuf));
+	if (ret) {
+		al_codec_err(dev, "failed to set the (i/d)Cache address");
+		return ret;
+	}
+
+	periph_addr = common_get_periph_addr(dev);
+
+	regbuf[0] = upper_32_bits(periph_addr);
+	regbuf[1] = lower_32_bits(periph_addr);
+	ret = al_common_write_multiple(dev, AL_CODEC_MCU_PERIPHERAL_ADDR,
+				       regbuf, 2);
+	if (ret) {
+		al_codec_err(dev, "failed to set the periph address");
+		return ret;
+	}
+
+	al_codec_dbg(3, "fw phys_addr = %pad", &dev->fw_phys_addr);
+	al_codec_dbg(3, "fw virt_addr = 0x%p", dev->fw_cpu_mem);
+	al_codec_dbg(3, "periph_addr  = %pad", &periph_addr);
+
+	return 0;
+}
+
+static void common_fw_callback(const struct firmware *fw, void *context)
+{
+	struct al_common_dev *dev = context;
+	struct boot_header bh, *bhw;
+	u64 periph_addr = 0;
+	int ret;
+
+	if (!fw) {
+		al_codec_err(dev, "The MCU firmware not found!");
+		return;
+	}
+
+	/* Copy the Firmware header */
+	memcpy(&bh, fw->data, sizeof(bh));
+	dev->fw_size = bh.vaddr_end - bh.vaddr_start;
+
+	if (bh.bh_version < AL_BOOT_VERSION(2, 0, 0) ||
+	    bh.bh_version >= AL_BOOT_VERSION(3, 0, 0)) {
+		al_codec_err(dev, "bad boot header version");
+		goto fw_release;
+	}
+
+	if (WARN(bh.machine_id != 2, "Wrong machine ID"))
+		goto fw_release;
+
+	periph_addr = common_get_periph_addr(dev);
+
+	if (!periph_addr) {
+		al_codec_err(dev, "Unable to get the periph addr");
+		goto fw_release;
+	}
+
+	al_codec_dbg(3, "bh version     = 0x%08x", bh.bh_version);
+	al_codec_dbg(3, "fw version     = 0x%08x", bh.fw_version);
+	al_codec_dbg(3, "fw model       = %s", bh.model);
+	al_codec_dbg(3, "vaddress start = 0x%016llx", bh.vaddr_start);
+	al_codec_dbg(3, "vaddress end   = 0x%016llx", bh.vaddr_end);
+	al_codec_dbg(3, "boot address   = 0x%016llx", bh.vaddr_boot);
+	al_codec_dbg(3, "machineid      = %lld", bh.machine_id);
+	al_codec_dbg(3, "periph address = 0x%016llx", periph_addr);
+
+	ret = common_alloc_and_setup_fw_memory(dev, &bh);
+	if (ret) {
+		al_codec_err(dev, "out of memory %d", ret);
+		goto fw_release;
+	}
+
+	al_codec_dbg(2, "Copy %zu bytes of fw", fw->size);
+	memcpy(dev->fw_cpu_mem, fw->data, fw->size);
+
+	al_codec_mb_init(&dev->mb_h2m,
+			 dev->fw_cpu_mem + bh.h2m.start - bh.vaddr_start,
+			 bh.h2m.end - bh.h2m.start, MB_IFT_MAGIC_H2M);
+	al_codec_mb_init(&dev->mb_m2h,
+			 dev->fw_cpu_mem + bh.m2h.start - bh.vaddr_start,
+			 bh.m2h.end - bh.m2h.start, MB_IFT_MAGIC_M2H);
+
+	/* give fw information about registers location */
+	bhw = dev->fw_cpu_mem;
+	bhw->ip_start = periph_addr + AL_CODEC_IP_OFFSET;
+	bhw->ip_end = bhw->ip_start + resource_size(&dev->regs_info);
+
+	al_codec_dbg(3, "ip start     = 0x%016llx", bhw->ip_start);
+	al_codec_dbg(3, "ip end       =   0x%016llx", bhw->ip_end);
+
+	if (dev->mcu_clk_rate)
+		bhw->mcu_clk_rate = dev->mcu_clk_rate;
+
+	al_codec_dbg(3, "mcu clock rate is %llu", bhw->mcu_clk_rate);
+
+	ret = common_start_fw(dev, &bh);
+	if (ret) {
+		al_codec_err(dev, "fw start has failed");
+		goto fw_release;
+	}
+
+	dev_info(&dev->pdev->dev, "mcu has boot successfully !\n");
+	dev->fw_ready_cb(dev->cb_arg);
+
+fw_release:
+	release_firmware(fw);
+}
+
+static int common_firmware_request_nowait(struct al_common_dev *dev)
+{
+	al_codec_dbg(2, "request fw %s", dev->fw_name);
+
+	return request_firmware_nowait(THIS_MODULE, true, dev->fw_name,
+				       &dev->pdev->dev, GFP_KERNEL, dev,
+				       common_fw_callback);
+}
+
+static int common_setup_dma(struct al_common_dev *dev)
+{
+	int ret;
+
+	/* setup dma memory mask */
+	ret = dma_set_mask_and_coherent(&dev->pdev->dev, DMA_BIT_MASK(64));
+	if (ret) {
+		al_codec_err(dev, "failed to set dma");
+		return -EINVAL;
+	}
+
+	/* Try to use reserved memory if we got one */
+	ret = of_reserved_mem_device_init(&dev->pdev->dev);
+	if (ret && ret != ENODEV)
+		dev_warn(&dev->pdev->dev,
+			 "No reserved memory, use cma instead\n");
+
+	return 0;
+}
+
+int al_common_probe(struct platform_device *pdev, struct al_common_dev *dev)
+{
+	struct resource *res;
+	void __iomem *regs;
+	struct clk *clk;
+	int irq;
+	int ret;
+
+	dev->pdev = pdev;
+	dev->mcu_clk_rate = 0;
+	mutex_init(&dev->buf_lock);
+	INIT_LIST_HEAD(&dev->alloc_buffers);
+	init_completion(&dev->completion);
+
+	clk = devm_clk_get(&pdev->dev, "mcu_clk");
+	if (IS_ERR(clk))
+		al_codec_dbg(1, "Cannot get mcu clock, keep the default value");
+	else if (clk_prepare_enable(clk))
+		al_codec_dbg(
+			1,
+			"Failed to enable the mcu clk, keep the default value");
+	else
+		dev->mcu_clk_rate = clk_get_rate(clk);
+
+	/* setup dma memory */
+	ret = common_setup_dma(dev);
+	if (ret)
+		return ret;
+
+	/* Hw registers */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+	if (!res) {
+		al_codec_err(dev, "regs resource missing from device tree");
+		return -EINVAL;
+	}
+
+	dev->regs_info = *res;
+
+	regs = devm_ioremap_resource(&pdev->dev, res);
+	if (!regs) {
+		al_codec_err(dev, "failed to map registers");
+		return -ENOMEM;
+	}
+
+	dev->regmap =
+		devm_regmap_init_mmio(&pdev->dev, regs, &al_regmap_config);
+
+	if (IS_ERR(dev->regmap)) {
+		al_codec_err(dev, "init regmap failed");
+		return PTR_ERR(dev->regmap);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		al_codec_err(dev, "Failed to get IRQ");
+		return -EINVAL;
+	}
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, common_hardirq_handler,
+					common_irq_handler, IRQF_SHARED,
+					dev_name(&pdev->dev), dev);
+	if (ret) {
+		al_codec_err(dev, "Unable to register irq handler");
+		return ret;
+	}
+
+	ret = common_probe_check_and_setup_hw(dev);
+	if (ret) {
+		al_codec_err(dev, "Unable to setup hw");
+		return ret;
+	}
+
+	/* ok so request the fw */
+	ret = common_firmware_request_nowait(dev);
+	if (ret) {
+		al_codec_err(dev, "failed to request firmware");
+		return ret;
+	}
+
+	return 0;
+}
+
+int al_common_send(struct al_common_dev *dev, struct msg_itf_header *hdr)
+{
+	return al_codec_msg_send(&dev->mb_h2m, hdr, common_trigger_mcu_irq,
+				 dev);
+}
+
+int al_common_send_req_reply(struct al_codec_dev *dev,
+			     struct list_head *cmd_list,
+			     struct msg_itf_header *hdr,
+			     struct al_common_mcu_req *req)
+{
+	struct al_codec_cmd *cmd = NULL;
+	int ret;
+
+	hdr->drv_cmd_hdl = 0;
+
+	if (req->reply_size && req->reply) {
+		cmd = al_codec_cmd_create(req->reply_size);
+		if (!cmd)
+			return -ENOMEM;
+
+		hdr->drv_cmd_hdl = al_virt_to_phys(cmd);
+	}
+
+	hdr->drv_ctx_hdl = req->pCtx;
+	hdr->type = req->req_type;
+	hdr->payload_len = req->req_size;
+
+	/* Add the list to the cmd list */
+	if (cmd)
+		list_add(&cmd->list, cmd_list);
+
+	ret = al_common_send(&dev->common, hdr);
+	if (ret)
+		goto remove_cmd;
+
+	al_v4l2_dbg(3, "Send req to mcu %d : %ld ", req->req_type,
+		    req->req_size);
+
+	if (!cmd)
+		return 0;
+
+	ret = wait_for_completion_timeout(&cmd->done, 5 * HZ);
+	if (ret <= 0) {
+		al_v4l2_err(dev, "cmd %p has %d (%s)", cmd, ret,
+			    (ret == 0) ? "failed" : "timedout");
+		ret = -ETIMEDOUT;
+		goto remove_cmd;
+	}
+
+	ret = 0;
+	memcpy(req->reply, cmd->reply, req->reply_size);
+
+remove_cmd:
+
+	if (cmd) {
+		list_del(&cmd->list);
+		al_codec_cmd_put(cmd);
+	}
+	return ret;
+}
+
+bool al_common_mcu_is_alive(struct al_codec_dev *dev)
+{
+	static const struct msg_itf_header hdr = {
+		.type = MSG_ITF_TYPE_MCU_ALIVE,
+		.payload_len = 0,
+	};
+	struct al_common_dev *cdev = &dev->common;
+	int ret;
+
+	ret = al_common_send(cdev, (struct msg_itf_header *)&hdr);
+	if (ret)
+		return false;
+
+	ret = wait_for_completion_timeout(&cdev->completion, 5 * HZ);
+	if (ret <= 0)
+		return false;
+
+	return true;
+}
diff --git a/drivers/media/platform/allegro-dvt/al300/al_codec_common.h b/drivers/media/platform/allegro-dvt/al300/al_codec_common.h
new file mode 100644
index 000000000000..aa2bb3d8b7b6
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_codec_common.h
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ */
+
+#ifndef __AL_CODEC_COMMON__
+#define __AL_CODEC_COMMON__
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <media/v4l2-device.h>
+
+#include "al_codec_util.h"
+
+#define fh_to_ctx(ptr, type) container_of(ptr, type, fh)
+
+enum {
+	MSG_ITF_TYPE_CREATE_INST_REQ = MSG_ITF_TYPE_NEXT_REQ,
+	MSG_ITF_TYPE_DESTROY_INST_REQ,
+	MSG_ITF_TYPE_PUSH_BITSTREAM_BUFFER_REQ,
+	MSG_ITF_TYPE_PUT_DISPLAY_PICTURE_REQ,
+	MSG_ITF_TYPE_FLUSH_REQ,
+	MSG_ITF_TYPE_INFO_REQ,
+	MSG_ITF_TYPE_CREATE_INST_REPLY = MSG_ITF_TYPE_NEXT_REPLY,
+	MSG_ITF_TYPE_DESTROY_INST_REPLY,
+	MSG_ITF_TYPE_PUSH_BITSTREAM_BUFFER_REPLY,
+	MSG_ITF_TYPE_PUT_DISPLAY_PICTURE_REPLY,
+	MSG_ITF_TYPE_FLUSH_REPLY,
+	MSG_ITF_TYPE_INFO_REPLY,
+	MSG_ITF_TYPE_EVT_ERROR = MSG_ITF_TYPE_NEXT_EVT,
+};
+
+struct msg_itf_write_req {
+	u32 fd;
+	u32 len;
+	/* payload follow */
+} __packed;
+DECLARE_FULL_REQ(msg_itf_write_req);
+
+struct msg_itf_free_mem_req {
+	phys_addr_t phyAddr;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_free_mem_req);
+
+struct msg_itf_alloc_mem_req {
+	u64 uSize;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_alloc_mem_req);
+
+struct msg_itf_alloc_mem_reply {
+	phys_addr_t phyAddr;
+} __packed;
+DECLARE_FULL_REPLY(msg_itf_alloc_mem_reply);
+
+struct msg_itf_free_mem_reply {
+	s64 ret;
+};
+DECLARE_FULL_REPLY(msg_itf_free_mem_reply);
+
+struct msg_itf_create_codec_reply {
+	phys_addr_t hCodec;
+	s32 ret;
+} __packed;
+DECLARE_FULL_REPLY(msg_itf_create_codec_reply);
+
+struct msg_itf_destroy_codec_req {
+	phys_addr_t hCodec;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_destroy_codec_req);
+
+/*
+ * Note : no need to know the status of this request
+ * The codec should be destroyed, in case of the mcu
+ * hasn't received any request with the codec handler
+ */
+struct msg_itf_destroy_codec_reply {
+	u32 unused;
+} __packed;
+DECLARE_FULL_REPLY(msg_itf_destroy_codec_reply);
+
+struct al_buffer_meta {
+	u64 timestamp;
+	struct v4l2_timecode timecode;
+	bool last;
+};
+
+struct msg_itf_push_src_buf_req {
+	phys_addr_t hCodec;
+	phys_addr_t bufferHandle;
+	phys_addr_t phyAddr;
+	u64 size;
+	struct al_buffer_meta meta;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_push_src_buf_req);
+
+struct msg_itf_push_dst_buf_req {
+	phys_addr_t hCodec;
+	phys_addr_t bufferHandle;
+	phys_addr_t phyAddr;
+	u64 size;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_push_dst_buf_req);
+
+struct msg_itf_push_buffer_req {
+	phys_addr_t hCodec;
+	phys_addr_t bufferHandle;
+	phys_addr_t phyAddr;
+	u64 size;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_push_buffer_req);
+
+struct msg_itf_push_buffer_reply {
+	s32 res;
+} __packed;
+DECLARE_FULL_REPLY(msg_itf_push_buffer_reply);
+
+struct msg_itf_info_req {
+	u64 unused;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_info_req);
+
+struct msg_itf_flush_req {
+	phys_addr_t hCodec;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_flush_req);
+
+struct msg_itf_flush_reply {
+	int32_t unused;
+} __packed;
+DECLARE_FULL_REPLY(msg_itf_flush_reply);
+
+struct msg_itf_evt_error {
+	uint32_t errno;
+} __packed;
+DECLARE_FULL_EVENT(msg_itf_evt_error);
+
+struct al_match_data {
+	const char *fw_name;
+};
+
+struct al_common_mcu_req {
+	phys_addr_t pCtx;
+	int req_type;
+	size_t req_size;
+	size_t reply_size;
+	void *reply;
+} __packed;
+
+struct al_common_dev {
+	struct platform_device *pdev;
+	void *fw_cpu_mem;
+	phys_addr_t fw_phys_addr;
+	const char *fw_name;
+	size_t fw_size;
+	unsigned long mcu_clk_rate;
+
+	struct al_codec_mb mb_h2m;
+	struct al_codec_mb mb_m2h;
+	struct completion completion;
+
+	struct resource regs_info;
+	struct regmap *regmap;
+
+	struct list_head alloc_buffers;
+	struct mutex buf_lock;
+
+	/* callbacks set by client before common_probe */
+	void *cb_arg;
+	void (*process_msg_cb)(void *cb_arg, struct msg_itf_header *hdr);
+	void (*fw_ready_cb)(void *cb_arg);
+};
+
+struct al_codec_dev {
+	struct v4l2_device v4l2_dev;
+	struct v4l2_m2m_dev *m2m_dev;
+	struct video_device video_dev;
+	struct al_common_dev common;
+
+	/* mutex protecting vb2_queue structure */
+	struct mutex lock;
+
+	/* list of ctx (aka decoder) */
+	struct mutex ctx_mlock;
+	struct list_head ctx_q_list;
+	int is_video_init_done;
+
+	struct completion res_done;
+	/* list of cap/out supported formats */
+	struct list_head codec_q_list;
+	struct al_codec_cmd *codec_info_cmd;
+};
+
+static inline int al_common_get_header(struct al_common_dev *dev,
+				       struct msg_itf_header *hdr)
+{
+	return al_codec_msg_get_header(&dev->mb_m2h, hdr);
+}
+
+static inline int al_common_get_data(struct al_common_dev *dev, char *data,
+				     int len)
+{
+	return al_codec_msg_get_data(&dev->mb_m2h, data, len);
+}
+
+static inline int al_common_skip_data(struct al_common_dev *dev, int len)
+{
+	return al_common_get_data(dev, NULL, len);
+}
+
+int al_common_send(struct al_common_dev *dev, struct msg_itf_header *hdr);
+int al_common_send_req_reply(struct al_codec_dev *dev,
+			     struct list_head *cmd_list,
+			     struct msg_itf_header *hdr,
+			     struct al_common_mcu_req *req);
+bool al_common_mcu_is_alive(struct al_codec_dev *dev);
+
+int al_common_probe(struct platform_device *pdev, struct al_common_dev *dev);
+void al_common_remove(struct al_common_dev *dev);
+
+#endif /*__AL_CODEC_COMMON__*/
diff --git a/drivers/media/platform/allegro-dvt/al300/al_codec_util.c b/drivers/media/platform/allegro-dvt/al300/al_codec_util.c
new file mode 100644
index 000000000000..eb468e467747
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_codec_util.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mailbox communication utilities for command creation
+ * and message exchange with the MCU
+ *
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ */
+
+#include <asm-generic/errno.h>
+#include <linux/errno.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/string.h>
+
+#include "al_codec_util.h"
+
+static int al_get_used_space(struct al_codec_mb *mb)
+{
+	u32 head = mb->hdr->head;
+	u32 tail = mb->hdr->tail;
+
+	return head >= tail ? head - tail : mb->size - (tail - head);
+}
+
+static int al_get_free_space(struct al_codec_mb *mb)
+{
+	return mb->size - al_get_used_space(mb) - 1;
+}
+
+static int al_has_enough_space(struct al_codec_mb *mb, int len)
+{
+	return al_get_free_space(mb) >= len;
+}
+
+static inline void al_copy_to_mb(struct al_codec_mb *mb, char *data, int len)
+{
+	u32 head = mb->hdr->head;
+	int copy_len = min(mb->size - head, (unsigned int)len);
+	int copied_len = len;
+
+	memcpy(&mb->data[head], data, copy_len);
+	len -= copy_len;
+	if (len)
+		memcpy(&mb->data[0], &data[copy_len], len);
+
+	/* Make sure that all messages are written before updating the head */
+	dma_wmb();
+	mb->hdr->head = (head + copied_len) % mb->size;
+	/* Make sure that the head is updated in DDR instead of cache */
+	dma_wmb();
+}
+
+static inline void al_copy_from_mb(struct al_codec_mb *mb, char *data, int len)
+{
+	u32 tail = mb->hdr->tail;
+	int copy_len = min(mb->size - tail, (unsigned int)len);
+	int copied_len = len;
+
+	if (!data)
+		goto update_tail;
+
+	memcpy(data, &mb->data[tail], copy_len);
+	len -= copy_len;
+	if (len)
+		memcpy(&data[copy_len], &mb->data[0], len);
+
+update_tail:
+	mb->hdr->tail = (tail + copied_len) % mb->size;
+	/* Make sure that the head is updated in DDR instead of cache */
+	dma_wmb();
+}
+
+static int al_codec_mb_send(struct al_codec_mb *mb, char *data, int len)
+{
+	if (!al_has_enough_space(mb, len))
+		return -ENOMEM;
+
+	al_copy_to_mb(mb, data, len);
+
+	return 0;
+}
+
+static int al_codec_mb_receive(struct al_codec_mb *mb, char *data, int len)
+{
+	if (al_get_used_space(mb) < len)
+		return -ENOMEM;
+
+	al_copy_from_mb(mb, data, len);
+
+	return 0;
+}
+
+void al_codec_mb_init(struct al_codec_mb *mb, char *addr, int size, u32 magic)
+{
+	mb->hdr = (struct al_mb_itf *)addr;
+	mb->hdr->magic = magic;
+	mb->hdr->version = MB_IFT_VERSION;
+	mb->hdr->head = 0;
+	mb->hdr->tail = 0;
+	mb->data = addr + sizeof(struct al_mb_itf);
+	mb->size = size - sizeof(struct al_mb_itf);
+	mutex_init(&mb->lock);
+}
+
+int al_codec_msg_get_header(struct al_codec_mb *mb, struct msg_itf_header *hdr)
+{
+	return al_codec_mb_receive(mb, (char *)hdr, sizeof(*hdr));
+}
+
+int al_codec_msg_get_data(struct al_codec_mb *mb, char *data, int len)
+{
+	return al_codec_mb_receive(mb, data, len);
+}
+
+int al_codec_msg_send(struct al_codec_mb *mb, struct msg_itf_header *hdr,
+		      void (*trigger)(void *), void *trigger_arg)
+{
+	unsigned long timeout;
+	int ret;
+
+	timeout = jiffies + HZ;
+	mutex_lock(&mb->lock);
+	do {
+		if (time_after(jiffies, timeout)) {
+			mutex_unlock(&mb->lock);
+			return -ETIMEDOUT;
+		}
+		ret = al_codec_mb_send(mb, (char *)hdr,
+				       hdr->payload_len +
+					       sizeof(struct msg_itf_header));
+
+	} while (ret);
+	mutex_unlock(&mb->lock);
+
+	trigger(trigger_arg);
+
+	return 0;
+}
+
+static void al_codec_cmd_cleanup(struct kref *ref)
+{
+	struct al_codec_cmd *cmd =
+		container_of(ref, struct al_codec_cmd, refcount);
+	if (cmd) {
+		kfree(cmd->reply);
+		kfree(cmd);
+	}
+}
+
+void al_codec_cmd_put(struct al_codec_cmd *cmd)
+{
+	if (WARN_ON(!cmd))
+		return;
+
+	kref_put(&cmd->refcount, al_codec_cmd_cleanup);
+}
+
+struct al_codec_cmd *al_codec_cmd_create(int reply_size)
+{
+	struct al_codec_cmd *cmd;
+
+	cmd = kmalloc(sizeof(*cmd), GFP_KERNEL);
+	if (!cmd)
+		return NULL;
+
+	cmd->reply = kmalloc(reply_size, GFP_KERNEL);
+	if (!cmd->reply) {
+		kfree(cmd);
+		return NULL;
+	}
+
+	kref_init(&cmd->refcount);
+	cmd->reply_size = reply_size;
+	init_completion(&cmd->done);
+
+	return cmd;
+}
diff --git a/drivers/media/platform/allegro-dvt/al300/al_codec_util.h b/drivers/media/platform/allegro-dvt/al300/al_codec_util.h
new file mode 100644
index 000000000000..6806820bdb8a
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_codec_util.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ */
+
+#ifndef __AL_CODEC_UTIL__
+#define __AL_CODEC_UTIL__
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/v4l2-common.h>
+
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-v4l2.h>
+
+#define MB_IFT_MAGIC_H2M 0xabcd1230
+#define MB_IFT_MAGIC_M2H 0xabcd1231
+#define MB_IFT_VERSION 0x00010000
+
+#define MAJOR_SHIFT 20
+#define MAJOR_MASK 0xfff
+#define MINOR_SHIFT 8
+#define MINOR_MASK 0xfff
+#define PATCH_SHIFT 0
+#define PATCH_MASK 0xff
+
+/*
+ * AL_BOOT_VERSION() - Version format 32-bit, 12 bits for the major,
+ * the same for minor, 8bits for the patch
+ */
+#define AL_BOOT_VERSION(major, minor, patch)       \
+	((((major) & MAJOR_MASK) << MAJOR_SHIFT) | \
+	 (((minor) & MINOR_MASK) << MINOR_SHIFT) | \
+	 (((patch) & PATCH_MASK) << PATCH_SHIFT))
+
+#define al_phys_to_virt(x) ((void *)(uintptr_t)x)
+#define al_virt_to_phys(x) ((phys_addr_t)(uintptr_t)x)
+
+#define DECLARE_FULL_REQ(s)                \
+	struct s##_full {                  \
+		struct msg_itf_header hdr; \
+		struct s req;              \
+	} __packed
+
+#define DECLARE_FULL_REPLY(s)              \
+	struct s##_full {                  \
+		struct msg_itf_header hdr; \
+		struct s reply;            \
+	} __packed
+
+#define DECLARE_FULL_EVENT(s)              \
+	struct s##_full {                  \
+		struct msg_itf_header hdr; \
+		struct s event;            \
+	} __packed
+
+struct al_mb_itf {
+	u32 magic;
+	u32 version;
+	u32 head;
+	u32 tail;
+} __packed;
+
+struct al_codec_mb {
+	struct al_mb_itf *hdr;
+	struct mutex lock;
+	char *data;
+	int size;
+};
+
+struct al_codec_cmd {
+	struct kref refcount;
+	struct list_head list;
+	struct completion done;
+	int reply_size;
+	void *reply;
+};
+
+#if defined(DEBUG)
+
+extern int debug;
+/* Common logs */
+#define al_codec_err(dev, fmt, args...)                                     \
+	pr_err("[ALG_CODEC][ERROR] %s():%d: " fmt "\n", __func__, __LINE__, \
+	       ##args)
+
+#define al_codec_dbg(level, fmt, args...)                                  \
+	do {                                                               \
+		if (debug >= level)                                        \
+			pr_info("[ALG_CODEC] level=%d %s(),%d: " fmt "\n", \
+				level, __func__, __LINE__, ##args);        \
+	} while (0)
+
+/* V4L2 logs */
+#define al_v4l2_err(dev, fmt, args...)                                     \
+	pr_err("[ALG_V4L2][ERROR] %s():%d: " fmt "\n", __func__, __LINE__, \
+	       ##args)
+
+#define al_v4l2_dbg(level, fmt, args...)                                  \
+	do {                                                              \
+		if (debug >= level)                                       \
+			pr_info("[ALG_V4L2] level=%d %s(),%d: " fmt "\n", \
+				level, __func__, __LINE__, ##args);       \
+	} while (0)
+
+/* MCU debug */
+#define al_mcu_dbg(fmt, args...) pr_info("[ALG_MCU]: " fmt, ##args);
+
+#else
+
+#define al_codec_err(_dev, fmt, args...) \
+	dev_err(&_dev->pdev->dev, fmt "\n", ##args)
+
+#define al_v4l2_err(_dev, fmt, args...) \
+	dev_err(&_dev->common.pdev->dev, fmt "\n", ##args)
+
+#define al_codec_dbg(level, fmt, args...)
+#define al_v4l2_dbg(level, fmt, args...)
+#define al_mcu_dbg(fmt, args...)
+
+#endif
+
+#define MSG_ITF_TYPE_LIMIT BIT(10)
+
+/* Message types host <-> mcu */
+enum {
+	MSG_ITF_TYPE_MCU_ALIVE = 0x0,
+	MSG_ITF_TYPE_WRITE_REQ = 0x02,
+	MSG_ITF_TYPE_FIRST_REQ = 1024,
+	MSG_ITF_TYPE_NEXT_REQ,
+	MSG_ITF_TYPE_FIRST_REPLY = 2048,
+	MSG_ITF_TYPE_NEXT_REPLY,
+	MSG_ITF_TYPE_ALLOC_MEM_REQ = 3072,
+	MSG_ITF_TYPE_FREE_MEM_REQ,
+	MSG_ITF_TYPE_ALLOC_MEM_REPLY = 4096,
+	MSG_ITF_TYPE_FREE_MEM_REPLY,
+	MSG_ITF_TYPE_FIRST_EVT = 5120,
+	MSG_ITF_TYPE_NEXT_EVT = MSG_ITF_TYPE_FIRST_EVT
+};
+
+struct msg_itf_header {
+	u64 drv_ctx_hdl;
+	u64 drv_cmd_hdl;
+	u16 type;
+	u16 payload_len;
+	u16 padding[2];
+} __packed;
+
+void al_codec_mb_init(struct al_codec_mb *mb, char *addr, int size, u32 magic);
+int al_codec_msg_get_header(struct al_codec_mb *mb, struct msg_itf_header *hdr);
+int al_codec_msg_get_data(struct al_codec_mb *mb, char *data, int len);
+int al_codec_msg_send(struct al_codec_mb *mb, struct msg_itf_header *hdr,
+		      void (*trigger)(void *), void *trigger_arg);
+
+static inline bool is_type_reply(uint16_t type)
+{
+	return type >= MSG_ITF_TYPE_FIRST_REPLY &&
+	       type < MSG_ITF_TYPE_FIRST_REPLY + MSG_ITF_TYPE_LIMIT;
+}
+
+static inline bool is_type_event(uint16_t type)
+{
+	return type >= MSG_ITF_TYPE_FIRST_EVT &&
+	       type < MSG_ITF_TYPE_FIRST_EVT + MSG_ITF_TYPE_LIMIT;
+}
+
+void al_codec_cmd_put(struct al_codec_cmd *cmd);
+
+struct al_codec_cmd *al_codec_cmd_create(int reply_size);
+
+static inline struct al_codec_cmd *al_codec_cmd_get(struct list_head *cmd_list,
+						    uint64_t hdl)
+{
+	struct al_codec_cmd *cmd = NULL;
+
+	list_for_each_entry(cmd, cmd_list, list) {
+		if (likely(cmd == al_phys_to_virt(hdl))) {
+			kref_get(&cmd->refcount);
+			break;
+		}
+	}
+	return list_entry_is_head(cmd, cmd_list, list) ? NULL : cmd;
+}
+
+#endif /* __AL_CODEC_UTIL__ */
diff --git a/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.c b/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.c
new file mode 100644
index 000000000000..70546af410d8
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.c
@@ -0,0 +1,1536 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ *
+ * Allegro DVT stateful video decoder driver for the IP Gen 3
+ */
+
+#include <asm-generic/errno-base.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/string.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "al_codec_common.h"
+#include "al_vdec_drv.h"
+
+#if defined(DEBUG)
+/* Log level */
+int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level (0-3)");
+#endif
+
+/* default decoder params */
+#define DECODER_WIDTH_DEFAULT 640
+#define DECODER_HEIGHT_DEFAULT 480
+#define DECODER_WIDTH_MAX 3840
+#define DECODER_HEIGHT_MAX 2160
+#define DECODER_WIDTH_MIN 16
+#define DECODER_HEIGHT_MIN 16
+#define DEC_REQ_TIMEOUT msecs_to_jiffies(1000)
+#define DEC_RES_EVT_TIMEOUT DEC_REQ_TIMEOUT
+
+/* Supported formats */
+static const struct al_fmt al_src_formats[] = {
+	{
+		.pixelformat = V4L2_PIX_FMT_H264,
+		.bpp = 20,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_HEVC,
+		.bpp = 20,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_JPEG,
+		.bpp = 8,
+	}
+};
+
+static const struct al_fmt al_dst_formats[] = {
+	{
+		.pixelformat = V4L2_PIX_FMT_NV12,
+		.bpp = 12,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_P010,
+		.bpp = 12,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_NV16,
+		.bpp = 16,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_YUV420, /* YUV 4:2:0 */
+		.bpp = 12,
+	},
+	{
+		.pixelformat = V4L2_PIX_FMT_YVU420, /* YVU 4:2:0 */
+		.bpp = 12,
+	},
+};
+
+/* Default format */
+static const struct al_frame al_default_fmt = {
+
+	.width = DECODER_WIDTH_DEFAULT,
+	.height = DECODER_HEIGHT_DEFAULT,
+	.bytesperline = DECODER_WIDTH_MAX * 4,
+	.sizeimage = DECODER_WIDTH_DEFAULT * DECODER_HEIGHT_DEFAULT * 4,
+	.nbuffers = 1,
+	.fmt = &al_dst_formats[0],
+	.field = V4L2_FIELD_NONE,
+	.colorspace = V4L2_COLORSPACE_REC709,
+	.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
+	.quantization = V4L2_QUANTIZATION_DEFAULT,
+	.xfer_func = V4L2_XFER_FUNC_DEFAULT
+};
+
+static struct al_frame *al_get_frame(struct al_dec_ctx *ctx,
+				     enum v4l2_buf_type type)
+{
+	if (WARN_ON(!ctx))
+		return ERR_PTR(-EINVAL);
+
+	if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		return &ctx->src;
+	else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return &ctx->dst;
+
+	al_v4l2_err(ctx->dev, "Unsupported type (%d)", type);
+
+	return ERR_PTR(-EINVAL);
+}
+
+static const struct al_fmt *al_find_fmt(u32 pixelformat)
+{
+	const struct al_fmt *fmt;
+	unsigned int i;
+
+	/* check if the pixelformat exist in the src formats list */
+	for (i = 0; i < ARRAY_SIZE(al_src_formats); i++) {
+		fmt = &al_src_formats[i];
+		if (fmt->pixelformat == pixelformat)
+			return fmt;
+	}
+
+	/* check if the pixelformat exist in the dst formats list */
+	for (i = 0; i < ARRAY_SIZE(al_dst_formats); i++) {
+		fmt = &al_dst_formats[i];
+		if (fmt->pixelformat == pixelformat)
+			return fmt;
+	}
+
+	return NULL;
+}
+
+static int dec_fw_create_decoder(struct al_dec_ctx *ctx)
+{
+	struct msg_itf_create_decoder_req_full req;
+	struct msg_itf_create_codec_reply reply;
+	struct al_common_mcu_req mreq;
+	int ret;
+
+	if (ctx->hDec) {
+		al_v4l2_dbg(3, "fw decoder already exist\n");
+		return 0;
+	}
+
+	req.req.codec = ctx->codec;
+
+	mreq.pCtx = al_virt_to_phys(ctx);
+	mreq.req_type = MSG_ITF_TYPE_CREATE_INST_REQ;
+	mreq.req_size = sizeof(req.req);
+	mreq.reply_size = sizeof(reply);
+	mreq.reply = &reply;
+
+	ret = al_common_send_req_reply(ctx->dev, &ctx->cmd_q_list, &req.hdr,
+				       &mreq);
+
+	if (!ret && !reply.ret)
+		ctx->hDec = reply.hCodec;
+	else if (reply.ret)
+		ret = -ENODEV;
+
+	return ret;
+}
+
+static void dec_fw_destroy_decoder(struct al_dec_ctx *ctx)
+{
+	struct msg_itf_destroy_codec_req_full req;
+	struct msg_itf_destroy_codec_reply reply;
+	struct al_common_mcu_req mreq;
+	int ret;
+
+	if (!ctx->hDec) {
+		al_v4l2_dbg(3, "fw decoder doesn't exist");
+		return;
+	}
+	al_v4l2_dbg(3, "Destroy decoder %lld ", ctx->hDec);
+
+	req.req.hCodec = ctx->hDec;
+
+	mreq.pCtx = al_virt_to_phys(ctx);
+	mreq.req_type = MSG_ITF_TYPE_DESTROY_INST_REQ;
+	mreq.req_size = sizeof(req.req);
+	mreq.reply_size = sizeof(reply);
+	mreq.reply = &reply;
+
+	ret = al_common_send_req_reply(ctx->dev, &ctx->cmd_q_list, &req.hdr,
+				       &mreq);
+
+	if (!ret)
+		ctx->hDec = 0;
+}
+
+static int al_dec_fw_push_frame_buf(struct al_dec_ctx *ctx,
+				    struct vb2_v4l2_buffer *vbuf)
+{
+	struct msg_itf_push_dst_buf_req_full req;
+	struct v4l2_m2m_buffer *m2m_buf;
+	struct al_common_mcu_req mreq = { 0 };
+	int ret;
+
+	if (WARN(!vbuf, "NULL frame Buffer to push!!"))
+		return -EINVAL;
+
+	req.req.hCodec = ctx->hDec;
+	m2m_buf = container_of(vbuf, struct v4l2_m2m_buffer, vb);
+	req.req.bufferHandle = al_virt_to_phys(m2m_buf);
+	req.req.phyAddr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0);
+	req.req.size = vb2_plane_size(&vbuf->vb2_buf, 0);
+
+	mreq.pCtx = al_virt_to_phys(ctx);
+	mreq.req_type = MSG_ITF_TYPE_PUT_DISPLAY_PICTURE_REQ;
+	mreq.req_size = sizeof(req.req);
+
+	ret = al_common_send_req_reply(ctx->dev, &ctx->cmd_q_list, &req.hdr,
+				       &mreq);
+	if (ret)
+		al_v4l2_err(ctx->dev, "Failed to push frame buffer %p %d",
+			    m2m_buf, ret);
+
+	return ret;
+}
+
+static int al_dec_fw_push_bitstream_buf(struct al_dec_ctx *ctx,
+					struct vb2_v4l2_buffer *vbuf)
+{
+	struct msg_itf_push_src_buf_req_full req;
+	struct v4l2_m2m_buffer *m2m_buf;
+	struct al_common_mcu_req mreq = { 0 };
+	int ret;
+
+	if (WARN(!vbuf, "NULL Buffer to push!!"))
+		return -EINVAL;
+
+	req.req.hCodec = ctx->hDec;
+	m2m_buf = container_of(vbuf, struct v4l2_m2m_buffer, vb);
+	req.req.bufferHandle = al_virt_to_phys(m2m_buf);
+	req.req.phyAddr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0);
+	req.req.size = vb2_plane_size(&vbuf->vb2_buf, 0);
+
+	/* Fill the v4l2 metadata*/
+	req.req.meta.timestamp = vbuf->vb2_buf.timestamp;
+	req.req.meta.timecode = vbuf->timecode;
+	req.req.meta.last = vbuf->flags & V4L2_BUF_FLAG_LAST;
+
+	mreq.pCtx = al_virt_to_phys(ctx);
+	mreq.req_type = MSG_ITF_TYPE_PUSH_BITSTREAM_BUFFER_REQ;
+	mreq.req_size = sizeof(req.req);
+
+	ret = al_common_send_req_reply(ctx->dev, &ctx->cmd_q_list, &req.hdr,
+				       &mreq);
+	if (ret)
+		al_v4l2_err(ctx->dev, "Failed to push bitstream buffer %p %d",
+			    m2m_buf, ret);
+
+	return ret;
+}
+
+static int dec_fw_flush_req(struct al_dec_ctx *ctx)
+{
+	struct msg_itf_flush_req_full req;
+	struct msg_itf_flush_reply reply;
+	struct al_common_mcu_req mreq;
+	int ret;
+
+	req.req.hCodec = ctx->hDec;
+
+	mreq.pCtx = al_virt_to_phys(ctx);
+	mreq.req_type = MSG_ITF_TYPE_FLUSH_REQ;
+	mreq.req_size = sizeof(req.req);
+	mreq.reply_size = sizeof(reply);
+	mreq.reply = &reply;
+
+	ret = al_common_send_req_reply(ctx->dev, &ctx->cmd_q_list, &req.hdr,
+				       &mreq);
+
+	if (ret)
+		al_v4l2_err(ctx->dev, "Failed to flush the decoder %d", ret);
+
+	return ret;
+}
+
+static inline struct vb2_v4l2_buffer *
+al_dec_dequeue_buf(struct al_dec_ctx *ctx, uint64_t hdl,
+		   struct list_head *buffer_list)
+{
+	struct v4l2_m2m_buffer *buf, *tmp;
+	struct vb2_v4l2_buffer *ret = NULL;
+
+	mutex_lock(&ctx->buf_q_mlock);
+	list_for_each_entry_safe(buf, tmp, buffer_list, list) {
+		if (buf == al_phys_to_virt(hdl)) {
+			list_del(&buf->list);
+			ret = &buf->vb;
+			break;
+		}
+	}
+	mutex_unlock(&ctx->buf_q_mlock);
+
+	return ret;
+}
+
+static struct vb2_v4l2_buffer *al_dec_dequeue_src_buf(struct al_dec_ctx *ctx,
+						      uint64_t hdl)
+{
+	return al_dec_dequeue_buf(ctx, hdl, &ctx->stream_q_list);
+}
+
+static struct vb2_v4l2_buffer *al_dec_dequeue_dst_buf(struct al_dec_ctx *ctx,
+						      uint64_t hdl)
+{
+	return al_dec_dequeue_buf(ctx, hdl, &ctx->frame_q_list);
+}
+
+static void al_ctx_cleanup(struct kref *ref)
+{
+	struct al_dec_ctx *ctx = container_of(ref, struct al_dec_ctx, refcount);
+
+	kfree(ctx);
+}
+
+static inline struct al_dec_ctx *al_ctx_get(struct al_codec_dev *dev,
+					    uint64_t hdl)
+{
+	struct al_dec_ctx *ctx;
+	struct al_dec_ctx *ret = NULL;
+
+	mutex_lock(&dev->ctx_mlock);
+	list_for_each_entry(ctx, &dev->ctx_q_list, list) {
+		if (ctx == al_phys_to_virt(hdl)) {
+			kref_get(&ctx->refcount);
+			ret = ctx;
+			break;
+		}
+	}
+	mutex_unlock(&dev->ctx_mlock);
+
+	return ret;
+}
+
+static void al_ctx_put(struct al_dec_ctx *ctx)
+{
+	kref_put(&ctx->refcount, al_ctx_cleanup);
+}
+
+static int al_dec_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct al_dec_ctx *ctx = vb2_get_drv_priv(q);
+	struct al_codec_dev *dev = ctx->dev;
+
+	v4l2_m2m_update_start_streaming_state(ctx->fh.m2m_ctx, q);
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+		struct v4l2_m2m_buffer *buf;
+		int ret;
+
+		if (list_empty(&ctx->stream_q_list)) {
+			al_v4l2_dbg(0, "Empty stream list.");
+			return -EINVAL;
+		}
+		if (!al_common_mcu_is_alive(dev)) {
+			al_v4l2_err(dev, "Unable to ping the mcu");
+			return -ENODEV;
+		}
+
+		ret = dec_fw_create_decoder(ctx);
+		if (ret) {
+			al_v4l2_err(dev, "Unable to create the fw decoder %d",
+				    ret);
+			return ret;
+		}
+
+		/* Get the first vid-out queued buffer */
+		buf = list_first_entry(&ctx->stream_q_list,
+				       struct v4l2_m2m_buffer, list);
+
+		if (!buf) {
+			al_v4l2_err(
+				dev,
+				"Unable to get the first buffer from the stream list");
+			return -EINVAL;
+		}
+
+		if (al_dec_fw_push_bitstream_buf(ctx, &buf->vb)) {
+			al_v4l2_err(ctx->dev,
+				    "Unable to push the bitstream buffer");
+			return -EINVAL;
+		}
+
+		/* Wait until the mcu detect the resolution of the stream */
+		ret = wait_for_completion_timeout(&ctx->res_done,
+						  DEC_RES_EVT_TIMEOUT);
+		if (!ret) {
+			al_v4l2_err(ctx->dev, "unsupported stream");
+			ctx->aborting = true;
+		}
+
+		ctx->osequence = 0;
+	} else
+		ctx->csequence = 0;
+
+	return 0;
+}
+
+static void al_dec_stop_streaming_cap(struct al_dec_ctx *ctx)
+{
+	struct vb2_v4l2_buffer *vbuf;
+	struct v4l2_m2m_buffer *entry, *tmp;
+
+	mutex_lock(&ctx->buf_q_mlock);
+	if (!list_empty(&ctx->frame_q_list))
+		list_for_each_entry_safe(entry, tmp, &ctx->frame_q_list, list) {
+			list_del(&entry->list);
+			vbuf = &entry->vb;
+			vb2_set_plane_payload(&vbuf->vb2_buf, 0, 0);
+			v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+		}
+	mutex_unlock(&ctx->buf_q_mlock);
+
+	while (v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) {
+		vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+		if (vbuf) {
+			vb2_set_plane_payload(&vbuf->vb2_buf, 0, 0);
+			v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+		}
+	}
+
+	v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx);
+}
+
+static void al_dec_stop_streaming_out(struct al_dec_ctx *ctx)
+{
+	struct vb2_v4l2_buffer *vbuf;
+	struct v4l2_m2m_buffer *entry, *tmp;
+
+	mutex_lock(&ctx->buf_q_mlock);
+	if (!list_empty(&ctx->stream_q_list))
+		list_for_each_entry_safe(entry, tmp, &ctx->stream_q_list,
+					 list) {
+			list_del(&entry->list);
+			v4l2_m2m_buf_done(&entry->vb, VB2_BUF_STATE_ERROR);
+		}
+	mutex_unlock(&ctx->buf_q_mlock);
+
+	if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx)) {
+		while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx)))
+			if (vbuf->vb2_buf.state == VB2_BUF_STATE_ACTIVE)
+				v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+	}
+
+	dec_fw_destroy_decoder(ctx);
+}
+
+static void al_dec_stop_streaming(struct vb2_queue *q)
+{
+	struct al_dec_ctx *ctx = vb2_get_drv_priv(q);
+
+	v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q);
+
+	/* Releasing the dst and src buffers */
+	ctx->stopped = true;
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type))
+		al_dec_stop_streaming_out(ctx);
+	else
+		al_dec_stop_streaming_cap(ctx);
+}
+
+static int al_dec_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+			      unsigned int *nplanes, unsigned int sizes[],
+			      struct device *alloc_devs[])
+{
+	struct al_dec_ctx *ctx = vb2_get_drv_priv(vq);
+	struct al_frame *format = al_get_frame(ctx, vq->type);
+
+	if (IS_ERR(format)) {
+		al_v4l2_err(ctx->dev, "Invalid format %p", format);
+		return PTR_ERR(format);
+	}
+
+	if (*nplanes)
+		return ((sizes[0] < format->sizeimage) ? -EINVAL : 0);
+
+	/* update queue num buffers */
+	format->nbuffers = max(*nbuffers, format->nbuffers);
+
+	*nplanes = 1;
+	sizes[0] = format->sizeimage;
+	*nbuffers = format->nbuffers;
+
+	al_v4l2_dbg(2, "%s: Get %d buffers of size %d each ",
+		    (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? "OUT" : "CAP",
+		    *nbuffers, sizes[0]);
+
+	return 0;
+}
+
+static int al_dec_buf_prepare(struct vb2_buffer *vb)
+{
+	struct al_dec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	if (ctx->aborting)
+		return -EINVAL;
+
+	if (V4L2_TYPE_IS_CAPTURE(vb->type)) {
+		if (vbuf->field == V4L2_FIELD_ANY)
+			vbuf->field = V4L2_FIELD_NONE;
+		if (vbuf->field != V4L2_FIELD_NONE)
+			return -EINVAL;
+	}
+
+	al_v4l2_dbg(3, "%s : Buffer (%p) prepared ",
+		    (V4L2_TYPE_IS_OUTPUT(vb->type) ? "OUT" : "CAP"), vbuf);
+
+	return 0;
+}
+
+static inline void al_dec_fill_bitstream(struct al_dec_ctx *ctx)
+{
+	struct vb2_v4l2_buffer *src_buf;
+	struct v4l2_m2m_buffer *m2m_buf;
+	struct vb2_queue *src_vq;
+
+	lockdep_assert_held(&ctx->buf_q_mlock);
+
+	if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) > 0) {
+		src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+		if (!src_buf)
+			return;
+
+		/* Dump empty buffers */
+		if (!vb2_get_plane_payload(&src_buf->vb2_buf, 0)) {
+			src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+			v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
+			return;
+		}
+
+		src_vq = v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx);
+		src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+
+		if (src_buf) {
+			src_buf->sequence = ctx->osequence++;
+
+			if (vb2_is_streaming(src_vq) &&
+			    al_dec_fw_push_bitstream_buf(ctx, src_buf)) {
+				v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
+				return;
+			}
+
+			m2m_buf = container_of(src_buf, struct v4l2_m2m_buffer,
+					       vb);
+			list_add_tail(&m2m_buf->list, &ctx->stream_q_list);
+		}
+	}
+}
+
+static void al_dec_buf_queue(struct vb2_buffer *vb)
+{
+	struct al_dec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->type)) {
+		mutex_lock(&ctx->buf_q_mlock);
+		al_dec_fill_bitstream(ctx);
+		mutex_unlock(&ctx->buf_q_mlock);
+	}
+
+	al_v4l2_dbg(3, "%s queued (%p) - (%d)",
+		    V4L2_TYPE_IS_OUTPUT(vb->type) ? "OUT" : "CAP", vbuf,
+		    vb->num_planes);
+}
+
+static const struct vb2_ops dec_queue_ops = {
+	.queue_setup = al_dec_queue_setup,
+	.buf_prepare = al_dec_buf_prepare,
+	.buf_queue = al_dec_buf_queue,
+	.start_streaming = al_dec_start_streaming,
+	.stop_streaming = al_dec_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+static int al_dec_queue_init(void *priv, struct vb2_queue *src_vq,
+			     struct vb2_queue *dst_vq)
+{
+	struct al_dec_ctx *ctx = priv;
+	int ret;
+
+	src_vq->dev = &ctx->dev->common.pdev->dev;
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->non_coherent_mem = false;
+	src_vq->dma_attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->drv_priv = ctx;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->ops = &dec_queue_ops;
+	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	src_vq->lock = &ctx->dev->lock;
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->dev = &ctx->dev->common.pdev->dev;
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->non_coherent_mem = false;
+	dst_vq->dma_attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->drv_priv = ctx;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->ops = &dec_queue_ops;
+	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+	dst_vq->lock = &ctx->dev->lock;
+	ret = vb2_queue_init(dst_vq);
+	if (ret) {
+		vb2_queue_release(src_vq);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int al_dec_querycap(struct file *file, void *fh,
+			   struct v4l2_capability *cap)
+{
+	struct al_codec_dev *dev = video_drvdata(file);
+
+	strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strscpy(cap->card, "Allegro DVT Video Decoder", sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 dev_name(&dev->common.pdev->dev));
+
+	return 0;
+}
+
+static int al_dec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	const struct al_fmt *fmt;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+	    f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+		if (f->index >= ARRAY_SIZE(al_src_formats))
+			return -EINVAL;
+
+		fmt = &al_src_formats[f->index];
+	} else {
+		if (f->index >= ARRAY_SIZE(al_dst_formats))
+			return -EINVAL;
+
+		fmt = &al_dst_formats[f->index];
+	}
+
+	f->pixelformat = fmt->pixelformat;
+	return 0;
+}
+
+static int al_dec_try_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct al_dec_ctx *ctx = fh_to_ctx(fh, struct al_dec_ctx);
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	const struct al_fmt *v_fmt;
+	struct al_frame *pix_fmt;
+
+	v_fmt = al_find_fmt(pix->pixelformat);
+	if (!v_fmt) {
+		al_v4l2_dbg(2, "Unknown format 0x%x", pix->pixelformat);
+		return -EINVAL;
+	}
+
+	pix_fmt = al_get_frame(ctx, f->type);
+	if (!pix_fmt) {
+		al_v4l2_dbg(2, "Unknown pix format 0x%x", pix->pixelformat);
+		return -EINVAL;
+	}
+
+	pix_fmt->fmt = v_fmt;
+	pix->field = V4L2_FIELD_NONE;
+	pix->width = clamp_t(__u32, pix->width, DECODER_WIDTH_MIN,
+			     DECODER_WIDTH_MAX);
+	pix->height = clamp_t(__u32, pix->height, DECODER_HEIGHT_MIN,
+			      DECODER_HEIGHT_MAX);
+
+	pix->bytesperline = pix->width;
+	pix->sizeimage = (pix->width * pix->height * v_fmt->bpp) / 8;
+
+	if (V4L2_TYPE_IS_CAPTURE(f->type))
+		if (pix->sizeimage < pix_fmt->sizeimage)
+			pix->sizeimage = pix_fmt->sizeimage;
+
+	al_v4l2_dbg(
+		3,
+		"%s : width (%d) , height (%d), bytesperline (%d), sizeimage (%d) ",
+		(f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? "CAP" : "OUT",
+		pix->width, pix->height, pix->bytesperline, pix->sizeimage);
+
+	return 0;
+}
+
+static int al_dec_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct al_dec_ctx *ctx = fh_to_ctx(fh, struct al_dec_ctx);
+	struct al_frame *pix_fmt = al_get_frame(ctx, f->type);
+	struct v4l2_pix_format *pix;
+
+	if (IS_ERR(pix_fmt)) {
+		al_v4l2_err(ctx->dev, "Invalid pixel format %p", pix_fmt);
+		return PTR_ERR(pix_fmt);
+	}
+
+	if (!pix_fmt->fmt) {
+		al_v4l2_err(ctx->dev, "Unknown format for %d", f->type);
+		return -EINVAL;
+	}
+
+	pix = &f->fmt.pix;
+	pix->width = pix_fmt->width;
+	pix->height = pix_fmt->height;
+	pix->bytesperline = pix_fmt->bytesperline;
+	pix->sizeimage = pix_fmt->sizeimage;
+	pix->pixelformat = pix_fmt->fmt->pixelformat;
+	pix->field = V4L2_FIELD_NONE;
+
+	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
+		pix->bytesperline = 0;
+		pix->pixelformat = ctx->codec;
+	}
+
+	pix->ycbcr_enc = pix_fmt->ycbcr_enc;
+	pix->quantization = pix_fmt->quantization;
+	pix->xfer_func = pix_fmt->xfer_func;
+	pix->colorspace = pix_fmt->colorspace;
+
+	al_v4l2_dbg(
+		3,
+		"%s : width (%d) , height (%d), bytesperline (%d) , sizeimage (%d)",
+		(f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? "CAP" : "OUT",
+		pix->width, pix->height, pix->bytesperline, pix->sizeimage);
+
+	return 0;
+}
+
+static int al_dec_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct al_dec_ctx *ctx = fh_to_ctx(fh, struct al_dec_ctx);
+	struct v4l2_pix_format *pix;
+	struct al_frame *frame;
+	struct vb2_queue *vq;
+	int ret;
+
+	ret = al_dec_try_fmt(file, fh, f);
+	if (ret) {
+		al_v4l2_err(ctx->dev, "Cannot set format (%d)", f->type);
+		return ret;
+	}
+
+	frame = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? &ctx->src : &ctx->dst;
+
+	pix = &f->fmt.pix;
+	frame->fmt = al_find_fmt(pix->pixelformat);
+	if (!frame->fmt) {
+		al_v4l2_err(ctx->dev, "Unknown format for %d",
+			    pix->pixelformat);
+		return -EINVAL;
+	}
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (vb2_is_streaming(vq)) {
+		al_v4l2_err(ctx->dev, "queue %d busy", f->type);
+		return -EBUSY;
+	}
+
+	frame->width = pix->width;
+	frame->height = pix->height;
+	frame->bytesperline = pix->bytesperline;
+	frame->sizeimage = pix->sizeimage;
+	frame->field = pix->field;
+
+	frame->ycbcr_enc = pix->ycbcr_enc;
+	frame->quantization = pix->quantization;
+	frame->xfer_func = pix->xfer_func;
+	frame->colorspace = pix->colorspace;
+
+	/* Set decoder pixelformat */
+	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		ctx->codec = pix->pixelformat;
+
+	al_v4l2_dbg(
+		3,
+		" %s : width (%d) , height (%d), bytesperline (%d), sizeimage (%d)",
+		(f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? "CAP" : "OUT",
+		pix->width, pix->height, pix->bytesperline, pix->sizeimage);
+
+	return 0;
+}
+
+static void al_queue_eos_event(struct al_dec_ctx *ctx)
+{
+	const struct v4l2_event eos_event = {
+		.id = 0,
+		.type = V4L2_EVENT_EOS,
+	};
+
+	v4l2_event_queue_fh(&ctx->fh, &eos_event);
+}
+
+static void al_queue_res_chg_event(struct al_dec_ctx *ctx)
+{
+	static const struct v4l2_event ev_src_ch = {
+		.id = 0,
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
+	};
+
+	v4l2_event_queue_fh(&ctx->fh, &ev_src_ch);
+}
+
+static int al_dec_decoder_cmd(struct file *file, void *fh,
+			      struct v4l2_decoder_cmd *dcmd)
+{
+	struct al_dec_ctx *ctx = fh_to_ctx(fh, struct al_dec_ctx);
+	struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx;
+	struct vb2_v4l2_buffer *vbuf;
+	struct vb2_queue *dst_vq;
+	int ret;
+
+	ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, dcmd);
+	if (ret)
+		return ret;
+
+	/* Get the vb2 queue for the Capture */
+	dst_vq = v4l2_m2m_get_dst_vq(m2m_ctx);
+
+	switch (dcmd->cmd) {
+	case V4L2_DEC_CMD_START:
+		vb2_clear_last_buffer_dequeued(dst_vq);
+		break;
+	case V4L2_DEC_CMD_STOP:
+		vbuf = v4l2_m2m_last_src_buf(m2m_ctx);
+		if (vbuf) {
+			al_v4l2_dbg(1, "marking last pending buffer");
+
+			vbuf->flags |= V4L2_BUF_FLAG_LAST;
+			if (v4l2_m2m_num_src_bufs_ready(m2m_ctx) == 0) {
+				al_v4l2_dbg(1, "all remaining buffers queued");
+				v4l2_m2m_try_schedule(m2m_ctx);
+			}
+		}
+		dec_fw_flush_req(ctx);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int al_dec_enum_framesizes(struct file *file, void *fh,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	if (!al_find_fmt(fsize->pixel_format))
+		return -EINVAL;
+
+	/* FIXME : check step size */
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = DECODER_WIDTH_MIN;
+	fsize->stepwise.max_width = DECODER_WIDTH_MAX;
+	fsize->stepwise.step_width = 8;
+	fsize->stepwise.min_height = DECODER_HEIGHT_MIN;
+	fsize->stepwise.max_height = DECODER_HEIGHT_MAX;
+	fsize->stepwise.step_height = 8;
+
+	return 0;
+}
+
+static int al_dec_subscribe_event(struct v4l2_fh *fh,
+				  const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_EOS:
+		return v4l2_event_subscribe(fh, sub, 0, NULL);
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subscribe(fh, sub);
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int al_dec_log_status(struct file *file, void *fh)
+{
+	struct al_codec_dev *al_dev = video_drvdata(file);
+
+	v4l2_device_call_all(&al_dev->v4l2_dev, 0, core, log_status);
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops al_dec_ioctl_ops = {
+	.vidioc_querycap = al_dec_querycap,
+	.vidioc_enum_fmt_vid_cap = al_dec_enum_fmt,
+	.vidioc_enum_fmt_vid_out = al_dec_enum_fmt,
+	.vidioc_g_fmt_vid_cap = al_dec_g_fmt,
+	.vidioc_g_fmt_vid_out = al_dec_g_fmt,
+	.vidioc_try_fmt_vid_cap = al_dec_try_fmt,
+	.vidioc_try_fmt_vid_out = al_dec_try_fmt,
+	.vidioc_s_fmt_vid_cap = al_dec_s_fmt,
+	.vidioc_s_fmt_vid_out = al_dec_s_fmt,
+
+	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+
+	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+
+	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+	.vidioc_log_status = al_dec_log_status,
+
+	.vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd,
+	.vidioc_decoder_cmd = al_dec_decoder_cmd,
+	.vidioc_enum_framesizes = al_dec_enum_framesizes,
+
+	.vidioc_subscribe_event = al_dec_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static void al_device_run(void *priv)
+{
+	struct al_dec_ctx *ctx = priv;
+	struct vb2_v4l2_buffer *dst_buf;
+	struct v4l2_m2m_buffer *m2m_buf;
+
+	if (unlikely(!ctx))
+		return;
+
+	if (ctx->aborting) {
+		vb2_queue_error(v4l2_m2m_get_src_vq(ctx->fh.m2m_ctx));
+		vb2_queue_error(v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx));
+		return;
+	}
+
+	if (!v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx))
+		goto job_finish;
+
+	dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+	if (!dst_buf)
+		goto job_finish;
+
+	if (!al_common_mcu_is_alive(ctx->dev) ||
+	    al_dec_fw_push_frame_buf(ctx, dst_buf)) {
+		vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0);
+		v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
+		goto job_finish;
+	}
+
+	mutex_lock(&ctx->buf_q_mlock);
+	m2m_buf = container_of(dst_buf, struct v4l2_m2m_buffer, vb);
+	list_add_tail(&m2m_buf->list, &ctx->frame_q_list);
+	mutex_unlock(&ctx->buf_q_mlock);
+
+job_finish:
+	v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+static const struct v4l2_m2m_ops al_dec_m2m_ops = {
+	.device_run = al_device_run,
+};
+
+static int al_dec_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct al_codec_dev *dev = video_get_drvdata(vdev);
+	struct al_dec_ctx *ctx = NULL;
+	int ret;
+
+	if (mutex_lock_interruptible(&dev->ctx_mlock))
+		return -ERESTARTSYS;
+
+	/* Aloocate memory for the dec ctx */
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	ctx->dev = dev;
+	/* Init ctx mutex */
+	mutex_init(&ctx->buf_q_mlock);
+	/* Init ctx LISTHEADs*/
+	INIT_LIST_HEAD(&ctx->cmd_q_list);
+	INIT_LIST_HEAD(&ctx->frame_q_list);
+	INIT_LIST_HEAD(&ctx->stream_q_list);
+
+	/* Init the irq queue */
+	init_completion(&ctx->res_done);
+
+	v4l2_fh_init(&ctx->fh, vdev);
+
+	v4l2_ctrl_handler_init(&ctx->ctrl_handler, 0);
+	if (ctx->ctrl_handler.error) {
+		ret = ctx->ctrl_handler.error;
+		al_v4l2_err(dev, "Failed to create control %d", ret);
+		goto handler_error;
+	}
+
+	ctx->fh.ctrl_handler = &ctx->ctrl_handler;
+	v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
+
+	file->private_data = &ctx->fh;
+	v4l2_fh_add(&ctx->fh);
+
+	/* Set default formats */
+	ctx->src = ctx->dst = al_default_fmt;
+
+	ctx->codec = V4L2_PIX_FMT_H264;
+	ctx->stopped = false;
+	ctx->aborting = false;
+
+	/* Setup the ctx for m2m mode */
+	ctx->fh.m2m_ctx =
+		v4l2_m2m_ctx_init(dev->m2m_dev, ctx, al_dec_queue_init);
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		ret = PTR_ERR(ctx->fh.m2m_ctx);
+		al_v4l2_err(dev, "Failed to initialize m2m mode %d", ret);
+		goto error_ctrls;
+	}
+
+	v4l2_m2m_set_src_buffered(ctx->fh.m2m_ctx, true);
+	/* v4l2_m2m_set_dst_buffered(ctx->fh.m2m_ctx, true); */
+
+	/* Add ctx to the LIST */
+	kref_init(&ctx->refcount);
+	list_add(&ctx->list, &dev->ctx_q_list);
+
+	mutex_unlock(&dev->ctx_mlock);
+
+	return 0;
+
+error_ctrls:
+	v4l2_fh_del(&ctx->fh);
+handler_error:
+	v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+	v4l2_fh_exit(&ctx->fh);
+	kfree(ctx);
+
+unlock:
+	mutex_unlock(&dev->ctx_mlock);
+	return ret;
+}
+
+static int al_dec_release(struct file *file)
+{
+	struct al_dec_ctx *ctx =
+		fh_to_ctx(file->private_data, struct al_dec_ctx);
+	struct al_codec_dev *dev = ctx->dev;
+
+	mutex_lock(&dev->ctx_mlock);
+
+	/* It is important to do this before removing ctx from dev list.
+	 * Those commands will trigger some traffic towards fw and so we
+	 * need completion to avoid deadlock if cmds can't find ctx.
+	 */
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+	v4l2_ctrl_handler_free(&ctx->ctrl_handler);
+	v4l2_fh_del(&ctx->fh);
+	v4l2_fh_exit(&ctx->fh);
+
+	list_del(&ctx->list);
+	al_ctx_put(ctx);
+	mutex_unlock(&dev->ctx_mlock);
+
+	return 0;
+}
+
+static inline bool al_mark_last_dst_buf(struct al_dec_ctx *ctx)
+{
+	struct vb2_v4l2_buffer *buf;
+	struct vb2_buffer *dst_vb;
+	struct vb2_queue *dst_vq;
+	unsigned long flags;
+
+	al_v4l2_dbg(1, "marking last capture buffer");
+
+	dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE);
+	spin_lock_irqsave(&dst_vq->done_lock, flags);
+	if (list_empty(&dst_vq->done_list)) {
+		spin_unlock_irqrestore(&dst_vq->done_lock, flags);
+		return false;
+	}
+
+	dst_vb = list_last_entry(&dst_vq->done_list, struct vb2_buffer,
+				 done_entry);
+	buf = to_vb2_v4l2_buffer(dst_vb);
+	buf->flags |= V4L2_BUF_FLAG_LAST;
+
+	spin_unlock_irqrestore(&dst_vq->done_lock, flags);
+	return true;
+}
+
+static const struct v4l2_file_operations al_dec_file_ops = {
+	.owner = THIS_MODULE,
+	.open = al_dec_open,
+	.release = al_dec_release,
+	.poll = v4l2_m2m_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = v4l2_m2m_fop_mmap,
+};
+
+static void handle_error_evt(struct al_dec_ctx *ctx, struct msg_itf_header *hdr)
+{
+	struct al_codec_dev *dev = ctx->dev;
+	struct msg_itf_evt_error evt;
+	struct v4l2_m2m_buffer *vbuf;
+
+	if (al_common_get_data(&dev->common, (char *)&evt, hdr->payload_len)) {
+		al_v4l2_err(dev, "Unable to get resolution found event");
+		return;
+	}
+
+	al_v4l2_err(dev, "Decoding error  %d", evt.errno);
+
+	mutex_lock(&ctx->buf_q_mlock);
+	if (!list_empty(&ctx->stream_q_list)) {
+		vbuf = list_last_entry(&ctx->frame_q_list,
+				       struct v4l2_m2m_buffer, list);
+		list_del(&vbuf->list);
+		v4l2_m2m_buf_done(&vbuf->vb, VB2_BUF_STATE_ERROR);
+	}
+	mutex_unlock(&ctx->buf_q_mlock);
+}
+
+static void handle_resolution_found_evt(struct al_dec_ctx *ctx,
+					struct msg_itf_header *hdr)
+{
+	struct msg_itf_evt_resolution_found evt;
+	struct al_codec_dev *dev = ctx->dev;
+	struct al_frame *frame;
+	struct vb2_queue *dst_vq;
+
+	if (al_common_get_data(&dev->common, (char *)&evt, hdr->payload_len)) {
+		al_v4l2_err(dev, "Unable to get resolution found event");
+		return;
+	}
+
+	frame = &ctx->dst;
+
+	if (frame->width != evt.width || frame->height != evt.height ||
+	    frame->nbuffers < evt.buffer_nb) {
+		/* Update frame properties */
+		frame->width = evt.width;
+		frame->height = evt.height;
+		frame->bytesperline = evt.bytesperline;
+		frame->sizeimage = evt.sizeimage;
+		frame->nbuffers = evt.buffer_nb;
+		frame->fmt = al_find_fmt(evt.pixelformat);
+
+		/* This has to be changed */
+		if (!frame->fmt)
+			return;
+
+		al_queue_res_chg_event(ctx);
+	}
+
+	dst_vq = v4l2_m2m_get_dst_vq(ctx->fh.m2m_ctx);
+	if (!vb2_is_streaming(dst_vq))
+		complete(&ctx->res_done);
+
+	al_v4l2_dbg(
+		3,
+		"width(%d) , height(%d), bytesperline(%d), sizeimage(%d), n_bufs(%d)",
+		frame->width, frame->height, frame->bytesperline,
+		frame->sizeimage, frame->nbuffers);
+}
+
+static void handle_bitstream_buffer_release_evt(struct al_dec_ctx *ctx,
+						struct msg_itf_header *hdr)
+{
+	struct msg_itf_evt_bitstream_buffer_release evt;
+	struct al_codec_dev *dev = ctx->dev;
+	struct vb2_v4l2_buffer *vbuf;
+
+	if (al_common_get_data(&dev->common, (char *)&evt, hdr->payload_len)) {
+		al_v4l2_err(dev, "Unable to get buffer release event");
+		return;
+	}
+
+	if (ctx->stopped)
+		return;
+
+	vbuf = al_dec_dequeue_src_buf(ctx, evt.bufferHandle);
+	if (!vbuf) {
+		al_v4l2_err(dev, "Unable to find bitsream buffer 0x%llx",
+			    evt.bufferHandle);
+		return;
+	}
+
+	al_v4l2_dbg(3, "Release bitstream buffer %p", vbuf);
+	v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
+}
+
+static void handle_eos_evt(struct al_dec_ctx *ctx, struct msg_itf_header *hdr)
+{
+	struct msg_itf_evt_frame_buffer_decode evt;
+	struct al_codec_dev *dev = ctx->dev;
+
+	if (al_common_get_data(&dev->common, (char *)&evt, hdr->payload_len)) {
+		al_v4l2_err(dev, "Unable to get frame buffer event");
+		return;
+	}
+
+	/* set LAST_FLAG to the last done CAPTURE buffer*/
+	al_mark_last_dst_buf(ctx);
+	/* Set eos event */
+	al_queue_eos_event(ctx);
+}
+
+static void handle_frame_buffer_decode_evt(struct al_dec_ctx *ctx,
+					   struct msg_itf_header *hdr)
+{
+	struct msg_itf_evt_frame_buffer_decode evt;
+	struct al_codec_dev *dev = ctx->dev;
+	struct vb2_v4l2_buffer *vbuf;
+	struct al_buffer_meta *meta;
+
+	if (al_common_get_data(&dev->common, (char *)&evt, hdr->payload_len)) {
+		al_v4l2_err(dev, "Unable to get frame buffer event");
+		return;
+	}
+
+	vbuf = al_dec_dequeue_dst_buf(ctx, evt.bufferHandle);
+	if (!vbuf) {
+		al_v4l2_err(dev, "Unable to find frame buffer 0x%llx",
+			    evt.bufferHandle);
+		return;
+	}
+
+	meta = &evt.meta;
+	al_v4l2_dbg(3, "Decoded frame done for buffer %p (%d) (%lld)", vbuf,
+		    meta->last, evt.size);
+
+	vb2_set_plane_payload(&vbuf->vb2_buf, 0, evt.size);
+	vbuf->field = V4L2_FIELD_NONE;
+	vbuf->sequence = ctx->csequence++;
+	vbuf->timecode = meta->timecode;
+	vbuf->vb2_buf.timestamp = meta->timestamp;
+
+	if (meta->last || (vbuf->flags & V4L2_BUF_FLAG_LAST)) {
+		vbuf->flags |= V4L2_BUF_FLAG_LAST;
+		v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx);
+	}
+
+	v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
+}
+
+static int al_handle_cmd_reply(struct al_codec_dev *dev,
+			       struct msg_itf_header *hdr)
+{
+	struct al_dec_ctx *ctx;
+	struct al_codec_cmd *cmd = NULL;
+	int ret = 0;
+
+	ctx = al_ctx_get(dev, hdr->drv_ctx_hdl);
+	if (IS_ERR_OR_NULL(ctx)) {
+		al_v4l2_err(dev, "Unable to find ctx %p for reply %d",
+			    al_phys_to_virt(hdr->drv_ctx_hdl), hdr->type);
+		return -EINVAL;
+	}
+
+	cmd = al_codec_cmd_get(&ctx->cmd_q_list, hdr->drv_cmd_hdl);
+	if (!cmd) {
+		al_v4l2_err(dev, "Unable to find command %p for reply %d",
+			    al_phys_to_virt(hdr->drv_cmd_hdl), hdr->type);
+		ret = -EINVAL;
+		goto ctx_put;
+	}
+
+	if (cmd->reply_size != hdr->payload_len) {
+		al_v4l2_err(dev, "mismatch size %d %d", cmd->reply_size,
+			    hdr->payload_len);
+		ret = -EINVAL;
+		goto cmd_put;
+	}
+
+	ret = al_common_get_data(&dev->common, cmd->reply, hdr->payload_len);
+	if (ret)
+		al_v4l2_err(dev, "Unable to copy reply");
+
+	complete(&cmd->done);
+	ret = 0;
+
+cmd_put:
+	al_codec_cmd_put(cmd);
+ctx_put:
+	al_ctx_put(ctx);
+
+	return ret;
+}
+
+static int al_handle_cmd_evt(struct al_codec_dev *dev,
+			     struct msg_itf_header *hdr, int type)
+{
+	static u32 evt_sizes[] = {
+		sizeof(struct msg_itf_evt_error),
+		sizeof(struct msg_itf_evt_resolution_found),
+		sizeof(struct msg_itf_evt_bitstream_buffer_release),
+		sizeof(struct msg_itf_evt_frame_buffer_decode),
+		sizeof(struct msg_itf_evt_eos),
+	};
+
+	u32 evt_size;
+	struct al_dec_ctx *ctx = NULL;
+	int ret = 0;
+
+	if (type < MSG_ITF_TYPE_NEXT_EVT || type > MSG_ITF_TYPE_END_EVT) {
+		al_v4l2_err(dev, "Unsupporting event type %d", type);
+		return -EINVAL;
+	}
+
+	ctx = al_ctx_get(dev, hdr->drv_ctx_hdl);
+	if (!ctx) {
+		al_v4l2_err(dev, "Unable to find ctx %p for evt %d",
+			    al_phys_to_virt(hdr->drv_ctx_hdl), type);
+		return -EINVAL;
+	}
+
+	// Check the received event size and the expected one
+	evt_size = evt_sizes[type - MSG_ITF_TYPE_NEXT_EVT];
+	if (hdr->payload_len != evt_size) {
+		al_v4l2_err(
+			dev,
+			"Invalid event size for client (%p) for evt (%d) : Got (%d), expected (%d)",
+			al_phys_to_virt(hdr->drv_ctx_hdl), type,
+			hdr->payload_len, evt_size);
+		ret = -EINVAL;
+		goto clean_ctx;
+	}
+
+	al_v4l2_dbg(3, "Event received from MCU (%d)", type);
+
+	switch (type) {
+	case MSG_ITF_TYPE_EVT_ERROR:
+		handle_error_evt(ctx, hdr);
+		break;
+	case MSG_ITF_TYPE_EVT_RESOLUTION_FOUND:
+		handle_resolution_found_evt(ctx, hdr);
+		break;
+	case MSG_ITF_TYPE_EVT_BITSTREAM_BUFFER_RELEASE:
+		handle_bitstream_buffer_release_evt(ctx, hdr);
+		break;
+	case MSG_ITF_TYPE_EVT_FRAME_BUFFER_DECODE:
+		handle_frame_buffer_decode_evt(ctx, hdr);
+		break;
+	case MSG_ITF_TYPE_EVT_EOS:
+		handle_eos_evt(ctx, hdr);
+		break;
+	default:
+		break;
+	}
+
+clean_ctx:
+	al_ctx_put(ctx);
+	return ret;
+}
+
+static void al_dec_process_msg(void *cb_arg, struct msg_itf_header *hdr)
+{
+	struct al_codec_dev *dev = cb_arg;
+	int ret;
+
+	if (is_type_reply(hdr->type))
+		ret = al_handle_cmd_reply(dev, hdr);
+	else if (is_type_event(hdr->type))
+		ret = al_handle_cmd_evt(dev, hdr, hdr->type);
+	else {
+		al_v4l2_err(dev, "Unsupported message type %d", hdr->type);
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		al_v4l2_err(dev, "Skip received data");
+		al_common_skip_data(&dev->common, hdr->payload_len);
+	}
+}
+
+static const struct video_device al_videodev = {
+	.name = "allegro-decoder",
+	.fops = &al_dec_file_ops,
+	.ioctl_ops = &al_dec_ioctl_ops,
+	.minor = -1,
+	.release = video_device_release_empty,
+	.vfl_dir = VFL_DIR_M2M,
+	.device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING,
+};
+
+static void al_dec_register_v4l2(void *cb_arg)
+{
+	struct al_codec_dev *dev = cb_arg;
+	struct video_device *video_dev = NULL;
+	int ret;
+
+	ret = v4l2_device_register(&dev->common.pdev->dev, &dev->v4l2_dev);
+	if (ret) {
+		al_v4l2_err(dev, "Unable to register v4l2 device %d", ret);
+		return;
+	}
+
+	dev->m2m_dev = v4l2_m2m_init(&al_dec_m2m_ops);
+	if (IS_ERR(dev->m2m_dev)) {
+		ret = PTR_ERR(dev->m2m_dev);
+		al_v4l2_err(dev, "failed to init mem2mem device %d", ret);
+		goto v4l2_m2m_init_error;
+	}
+
+	video_dev = &dev->video_dev;
+	*video_dev = al_videodev;
+
+	video_dev->lock = &dev->lock;
+	video_dev->v4l2_dev = &dev->v4l2_dev;
+
+	video_set_drvdata(video_dev, dev);
+	ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		al_v4l2_err(dev, "failed to register video device %d", ret);
+		goto video_register_device_error;
+	}
+
+	v4l2_info(&dev->v4l2_dev, "registered as /dev/video%d\n",
+		  dev->video_dev.num);
+
+	dev->is_video_init_done = 1;
+
+	return;
+
+video_register_device_error:
+	v4l2_m2m_release(dev->m2m_dev);
+v4l2_m2m_init_error:
+	v4l2_device_unregister(&dev->v4l2_dev);
+}
+
+static int al_dec_probe(struct platform_device *pdev)
+{
+	struct al_codec_dev *al_dev;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	const struct al_match_data *match_data;
+	int ret;
+
+	dev_info(dev, "Probing ...\n");
+
+	match_data = device_get_match_data(dev);
+	if (!match_data) {
+		dev_err(dev, "Missing device match data\n");
+		return -EINVAL;
+	}
+
+	al_dev = devm_kzalloc(dev, sizeof(*al_dev), GFP_KERNEL);
+	if (!al_dev)
+		return -ENOMEM;
+
+	al_dev->is_video_init_done = 0;
+	mutex_init(&al_dev->lock);
+	mutex_init(&al_dev->ctx_mlock);
+	INIT_LIST_HEAD(&al_dev->ctx_q_list);
+
+	al_dev->common.cb_arg = al_dev;
+	al_dev->common.process_msg_cb = al_dec_process_msg;
+	al_dev->common.fw_ready_cb = al_dec_register_v4l2;
+
+	/* firmware-name is optional in DT */
+	of_property_read_string(np, "firmware-name", &al_dev->common.fw_name);
+	if (!al_dev->common.fw_name)
+		al_dev->common.fw_name = match_data->fw_name;
+
+	ret = al_common_probe(pdev, &al_dev->common);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, al_dev);
+	dev_info(dev, "Probing done successfully %p\n", al_dev);
+
+	return 0;
+}
+
+static void al_dec_remove(struct platform_device *pdev)
+{
+	struct al_codec_dev *dev = platform_get_drvdata(pdev);
+
+	dev_info(&pdev->dev, "remove %p\n", dev);
+
+	if (dev->is_video_init_done) {
+		video_unregister_device(&dev->video_dev);
+		if (dev->m2m_dev)
+			v4l2_m2m_release(dev->m2m_dev);
+		v4l2_device_unregister(&dev->v4l2_dev);
+	}
+
+	al_common_remove(&dev->common);
+}
+
+static const struct al_match_data ald300_data = {
+	.fw_name = "al300-vdec.fw",
+};
+
+static const struct of_device_id v4l2_al_dec_dt_match[] = {
+	{ .compatible = "allegrodvt,al300-vdec", .data = &ald300_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, v4l2_al_dec_dt_match);
+
+static struct platform_driver al300_vdec_drv = {
+	.probe = al_dec_probe,
+	.remove = al_dec_remove,
+	.driver = {
+		.name = "al300_vdec",
+		.of_match_table = of_match_ptr(v4l2_al_dec_dt_match),
+	},
+};
+
+module_platform_driver(al300_vdec_drv);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:al300-vdec");
+MODULE_AUTHOR("Yassine OUAISSA <yassine.ouaissa at allegrodvt.com>");
+MODULE_DESCRIPTION("Allegro DVT V4l2 decoder driver gen 3");
diff --git a/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.h b/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.h
new file mode 100644
index 000000000000..ec1a7c450300
--- /dev/null
+++ b/drivers/media/platform/allegro-dvt/al300/al_vdec_drv.h
@@ -0,0 +1,94 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Allegro DVT.
+ * Author: Yassine OUAISSA <yassine.ouaissa at allegrodvt.fr>
+ */
+
+#ifndef __AL_VDEC_DRV__
+#define __AL_VDEC_DRV__
+
+#include "al_codec_util.h"
+
+enum {
+	MSG_ITF_TYPE_EVT_RESOLUTION_FOUND = MSG_ITF_TYPE_NEXT_EVT + 1,
+	MSG_ITF_TYPE_EVT_BITSTREAM_BUFFER_RELEASE,
+	MSG_ITF_TYPE_EVT_FRAME_BUFFER_DECODE,
+	MSG_ITF_TYPE_EVT_EOS,
+	/* Mark the end of the events list.*/
+	MSG_ITF_TYPE_END_EVT,
+};
+
+struct msg_itf_create_decoder_req {
+	unsigned int codec;
+} __packed;
+DECLARE_FULL_REQ(msg_itf_create_decoder_req);
+
+struct msg_itf_evt_resolution_found {
+	u16 buffer_nb;
+	u16 width;
+	u16 height;
+	u32 pixelformat;
+	u32 sizeimage;
+	u32 bytesperline;
+} __packed;
+DECLARE_FULL_EVENT(msg_itf_evt_resolution_found);
+
+struct msg_itf_evt_bitstream_buffer_release {
+	u64 bufferHandle;
+} __packed;
+DECLARE_FULL_EVENT(msg_itf_evt_bitstream_buffer_release);
+
+struct msg_itf_evt_frame_buffer_decode {
+	u64 bufferHandle;
+	u64 size;
+	struct al_buffer_meta meta;
+} __packed;
+DECLARE_FULL_EVENT(msg_itf_evt_frame_buffer_decode);
+
+struct msg_itf_evt_eos {
+	u32 unused;
+} __packed;
+DECLARE_FULL_EVENT(msg_itf_evt_eos);
+
+struct al_fmt {
+	u32 pixelformat;
+	u8 bpp;
+};
+
+struct al_frame {
+	u32 width;
+	u32 height;
+	u32 bytesperline;
+	u32 sizeimage;
+	u32 nbuffers;
+	const struct al_fmt *fmt;
+	enum v4l2_field field;
+	enum v4l2_colorspace colorspace;
+	enum v4l2_ycbcr_encoding ycbcr_enc;
+	enum v4l2_quantization quantization;
+	enum v4l2_xfer_func xfer_func;
+};
+
+struct al_dec_ctx {
+	struct al_codec_dev *dev;
+	struct v4l2_fh fh;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct kref refcount;
+	struct list_head list;
+	/* CAP and OUT frames */
+	struct al_frame src;
+	struct al_frame dst;
+	struct completion res_done; /* Resolution found event */
+	u32 codec;
+	u64 hDec;
+	struct list_head cmd_q_list; /* Store active commands */
+	struct mutex buf_q_mlock;
+	struct list_head frame_q_list;
+	struct list_head stream_q_list;
+	u32 csequence;
+	u32 osequence;
+	bool stopped;
+	bool aborting;
+};
+
+#endif /*__AL_VDEC_DRV__*/
-- 
2.30.2




More information about the linux-arm-kernel mailing list