[PATCH v4 2/7] remoteproc: mediatek: Add VCP remoteproc driver

Xiangzhi Tang
2026-04-27T11:15:32Z

Add support for the MediaTek Video Companion Processor (VCP)
remoteproc driver. This driver provides the following functionality:

- Uses the remoteproc framework to load VCP firmware from the
filesystem
- Integrates with ARM Trusted Firmware (ATF) via SMC calls to
configure the VCP boot sequence and manage processor states

The VCP is a RISC-V coprocessor found on MediaTek SoCs that handles
video processing tasks and requires coordination with the host CPU
for power management and inter-processor communication.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
drivers/remoteproc/Kconfig | 10 +
drivers/remoteproc/Makefile | 3 +
drivers/remoteproc/mtk_vcp_common.c | 493 ++++++++++++++++++++++
drivers/remoteproc/mtk_vcp_common.h | 220 ++++++++++
drivers/remoteproc/mtk_vcp_rproc.c | 350 +++++++++++++++
drivers/remoteproc/mtk_vcp_rproc.h | 69 +++
include/linux/remoteproc/mtk_vcp_public.h | 78 ++++
include/linux/soc/mediatek/mtk_sip_svc.h | 2 +
8 files changed, 1225 insertions(+)
create mode 100644 drivers/remoteproc/mtk_vcp_common.c
create mode 100644 drivers/remoteproc/mtk_vcp_common.h
create mode 100644 drivers/remoteproc/mtk_vcp_rproc.c
create mode 100644 drivers/remoteproc/mtk_vcp_rproc.h
create mode 100644 include/linux/remoteproc/mtk_vcp_public.h

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index ee54436fea5a..93827f6fd3c5 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -66,6 +66,16 @@ config MTK_SCP

It's safe to say N here.

+config MTK_VCP_RPROC
+ tristate "MediaTek VCP support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on ARCH_DMA_ADDR_T_64BIT
+ help
+ Say y here to support MediaTek's Video Companion Processor (VCP) via
+ the remote processor framework.
+
+ It's safe to say N here.
+
config OMAP_REMOTEPROC
tristate "OMAP remoteproc support"
depends on ARCH_OMAP4 || SOC_OMAP5 || SOC_DRA7XX
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 1c7598b8475d..ad48d85c5019 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -15,6 +15,9 @@ obj-$(CONFIG_IMX_REMOTEPROC) += imx_rproc.o
obj-$(CONFIG_IMX_DSP_REMOTEPROC) += imx_dsp_rproc.o
obj-$(CONFIG_INGENIC_VPU_RPROC) += ingenic_rproc.o
obj-$(CONFIG_MTK_SCP) += mtk_scp.o mtk_scp_ipi.o
+obj-$(CONFIG_MTK_VCP_RPROC) += mtk_vcp.o
+mtk_vcp-$(CONFIG_MTK_VCP_RPROC) += mtk_vcp_rproc.o
+mtk_vcp-$(CONFIG_MTK_VCP_RPROC) += mtk_vcp_common.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
diff --git a/drivers/remoteproc/mtk_vcp_common.c b/drivers/remoteproc/mtk_vcp_common.c
new file mode 100644
index 000000000000..9872d0caf331
--- /dev/null
+++ b/drivers/remoteproc/mtk_vcp_common.c
@@ -0,0 +1,493 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 MediaTek Corporation. All rights reserved.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/slab.h>
+#include <uapi/linux/dma-heap.h>
+
+#include "mtk_vcp_common.h"
+#include "mtk_vcp_rproc.h"
+
+phys_addr_t vcp_get_reserve_mem_phys(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id)
+{
+ if (id >= 0 && id < NUMS_MEM_ID)
+ return vcp->vcp_cluster->vcp_memory_tb[id].phys;
+
+ return 0;
+}
+
+dma_addr_t vcp_get_reserve_mem_iova(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id)
+{
+ if (id >= 0 && id < NUMS_MEM_ID)
+ return vcp->vcp_cluster->vcp_memory_tb[id].iova;
+
+ return 0;
+}
+
+void __iomem *vcp_get_reserve_mem_virt(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id)
+{
+ if (id >= 0 && id < NUMS_MEM_ID)
+ return vcp->vcp_cluster->vcp_memory_tb[id].virt;
+
+ return NULL;
+}
+
+size_t vcp_get_reserve_mem_size(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id)
+{
+ if (id >= 0 && id < NUMS_MEM_ID)
+ return vcp->vcp_cluster->vcp_memory_tb[id].size;
+
+ return 0;
+}
+
+void __iomem *vcp_get_internal_sram_virt(struct mtk_vcp_device *vcp)
+{
+ return vcp->vcp_cluster->sram_base;
+}
+
+int vcp_reserve_memory_init(struct mtk_vcp_device *vcp)
+{
+ struct device_node *rmem_node;
+ struct resource res;
+ struct iommu_domain *domain;
+ void __iomem *share_memory_virt;
+ void __iomem *rtos_memory_virt;
+ phys_addr_t mblock_phys;
+ phys_addr_t share_memory_phys;
+ dma_addr_t share_memory_iova;
+ size_t mblock_size;
+ size_t share_memory_size;
+ enum vcp_reserve_mem_id id;
+ u32 offset;
+ int ret;
+
+ rmem_node = of_parse_phandle(vcp->dev->of_node, "memory-region", 0);
+ if (!rmem_node)
+ return dev_err_probe(vcp->dev, -ENODEV, "No reserved memory-region found.\n");
+
+ ret = of_address_to_resource(rmem_node, 0, &res);
+ of_node_put(rmem_node);
+ if (ret)
+ return dev_err_probe(vcp->dev, ret, "failed to parse reserved memory\n");
+
+ mblock_phys = (phys_addr_t)res.start;
+ mblock_size = (size_t)resource_size(&res);
+
+ offset = 0;
+ for (id = 0; id < NUMS_MEM_ID; id++) {
+ vcp->vcp_cluster->vcp_memory_tb[id].phys = mblock_phys + offset;
+ vcp->vcp_cluster->vcp_memory_tb[id].size = vcp->platdata->memory_tb[id].size;
+ offset += vcp->vcp_cluster->vcp_memory_tb[id].size;
+ }
+ if (offset > mblock_size)
+ return dev_err_probe(vcp->dev, -EINVAL, "Not enough reserved memory\n");
+
+ share_memory_size = offset - vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].size;
+ share_memory_phys = vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].phys +
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].size;
+
+ rtos_memory_virt = devm_ioremap(vcp->dev,
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].phys,
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].size);
+ if (!rtos_memory_virt)
+ return dev_err_probe(vcp->dev, -ENOMEM, "Failed to map RTOS memory\n");
+
+ domain = iommu_get_domain_for_dev(vcp->dev);
+ ret = iommu_map(domain, vcp->platdata->rtos_static_iova,
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].phys,
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].size,
+ IOMMU_READ | IOMMU_WRITE | IOMMU_PRIV, GFP_KERNEL);
+ if (ret)
+ return dev_err_probe(vcp->dev, ret, "iommu map failed\n");
+
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].virt = rtos_memory_virt;
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].iova = vcp->platdata->rtos_static_iova;
+
+ ret = dma_set_mask_and_coherent(vcp->dev, DMA_BIT_MASK(DMA_MAX_MASK_BIT));
+ if (ret) {
+ ret = dev_err_probe(vcp->dev, ret, "Failed to set DMA mask\n");
+ goto unmap_iommu;
+ }
+
+ if (!vcp->dev->dma_parms) {
+ vcp->dev->dma_parms = devm_kzalloc(vcp->dev,
+ sizeof(*vcp->dev->dma_parms),
+ GFP_KERNEL);
+ if (!vcp->dev->dma_parms) {
+ ret = -ENOMEM;
+ goto unmap_iommu;
+ }
+ dma_set_max_seg_size(vcp->dev, (u32)DMA_BIT_MASK(33));
+ }
+
+ share_memory_virt = dma_alloc_coherent(vcp->dev,
+ share_memory_size,
+ &share_memory_iova,
+ GFP_KERNEL);
+ if (!share_memory_virt) {
+ ret = dev_err_probe(vcp->dev, -ENOMEM, "dma_alloc_coherent failed\n");
+ goto unmap_iommu;
+ }
+
+ offset = 0;
+ for (id = VCP_RTOS_MEM_ID + 1; id < NUMS_MEM_ID; id++) {
+ vcp->vcp_cluster->vcp_memory_tb[id].phys = share_memory_phys + offset;
+ vcp->vcp_cluster->vcp_memory_tb[id].iova = share_memory_iova + offset;
+ vcp->vcp_cluster->vcp_memory_tb[id].virt = share_memory_virt + offset;
+ offset += (u32)vcp->vcp_cluster->vcp_memory_tb[id].size;
+ }
+
+ vcp->vcp_cluster->share_mem_iova = share_memory_iova;
+ vcp->vcp_cluster->share_mem_size = share_memory_size;
+
+ return 0;
+
+unmap_iommu:
+ iommu_unmap(domain, vcp->platdata->rtos_static_iova,
+ vcp->vcp_cluster->vcp_memory_tb[VCP_RTOS_MEM_ID].size);
+ return ret;
+}
+
+int wait_core_hart_shutdown(struct mtk_vcp_device *vcp,
+ enum vcp_core_id core_id)
+{
+ u32 num_harts;
+ u32 status;
+ int ret;
+
+ if (core_id >= VCP_CORE_TOTAL) {
+ dev_err(vcp->dev, "%s, Invalid core id %d\n", __func__, core_id);
+ return -EINVAL;
+ }
+
+ num_harts = vcp->vcp_cluster->hart_count[core_id];
+
+ /* Wait for hart0 shutdown */
+ if (core_id == VCP_ID) {
+ ret = readl_poll_timeout(vcp->vcp_cluster->cfg + VCP_C0_GPR5_H0_REBOOT,
+ status, (status & CORE_RDY_TO_REBOOT),
+ USEC_PER_MSEC,
+ CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(vcp->dev, "VCP hart0 shutdown timeout\n");
+ return ret;
+ }
+
+ /* Wait for hart1 shutdown if this core has 2 harts */
+ if (num_harts > 1) {
+ ret = readl_poll_timeout(vcp->vcp_cluster->cfg + VCP_C0_GPR6_H1_REBOOT,
+ status, (status & CORE_RDY_TO_REBOOT),
+ USEC_PER_MSEC,
+ CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(vcp->dev, "VCP hart1 shutdown timeout\n");
+ return ret;
+ }
+ }
+ } else if (core_id == MMUP_ID) {
+ ret = readl_poll_timeout(vcp->vcp_cluster->cfg + VCP_C1_GPR5_H0_REBOOT,
+ status, (status & CORE_RDY_TO_REBOOT),
+ USEC_PER_MSEC,
+ CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(vcp->dev, "MMUP hart0 shutdown timeout\n");
+ return ret;
+ }
+
+ /* Wait for hart1 shutdown if this core has 2 harts */
+ if (num_harts > 1) {
+ ret = readl_poll_timeout(vcp->vcp_cluster->cfg + VCP_C1_GPR6_H1_REBOOT,
+ status, (status & CORE_RDY_TO_REBOOT),
+ USEC_PER_MSEC,
+ CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+ if (ret) {
+ dev_err(vcp->dev, "MMUP hart1 shutdown timeout\n");
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int reset_vcp(struct mtk_vcp_device *vcp)
+{
+ struct arm_smccc_res res;
+
+ if (vcp->vcp_cluster->core_nums > MMUP_ID) {
+ writel((u32)VCP_PACK_IOVA(vcp->vcp_cluster->share_mem_iova),
+ vcp->vcp_cluster->cfg + VCP_C1_GPR1_DRAM_RESV_ADDR);
+ writel((u32)vcp->vcp_cluster->share_mem_size,
+ vcp->vcp_cluster->cfg + VCP_C1_GPR2_DRAM_RESV_SIZE);
+
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_MMUP_KERNEL_OP_RESET_RELEASE,
+ 1, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "MMUP reset release SMC failed: %ld\n", res.a0);
+ return -EIO;
+ }
+ }
+
+ writel((u32)VCP_PACK_IOVA(vcp->vcp_cluster->share_mem_iova),
+ vcp->vcp_cluster->cfg + VCP_C0_GPR1_DRAM_RESV_ADDR);
+ writel((u32)vcp->vcp_cluster->share_mem_size,
+ vcp->vcp_cluster->cfg + VCP_C0_GPR2_DRAM_RESV_SIZE);
+
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_VCP_KERNEL_OP_RESET_RELEASE,
+ 1, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "VCP reset release SMC failed: %ld\n", res.a0);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static size_t load_part_binary(void __iomem *image_buf,
+ const u8 *fw_src,
+ size_t size,
+ const char *part_bin_name)
+{
+ const u8 *fw_ptr = fw_src;
+ u32 offset;
+ u32 align_size;
+ const struct mtk_vcp_img_hdr *img_hdr_info;
+
+ if (!fw_src || !image_buf || size < VCP_IMAGE_HEADER_SIZE)
+ return 0;
+
+ offset = 0;
+ while (offset < size) {
+ img_hdr_info = (const struct mtk_vcp_img_hdr *)(fw_ptr + offset);
+ align_size = round_up(img_hdr_info->dsz, ALIGN_16);
+ offset += VCP_IMAGE_HEADER_SIZE;
+ if (img_hdr_info->magic != VCM_IMAGE_MAGIC ||
+ strncmp(img_hdr_info->name, part_bin_name, VCM_IMAGE_NAME_MAXSZ - 1)) {
+ offset += align_size;
+ } else {
+ memcpy_toio(image_buf, fw_ptr + offset, img_hdr_info->dsz);
+ offset += align_size;
+ return img_hdr_info->dsz;
+ }
+ }
+
+ return 0;
+}
+
+static int load_vcp_bin(const u8 *fw_src,
+ size_t size,
+ void __iomem *img_buf_va,
+ phys_addr_t img_buf_pa,
+ dma_addr_t img_buf_iova,
+ struct mtk_vcp_device *vcp)
+{
+ u32 fw_size;
+ u32 dram_img_size;
+ u32 dram_backup_img_offset;
+ struct vcp_region_info_st vcp_region_info = {};
+ struct arm_smccc_res res;
+
+ fw_size = load_part_binary(vcp->vcp_cluster->sram_base +
+ vcp->vcp_cluster->sram_offset[VCP_ID],
+ fw_src, size, VCP_HFRP_PART_NAME);
+ if (!fw_size) {
+ dev_err(vcp->dev, "Failed to load %s\n", VCP_HFRP_PART_NAME);
+ return -EINVAL;
+ }
+
+ dram_img_size = load_part_binary(img_buf_va + VCP_DRAM_IMG_OFFSET,
+ fw_src, size, VCP_HFRP_DRAM_PART_NAME);
+ if (!dram_img_size) {
+ dev_err(vcp->dev, "Failed to load %s\n", VCP_HFRP_DRAM_PART_NAME);
+ return -EINVAL;
+ }
+
+ vcp_region_info.struct_size = sizeof(struct vcp_region_info_st);
+
+ dram_backup_img_offset = VCP_DRAM_IMG_OFFSET + round_up(dram_img_size, ALIGN_1024);
+
+ vcp_region_info.ap_dram_start = VCP_PACK_IOVA(img_buf_iova + VCP_DRAM_IMG_OFFSET);
+ vcp_region_info.ap_dram_backup_start = VCP_PACK_IOVA(img_buf_iova + dram_backup_img_offset);
+ vcp_region_info.ap_dram_size = dram_img_size;
+
+ vcp_region_info.l2tcm_offset = vcp->vcp_cluster->sram_offset[MMUP_ID];
+
+ memcpy_toio(vcp->vcp_cluster->sram_base +
+ vcp->vcp_cluster->sram_offset[VCP_ID] + REGION_OFFSET,
+ &vcp_region_info, sizeof(vcp_region_info));
+
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_MMUP_KERNEL_OP_SET_L2TCM_OFFSET,
+ vcp->vcp_cluster->sram_offset[MMUP_ID],
+ 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "Set L2TCM offset SMC failed: %ld\n", res.a0);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int load_mmup_bin(const u8 *fw_src,
+ size_t size,
+ void __iomem *img_buf_va,
+ phys_addr_t img_buf_pa,
+ dma_addr_t img_buf_iova,
+ struct mtk_vcp_device *vcp)
+{
+ u32 fw_size;
+ u32 dram_img_size;
+ u32 dram_backup_img_offset;
+ struct vcp_region_info_st vcp_region_info = {};
+ struct arm_smccc_res res;
+
+ fw_size = load_part_binary(vcp->vcp_cluster->sram_base +
+ vcp->vcp_cluster->sram_offset[MMUP_ID],
+ fw_src, size, VCP_MMUP_PART_NAME);
+ if (!fw_size) {
+ dev_err(vcp->dev, "Failed to load %s\n", VCP_MMUP_PART_NAME);
+ return -EINVAL;
+ }
+
+ dram_img_size = load_part_binary(img_buf_va + MMUP_DRAM_IMG_OFFSET, fw_src, size,
+ VCP_MMUP_DRAM_PART_NAME);
+ if (!dram_img_size) {
+ dev_err(vcp->dev, "Failed to load %s\n", VCP_MMUP_DRAM_PART_NAME);
+ return -EINVAL;
+ }
+
+ vcp_region_info.struct_size = sizeof(struct vcp_region_info_st);
+
+ dram_backup_img_offset = MMUP_DRAM_IMG_OFFSET + round_up(dram_img_size, ALIGN_1024);
+ vcp_region_info.ap_dram_start = VCP_PACK_IOVA(img_buf_iova + MMUP_DRAM_IMG_OFFSET);
+ vcp_region_info.ap_dram_backup_start = VCP_PACK_IOVA(img_buf_iova + dram_backup_img_offset);
+ vcp_region_info.ap_dram_size = dram_img_size;
+
+ memcpy_toio(vcp->vcp_cluster->sram_base +
+ vcp->vcp_cluster->sram_offset[MMUP_ID] + REGION_OFFSET,
+ &vcp_region_info, sizeof(vcp_region_info));
+
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_MMUP_KERNEL_OP_SET_FW_SIZE,
+ fw_size, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "Set firmware size SMC failed: %ld\n", res.a0);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int mtk_vcp_load(struct rproc *rproc, const struct firmware *fw)
+{
+ struct arm_smccc_res res;
+ struct mtk_vcp_device *vcp = rproc->priv;
+ dma_addr_t img_buf_iova;
+ phys_addr_t img_buf_phys;
+ void __iomem *img_buf_va;
+ int ret;
+
+ if (!vcp)
+ return -EINVAL;
+
+ if (fw->size < VCP_IMAGE_HEADER_SIZE ||
+ fw->size > vcp->ops->get_mem_size(vcp, VCP_RTOS_MEM_ID)) {
+ dev_err(vcp->dev, "Invalid firmware size\n");
+ return -EINVAL;
+ }
+
+ writel(0x1, vcp->vcp_cluster->cfg_core + VCP_R_CORE0_SW_RSTN_SET);
+ writel(0x1, vcp->vcp_cluster->cfg_core + VCP_R_CORE1_SW_RSTN_SET);
+
+ memset_io(vcp->vcp_cluster->sram_base, 0, vcp->vcp_cluster->sram_size);
+
+ img_buf_iova = vcp->ops->get_mem_iova(vcp, VCP_RTOS_MEM_ID);
+ img_buf_phys = vcp->ops->get_mem_phys(vcp, VCP_RTOS_MEM_ID);
+ img_buf_va = vcp->ops->get_mem_virt(vcp, VCP_RTOS_MEM_ID);
+
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_VCP_KERNEL_OP_COLD_BOOT_VCP,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "Cold boot SMC failed: %ld\n", res.a0);
+ return -EIO;
+ }
+
+ ret = load_vcp_bin(fw->data, fw->size,
+ img_buf_va, img_buf_phys,
+ img_buf_iova, vcp);
+ if (ret)
+ return ret;
+
+ ret = load_mmup_bin(fw->data, fw->size,
+ img_buf_va, img_buf_phys,
+ img_buf_iova, vcp);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static irqreturn_t vcp_irq_handler(int irq, void *priv)
+{
+ u32 reg0, reg1;
+ struct mtk_vcp_device *vcp = priv;
+
+ reg0 = readl(vcp->vcp_cluster->cfg_core + R_CORE0_WDT_IRQ);
+ reg1 = vcp->vcp_cluster->core_nums > VCP_ID ?
+ readl(vcp->vcp_cluster->cfg_core + R_CORE1_WDT_IRQ) : 0;
+
+ if (!reg0 && !reg1)
+ return IRQ_NONE;
+
+ if (reg0) {
+ writel(B_WDT_IRQ, vcp->vcp_cluster->cfg_core + R_CORE0_WDT_IRQ);
+ dev_err(vcp->dev, "VCP core watchdog timeout\n");
+ }
+
+ if (reg1) {
+ writel(B_WDT_IRQ, vcp->vcp_cluster->cfg_core + R_CORE1_WDT_IRQ);
+ dev_err(vcp->dev, "MMUP core watchdog timeout\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+int vcp_wdt_irq_init(struct mtk_vcp_device *vcp)
+{
+ int ret;
+
+ ret = devm_request_irq(vcp->dev, platform_get_irq(vcp->pdev, 0),
+ vcp_irq_handler, IRQF_ONESHOT,
+ vcp->pdev->name, vcp);
+ if (ret)
+ dev_err_probe(vcp->dev, ret, "failed to request wdt irq\n");
+
+ return ret;
+}
+
+MODULE_AUTHOR("Xiangzhi Tang <xiangzhi.tang@mediatek.com>");
+MODULE_DESCRIPTION("MTK VCP Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/remoteproc/mtk_vcp_common.h b/drivers/remoteproc/mtk_vcp_common.h
new file mode 100644
index 000000000000..96eaed1b8f02
--- /dev/null
+++ b/drivers/remoteproc/mtk_vcp_common.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2025 MediaTek Inc.
+ */
+
+#ifndef __MTK_VCP_COMMON_H
+#define __MTK_VCP_COMMON_H
+
+#include <linux/arm-smccc.h>
+#include <linux/bitops.h>
+#include <linux/firmware.h>
+#include <linux/soc/mediatek/mtk_sip_svc.h>
+#include <linux/remoteproc/mtk_vcp_public.h>
+
+/* VCP timeout definition */
+#define CORE_HART_SHUTDOWN_TIMEOUT_MS 10
+
+/* VCP platform definition */
+#define DMA_MAX_MASK_BIT 33
+
+/* VCP load image definition */
+#define VCM_IMAGE_MAGIC (0x58881688)
+#define VCM_IMAGE_NAME_MAXSZ (32)
+#define VCP_IMAGE_HEADER_SIZE (0x200)
+
+#define VCP_DRAM_IMG_OFFSET (0x200000)
+#define MMUP_DRAM_IMG_OFFSET (0x1200000)
+
+#define REGION_OFFSET (0x4)
+#define ALIGN_1024 (1024)
+#define ALIGN_16 (16)
+#define VCP_HFRP_PART_NAME "tinysys-vcp-RV55_A"
+#define VCP_MMUP_PART_NAME "tinysys-mmup-RV33_A"
+#define VCP_HFRP_DRAM_PART_NAME "tinysys-vcp-RV55_A_dram"
+#define VCP_MMUP_DRAM_PART_NAME "tinysys-mmup-RV33_A_dram"
+
+/* VCP memory iova pack convert definition */
+#define VCP_PACK_IOVA(addr) ((u32)((addr) | (((u64)(addr) >> 32) & 0xF)))
+#define VCP_UNPACK_IOVA(addr) \
+ ((u64)((addr) & 0xFFFFFFF0) | (((u64)(addr) & 0xF) << 32))
+
+/* VCP cfg_core register offset definition */
+#define VCP_R_CORE0_SW_RSTN_SET (0x0004)
+#define VCP_R_CORE1_SW_RSTN_SET (0x000C)
+#define R_GIPC_IN_SET (0x0028)
+#define R_GIPC_IN_CLR (0x002C)
+#define GIPC_MMUP_SHUT BIT(10)
+#define GIPC_VCP_HART0_SHUT BIT(14)
+#define B_GIPC4_SETCLR_3 BIT(19)
+#define R_CORE0_WDT_IRQ (0x0050)
+#define R_CORE1_WDT_IRQ (0x0054)
+#define B_WDT_IRQ BIT(0)
+#define AP_R_GPR2 (0x0068)
+#define B_CORE0_SUSPEND BIT(0)
+#define B_CORE0_RESUME BIT(1)
+#define AP_R_GPR3 (0x006C)
+#define B_CORE1_SUSPEND BIT(0)
+#define B_CORE1_RESUME BIT(1)
+
+/* VCP cfg register offset definition */
+#define R_CORE0_STATUS (0x6070)
+#define B_CORE_GATED BIT(0)
+#define B_HART0_HALT BIT(1)
+#define B_HART1_HALT BIT(2)
+#define B_CORE_AXIS_BUSY BIT(4)
+#define R_CORE1_STATUS (0x9070)
+#define VCP_C0_GPR0_SUSPEND_RESUME (0x6040)
+#define VCP_C0_GPR1_DRAM_RESV_ADDR (0x6044)
+#define VCP_C0_GPR2_DRAM_RESV_SIZE (0x6048)
+#define VCP_C0_GPR3_DRAM_RESV_LOGGER (0x604C)
+#define VCP_C0_GPR5_H0_REBOOT (0x6054)
+#define CORE_RDY_TO_REBOOT (0x0034)
+#define VCP_C0_GPR6_H1_REBOOT (0x6058)
+#define VCP_C1_GPR0_SUSPEND_RESUME (0x9040)
+#define VCP_C1_GPR1_DRAM_RESV_ADDR (0x9044)
+#define VCP_C1_GPR2_DRAM_RESV_SIZE (0x9048)
+#define VCP_C1_GPR3_DRAM_RESV_LOGGER (0x904C)
+#define VCP_C1_GPR5_H0_REBOOT (0x9054)
+#define VCP_C1_GPR6_H1_REBOOT (0x9058)
+
+/* VCP cfg_sec register offset definition */
+#define R_GPR2_SEC (0x0008)
+#define MMUP_AP_SUSPEND BIT(0)
+#define R_GPR3_SEC (0x000C)
+#define VCP_AP_SUSPEND BIT(0)
+
+enum vcp_core_id {
+ VCP_ID = 0,
+ MMUP_ID,
+ VCP_CORE_TOTAL,
+};
+
+enum mtk_tinysys_vcp_kernel_op {
+ MTK_TINYSYS_VCP_KERNEL_OP_RESET_SET = 0,
+ MTK_TINYSYS_VCP_KERNEL_OP_RESET_RELEASE,
+ MTK_TINYSYS_VCP_KERNEL_OP_COLD_BOOT_VCP,
+ MTK_TINYSYS_MMUP_KERNEL_OP_RESET_SET,
+ MTK_TINYSYS_MMUP_KERNEL_OP_RESET_RELEASE,
+ MTK_TINYSYS_MMUP_KERNEL_OP_SET_L2TCM_OFFSET,
+ MTK_TINYSYS_MMUP_KERNEL_OP_SET_FW_SIZE,
+ MTK_TINYSYS_MMUP_KERNEL_OP_COLD_BOOT_MMUP,
+ MTK_TINYSYS_VCP_KERNEL_OP_NUM,
+};
+
+/**
+ * struct mtk_vcp_img_hdr - mtk image header format.
+ *
+ * @magic: mtk vcp image magic id
+ * @dsz: mtk vcp image part binary size
+ * @name: mtk vcp image part binary parttion name
+ */
+struct mtk_vcp_img_hdr {
+ u32 magic;
+ u32 dsz;
+ char name[VCM_IMAGE_NAME_MAXSZ];
+};
+
+/**
+ * struct mtk_vcp_feature_table - feature table structure definition.
+ *
+ * @feature_id: feature id
+ * @core_id: feature using vcp core id
+ */
+struct mtk_vcp_feature_table {
+ enum vcp_feature_id feature_id;
+ enum vcp_core_id core_id;
+};
+
+/**
+ * struct mtk_vcp_reserved_mem_table - memory table structure definition.
+ *
+ * @memory_id: memory_id id
+ * @size: predistribution memory size
+ */
+struct mtk_vcp_reserved_mem_table {
+ enum vcp_reserve_mem_id memory_id;
+ size_t size;
+};
+
+/**
+ * struct vcp_reserve_mblock - vcp reserved memory structure.
+ *
+ * @vcp_reserve_mem_id: reserved memory id
+ * @phys: reserved memory phy addr
+ * @iova: reserved memory dma map addr
+ * @virt: reserved memory CPU virt addr
+ * @size: reserved memory size
+ */
+struct vcp_reserve_mblock {
+ enum vcp_reserve_mem_id num;
+ phys_addr_t phys;
+ dma_addr_t iova;
+ void __iomem *virt;
+ size_t size;
+};
+
+/**
+ * struct vcp_region_info_st - config vcp image info sync to vcp bootloader.
+ *
+ * @ap_loader_start: config vcp bootloader to copy loader start addr
+ * @ap_loader_size: config vcp bootloader to copy loader size
+ * @ap_firmware_start: config vcp bootloader to copy firmware start addr
+ * @ap_firmware_size: config vcp bootloader to copy firmware size
+ * @ap_dram_start: config vcp run dram binary start addr
+ * @ap_dram_size: config vcp run dram binary size
+ * @ap_dram_backup_start: config vcp backup dram binary start addr
+ * @struct_size: vcp image region info structure size
+ * @l2tcm_offset: vcp two core using l2sram layout
+ * @TaskContext_ptr: vcp task context ptr for debug
+ * @vcpctl: - vcp control info
+ * @regdump_start: regdump start addr for debug
+ * @regdump_size: regdump size for debug
+ * @ap_params_start: params start addr
+ * @sramlog_buf_offset: sramlog_buf_offset for debug
+ * @sramlog_end_idx_offset: sramlog_end_idx_offset for debug
+ * @sramlog_buf_maxlen: sramlog_buf_maxlen for debug
+ * @ap_loader_start_pa: config vcp bootloader for loader start pa
+ * @coredump_offset: coredump_offset offset for debug
+ * @coredump_dram_offset: coredump_dram_offset offset for debug
+ *
+ * This structure is shared with VCP firmware and must be kept in sync.
+ */
+struct vcp_region_info_st {
+ u32 ap_loader_start;
+ u32 ap_loader_size;
+ u32 ap_firmware_start;
+ u32 ap_firmware_size;
+ u32 ap_dram_start;
+ u32 ap_dram_size;
+ u32 ap_dram_backup_start;
+ u32 struct_size;
+ u32 l2tcm_offset;
+ u32 TaskContext_ptr;
+ u32 vcpctl;
+ u32 regdump_start;
+ u32 regdump_size;
+ u32 ap_params_start;
+ u32 sramlog_buf_offset;
+ u32 sramlog_end_idx_offset;
+ u32 sramlog_buf_maxlen;
+ u32 ap_loader_start_pa;
+ u32 coredump_offset;
+ u32 coredump_dram_offset;
+} __packed;
+
+
+int vcp_reserve_memory_init(struct mtk_vcp_device *vcp);
+phys_addr_t vcp_get_reserve_mem_phys(struct mtk_vcp_device *vcp, enum vcp_reserve_mem_id id);
+dma_addr_t vcp_get_reserve_mem_iova(struct mtk_vcp_device *vcp, enum vcp_reserve_mem_id id);
+size_t vcp_get_reserve_mem_size(struct mtk_vcp_device *vcp, enum vcp_reserve_mem_id id);
+void __iomem *vcp_get_reserve_mem_virt(struct mtk_vcp_device *vcp, enum vcp_reserve_mem_id id);
+void __iomem *vcp_get_internal_sram_virt(struct mtk_vcp_device *vcp);
+
+int reset_vcp(struct mtk_vcp_device *vcp);
+int mtk_vcp_load(struct rproc *rproc, const struct firmware *fw);
+
+int vcp_wdt_irq_init(struct mtk_vcp_device *vcp);
+
+int wait_core_hart_shutdown(struct mtk_vcp_device *vcp, enum vcp_core_id core_id);
+#endif
diff --git a/drivers/remoteproc/mtk_vcp_rproc.c b/drivers/remoteproc/mtk_vcp_rproc.c
new file mode 100644
index 000000000000..f12df45d782e
--- /dev/null
+++ b/drivers/remoteproc/mtk_vcp_rproc.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 MediaTek Corporation. All rights reserved.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/remoteproc.h>
+
+#include "mtk_vcp_common.h"
+#include "mtk_vcp_rproc.h"
+#include "remoteproc_internal.h"
+
+/**
+ * vcp_get() - get a reference to VCP.
+ *
+ * @pdev: the platform device of the module requesting VCP platform
+ * device for using VCP API.
+ *
+ * Return: Return NULL if failed. otherwise reference to VCP.
+ **/
+struct mtk_vcp_device *vcp_get(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *vcp_node;
+ struct platform_device *vcp_pdev;
+
+ vcp_node = of_parse_phandle(dev->of_node, "mediatek,vcp", 0);
+ if (!vcp_node) {
+ dev_err(dev, "can't get VCP node\n");
+ return NULL;
+ }
+
+ vcp_pdev = of_find_device_by_node(vcp_node);
+ of_node_put(vcp_node);
+
+ if (!vcp_pdev)
+ return NULL;
+
+ return platform_get_drvdata(vcp_pdev);
+}
+EXPORT_SYMBOL_GPL(vcp_get);
+
+/**
+ * vcp_put() - release the reference to VCP.
+ *
+ * @vcp: the VCP device obtained from vcp_get().
+ */
+void vcp_put(struct mtk_vcp_device *vcp)
+{
+ put_device(vcp->dev);
+}
+EXPORT_SYMBOL_GPL(vcp_put);
+
+static int mtk_vcp_start(struct rproc *rproc)
+{
+ struct mtk_vcp_device *vcp = rproc->priv;
+ struct arm_smccc_res res;
+
+ /* core 0 */
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_VCP_KERNEL_OP_RESET_SET,
+ 1, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "VCP reset set SMC failed: %ld\n", res.a0);
+ ret = -EIO;
+ goto reset_failed;
+ }
+
+ /* core 1 */
+ arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
+ MTK_TINYSYS_MMUP_KERNEL_OP_RESET_SET,
+ 1, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != 1) {
+ dev_err(vcp->dev, "MMUP reset set SMC failed: %ld\n", res.a0);
+ ret = -EIO;
+ goto reset_failed;
+ }
+
+ ret = reset_vcp(vcp);
+ if (ret) {
+ dev_err(vcp->dev, "%s, VCP bootup failed\n", __func__);
+ goto reset_failed;
+ }
+
+ dev_info(vcp->dev, "VCP bootup successfully\n");
+
+ return 0;
+
+reset_failed:
+
+ return ret;
+}
+
+static int mtk_vcp_stop(struct rproc *rproc)
+{
+ return 0;
+}
+
+static const struct rproc_ops mtk_vcp_ops = {
+ .load = mtk_vcp_load,
+ .start = mtk_vcp_start,
+ .stop = mtk_vcp_stop,
+};
+
+static int vcp_multi_core_init(struct platform_device *pdev,
+ struct mtk_vcp_of_cluster *vcp_cluster,
+ enum vcp_core_id core_id)
+{
+ u32 num_harts;
+ int ret;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "mediatek,vcp-core-harts",
+ &num_harts);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to get harts property\n");
+
+ vcp_cluster->hart_count[core_id] = num_harts;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "mediatek,vcp-core-sram-offset",
+ &vcp_cluster->sram_offset[core_id]);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to get sram-offset property\n");
+
+ return 0;
+}
+
+static struct mtk_vcp_device *vcp_rproc_init(struct platform_device *pdev,
+ struct mtk_vcp_of_cluster *vcp_cluster)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct device_node *child;
+ struct platform_device *cpdev;
+ struct mtk_vcp_device *vcp;
+ struct rproc *rproc;
+ const struct mtk_vcp_of_data *vcp_of_data;
+ u32 core_id;
+ int ret;
+
+ vcp_of_data = of_device_get_match_data(dev);
+ rproc = devm_rproc_alloc(dev, np->name, &mtk_vcp_ops,
+ vcp_of_data->platdata.fw_name,
+ sizeof(struct mtk_vcp_device));
+ if (!rproc)
+ return ERR_PTR(dev_err_probe(dev, -ENOMEM, "allocate remoteproc failed\n"));
+
+ vcp = rproc->priv;
+ vcp->rproc = rproc;
+ vcp->pdev = pdev;
+ vcp->dev = dev;
+ vcp->ops = &vcp_of_data->ops;
+ vcp->platdata = &vcp_of_data->platdata;
+ vcp->vcp_cluster = vcp_cluster;
+
+ rproc->auto_boot = vcp_of_data->platdata.auto_boot;
+ rproc->sysfs_read_only = vcp_of_data->platdata.sysfs_read_only;
+ platform_set_drvdata(pdev, vcp);
+
+ ret = vcp_reserve_memory_init(vcp);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "reserve memory failed\n"));
+
+ core_id = 0;
+ for_each_available_child_of_node(np, child) {
+ if (of_device_is_compatible(child, "mediatek,vcp-core")) {
+ cpdev = of_find_device_by_node(child);
+ if (!cpdev) {
+ of_node_put(child);
+ return ERR_PTR(dev_err_probe(dev, -ENODEV, "Not child node\n"));
+ }
+ ret = vcp_multi_core_init(cpdev, vcp_cluster, core_id);
+ if (ret) {
+ of_node_put(child);
+ return ERR_PTR(dev_err_probe(dev, ret,
+ "vcp_multi_core_init failed\n"));
+ }
+ core_id++;
+ }
+ }
+ vcp->vcp_cluster->core_nums = core_id;
+
+ ret = vcp_wdt_irq_init(vcp);
+ if (ret)
+ return ERR_PTR(dev_err_probe(dev, ret, "vcp_wdt_irq_init failed\n"));
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ return ERR_PTR(dev_err_probe(dev, ret, "Failed to runtime resume\n"));
+ }
+
+ return vcp;
+}
+
+static int vcp_cluster_init(struct platform_device *pdev,
+ struct mtk_vcp_of_cluster *vcp_cluster)
+{
+ struct mtk_vcp_device *vcp;
+ int ret;
+
+ vcp = vcp_rproc_init(pdev, vcp_cluster);
+ if (IS_ERR(vcp))
+ return dev_err_probe(vcp->dev, PTR_ERR(vcp), "vcp_rproc_init failed\n");
+
+ ret = rproc_add(vcp->rproc);
+ if (ret)
+ return dev_err_probe(vcp->dev, ret, "Failed to add rproc\n");
+
+ return 0;
+}
+
+static int vcp_device_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ struct mtk_vcp_of_cluster *vcp_cluster;
+ int ret;
+
+ pm_runtime_enable(dev);
+
+ vcp_cluster = devm_kzalloc(dev, sizeof(*vcp_cluster), GFP_KERNEL);
+ if (!vcp_cluster)
+ return dev_err_probe(dev, -ENOMEM, "allocate resource failed\n");
+
+ vcp_cluster->cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+ if (IS_ERR(vcp_cluster->cfg))
+ return dev_err_probe(dev, PTR_ERR(vcp_cluster->cfg),
+ "Failed to parse and map cfg memory\n");
+
+ vcp_cluster->cfg_sec = devm_platform_ioremap_resource_byname(pdev, "cfg_sec");
+ if (IS_ERR(vcp_cluster->cfg_sec))
+ return dev_err_probe(dev, PTR_ERR(vcp_cluster->cfg_sec),
+ "Failed to parse and map cfg_sec memory\n");
+
+ vcp_cluster->cfg_core = devm_platform_ioremap_resource_byname(pdev, "cfg_core");
+ if (IS_ERR(vcp_cluster->cfg_core))
+ return dev_err_probe(dev, PTR_ERR(vcp_cluster->cfg_core),
+ "Failed to parse and map cfg_core memory\n");
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ vcp_cluster->sram_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(vcp_cluster->sram_base))
+ return dev_err_probe(dev, PTR_ERR(vcp_cluster->sram_base),
+ "Failed to parse and map sram memory\n");
+ vcp_cluster->sram_size = (u32)resource_size(res);
+
+ ret = devm_of_platform_populate(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to populate platform devices\n");
+
+ ret = vcp_cluster_init(pdev, vcp_cluster);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void vcp_device_remove(struct platform_device *pdev)
+{
+ struct mtk_vcp_device *vcp = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ rproc_del(vcp->rproc);
+}
+
+static void vcp_device_shutdown(struct platform_device *pdev)
+{
+ struct mtk_vcp_device *vcp = platform_get_drvdata(pdev);
+ int ret;
+
+ writel(GIPC_VCP_HART0_SHUT, vcp->vcp_cluster->cfg_core + R_GIPC_IN_SET);
+ ret = wait_core_hart_shutdown(vcp, VCP_ID);
+ if (ret)
+ dev_err(&pdev->dev, "wait VCP_ID core hart shutdown timeout\n");
+
+ if (vcp->vcp_cluster->core_nums > MMUP_ID) {
+ writel(GIPC_MMUP_SHUT, vcp->vcp_cluster->cfg_core + R_GIPC_IN_SET);
+ ret = wait_core_hart_shutdown(vcp, MMUP_ID);
+ if (ret)
+ dev_err(&pdev->dev, "wait MMUP_ID core hart shutdown timeout\n");
+ }
+}
+
+static struct mtk_vcp_feature_table mt8196_feature_tb[NUM_FEATURE_ID] = {
+ { .feature_id = RTOS_FEATURE_ID, .core_id = VCP_CORE_TOTAL },
+ { .feature_id = VDEC_FEATURE_ID, .core_id = VCP_ID },
+ { .feature_id = VENC_FEATURE_ID, .core_id = VCP_ID },
+ { .feature_id = MMDVFS_MMUP_FEATURE_ID, .core_id = MMUP_ID },
+ { .feature_id = MMDVFS_VCP_FEATURE_ID, .core_id = VCP_ID },
+ { .feature_id = MMDEBUG_FEATURE_ID, .core_id = MMUP_ID },
+ { .feature_id = VMM_FEATURE_ID, .core_id = MMUP_ID },
+ { .feature_id = VDISP_FEATURE_ID, .core_id = MMUP_ID },
+ { .feature_id = MMQOS_FEATURE_ID, .core_id = VCP_ID },
+};
+
+static struct mtk_vcp_reserved_mem_table mt8196_memory_tb[NUMS_MEM_ID] = {
+ { .memory_id = VCP_RTOS_MEM_ID, .size = 0x1a00000 },
+ { .memory_id = VDEC_MEM_ID, .size = 0x30000 },
+ { .memory_id = VENC_MEM_ID, .size = 0x12000 },
+ { .memory_id = MMDVFS_VCP_MEM_ID, .size = 0x1000 },
+ { .memory_id = MMDVFS_MMUP_MEM_ID, .size = 0x1000 },
+ { .memory_id = MMQOS_MEM_ID, .size = 0x1000 },
+};
+
+static const struct mtk_vcp_of_data mt8196_of_data = {
+ .ops = {
+ .get_mem_phys = vcp_get_reserve_mem_phys,
+ .get_mem_iova = vcp_get_reserve_mem_iova,
+ .get_mem_virt = vcp_get_reserve_mem_virt,
+ .get_mem_size = vcp_get_reserve_mem_size,
+ .get_vcp_sram_virt = vcp_get_internal_sram_virt,
+ },
+ .platdata = {
+ .auto_boot = true,
+ .sysfs_read_only = true,
+ .rtos_static_iova = 0x180600000,
+ .feature_tb = mt8196_feature_tb,
+ .memory_tb = mt8196_memory_tb,
+ .fw_name = "mediatek/mt8196/vcp.img",
+ },
+};
+
+static const struct of_device_id mtk_vcp_of_match[] = {
+ { .compatible = "mediatek,mt8196-vcp", .data = &mt8196_of_data},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mtk_vcp_of_match);
+
+static struct platform_driver mtk_vcp_device = {
+ .probe = vcp_device_probe,
+ .remove = vcp_device_remove,
+ .shutdown = vcp_device_shutdown,
+ .driver = {
+ .name = "mtk-vcp",
+ .of_match_table = mtk_vcp_of_match,
+ },
+};
+
+module_platform_driver(mtk_vcp_device);
+
+MODULE_AUTHOR("Xiangzhi Tang <xiangzhi.tang@mediatek.com>");
+MODULE_DESCRIPTION("MTK VCP Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/remoteproc/mtk_vcp_rproc.h b/drivers/remoteproc/mtk_vcp_rproc.h
new file mode 100644
index 000000000000..3b989c8eb337
--- /dev/null
+++ b/drivers/remoteproc/mtk_vcp_rproc.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2025 MediaTek Inc.
+ */
+
+#ifndef __MTK_VCP_RPROC_H__
+#define __MTK_VCP_RPROC_H__
+
+#include <linux/remoteproc/mtk_vcp_public.h>
+
+/*
+ * struct mtk_vcp_of_cluster - vcp cluster priv data.
+ *
+ * @sram_base: sram_base get from dtb
+ * @cfg: cfg register get from dtb
+ * @cfg_sec: cfg_sec register get from dtb
+ * @cfg_core: cfg_core register get from dtb
+ * @sram_size: total sram size get from dtb
+ * @core_nums: total core numbers get from dtb
+ * @hart_count: number of hardware threads (harts) per core
+ * @sram_offset: core sram memory layout
+ * @share_mem_iova: shared memory iova base
+ * @share_mem_size: shared memory size
+ * @vcp_memory_tb: vcp memory allocated table
+ */
+struct mtk_vcp_of_cluster {
+ void __iomem *sram_base;
+ void __iomem *cfg;
+ void __iomem *cfg_sec;
+ void __iomem *cfg_core;
+ u32 sram_size;
+ u32 core_nums;
+ u32 hart_count[VCP_CORE_TOTAL];
+ u32 sram_offset[VCP_CORE_TOTAL];
+ dma_addr_t share_mem_iova;
+ size_t share_mem_size;
+ struct vcp_reserve_mblock vcp_memory_tb[NUMS_MEM_ID];
+};
+
+/**
+ * struct mtk_vcp_platdata - vcp platform priv data.
+ *
+ * @auto_boot: rproc auto_boot flag
+ * @sysfs_read_only: rproc sysfs_read_only flag
+ * @rtos_static_iova: vcp dram binary static map iova
+ * @feature_tb: vcp feature table structure
+ * @memory_tb: vcp memory table structure
+ * @fw_name: vcp image name and path
+ */
+struct mtk_vcp_platdata {
+ bool auto_boot;
+ bool sysfs_read_only;
+ dma_addr_t rtos_static_iova;
+ struct mtk_vcp_feature_table *feature_tb;
+ struct mtk_vcp_reserved_mem_table *memory_tb;
+ char *fw_name;
+};
+
+/**
+ * struct mtk_vcp_of_data - const vcp device data.
+ *
+ * @mtk_vcp_ops: mtk_vcp_ops structure
+ * @mtk_vcp_platdata: mtk_vcp_platdata structure
+ */
+struct mtk_vcp_of_data {
+ const struct mtk_vcp_ops ops;
+ const struct mtk_vcp_platdata platdata;
+};
+#endif
diff --git a/include/linux/remoteproc/mtk_vcp_public.h b/include/linux/remoteproc/mtk_vcp_public.h
new file mode 100644
index 000000000000..7f326f9a1921
--- /dev/null
+++ b/include/linux/remoteproc/mtk_vcp_public.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2025 MediaTek Inc.
+ */
+
+#ifndef __MTK_VCP_PUBLIC_H__
+#define __MTK_VCP_PUBLIC_H__
+
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+
+enum vcp_reserve_mem_id {
+ VCP_RTOS_MEM_ID,
+ VDEC_MEM_ID,
+ VENC_MEM_ID,
+ MMDVFS_VCP_MEM_ID,
+ MMDVFS_MMUP_MEM_ID,
+ MMQOS_MEM_ID,
+ NUMS_MEM_ID,
+};
+
+enum vcp_feature_id {
+ RTOS_FEATURE_ID,
+ VDEC_FEATURE_ID,
+ VENC_FEATURE_ID,
+ GCE_FEATURE_ID,
+ MMDVFS_MMUP_FEATURE_ID,
+ MMDVFS_VCP_FEATURE_ID,
+ MMQOS_FEATURE_ID,
+ MMDEBUG_FEATURE_ID,
+ HWCCF_FEATURE_ID,
+ HWCCF_DEBUG_FEATURE_ID,
+ IMGSYS_FEATURE_ID,
+ VDISP_FEATURE_ID,
+ VMM_FEATURE_ID,
+ NUM_FEATURE_ID,
+};
+
+struct mtk_vcp_device {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct rproc *rproc;
+ struct mtk_vcp_of_cluster *vcp_cluster;
+ const struct mtk_vcp_ops *ops;
+ const struct mtk_vcp_platdata *platdata;
+};
+
+struct mtk_vcp_ops {
+ phys_addr_t (*get_mem_phys)(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id);
+ dma_addr_t (*get_mem_iova)(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id);
+ void __iomem *(*get_mem_virt)(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id);
+ size_t (*get_mem_size)(struct mtk_vcp_device *vcp,
+ enum vcp_reserve_mem_id id);
+ void __iomem *(*get_vcp_sram_virt)(struct mtk_vcp_device *vcp);
+};
+
+struct mtk_vcp_device *vcp_get(struct platform_device *pdev);
+void vcp_put(struct mtk_vcp_device *vcp);
+
+/*
+ * These inline functions are intended for user drivers that are loaded
+ * earlier than the VCP driver, or for built-in drivers that cannot access
+ * the symbols of VCP module.
+ */
+static inline struct mtk_vcp_device *mtk_vcp_get_by_phandle(phandle phandle)
+{
+ struct rproc *rproc = NULL;
+
+ rproc = rproc_get_by_phandle(phandle);
+ if (IS_ERR_OR_NULL(rproc))
+ return NULL;
+
+ return rproc->priv;
+}
+#endif
diff --git a/include/linux/soc/mediatek/mtk_sip_svc.h b/include/linux/soc/mediatek/mtk_sip_svc.h
index abe24a73ee19..e59fadc8cbee 100644
--- a/include/linux/soc/mediatek/mtk_sip_svc.h
+++ b/include/linux/soc/mediatek/mtk_sip_svc.h
@@ -28,4 +28,6 @@
/* IOMMU related SMC call */
#define MTK_SIP_KERNEL_IOMMU_CONTROL MTK_SIP_SMC_CMD(0x514)

+#define MTK_SIP_TINYSYS_VCP_CONTROL MTK_SIP_SMC_CMD(0x52C)
+
#endif
--
2.46.0