public inbox for linux-arm-kernel@lists.infradead.org
 help / color / mirror / Atom feed
* [PATCH 0/7] Add MediaTek VCP remoteproc driver support
@ 2026-04-27 11:04 Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
                   ` (6 more replies)
  0 siblings, 7 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

This patch series adds support for the MediaTek Video Companion
Processor (VCP), a RISC-V based coprocessor found on MediaTek SoCs
starting from mt8196. The VCP handles video processing and multimedia
tasks, offloading work from the main CPU cores.

Architecture Overview
=====================

The VCP subsystem consists of several components:

1. Hardware Architecture
   - RISC-V coprocessor with 1-2 harts per core
   - Multi-core capable (mt8196 has 2 VCP cores)
   - Shared SRAM (up to 384KB) partitioned among cores
   - Dedicated power domain for power management
   - Integrated with SoC IOMMU for memory protection

2. Communication Infrastructure
   - 5 hardware mailbox channels for IPI (Inter-Processor Interrupt)
   - Shared memory regions for bulk data transfer
   - IPI routing tables mapping message types to mailboxes
   - Support for both blocking and non-blocking IPI operations

3. Boot and Runtime Management
   - Firmware loaded via remoteproc framework from filesystem
   - Boot sequence coordinated with ARM Trusted Firmware (ATF) via SMC
   - Runtime power management with suspend/resume support
   - Feature registration mechanism for cross-subsystem coordination

Patch Series Structure
======================

[PATCH 1/7] dt-bindings: Device tree binding for mt8196 VCP
[PATCH 2/7] remoteproc: Core VCP remoteproc driver with ATF integration
[PATCH 3/7] firmware: IPC protocol layer for VCP communication
[PATCH 4/7] remoteproc: IPI mailbox initialization and routing
[PATCH 5/7] remoteproc: IPI synchronization mechanism
[PATCH 6/7] remoteproc: Suspend/resume power management
[PATCH 7/7] MAINTAINERS: Add maintainer entry

Differences from MediaTek SCP
==============================

MediaTek SoCs have two types of companion processors:
- SCP (System Companion Processor): Cortex-M based, for system tasks
- VCP (Video Companion Processor): RISC-V based, for multimedia

While both use the remoteproc framework, VCP has distinct characteristics:
- Different ISA (RISC-V vs ARM Cortex-M)
- Different firmware and memory layout
- Different IPC protocol (5 mailboxes vs 1)
- ATF-coordinated boot sequence
- Multi-core capable architecture

Testing
=======

This patch series has been tested on mt8196 development boards with:
- Firmware loading and boot sequence verification
- IPI communication with video encoder/decoder subsystems
- Suspend/resume cycles with multimedia workloads active
- Multi-core VCP configuration
- IOMMU integration with multimedia memory management

Dependencies
============

- Mediatek Power management driver
- MediaTek VCP mailbox driver (MTK_VCP_MBOX)
- ARM SMCCC support for ATF communication
- IOMMU support for memory protection

Checkpatch Status
=================

All patches pass checkpatch.pl with no errors. Warnings about MAINTAINERS
are addressed in the final patch of the series.

Future Work
===========

- Support for additional SoC variants (mt8197, mt8198)
- Enhanced debugging infrastructure (trace, core dump)
- Performance optimizations for IPI latency
- Documentation under Documentation/remoteproc/

Xiangzhi Tang (7):
  dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding
  remoteproc: mediatek: Add VCP remoteproc driver
  firmware: mediatek: Add VCP IPC protocol driver
  remoteproc: mediatek: Add VCP IPI mailbox initialization
  remoteproc: mediatek: Add VCP ipi communication sync mechanism
  remoteproc: mediatek: vcp: Add vcp suspend and resume feature
  MAINTAINERS: Add entry for MediaTek VCP remoteproc driver

 .../bindings/remoteproc/mediatek,mt8196-vcp.yaml   | 166 ++++
 MAINTAINERS                                        |  14 +
 drivers/firmware/Kconfig                           |   9 +
 drivers/firmware/Makefile                          |   1 +
 drivers/firmware/mtk-vcp-ipc.c                     | 481 +++++++++++
 drivers/remoteproc/Kconfig                         |  12 +
 drivers/remoteproc/Makefile                        |   3 +
 drivers/remoteproc/mtk_vcp_common.c                | 881 +++++++++++++++++++++
 drivers/remoteproc/mtk_vcp_common.h                | 281 +++++++
 drivers/remoteproc/mtk_vcp_rproc.c                 | 581 ++++++++++++++
 drivers/remoteproc/mtk_vcp_rproc.h                 |  95 +++
 include/linux/firmware/mediatek/mtk-vcp-ipc.h      | 151 ++++
 include/linux/remoteproc/mtk_vcp_public.h          | 146 ++++
 include/linux/soc/mediatek/mtk_sip_svc.h           |   2 +
 14 files changed, 2823 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
 create mode 100644 drivers/firmware/mtk-vcp-ipc.c
 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/firmware/mediatek/mtk-vcp-ipc.h
 create mode 100644 include/linux/remoteproc/mtk_vcp_public.h

-- 
2.46.0



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 12:39   ` Rob Herring (Arm)
  2026-04-27 13:51   ` Rob Herring
  2026-04-27 11:04 ` [PATCH v4 2/7] remoteproc: mediatek: Add VCP remoteproc driver Xiangzhi Tang
                   ` (5 subsequent siblings)
  6 siblings, 2 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Add device tree binding for the MediaTek Video Companion Processor
(VCP), a RISC-V based coprocessor used for video processing and
multimedia tasks on mt8196 and future MediaTek SoCs.

The VCP is a heterogeneous multi-core processor that can contain
multiple RISC-V cores with different hart (hardware thread)
configurations. Key features:

- Supports both single-core and multi-core VCP configurations
- Each core can have 1 or 2 harts (hardware threads)
- Shared SRAM memory space partitioned among cores
- Communication via 5 dedicated mailbox channels for IPI messaging
- Integrated with SoC IOMMU for multimedia memory management
- Boot and power management coordinated with ARM Trusted Firmware

The binding defines both the top-level VCP device (with mailboxes,
interrupts, and power domains) and child nodes for individual VCP
cores (with SRAM allocation and hart configuration).

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 .../remoteproc/mediatek,mt8196-vcp.yaml       | 166 ++++++++++++++++++
 1 file changed, 166 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml

diff --git a/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml b/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
new file mode 100644
index 000000000000..8ecb643cbdc5
--- /dev/null
+++ b/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
@@ -0,0 +1,166 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/remoteproc/mediatek,mt8196-vcp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek Video Companion Processor (VCP)
+
+maintainers:
+  - Xiangzhi Tang <xiangzhi.tang@mediatek.com>
+
+description:
+  This binding provides support for the MediaTek Video Companion Processor
+  (VCP), a Risc-V coprocessor found on some MediaTek SoCs.
+
+properties:
+  compatible:
+    enum:
+      - mediatek,mt8196-vcp
+
+  reg:
+    items:
+      - description: sram base
+      - description: cfg group IO
+      - description: cfg core group IO
+      - description: cfg sec group IO
+
+  reg-names:
+    items:
+      - const: sram
+      - const: cfg
+      - const: cfg-core
+      - const: cfg-sec
+
+  interrupts:
+    maxItems: 1
+
+  mboxes:
+    maxItems: 5
+
+  mbox-names:
+    items:
+      - const: mbox0
+      - const: mbox1
+      - const: mbox2
+      - const: mbox3
+      - const: mbox4
+
+  power-domains:
+    maxItems: 1
+
+  iommus:
+    description:
+      Using MediaTek IOMMU to apply larb ports for Multimedia Memory
+      Management Unit and address translation.
+    maxItems: 1
+
+  memory-region:
+    maxItems: 1
+
+patternProperties:
+  "^vcp@[a-f0-9]+$":
+    type: object
+    description:
+      The MediaTek VCP integrated to SoC might be a multi-core version.
+      The other cores are represented as child nodes of the boot core.
+      There are some integration differences for the IP like the usage of
+      address translator for translating SoC bus addresses into address
+      space for the processor.
+
+      The SRAM is shared by all cores, each VCP core only using a piece of
+      SRAM memory. The power of SRAM should be enabled before booting VCP cores.
+      The size of SRAM varies on different SoCs.
+
+      The VCP cores have differences on different SoCs for Hart support.
+
+    properties:
+      compatible:
+        enum:
+          - mediatek,vcp-core
+
+      reg:
+        description: The base address and size of SRAM.
+        maxItems: 1
+
+      reg-names:
+        const: sram
+
+      mediatek,vcp-core-harts:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: Number of harts in this VCP core.
+        enum: [1, 2]
+
+      mediatek,vcp-core-sram-offset:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description:
+          Offset of the allocated SRAM memory for this VCP core.
+
+    required:
+      - compatible
+      - reg
+      - reg-names
+      - mediatek,vcp-core-harts
+      - mediatek,vcp-core-sram-offset
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - mboxes
+  - mbox-names
+  - power-domains
+  - iommus
+  - memory-region
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/power/mt8196-power.h>
+
+    vcp: vcp@31800000 {
+        compatible = "mediatek,mt8196-vcp";
+        reg = <0x31800000 0x60000>,
+              <0x31a04000 0xa000>,
+              <0x31bd0000 0x1000>,
+              <0x31a70020 0x100>;
+        reg-names = "sram",
+                    "cfg",
+                    "cfg-core",
+                    "cfg-sec";
+
+        interrupts = <GIC_SPI 787 IRQ_TYPE_LEVEL_HIGH 0>;
+
+        mboxes = <&vcp_mailbox0>,
+                 <&vcp_mailbox1>,
+                 <&vcp_mailbox2>,
+                 <&vcp_mailbox3>,
+                 <&vcp_mailbox4>;
+        mbox-names = "mbox0", "mbox1", "mbox2", "mbox3", "mbox4";
+
+        power-domains = <&scpsys MT8196_POWER_DOMAIN_MM_PROC_DORMANT>;
+        iommus = <&mm_smmu 160>;
+        memory-region = <&vcp_resv_mem>;
+
+        vcp@0 {
+            compatible = "mediatek,vcp-core";
+            reg = <0x0 0x31000>;
+            reg-names = "sram";
+            mediatek,vcp-core-harts = <2>;
+            mediatek,vcp-core-sram-offset = <0x0>;
+        };
+
+        vcp@31000 {
+            compatible = "mediatek,vcp-core";
+            reg = <0x31000 0x60000>;
+            reg-names = "sram";
+            mediatek,vcp-core-harts = <1>;
+            mediatek,vcp-core-sram-offset = <0x31000>;
+        };
+    };
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 2/7] remoteproc: mediatek: Add VCP remoteproc driver
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 3/7] firmware: mediatek: Add VCP IPC protocol driver Xiangzhi Tang
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

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



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 3/7] firmware: mediatek: Add VCP IPC protocol driver
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 2/7] remoteproc: mediatek: Add VCP remoteproc driver Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 4/7] remoteproc: mediatek: Add VCP IPI mailbox initialization Xiangzhi Tang
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Add the Inter-Processor Communication (IPC) protocol driver for
MediaTek Video Companion Processor (VCP), a RISC-V coprocessor
found on some MediaTek SoCs.

The VCP IPC protocol provides the communication layer between the
host CPU (AP) and VCP firmware using shared memory for message
passing and hardware mailboxes for signaling. The driver implements:

- Send/receive interfaces for IPI messages via mailbox channels
- Support for both blocking (send_compl) and non-blocking (send) modes
- Configurable IPI routing tables that map IPI IDs to mailbox channels
- Per-IPI callback registration for handling incoming messages
- Two receive modes: direct receive (recv_opt=0) and response mode
  (recv_opt=1) for request-reply patterns

The IPC driver acts as a middle layer between the remoteproc framework
and the MediaTek mailbox hardware, abstracting the low-level mailbox
operations into higher-level IPI communication primitives.

This is required for the VCP remoteproc driver to communicate with
VCP firmware for multimedia processing tasks.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 drivers/firmware/Kconfig                      |   9 +
 drivers/firmware/Makefile                     |   1 +
 drivers/firmware/mtk-vcp-ipc.c                | 481 ++++++++++++++++++
 include/linux/firmware/mediatek/mtk-vcp-ipc.h | 151 ++++++
 4 files changed, 642 insertions(+)
 create mode 100644 drivers/firmware/mtk-vcp-ipc.c
 create mode 100644 include/linux/firmware/mediatek/mtk-vcp-ipc.h

diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index bbd2155d8483..80f63b733820 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -178,6 +178,15 @@ config MTK_ADSP_IPC
 	  ADSP exists on some mtk processors.
 	  Client might use shared memory to exchange information with ADSP.
 
+config MTK_VCP_IPC
+	tristate "MTK VCP IPC Protocol driver"
+	depends on MTK_VCP_MBOX
+	help
+	  Say yes here to add support for the MediaTek VCP IPC
+	  between host AP (Linux) and the firmware running on VCP.
+	  VCP exists on some mtk processors.
+	  Client might use shared memory to exchange information with VCP.
+
 config SYSFB
 	bool
 	select BOOT_VESA_SUPPORT
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 4ddec2820c96..d6b6197cb54c 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_ISCSI_IBFT_FIND)	+= iscsi_ibft_find.o
 obj-$(CONFIG_ISCSI_IBFT)	+= iscsi_ibft.o
 obj-$(CONFIG_FIRMWARE_MEMMAP)	+= memmap.o
 obj-$(CONFIG_MTK_ADSP_IPC)	+= mtk-adsp-ipc.o
+obj-$(CONFIG_MTK_VCP_IPC)	+= mtk-vcp-ipc.o
 obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
 obj-$(CONFIG_FW_CFG_SYSFS)	+= qemu_fw_cfg.o
 obj-$(CONFIG_SYSFB)		+= sysfb.o
diff --git a/drivers/firmware/mtk-vcp-ipc.c b/drivers/firmware/mtk-vcp-ipc.c
new file mode 100644
index 000000000000..8c9991018895
--- /dev/null
+++ b/drivers/firmware/mtk-vcp-ipc.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2024 MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/firmware/mediatek/mtk-vcp-ipc.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/sched/clock.h>
+#include <linux/time64.h>
+#include <linux/vmalloc.h>
+
+/**
+ * struct mtk_ipi_chan_table - channel table that belong to mtk_ipi_device
+ * @mbox: the mbox channel number
+ * @mbox_pin_cb: callback function
+ * @holder: keep 1 if there are ipi waiters (to wait the reply)
+ * @ipi_record: timestamp of each ipi transmission stage
+ * @pin_buf: buffer point
+ * @prdata: private data
+ * @recv_opt: recv option,  0:receive ,1: response
+ * @notify: completion notify process
+ * @send_ofs: message offset in the slots of a mbox
+ * @send_index: bit offset in the mbox
+ * @msg_size: slot size of the ipi message
+ *
+ * All of these data should be initialized by mtk_ipi_device_register()
+ */
+struct mtk_ipi_chan_table {
+	u32 mbox;
+	mbox_pin_cb_t mbox_pin_cb;
+	atomic_t holder;
+	void *pin_buf;
+	void *prdata;
+	u32 recv_opt;
+	struct completion notify;
+	/* define a mutex for remote response */
+	struct mutex mutex_send;
+	u32 send_ofs;
+	u32 send_index;
+	u32 msg_size;
+};
+
+/**
+ * mbox information
+ *
+ * @mbdev: mbox device
+ * @mbox_id: mbox id
+ * @slot: how many slots that mbox used
+ * @opt: option for tx mode, 0:mbox, 1:share memory 2:queue
+ * @base: mbox base address
+ * @mbox_client: mbox client
+ * @mbox_chan: mbox channel
+ */
+struct mtk_mbox_info {
+	struct mtk_vcp_ipc *vcp_ipc;
+	u32 mbox_id;
+	u32 slot;
+	u32 opt;
+	/* lock of mbox */
+	spinlock_t mbox_lock;
+	struct mbox_client cl;
+	struct mbox_chan *ch;
+	struct mtk_ipi_info ipi_info;
+};
+
+static const char * const mbox_names[VCP_MBOX_NUM] = {
+	"mbox0", "mbox1", "mbox2", "mbox3", "mbox4"
+};
+
+/**
+ * mtk_vcp_ipc_recv - recv callback used by MTK VCP mailbox
+ *
+ * @c: mbox client
+ * @msg: message received
+ *
+ * Users of VCP IPC will need to provide handle_reply and handle_request
+ * callbacks.
+ */
+static void mtk_vcp_ipc_recv(struct mbox_client *c, void *msg)
+{
+	struct mtk_mbox_info *minfo = container_of(c, struct mtk_mbox_info, cl);
+	struct mtk_vcp_ipc *vcp_ipc = minfo->vcp_ipc;
+	struct mtk_ipi_info *ipi_info = msg;
+	struct mtk_ipi_device *ipidev = vcp_ipc->ipi_priv;
+	struct mtk_ipi_chan_table *table;
+	struct mtk_mbox_recv_table *mbox_recv;
+	u32 id;
+
+	/* execute all receive pin handler */
+	for (id = 0; id < vcp_ipc->mbdev->recv_count; id++) {
+		mbox_recv = &vcp_ipc->mbdev->recv_table[id];
+		if (mbox_recv->mbox_id != minfo->mbox_id)
+			continue;
+
+		if (!(BIT(mbox_recv->pin_index) & ipi_info->irq_status))
+			continue;
+
+		table = &ipidev->table[mbox_recv->ipi_id];
+		if (!table->pin_buf) {
+			dev_err(vcp_ipc->dev, "IPI%d buf is null.\n",
+				mbox_recv->ipi_id);
+			continue;
+		}
+
+		memcpy(table->pin_buf,
+		       ipi_info->msg + mbox_recv->offset * MBOX_SLOT_SIZE,
+		       mbox_recv->msg_size * MBOX_SLOT_SIZE);
+
+		if (!mbox_recv->recv_opt && table->mbox_pin_cb)
+			table->mbox_pin_cb(mbox_recv->ipi_id,
+					   table->prdata,
+					   table->pin_buf,
+					   mbox_recv->msg_size * MBOX_SLOT_SIZE);
+
+		/* notify task */
+		if (table->recv_opt == MBOX_RECV_MESSAGE ||
+		    atomic_read(&table->holder))
+			complete(&table->notify);
+	}
+}
+
+/*
+ * mtk_vcp_ipc_send - send ipc command to MTK VCP
+ *
+ * @ipidev: VCP struct mtk_ipi_device handle
+ * @id: id of the feature IPI
+ * @data: message address
+ * @len: message length
+ *
+ * Return: Zero for success from mbox_send_message
+ *         negative value for error
+ */
+int mtk_vcp_ipc_send(struct mtk_ipi_device *ipidev, u32 id, void *data, u32 len)
+{
+	struct device *dev;
+	struct mtk_mbox_info *minfo;
+	struct mtk_ipi_chan_table *table;
+	struct mtk_vcp_ipc *vcp_ipc;
+	int ret;
+
+	if (!ipidev || !ipidev->ipi_inited || !data)
+		return IPI_UNAVAILABLE;
+	vcp_ipc = ipidev->vcp_ipc;
+	if (!vcp_ipc)
+		return IPI_UNAVAILABLE;
+
+	table = ipidev->table;
+	dev = ipidev->vcp_ipc->dev;
+	minfo = &ipidev->vcp_ipc->info_table[table[id].mbox];
+	if (!minfo) {
+		dev_err(dev, "%s IPI%d minfo is invalid.\n", ipidev->name, id);
+		return IPI_UNAVAILABLE;
+	}
+
+	if (len > table[id].msg_size)
+		return IPI_MSG_TOO_BIG;
+	else if (!len)
+		len = table[id].msg_size;
+
+	mutex_lock(&table[id].mutex_send);
+
+	minfo->ipi_info.msg = data;
+	minfo->ipi_info.len = len;
+	minfo->ipi_info.id = id;
+	minfo->ipi_info.index = table[id].send_index;
+	minfo->ipi_info.slot_ofs = table[id].send_ofs * MBOX_SLOT_SIZE;
+
+	ret = mbox_send_message(minfo->ch, &minfo->ipi_info);
+	mutex_unlock(&table[id].mutex_send);
+	if (ret < 0) {
+		dev_err(dev, "%s IPI%d send failed.\n", ipidev->name, id);
+		return IPI_MBOX_ERR;
+	}
+
+	return IPI_ACTION_DONE;
+}
+EXPORT_SYMBOL(mtk_vcp_ipc_send);
+
+/*
+ * mtk_vcp_ipc_send_compl - send ipc command to MTK VCP
+ *
+ * @ipidev: VCP struct mtk_ipi_device handle
+ * @id: id of the feature IPI
+ * @data: message address
+ * @len: message length
+ * @timeout_ms:
+ *
+ * Return: Zero for success from mbox_send_message
+ *         negative value for error
+ */
+int mtk_vcp_ipc_send_compl(struct mtk_ipi_device *ipidev, u32 id,
+			   void *data, u32 len, u32 timeout_ms)
+{
+	struct device *dev;
+	struct mtk_mbox_info *minfo;
+	struct mtk_ipi_chan_table *table;
+	struct mtk_vcp_ipc *vcp_ipc;
+	int ret;
+
+	if (!ipidev || !ipidev->ipi_inited || !data)
+		return IPI_UNAVAILABLE;
+	vcp_ipc = ipidev->vcp_ipc;
+	if (!vcp_ipc)
+		return IPI_UNAVAILABLE;
+
+	table = ipidev->table;
+	dev = ipidev->vcp_ipc->dev;
+	minfo = &ipidev->vcp_ipc->info_table[table[id].mbox];
+	if (!minfo) {
+		dev_err(dev, "%s IPI%d minfo is invalid.\n", ipidev->name, id);
+		return IPI_UNAVAILABLE;
+	}
+
+	if (len > table[id].msg_size)
+		return IPI_MSG_TOO_BIG;
+	else if (!len)
+		len = table[id].msg_size;
+
+	mutex_lock(&table[id].mutex_send);
+
+	minfo->ipi_info.msg = data;
+	minfo->ipi_info.len = len;
+	minfo->ipi_info.id = id;
+	minfo->ipi_info.index = table[id].send_index;
+	minfo->ipi_info.slot_ofs = table[id].send_ofs * MBOX_SLOT_SIZE;
+
+	atomic_inc(&table[id].holder);
+
+	ret = mbox_send_message(minfo->ch, &minfo->ipi_info);
+	if (ret < 0) {
+		atomic_set(&table[id].holder, 0);
+		mutex_unlock(&table[id].mutex_send);
+		dev_err(dev, "%s IPI%d send failed.\n", ipidev->name, id);
+		return IPI_MBOX_ERR;
+	}
+
+	/* wait for completion */
+	ret = wait_for_completion_timeout(&table[id].notify,
+					  msecs_to_jiffies(timeout_ms));
+	atomic_set(&table[id].holder, 0);
+	if (ret > 0)
+		ret = IPI_ACTION_DONE;
+
+	mutex_unlock(&table[id].mutex_send);
+
+	return ret;
+}
+EXPORT_SYMBOL(mtk_vcp_ipc_send_compl);
+
+int mtk_vcp_mbox_ipc_register(struct mtk_ipi_device *ipidev, int id,
+			      mbox_pin_cb_t cb, void *prdata, void *msg)
+{
+	if (!ipidev || !ipidev->ipi_inited)
+		return IPI_DEV_ILLEGAL;
+	if (!msg)
+		return IPI_NO_MSGBUF;
+
+	if (ipidev->table[id].pin_buf)
+		return IPI_ALREADY_USED;
+	ipidev->table[id].mbox_pin_cb = cb;
+	ipidev->table[id].pin_buf = msg;
+	ipidev->table[id].prdata = prdata;
+
+	return IPI_ACTION_DONE;
+}
+EXPORT_SYMBOL(mtk_vcp_mbox_ipc_register);
+
+int mtk_vcp_mbox_ipc_unregister(struct mtk_ipi_device *ipidev, int id)
+{
+	if (!ipidev || !ipidev->ipi_inited)
+		return IPI_DEV_ILLEGAL;
+
+	/* Drop the ipi and reset the record */
+	complete(&ipidev->table[id].notify);
+
+	ipidev->table[id].mbox_pin_cb = NULL;
+	ipidev->table[id].pin_buf = NULL;
+	ipidev->table[id].prdata = NULL;
+
+	return IPI_ACTION_DONE;
+}
+EXPORT_SYMBOL(mtk_vcp_mbox_ipc_unregister);
+
+static void mtk_fill_in_entry(struct mtk_ipi_chan_table *entry, const u32 ipi_id,
+			      const struct mtk_mbox_table *mbdev)
+{
+	const struct mtk_mbox_send_table *mbox_send = mbdev->send_table;
+	u32 index;
+
+	for (index = 0; index < mbdev->send_count; index++) {
+		if (ipi_id != mbox_send[index].ipi_id)
+			continue;
+
+		entry->send_ofs = mbox_send[index].offset;
+		entry->send_index = mbox_send[index].pin_index;
+		entry->msg_size = mbox_send[index].msg_size;
+		entry->mbox = mbox_send[index].mbox_id;
+		return;
+	}
+
+	entry->mbox = -ENOENT;
+}
+
+int mtk_vcp_ipc_device_register(struct mtk_ipi_device *ipidev,
+				u32 ipi_chan_count, struct mtk_vcp_ipc *vcp_ipc)
+{
+	struct mtk_ipi_chan_table *ipi_chan_table;
+	struct mtk_mbox_table *mbdev;
+	u32 index;
+
+	if (!vcp_ipc || !ipidev)
+		return -EINVAL;
+
+	ipi_chan_table = kcalloc(ipi_chan_count,
+				 sizeof(struct mtk_ipi_chan_table), GFP_KERNEL);
+	if (!ipi_chan_table)
+		return -ENOMEM;
+
+	mbdev = vcp_ipc->mbdev;
+	vcp_ipc->ipi_priv = (void *)ipidev;
+	ipidev->table = ipi_chan_table;
+	ipidev->vcp_ipc = vcp_ipc;
+
+	for (index = 0; index < ipi_chan_count; index++) {
+		atomic_set(&ipi_chan_table[index].holder, 0);
+		mutex_init(&ipi_chan_table[index].mutex_send);
+		init_completion(&ipi_chan_table[index].notify);
+		mtk_fill_in_entry(&ipi_chan_table[index], index, mbdev);
+	}
+
+	ipidev->ipi_inited = 1;
+
+	dev_dbg(vcp_ipc->dev, "%s (with %d IPI) has registered.\n",
+		ipidev->name, ipi_chan_count);
+
+	return IPI_ACTION_DONE;
+}
+EXPORT_SYMBOL(mtk_vcp_ipc_device_register);
+
+static int setup_mbox_table(struct mtk_mbox_table *mbdev, u32 mbox)
+{
+	struct mtk_mbox_send_table *mbox_send = &mbdev->send_table[0];
+	struct mtk_mbox_recv_table *mbox_recv = &mbdev->recv_table[0];
+	u32 i, last_ofs = 0, last_idx = 0, last_slot = 0, last_sz = 0;
+
+	for (i = 0; i < mbdev->send_count; i++) {
+		if (mbox == mbox_send[i].mbox_id) {
+			mbox_send[i].offset = last_ofs + last_slot;
+			mbox_send[i].pin_index = last_idx + last_sz;
+			last_idx = mbox_send[i].pin_index;
+			last_sz = DIV_ROUND_UP(mbox_send[i].msg_size, MBOX_SLOT_ALIGN);
+			last_ofs = last_sz * MBOX_SLOT_ALIGN;
+			last_slot = last_idx * MBOX_SLOT_ALIGN;
+		} else if (mbox < mbox_send[i].mbox_id) {
+			/* no need to search the rest id */
+			break;
+		}
+	}
+
+	for (i = 0; i < mbdev->recv_count; i++) {
+		if (mbox == mbox_recv[i].mbox_id) {
+			mbox_recv[i].offset = last_ofs + last_slot;
+			mbox_recv[i].pin_index = last_idx + last_sz;
+			last_idx = mbox_recv[i].pin_index;
+			last_sz = DIV_ROUND_UP(mbox_recv[i].msg_size, MBOX_SLOT_ALIGN);
+			last_ofs = last_sz * MBOX_SLOT_ALIGN;
+			last_slot = last_idx * MBOX_SLOT_ALIGN;
+		} else if (mbox < mbox_recv[i].mbox_id) {
+			/* no need to search the rest id */
+			break;
+		}
+	}
+
+	if (last_idx > MBOX_MAX_PIN || (last_ofs + last_slot) > MTK_VCP_MBOX_SLOT_MAX_SIZE / 4)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int mtk_vcp_ipc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mtk_vcp_ipc *vcp_ipc;
+	struct mbox_client *cl;
+	struct mtk_mbox_info *minfo;
+	int ret;
+	u32 mbox, i;
+	struct mtk_mbox_table *mbox_data = dev_get_platdata(dev);
+
+	device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
+
+	vcp_ipc = devm_kzalloc(dev, sizeof(*vcp_ipc), GFP_KERNEL);
+	if (!vcp_ipc)
+		return -ENOMEM;
+
+	if (!mbox_data) {
+		dev_err(dev, "No platform data available\n");
+		return -EINVAL;
+	}
+	vcp_ipc->mbdev = mbox_data;
+
+	/* alloc and init mmup_mbox_info */
+	vcp_ipc->info_table = vzalloc(sizeof(*vcp_ipc->info_table) * VCP_MBOX_NUM);
+	if (!vcp_ipc->info_table)
+		return -ENOMEM;
+
+	/* create mbox dev */
+	for (mbox = 0; mbox < VCP_MBOX_NUM; mbox++) {
+		minfo = &vcp_ipc->info_table[mbox];
+		minfo->mbox_id = mbox;
+		minfo->vcp_ipc = vcp_ipc;
+		spin_lock_init(&minfo->mbox_lock);
+
+		ret = setup_mbox_table(vcp_ipc->mbdev, mbox);
+		if (ret)
+			return ret;
+
+		cl = &minfo->cl;
+		cl->dev = &pdev->dev;
+		cl->tx_block = false;
+		cl->knows_txdone = false;
+		cl->tx_prepare = NULL;
+		cl->rx_callback = mtk_vcp_ipc_recv;
+		minfo->ch = mbox_request_channel_byname(cl, mbox_names[mbox]);
+		if (IS_ERR(minfo->ch)) {
+			ret = PTR_ERR(minfo->ch);
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "Failed to request mbox channel %s ret %d\n",
+					mbox_names[mbox], ret);
+
+			for (i = 0; i < mbox; i++) {
+				minfo = &vcp_ipc->info_table[i];
+				mbox_free_channel(minfo->ch);
+			}
+
+			vfree(vcp_ipc->info_table);
+			return ret;
+		}
+	}
+
+	vcp_ipc->dev = dev;
+	dev_set_drvdata(dev, vcp_ipc);
+	dev_dbg(dev, "MTK VCP IPC initialized\n");
+
+	return 0;
+}
+
+static void mtk_vcp_ipc_remove(struct platform_device *pdev)
+{
+	struct mtk_vcp_ipc *vcp_ipc = dev_get_drvdata(&pdev->dev);
+	struct mtk_mbox_info *minfo;
+	int i;
+
+	for (i = 0; i < VCP_MBOX_NUM; i++) {
+		minfo = &vcp_ipc->info_table[i];
+		mbox_free_channel(minfo->ch);
+	}
+
+	vfree(vcp_ipc->info_table);
+}
+
+static struct platform_driver mtk_vcp_ipc_driver = {
+	.probe = mtk_vcp_ipc_probe,
+	.remove = mtk_vcp_ipc_remove,
+	.driver = {
+		.name = "mtk-vcp-ipc",
+	},
+};
+builtin_platform_driver(mtk_vcp_ipc_driver);
+
+MODULE_AUTHOR("Jjian Zhou <jjian.zhou@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek VCP IPC Controller");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/firmware/mediatek/mtk-vcp-ipc.h b/include/linux/firmware/mediatek/mtk-vcp-ipc.h
new file mode 100644
index 000000000000..dc34b0ba9dd8
--- /dev/null
+++ b/include/linux/firmware/mediatek/mtk-vcp-ipc.h
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Copyright (c) 2024 MediaTek Inc.
+ */
+
+#ifndef __MTK_VCP_IPC_H__
+#define __MTK_VCP_IPC_H__
+
+#include <linux/completion.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox/mtk-vcp-mailbox.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+/* IPI result definition */
+#define IPI_ACTION_DONE	  0
+#define IPI_DEV_ILLEGAL	 -1 /* ipi device is not initialized */
+#define IPI_ALREADY_USED	 -2 /* the ipi has be registered */
+#define IPI_UNAVAILABLE	 -3 /* the ipi can't be found */
+#define IPI_NO_MSGBUF		 -4 /* receiver doesn't have message buffer */
+#define IPI_MSG_TOO_BIG		 -5 /* message length is larger than defined */
+#define IPI_MBOX_ERR		-99 /* some error from rpmsg layer */
+
+/* mbox recv action definition */
+enum mtk_ipi_recv_opt {
+	MBOX_RECV_MESSAGE  = 0,
+	MBOX_RECV_ACK      = 1,
+};
+
+/* mbox table item number definition */
+#define send_item_num	3
+#define recv_item_num	4
+#define VCP_MBOX_NUM	5
+
+/* mbox slot size definition: 1 slot for 4 bytes */
+#define MBOX_SLOT_SIZE	0x4
+#define MBOX_MAX_PIN	32
+#define VCP_MBOX_NUM	5
+#define MBOX_SLOT_ALIGN	2
+
+struct mtk_vcp_ipc;
+struct mtk_ipi_chan_table;
+
+typedef int (*mbox_pin_cb_t)(u32 ipi_id, void *prdata, void *data, u32 len);
+
+/**
+ * mbox pin structure, this is for send definition,
+ * @offset: message offset in the slots of a mbox
+ * @msg_size: message used slots in the mbox, 4 bytes alignment
+ * @pin_index: bit offset in the mbox
+ * @ipi_id: ipi enum number
+ * @mbox_id: mbox number id
+ */
+struct mtk_mbox_send_table {
+	u32 offset;
+	u32 msg_size;
+	u32 pin_index;
+	u32 ipi_id;
+	u32 mbox_id;
+};
+
+/**
+ * mbox pin structure, this is for receive definition,
+ * @offset: message offset in the slots of a mbox
+ * @recv_opt: recv option,  0:receive ,1: response
+ * @msg_size: message used slots in the mbox, 4 bytes alignment
+ * @pin_index: bit offset in the mbox
+ * @ipi_id: ipi enum number
+ * @mbox_id: mbox number id
+ */
+struct mtk_mbox_recv_table {
+	u32 offset;
+	u32 recv_opt;
+	u32 msg_size;
+	u32 pin_index;
+	u32 ipi_id;
+	u32 mbox_id;
+};
+
+/**
+ * struct mtk_ipi_device - device for represent the tinysys using mtk ipi
+ * @name: name of tinysys device
+ * @id: device id (used to match between rpmsg drivers and devices)
+ * @vcp_ipc: vcp ipc structure for tinysys device
+ * @table: channel table with endpoint & channel_info & mbox_pin info
+ * @prdata: private data for the callback use
+ * @ipi_inited: set when vcp_ipi_device_register() done
+ */
+struct mtk_ipi_device  {
+	const char *name;
+	struct mtk_vcp_ipc *vcp_ipc;
+	struct mtk_ipi_chan_table *table;
+	void *prdata;
+	int ipi_inited;
+};
+
+/**
+ * The mtk_mbox_table is a structure used to record the send
+ * table and recv table. The send table is used to record
+ * the feature ID and size of the sent data. The recv table
+ * is used to record the feature ID and size of the received
+ * data, and whether a callback needs to be invoked.
+ *
+ * Following are platform specific interfacer
+ * @recv_table: structure mtk_mbox_recv_table
+ * @send_table: structure mtk_mbox_send_table
+ * @recv_count: receive feature number in this channel
+ * @send_count: send feature number in this channel
+ */
+struct mtk_mbox_table {
+	struct mtk_mbox_recv_table recv_table[32];
+	struct mtk_mbox_send_table send_table[32];
+	u32 recv_count;
+	u32 send_count;
+};
+
+/**
+ * Mbox is a dedicate hardware of a tinysys consists of:
+ * 1) a share memory tightly coupled to the tinysys
+ * 2) several IRQs
+ *
+ * Following are platform specific interface
+ * @dev: vcp device
+ * @name: identity of the device
+ * @info_table: mbox info structure
+ * @ipi_priv: private data for synchronization layer
+ * @mbox_id: mbox number
+ * @mbdev: mtk_mbox_table structure
+ */
+struct mtk_vcp_ipc {
+	struct device *dev;
+	const char *name;
+	struct mtk_mbox_info *info_table;
+	void *ipi_priv;
+	void *mbox_id;
+	struct mtk_mbox_table *mbdev;
+};
+
+int mtk_vcp_ipc_device_register(struct mtk_ipi_device *ipidev,
+				u32 ipi_chan_count,
+				struct mtk_vcp_ipc *vcp_ipc);
+int mtk_vcp_ipc_send(struct mtk_ipi_device *ipidev, u32 ipi_id,
+		     void *data, u32 len);
+int mtk_vcp_ipc_send_compl(struct mtk_ipi_device *ipidev, u32 ipi_id,
+			   void *data, u32 len, u32 timeout_ms);
+int mtk_vcp_mbox_ipc_register(struct mtk_ipi_device *ipidev, int ipi_id,
+			      mbox_pin_cb_t cb, void *prdata, void *msg);
+int mtk_vcp_mbox_ipc_unregister(struct mtk_ipi_device *ipidev, int ipi_id);
+
+#endif
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 4/7] remoteproc: mediatek: Add VCP IPI mailbox initialization
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
                   ` (2 preceding siblings ...)
  2026-04-27 11:04 ` [PATCH v4 3/7] firmware: mediatek: Add VCP IPC protocol driver Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 5/7] remoteproc: mediatek: Add VCP ipi communication sync mechanism Xiangzhi Tang
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Initialize the IPI (Inter-Processor Interrupt) communication channels
between the host AP and VCP firmware using the MediaTek mailbox
framework.

The VCP uses 5 hardware mailboxes (mbox0-4) to route different types
of IPI messages between the host CPU and the VCP coprocessor. Each IPI
channel has a specific purpose and is configured with message size and
routing information:

- mbox0: Video decoder (VDEC) communication
- mbox1: Video encoder (VENC), multimedia DVFS, and hardware voter
- mbox2: Multimedia QoS (MMQOS), power sleep control, and test interface
- mbox3: DVFS for multimedia utility processor, debug, and display
- mbox4: Logger control for both VCP cores

The IPI routing tables (mt8196_ipc_tb) define 14 send channels and
11 receive channels, specifying message sizes (in 4-byte slots) and
mailbox assignments for each IPI ID.

The vcp_ipi_mbox_init() function:
1. Registers the mtk-vcp-ipc platform device with the IPC routing table
2. Waits for the IPC driver to probe and become ready
3. Registers the IPI device with the VCP for subsequent IPI operations

A new vcp_get_ipidev() API is exported to allow other kernel drivers
to obtain the IPI device handle for sending messages to VCP.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 drivers/remoteproc/Kconfig                |  2 +
 drivers/remoteproc/mtk_vcp_rproc.c        | 99 +++++++++++++++++++++++
 drivers/remoteproc/mtk_vcp_rproc.h        |  6 ++
 include/linux/remoteproc/mtk_vcp_public.h | 55 +++++++++++++
 4 files changed, 162 insertions(+)

diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 93827f6fd3c5..54b416db0c0b 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -70,6 +70,8 @@ config MTK_VCP_RPROC
 	tristate "MediaTek VCP support"
 	depends on ARCH_MEDIATEK || COMPILE_TEST
 	depends on ARCH_DMA_ADDR_T_64BIT
+	select MTK_VCP_IPC
+	select MTK_VCP_MBOX
 	help
 	  Say y here to support MediaTek's Video Companion Processor (VCP) via
 	  the remote processor framework.
diff --git a/drivers/remoteproc/mtk_vcp_rproc.c b/drivers/remoteproc/mtk_vcp_rproc.c
index f12df45d782e..f3b2646f79f6 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.c
+++ b/drivers/remoteproc/mtk_vcp_rproc.c
@@ -5,6 +5,7 @@
 
 #include <linux/device.h>
 #include <linux/module.h>
+#include <linux/iopoll.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_platform.h>
@@ -57,6 +58,19 @@ void vcp_put(struct mtk_vcp_device *vcp)
 }
 EXPORT_SYMBOL_GPL(vcp_put);
 
+/**
+ * vcp_get_ipidev() - get a vcp ipi device struct to reference vcp ipi.
+ *
+ * @vcp: mtk_vcp_device structure from vcp_get().
+ *
+ * Return: Pointer to mtk_ipi_device structure.
+ */
+struct mtk_ipi_device *vcp_get_ipidev(struct mtk_vcp_device *vcp)
+{
+	return vcp->ipi_dev;
+}
+EXPORT_SYMBOL_GPL(vcp_get_ipidev);
+
 static int mtk_vcp_start(struct rproc *rproc)
 {
 	struct mtk_vcp_device *vcp = rproc->priv;
@@ -108,6 +122,34 @@ static const struct rproc_ops mtk_vcp_ops = {
 	.stop		= mtk_vcp_stop,
 };
 
+static int vcp_ipi_mbox_init(struct mtk_vcp_device *vcp)
+{
+	struct mtk_vcp_ipc *vcp_ipc;
+	struct platform_device *pdev;
+	int ret;
+
+	pdev = platform_device_register_data(vcp->dev, "mtk-vcp-ipc",
+					     PLATFORM_DEVID_NONE,
+					     vcp->platdata->ipc_data,
+					     sizeof(struct mtk_mbox_table));
+	if (IS_ERR(pdev))
+		return dev_err_probe(vcp->dev, PTR_ERR(pdev), "ipc_data register failed\n");
+
+	ret = read_poll_timeout_atomic(dev_get_drvdata,
+				       vcp_ipc, vcp_ipc,
+				       USEC_PER_MSEC,
+				       VCP_IPI_DEV_READY_TIMEOUT * USEC_PER_MSEC,
+				       false, &pdev->dev);
+	if (ret)
+		return dev_err_probe(vcp->dev, -EPROBE_DEFER, "get vcp_ipc drvdata failed\n");
+
+	ret = mtk_vcp_ipc_device_register(vcp->ipi_dev, VCP_IPI_COUNT, vcp_ipc);
+	if (ret)
+		dev_err_probe(vcp->dev, ret, "ipi_dev register failed, ret %d\n", ret);
+
+	return ret;
+}
+
 static int vcp_multi_core_init(struct platform_device *pdev,
 			       struct mtk_vcp_of_cluster *vcp_cluster,
 			       enum vcp_core_id core_id)
@@ -156,7 +198,9 @@ static struct mtk_vcp_device *vcp_rproc_init(struct platform_device *pdev,
 	vcp->dev = dev;
 	vcp->ops = &vcp_of_data->ops;
 	vcp->platdata = &vcp_of_data->platdata;
+	vcp->ipi_ops = vcp_of_data->platdata.ipi_ops;
 	vcp->vcp_cluster = vcp_cluster;
+	vcp->ipi_dev = &vcp_cluster->vcp_ipidev;
 
 	rproc->auto_boot = vcp_of_data->platdata.auto_boot;
 	rproc->sysfs_read_only = vcp_of_data->platdata.sysfs_read_only;
@@ -189,6 +233,10 @@ static struct mtk_vcp_device *vcp_rproc_init(struct platform_device *pdev,
 	if (ret)
 		return ERR_PTR(dev_err_probe(dev, ret, "vcp_wdt_irq_init failed\n"));
 
+	ret = vcp_ipi_mbox_init(vcp);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "vcp_ipi_mbox_init failed\n"));
+
 	ret = pm_runtime_get_sync(dev);
 	if (ret < 0) {
 		pm_runtime_put_noidle(dev);
@@ -309,6 +357,55 @@ static struct mtk_vcp_reserved_mem_table mt8196_memory_tb[NUMS_MEM_ID] = {
 	{ .memory_id = MMQOS_MEM_ID,        .size = 0x1000 },
 };
 
+static struct mtk_mbox_table mt8196_ipc_tb = {
+	.send_table = {
+		{ .msg_size = 18, .ipi_id =  0, .mbox_id = 0 },
+
+		{ .msg_size =  8, .ipi_id = 15, .mbox_id = 1 },
+		{ .msg_size = 18, .ipi_id = 16, .mbox_id = 1 },
+		{ .msg_size =  2, .ipi_id =  9, .mbox_id = 1 },
+
+		{ .msg_size = 18, .ipi_id = 11, .mbox_id = 2 },
+		{ .msg_size =  2, .ipi_id =  2, .mbox_id = 2 },
+		{ .msg_size =  3, .ipi_id =  3, .mbox_id = 2 },
+		{ .msg_size =  2, .ipi_id = 32, .mbox_id = 2 },
+
+		{ .msg_size =  2, .ipi_id = 33, .mbox_id = 3 },
+		{ .msg_size =  2, .ipi_id = 13, .mbox_id = 3 },
+		{ .msg_size =  2, .ipi_id = 35, .mbox_id = 3 },
+
+		{ .msg_size =  2, .ipi_id = 20, .mbox_id = 4 },
+		{ .msg_size =  3, .ipi_id = 21, .mbox_id = 4 },
+		{ .msg_size =  2, .ipi_id = 23, .mbox_id = 4 }
+	},
+	.recv_table = {
+		{ .recv_opt = 0, .msg_size = 18, .ipi_id =  1, .mbox_id = 0 },
+
+		{ .recv_opt = 1, .msg_size =  8, .ipi_id = 15, .mbox_id = 1 },
+		{ .recv_opt = 0, .msg_size = 18, .ipi_id = 17, .mbox_id = 1 },
+		{ .recv_opt = 0, .msg_size =  2, .ipi_id = 10, .mbox_id = 1 },
+
+		{ .recv_opt = 0, .msg_size = 18, .ipi_id = 12, .mbox_id = 2 },
+		{ .recv_opt = 0, .msg_size =  1, .ipi_id =  5, .mbox_id = 2 },
+		{ .recv_opt = 1, .msg_size =  1, .ipi_id =  2, .mbox_id = 2 },
+
+		{ .recv_opt = 0, .msg_size =  2, .ipi_id = 34, .mbox_id = 3 },
+		{ .recv_opt = 0, .msg_size =  2, .ipi_id = 14, .mbox_id = 3 },
+
+		{ .recv_opt = 0, .msg_size =  1, .ipi_id = 26, .mbox_id = 4 },
+		{ .recv_opt = 1, .msg_size =  1, .ipi_id = 20, .mbox_id = 4 }
+	},
+	.recv_count = 11,
+	.send_count = 14,
+};
+
+static struct mtk_vcp_ipi_ops mt8196_vcp_ipi_ops = {
+	.ipi_send = mtk_vcp_ipc_send,
+	.ipi_send_compl = mtk_vcp_ipc_send_compl,
+	.ipi_register = mtk_vcp_mbox_ipc_register,
+	.ipi_unregister = mtk_vcp_mbox_ipc_unregister,
+};
+
 static const struct mtk_vcp_of_data mt8196_of_data = {
 	.ops = {
 		.get_mem_phys = vcp_get_reserve_mem_phys,
@@ -321,6 +418,8 @@ static const struct mtk_vcp_of_data mt8196_of_data = {
 		.auto_boot = true,
 		.sysfs_read_only = true,
 		.rtos_static_iova = 0x180600000,
+		.ipc_data = &mt8196_ipc_tb,
+		.ipi_ops = &mt8196_vcp_ipi_ops,
 		.feature_tb = mt8196_feature_tb,
 		.memory_tb = mt8196_memory_tb,
 		.fw_name = "mediatek/mt8196/vcp.img",
diff --git a/drivers/remoteproc/mtk_vcp_rproc.h b/drivers/remoteproc/mtk_vcp_rproc.h
index 3b989c8eb337..c34d3a2757a9 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.h
+++ b/drivers/remoteproc/mtk_vcp_rproc.h
@@ -21,6 +21,7 @@
  * @sram_offset: core sram memory layout
  * @share_mem_iova: shared memory iova base
  * @share_mem_size: shared memory size
+ * @vcp_ipidev: struct mtk_ipi_device
  * @vcp_memory_tb: vcp memory allocated table
  */
 struct mtk_vcp_of_cluster {
@@ -34,6 +35,7 @@ struct mtk_vcp_of_cluster {
 	u32 sram_offset[VCP_CORE_TOTAL];
 	dma_addr_t share_mem_iova;
 	size_t share_mem_size;
+	struct mtk_ipi_device vcp_ipidev;
 	struct vcp_reserve_mblock vcp_memory_tb[NUMS_MEM_ID];
 };
 
@@ -43,6 +45,8 @@ struct mtk_vcp_of_cluster {
  * @auto_boot: rproc auto_boot flag
  * @sysfs_read_only: rproc sysfs_read_only flag
  * @rtos_static_iova: vcp dram binary static map iova
+ * @mtk_mbox_table: mtk_mbox_table structure
+ * @mtk_vcp_ipi_ops: vcp ipi api ops structure
  * @feature_tb: vcp feature table structure
  * @memory_tb: vcp memory table structure
  * @fw_name: vcp image name and path
@@ -51,6 +55,8 @@ struct mtk_vcp_platdata {
 	bool auto_boot;
 	bool sysfs_read_only;
 	dma_addr_t rtos_static_iova;
+	struct mtk_mbox_table *ipc_data;
+	struct mtk_vcp_ipi_ops *ipi_ops;
 	struct mtk_vcp_feature_table *feature_tb;
 	struct mtk_vcp_reserved_mem_table *memory_tb;
 	char *fw_name;
diff --git a/include/linux/remoteproc/mtk_vcp_public.h b/include/linux/remoteproc/mtk_vcp_public.h
index 7f326f9a1921..fda3cf5061e3 100644
--- a/include/linux/remoteproc/mtk_vcp_public.h
+++ b/include/linux/remoteproc/mtk_vcp_public.h
@@ -7,8 +7,18 @@
 #define __MTK_VCP_PUBLIC_H__
 
 #include <linux/platform_device.h>
+#include <linux/firmware/mediatek/mtk-vcp-ipc.h>
 #include <linux/remoteproc.h>
 
+#define VCP_SYNC_TIMEOUT_MS             (50)
+
+enum vcp_notify_event {
+	VCP_EVENT_READY = 0,
+	VCP_EVENT_STOP,
+	VCP_EVENT_SUSPEND,
+	VCP_EVENT_RESUME,
+};
+
 enum vcp_reserve_mem_id {
 	VCP_RTOS_MEM_ID,
 	VDEC_MEM_ID,
@@ -36,15 +46,59 @@ enum vcp_feature_id {
 	NUM_FEATURE_ID,
 };
 
+enum {
+	IPI_OUT_VDEC_1                 =  0,
+	IPI_IN_VDEC_1                  =  1,
+	IPI_OUT_C_SLEEP_0              =  2,
+	IPI_OUT_TEST_0                 =  3,
+	IPI_IN_VCP_READY_0             =  5,
+	IPI_OUT_MMDVFS_VCP             =  9,
+	IPI_IN_MMDVFS_VCP              = 10,
+	IPI_OUT_MMQOS                  = 11,
+	IPI_IN_MMQOS                   = 12,
+	IPI_OUT_MMDEBUG                = 13,
+	IPI_IN_MMDEBUG                 = 14,
+	IPI_OUT_C_VCP_HWVOTER_DEBUG    = 15,
+	IPI_OUT_VENC_0                 = 16,
+	IPI_IN_VENC_0                  = 17,
+	IPI_OUT_C_SLEEP_1              = 20,
+	IPI_OUT_TEST_1                 = 21,
+	IPI_OUT_LOGGER_CTRL_0          = 22,
+	IPI_OUT_VCPCTL_1               = 23,
+	IPI_IN_LOGGER_CTRL_0           = 25,
+	IPI_IN_VCP_READY_1             = 26,
+	IPI_OUT_LOGGER_CTRL_1          = 30,
+	IPI_IN_LOGGER_CTRL_1           = 31,
+	IPI_OUT_VCPCTL_0               = 32,
+	IPI_OUT_MMDVFS_MMUP            = 33,
+	IPI_IN_MMDVFS_MMUP             = 34,
+	IPI_OUT_VDISP                  = 35,
+	VCP_IPI_COUNT,
+	VCP_IPI_NS_SERVICE             = 0xff,
+	VCP_IPI_NS_SERVICE_COUNT       = 0x100,
+};
+
 struct mtk_vcp_device {
 	struct platform_device *pdev;
 	struct device *dev;
 	struct rproc *rproc;
+	struct mtk_ipi_device *ipi_dev;
 	struct mtk_vcp_of_cluster *vcp_cluster;
+	const struct mtk_vcp_ipi_ops *ipi_ops;
 	const struct mtk_vcp_ops *ops;
 	const struct mtk_vcp_platdata *platdata;
 };
 
+struct mtk_vcp_ipi_ops {
+	int (*ipi_send)(struct mtk_ipi_device *ipidev, u32 ipi_id,
+			void *data, u32 len);
+	int (*ipi_send_compl)(struct mtk_ipi_device *ipidev, u32 ipi_id,
+			      void *data, u32 len, u32 timeout_ms);
+	int (*ipi_register)(struct mtk_ipi_device *ipidev, int ipi_id,
+			    mbox_pin_cb_t cb, void *prdata, void *msg);
+	int (*ipi_unregister)(struct mtk_ipi_device *ipidev, int ipi_id);
+};
+
 struct mtk_vcp_ops {
 	phys_addr_t (*get_mem_phys)(struct mtk_vcp_device *vcp,
 				    enum vcp_reserve_mem_id id);
@@ -59,6 +113,7 @@ struct mtk_vcp_ops {
 
 struct mtk_vcp_device *vcp_get(struct platform_device *pdev);
 void vcp_put(struct mtk_vcp_device *vcp);
+struct mtk_ipi_device *vcp_get_ipidev(struct mtk_vcp_device *vcp);
 
 /*
  * These inline functions are intended for user drivers that are loaded
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 5/7] remoteproc: mediatek: Add VCP ipi communication sync mechanism
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
                   ` (3 preceding siblings ...)
  2026-04-27 11:04 ` [PATCH v4 4/7] remoteproc: mediatek: Add VCP IPI mailbox initialization Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 6/7] remoteproc: mediatek: vcp: Add vcp suspend and resume feature Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 7/7] MAINTAINERS: Add entry for MediaTek VCP remoteproc driver Xiangzhi Tang
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Add the inter-processor interrupt (IPI) communication synchronization
mechanism for the VCP remoteproc driver:

- Implement VCP ready IPI registration to receive notifications when
  VCP firmware is initialized and ready for communication
- Add VCP ready notification work queue mechanism to handle firmware
  ready events asynchronously
- Implement VCP feature registration mechanism to allow different
  subsystems to register their capabilities and dependencies with VCP

This synchronization mechanism ensures proper coordination between
the host CPU and VCP firmware during boot and runtime operations.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 drivers/remoteproc/mtk_vcp_common.c       | 277 ++++++++++++++++++++++
 drivers/remoteproc/mtk_vcp_common.h       |  55 +++++
 drivers/remoteproc/mtk_vcp_rproc.c        |  65 +++++
 drivers/remoteproc/mtk_vcp_rproc.h        |  18 ++
 include/linux/remoteproc/mtk_vcp_public.h |  13 +
 5 files changed, 428 insertions(+)

diff --git a/drivers/remoteproc/mtk_vcp_common.c b/drivers/remoteproc/mtk_vcp_common.c
index 9872d0caf331..039c0a469631 100644
--- a/drivers/remoteproc/mtk_vcp_common.c
+++ b/drivers/remoteproc/mtk_vcp_common.c
@@ -24,6 +24,9 @@
 #include "mtk_vcp_common.h"
 #include "mtk_vcp_rproc.h"
 
+static BLOCKING_NOTIFIER_HEAD(vcp_notifier_list);
+static BLOCKING_NOTIFIER_HEAD(mmup_notifier_list);
+
 phys_addr_t vcp_get_reserve_mem_phys(struct mtk_vcp_device *vcp,
 				     enum vcp_reserve_mem_id id)
 {
@@ -168,6 +171,42 @@ int vcp_reserve_memory_init(struct mtk_vcp_device *vcp)
 	return ret;
 }
 
+static bool vcp_is_core_ready(struct mtk_vcp_device *vcp,
+			      enum vcp_core_id core_id)
+{
+	switch (core_id) {
+	case VCP_ID:
+		return vcp->vcp_cluster->vcp_ready[VCP_ID];
+	case MMUP_ID:
+		return vcp->vcp_cluster->vcp_ready[MMUP_ID];
+	case VCP_CORE_TOTAL:
+	default:
+		return vcp->vcp_cluster->vcp_ready[VCP_ID] &
+		       vcp->vcp_cluster->vcp_ready[MMUP_ID];
+	}
+}
+
+static enum vcp_core_id get_core_by_feature(struct mtk_vcp_device *vcp,
+					    enum vcp_feature_id id)
+{
+	u32 f_id;
+
+	for (f_id = 0; f_id < NUM_FEATURE_ID; f_id++) {
+		if (vcp->platdata->feature_tb[f_id].feature_id == id)
+			return vcp->platdata->feature_tb[f_id].core_id;
+	}
+
+	return 0;
+}
+
+bool is_vcp_ready(struct mtk_vcp_device *vcp,
+		  enum vcp_feature_id id)
+{
+	enum vcp_core_id core_id = get_core_by_feature(vcp, id);
+
+	return vcp_is_core_ready(vcp, core_id);
+}
+
 int wait_core_hart_shutdown(struct mtk_vcp_device *vcp,
 			    enum vcp_core_id core_id)
 {
@@ -230,9 +269,119 @@ int wait_core_hart_shutdown(struct mtk_vcp_device *vcp,
 	return 0;
 }
 
+void vcp_register_notify(struct mtk_vcp_device *vcp,
+			 enum vcp_feature_id id,
+			 struct notifier_block *nb)
+{
+	enum vcp_core_id core_id = get_core_by_feature(vcp, id);
+
+	switch (core_id) {
+	case VCP_ID:
+		blocking_notifier_chain_register(&vcp_notifier_list, nb);
+		if (vcp_is_core_ready(vcp, VCP_ID))
+			nb->notifier_call(nb, VCP_EVENT_READY, NULL);
+		break;
+	case MMUP_ID:
+		blocking_notifier_chain_register(&mmup_notifier_list, nb);
+		if (vcp_is_core_ready(vcp, MMUP_ID))
+			nb->notifier_call(nb, VCP_EVENT_READY, NULL);
+		break;
+	default:
+		dev_err(vcp->dev, "%s, Unsupported core id\n", __func__);
+		break;
+	}
+}
+
+void vcp_unregister_notify(struct mtk_vcp_device *vcp,
+			   enum vcp_feature_id id,
+			   struct notifier_block *nb)
+{
+	enum vcp_core_id core_id = get_core_by_feature(vcp, id);
+
+	switch (core_id) {
+	case VCP_ID:
+		blocking_notifier_chain_unregister(&vcp_notifier_list, nb);
+		break;
+	case MMUP_ID:
+		blocking_notifier_chain_unregister(&mmup_notifier_list, nb);
+		break;
+	default:
+		dev_err(vcp->dev, "%s, Unsupported core id\n", __func__);
+		break;
+	}
+}
+
+void vcp_extern_notify(enum vcp_core_id core_id,
+		       enum vcp_notify_event notify_status)
+{
+	switch (core_id) {
+	case VCP_ID:
+		blocking_notifier_call_chain(&vcp_notifier_list, notify_status, NULL);
+		break;
+	case MMUP_ID:
+		blocking_notifier_call_chain(&mmup_notifier_list, notify_status, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void vcp_notify_ws(struct work_struct *ws)
+{
+	struct vcp_work_struct *sws =
+		container_of(ws, struct vcp_work_struct, work);
+	struct mtk_vcp_device *vcp = platform_get_drvdata(to_platform_device(sws->dev));
+	enum vcp_core_id core_id = sws->flags;
+
+	if (core_id < VCP_CORE_TOTAL) {
+		mutex_lock(&vcp->vcp_cluster->vcp_ready_mutex);
+		vcp->vcp_cluster->vcp_ready[core_id] = true;
+		mutex_unlock(&vcp->vcp_cluster->vcp_ready_mutex);
+
+		vcp_extern_notify(core_id, VCP_EVENT_READY);
+
+		dev_info(sws->dev, "%s, VCP core %u ready\n", __func__, core_id);
+	} else {
+		dev_err(sws->dev, "%s, Invalid core id %u\n", __func__, core_id);
+	}
+}
+
+static void vcp_set_ready(struct mtk_vcp_device *vcp,
+			  enum vcp_core_id core_id)
+{
+	if (core_id < VCP_CORE_TOTAL) {
+		vcp->vcp_cluster->vcp_ready_notify_wk[core_id].flags = core_id;
+		queue_work(vcp->vcp_cluster->vcp_workqueue,
+			   &vcp->vcp_cluster->vcp_ready_notify_wk[core_id].work);
+	}
+}
+
+int vcp_ready_ipi_handler(u32 id, void *prdata, void *data, u32 len)
+{
+	struct mtk_vcp_device *vcp = prdata;
+
+	switch (id) {
+	case IPI_IN_VCP_READY_0:
+		if (!vcp_is_core_ready(vcp, VCP_ID))
+			vcp_set_ready(vcp, VCP_ID);
+		break;
+	case IPI_IN_VCP_READY_1:
+		if (!vcp_is_core_ready(vcp, MMUP_ID))
+			vcp_set_ready(vcp, MMUP_ID);
+		break;
+	default:
+		dev_err(vcp->dev, "%s, Unsupported IPI id\n", __func__);
+		break;
+	}
+
+	return 0;
+}
+
 int reset_vcp(struct mtk_vcp_device *vcp)
 {
 	struct arm_smccc_res res;
+	bool mmup_status, vcp_status;
+	int ret;
 
 	if (vcp->vcp_cluster->core_nums > MMUP_ID) {
 		writel((u32)VCP_PACK_IOVA(vcp->vcp_cluster->share_mem_iova),
@@ -247,6 +396,16 @@ int reset_vcp(struct mtk_vcp_device *vcp)
 			dev_err(vcp->dev, "MMUP reset release SMC failed: %ld\n", res.a0);
 			return -EIO;
 		}
+
+		ret = read_poll_timeout(vcp_is_core_ready,
+					mmup_status, mmup_status,
+					USEC_PER_MSEC,
+					VCP_READY_TIMEOUT_MS * USEC_PER_MSEC,
+					false, vcp, MMUP_ID);
+		if (ret) {
+			dev_err(vcp->dev, "MMUP bootup timeout\n");
+			return ret;
+		}
 	}
 
 	writel((u32)VCP_PACK_IOVA(vcp->vcp_cluster->share_mem_iova),
@@ -262,6 +421,124 @@ int reset_vcp(struct mtk_vcp_device *vcp)
 		return -EIO;
 	}
 
+	ret = read_poll_timeout(vcp_is_core_ready,
+				vcp_status, vcp_status,
+				USEC_PER_MSEC,
+				VCP_READY_TIMEOUT_MS * USEC_PER_MSEC,
+				false, vcp, VCP_ID);
+	if (ret)
+		dev_err(vcp->dev, "VCP bootup timeout\n");
+
+	return ret;
+}
+
+static int vcp_enable_pm_clk(struct mtk_vcp_device *vcp, enum vcp_feature_id id)
+{
+	struct vcp_slp_ctrl slp_data;
+	bool suspend_status;
+	int ret;
+
+	if (vcp->vcp_cluster->feature_enable[id]) {
+		dev_err(vcp->dev, "%s feature(id=%d) already enabled\n",
+			__func__, id);
+		return -EINVAL;
+	}
+
+	if (id != RTOS_FEATURE_ID) {
+		slp_data.cmd = SLP_WAKE_LOCK;
+		slp_data.feature = id;
+		ret = vcp->ipi_ops->ipi_send_compl(vcp->ipi_dev, IPI_OUT_C_SLEEP_0,
+					     &slp_data, PIN_OUT_C_SIZE_SLEEP_0, 500);
+		if (ret < 0) {
+			dev_err(vcp->dev, "%s ipc_send_compl failed. ret %d\n",
+				__func__, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int vcp_disable_pm_clk(struct mtk_vcp_device *vcp, enum vcp_feature_id id)
+{
+	struct vcp_slp_ctrl slp_data;
+	bool suspend_status;
+	int ret;
+
+	if (!vcp->vcp_cluster->feature_enable[id]) {
+		dev_err(vcp->dev, "%s feature(id=%d) already disabled\n",
+			__func__, id);
+		return -EINVAL;
+	}
+
+	if (id != RTOS_FEATURE_ID) {
+		slp_data.cmd = SLP_WAKE_UNLOCK;
+		slp_data.feature = id;
+		ret = vcp->ipi_ops->ipi_send_compl(vcp->ipi_dev, IPI_OUT_C_SLEEP_0,
+					 &slp_data, PIN_OUT_C_SIZE_SLEEP_0, 500);
+		if (ret < 0) {
+			dev_err(vcp->dev, "%s ipc_send_compl failed. ret %d\n",
+				__func__, ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+int vcp_register_feature(struct mtk_vcp_device *vcp, enum vcp_feature_id id)
+{
+	int ret;
+
+	if (id >= NUM_FEATURE_ID) {
+		dev_err(vcp->dev, "%s, Unsupported feature id %d\n", __func__, id);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vcp->vcp_cluster->vcp_feature_mutex);
+	ret = vcp_enable_pm_clk(vcp, id);
+	if (ret)
+		dev_err(vcp->dev, "%s, Feature %d register failed\n", __func__, id);
+	else
+		vcp->vcp_cluster->feature_enable[id] = true;
+	mutex_unlock(&vcp->vcp_cluster->vcp_feature_mutex);
+
+	return ret;
+}
+
+int vcp_deregister_feature(struct mtk_vcp_device *vcp, enum vcp_feature_id id)
+{
+	int ret;
+
+	if (id >= NUM_FEATURE_ID) {
+		dev_err(vcp->dev, "%s, Unsupported feature id %d\n", __func__, id);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vcp->vcp_cluster->vcp_feature_mutex);
+	ret = vcp_disable_pm_clk(vcp, id);
+	if (ret)
+		dev_err(vcp->dev, "%s, Feature %d deregister failed\n", __func__, id);
+	else
+		vcp->vcp_cluster->feature_enable[id] = false;
+	mutex_unlock(&vcp->vcp_cluster->vcp_feature_mutex);
+
+	return ret;
+}
+
+int vcp_notify_work_init(struct mtk_vcp_device *vcp)
+{
+	u32 c_id;
+
+	vcp->vcp_cluster->vcp_workqueue = create_singlethread_workqueue("VCP_WQ");
+	if (!vcp->vcp_cluster->vcp_workqueue)
+		return dev_err_probe(vcp->dev, -ENOMEM, "Failed to create workqueue\n");
+
+	for (c_id = 0; c_id < VCP_CORE_TOTAL; c_id++) {
+		vcp->vcp_cluster->vcp_ready_notify_wk[c_id].dev = vcp->dev;
+		INIT_WORK(&vcp->vcp_cluster->vcp_ready_notify_wk[c_id].work, vcp_notify_ws);
+	}
+
 	return 0;
 }
 
diff --git a/drivers/remoteproc/mtk_vcp_common.h b/drivers/remoteproc/mtk_vcp_common.h
index 96eaed1b8f02..1238a165cac4 100644
--- a/drivers/remoteproc/mtk_vcp_common.h
+++ b/drivers/remoteproc/mtk_vcp_common.h
@@ -13,10 +13,13 @@
 #include <linux/remoteproc/mtk_vcp_public.h>
 
 /* VCP timeout definition */
+#define VCP_READY_TIMEOUT_MS 3000
+#define VCP_IPI_DEV_READY_TIMEOUT 1000
 #define CORE_HART_SHUTDOWN_TIMEOUT_MS 10
 
 /* VCP platform definition */
 #define DMA_MAX_MASK_BIT 33
+#define PIN_OUT_C_SIZE_SLEEP_0 2
 
 /* VCP load image definition */
 #define VCM_IMAGE_MAGIC             (0x58881688)
@@ -90,6 +93,14 @@ enum vcp_core_id {
 	VCP_CORE_TOTAL,
 };
 
+enum vcp_slp_cmd {
+	SLP_WAKE_LOCK = 0,
+	SLP_WAKE_UNLOCK,
+	SLP_STATUS_DBG,
+	SLP_SUSPEND,
+	SLP_RESUME,
+};
+
 enum mtk_tinysys_vcp_kernel_op {
 	MTK_TINYSYS_VCP_KERNEL_OP_RESET_SET = 0,
 	MTK_TINYSYS_VCP_KERNEL_OP_RESET_RELEASE,
@@ -154,6 +165,32 @@ struct vcp_reserve_mblock {
 	size_t size;
 };
 
+/**
+ * struct vcp_slp_ctrl - sleep ctrl data sync with AP and VCP
+ *
+ * @feature: Feature id
+ * @cmd: sleep cmd flag.
+ */
+struct vcp_slp_ctrl {
+	u32 feature;
+	u32 cmd;
+};
+
+/**
+ * struct vcp_work_struct - vcp notify work structure.
+ *
+ * @work: struct work_struct member
+ * @dev: struct device member
+ * @u32 flags: vcp notify work flag
+ * @id: vcp core id
+ */
+struct vcp_work_struct {
+	struct work_struct work;
+	struct device *dev;
+	u32 flags;
+	u32 id;
+};
+
 /**
  * struct vcp_region_info_st - config vcp image info sync to vcp bootloader.
  *
@@ -203,6 +240,19 @@ struct vcp_region_info_st {
 	u32 coredump_dram_offset;
 } __packed;
 
+int vcp_ready_ipi_handler(u32 id, void *prdata,
+			  void *data, u32 len);
+bool is_vcp_ready(struct mtk_vcp_device *vcp,
+		  enum vcp_feature_id id);
+int vcp_notify_work_init(struct mtk_vcp_device *vcp);
+void vcp_extern_notify(enum vcp_core_id core_id,
+		       enum vcp_notify_event notify_status);
+void vcp_register_notify(struct mtk_vcp_device *vcp,
+			 enum vcp_feature_id id,
+			 struct notifier_block *nb);
+void vcp_unregister_notify(struct mtk_vcp_device *vcp,
+			   enum vcp_feature_id id,
+			   struct notifier_block *nb);
 
 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);
@@ -216,5 +266,10 @@ int mtk_vcp_load(struct rproc *rproc, const struct firmware *fw);
 
 int vcp_wdt_irq_init(struct mtk_vcp_device *vcp);
 
+int vcp_register_feature(struct mtk_vcp_device *vcp,
+			 enum vcp_feature_id id);
+int vcp_deregister_feature(struct mtk_vcp_device *vcp,
+			   enum vcp_feature_id id);
+
 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
index f3b2646f79f6..2f320849fe15 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.c
+++ b/drivers/remoteproc/mtk_vcp_rproc.c
@@ -75,6 +75,30 @@ static int mtk_vcp_start(struct rproc *rproc)
 {
 	struct mtk_vcp_device *vcp = rproc->priv;
 	struct arm_smccc_res res;
+	int ret;
+
+	ret = vcp->ipi_ops->ipi_register(vcp->ipi_dev, IPI_OUT_C_SLEEP_0,
+					 NULL, NULL, &vcp->vcp_cluster->slp_ipi_ack_data);
+	if (ret) {
+		dev_err(vcp->dev, "Failed to register IPI_OUT_C_SLEEP_0\n");
+		return ret;
+	}
+
+	ret = vcp->ipi_ops->ipi_register(vcp->ipi_dev, IPI_IN_VCP_READY_0,
+					 (void *)vcp_ready_ipi_handler,
+					 vcp, &vcp->vcp_cluster->msg_vcp_ready0);
+	if (ret) {
+		dev_err(vcp->dev, "Failed to register IPI_IN_VCP_READY_0\n");
+		goto vcp0_ready_ipi_unregister;
+	}
+
+	ret = vcp->ipi_ops->ipi_register(vcp->ipi_dev, IPI_IN_VCP_READY_1,
+					 (void *)vcp_ready_ipi_handler,
+					 vcp, &vcp->vcp_cluster->msg_vcp_ready1);
+	if (ret) {
+		dev_err(vcp->dev, "Failed to register IPI_IN_VCP_READY_1\n");
+		goto vcp1_ready_ipi_unregister;
+	}
 
 	/* core 0 */
 	arm_smccc_smc(MTK_SIP_TINYSYS_VCP_CONTROL,
@@ -103,16 +127,37 @@ static int mtk_vcp_start(struct rproc *rproc)
 	}
 
 	dev_info(vcp->dev, "VCP bootup successfully\n");
+	ret = vcp_register_feature(vcp, RTOS_FEATURE_ID);
+	if (ret) {
+		dev_err(vcp->dev, "Failed to register RTOS feature\n");
+		goto reset_failed;
+	}
 
 	return 0;
 
 reset_failed:
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_IN_VCP_READY_1);
+vcp1_ready_ipi_unregister:
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_IN_VCP_READY_0);
+vcp0_ready_ipi_unregister:
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_OUT_C_SLEEP_0);
 
 	return ret;
 }
 
 static int mtk_vcp_stop(struct rproc *rproc)
 {
+	struct mtk_vcp_device *vcp = rproc->priv;
+
+	vcp_deregister_feature(vcp, RTOS_FEATURE_ID);
+
+	vcp_extern_notify(VCP_ID, VCP_EVENT_STOP);
+	vcp_extern_notify(MMUP_ID, VCP_EVENT_STOP);
+
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_IN_VCP_READY_1);
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_IN_VCP_READY_0);
+	vcp->ipi_ops->ipi_unregister(vcp->ipi_dev, IPI_OUT_C_SLEEP_0);
+
 	return 0;
 }
 
@@ -204,6 +249,8 @@ static struct mtk_vcp_device *vcp_rproc_init(struct platform_device *pdev,
 
 	rproc->auto_boot = vcp_of_data->platdata.auto_boot;
 	rproc->sysfs_read_only = vcp_of_data->platdata.sysfs_read_only;
+	mutex_init(&vcp->vcp_cluster->vcp_feature_mutex);
+	mutex_init(&vcp->vcp_cluster->vcp_ready_mutex);
 	platform_set_drvdata(pdev, vcp);
 
 	ret = vcp_reserve_memory_init(vcp);
@@ -237,6 +284,10 @@ static struct mtk_vcp_device *vcp_rproc_init(struct platform_device *pdev,
 	if (ret)
 		return ERR_PTR(dev_err_probe(dev, ret, "vcp_ipi_mbox_init failed\n"));
 
+	ret = vcp_notify_work_init(vcp);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "vcp_notify_work_init failed\n"));
+
 	ret = pm_runtime_get_sync(dev);
 	if (ret < 0) {
 		pm_runtime_put_noidle(dev);
@@ -313,6 +364,8 @@ static void vcp_device_remove(struct platform_device *pdev)
 {
 	struct mtk_vcp_device *vcp = platform_get_drvdata(pdev);
 
+	flush_workqueue(vcp->vcp_cluster->vcp_workqueue);
+	destroy_workqueue(vcp->vcp_cluster->vcp_workqueue);
 	pm_runtime_disable(&pdev->dev);
 
 	rproc_del(vcp->rproc);
@@ -323,6 +376,12 @@ static void vcp_device_shutdown(struct platform_device *pdev)
 	struct mtk_vcp_device *vcp = platform_get_drvdata(pdev);
 	int ret;
 
+	vcp->vcp_cluster->vcp_ready[VCP_ID] = false;
+	vcp->vcp_cluster->vcp_ready[MMUP_ID] = false;
+
+	vcp_extern_notify(VCP_ID, VCP_EVENT_STOP);
+	vcp_extern_notify(MMUP_ID, VCP_EVENT_STOP);
+
 	writel(GIPC_VCP_HART0_SHUT, vcp->vcp_cluster->cfg_core + R_GIPC_IN_SET);
 	ret = wait_core_hart_shutdown(vcp, VCP_ID);
 	if (ret)
@@ -408,6 +467,12 @@ static struct mtk_vcp_ipi_ops mt8196_vcp_ipi_ops = {
 
 static const struct mtk_vcp_of_data mt8196_of_data = {
 	.ops = {
+		.vcp_is_ready = is_vcp_ready,
+		.vcp_is_suspending = is_vcp_suspending,
+		.register_feature = vcp_register_feature,
+		.deregister_feature = vcp_deregister_feature,
+		.register_notify = vcp_register_notify,
+		.unregister_notify = vcp_unregister_notify,
 		.get_mem_phys = vcp_get_reserve_mem_phys,
 		.get_mem_iova = vcp_get_reserve_mem_iova,
 		.get_mem_virt = vcp_get_reserve_mem_virt,
diff --git a/drivers/remoteproc/mtk_vcp_rproc.h b/drivers/remoteproc/mtk_vcp_rproc.h
index c34d3a2757a9..64a25287dc5c 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.h
+++ b/drivers/remoteproc/mtk_vcp_rproc.h
@@ -19,10 +19,19 @@
  * @core_nums: total core numbers get from dtb
  * @hart_count: number of hardware threads (harts) per core
  * @sram_offset: core sram memory layout
+ * @msg_vcp_ready0: core0 ready ipi msg data
+ * @msg_vcp_ready1: core1 ready ipi msg data
+ * @slp_ipi_ack_data: sleep ipi msg data
+ * @feature_enable: feature status count data
+ * @vcp_ready: vcp core status flag
  * @share_mem_iova: shared memory iova base
  * @share_mem_size: shared memory size
+ * @vcp_feature_mutex: vcp feature register mutex structure
+ * @vcp_ready_mutex: vcp core ready mutex structure
  * @vcp_ipidev: struct mtk_ipi_device
+ * @vcp_workqueue: ready workqueue_struct
  * @vcp_memory_tb: vcp memory allocated table
+ * @vcp_ready_notify_wk: vcp_work_struct structure
  */
 struct mtk_vcp_of_cluster {
 	void __iomem *sram_base;
@@ -33,10 +42,19 @@ struct mtk_vcp_of_cluster {
 	u32 core_nums;
 	u32 hart_count[VCP_CORE_TOTAL];
 	u32 sram_offset[VCP_CORE_TOTAL];
+	u32 msg_vcp_ready0;
+	u32 msg_vcp_ready1;
+	u32 slp_ipi_ack_data;
+	bool feature_enable[NUM_FEATURE_ID];
+	bool vcp_ready[VCP_CORE_TOTAL];
 	dma_addr_t share_mem_iova;
 	size_t share_mem_size;
+	struct mutex vcp_feature_mutex;
+	struct mutex vcp_ready_mutex;
 	struct mtk_ipi_device vcp_ipidev;
+	struct workqueue_struct *vcp_workqueue;
 	struct vcp_reserve_mblock vcp_memory_tb[NUMS_MEM_ID];
+	struct vcp_work_struct vcp_ready_notify_wk[VCP_CORE_TOTAL];
 };
 
 /**
diff --git a/include/linux/remoteproc/mtk_vcp_public.h b/include/linux/remoteproc/mtk_vcp_public.h
index fda3cf5061e3..91634e807543 100644
--- a/include/linux/remoteproc/mtk_vcp_public.h
+++ b/include/linux/remoteproc/mtk_vcp_public.h
@@ -100,6 +100,19 @@ struct mtk_vcp_ipi_ops {
 };
 
 struct mtk_vcp_ops {
+	bool (*vcp_is_suspending)(struct mtk_vcp_device *vcp);
+	bool (*vcp_is_ready)(struct mtk_vcp_device *vcp,
+			     enum vcp_feature_id id);
+	int (*register_feature)(struct mtk_vcp_device *vcp,
+				enum vcp_feature_id id);
+	int (*deregister_feature)(struct mtk_vcp_device *vcp,
+				  enum vcp_feature_id id);
+	void (*register_notify)(struct mtk_vcp_device *vcp,
+				enum vcp_feature_id id,
+				struct notifier_block *nb);
+	void (*unregister_notify)(struct mtk_vcp_device *vcp,
+				  enum vcp_feature_id id,
+				  struct notifier_block *nb);
 	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,
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 6/7] remoteproc: mediatek: vcp: Add vcp suspend and resume feature
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
                   ` (4 preceding siblings ...)
  2026-04-27 11:04 ` [PATCH v4 5/7] remoteproc: mediatek: Add VCP ipi communication sync mechanism Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  2026-04-27 11:04 ` [PATCH v4 7/7] MAINTAINERS: Add entry for MediaTek VCP remoteproc driver Xiangzhi Tang
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Implement power management support for the VCP remoteproc driver by
adding suspend and resume callbacks. This allows the VCP coprocessor
to properly transition to low-power states when the system suspends,
and restore functionality when the system resumes.

The suspend/resume functionality coordinates with the VCP firmware
to ensure graceful state transitions and maintain communication
channel integrity across power state changes.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 drivers/remoteproc/mtk_vcp_common.c | 111 ++++++++++++++++++++++++++++
 drivers/remoteproc/mtk_vcp_common.h |   6 ++
 drivers/remoteproc/mtk_vcp_rproc.c  |  67 +++++++++++++++++
 drivers/remoteproc/mtk_vcp_rproc.h  |   2 +
 4 files changed, 186 insertions(+)

diff --git a/drivers/remoteproc/mtk_vcp_common.c b/drivers/remoteproc/mtk_vcp_common.c
index 039c0a469631..a1e2e6e0ada2 100644
--- a/drivers/remoteproc/mtk_vcp_common.c
+++ b/drivers/remoteproc/mtk_vcp_common.c
@@ -207,6 +207,11 @@ bool is_vcp_ready(struct mtk_vcp_device *vcp,
 	return vcp_is_core_ready(vcp, core_id);
 }
 
+bool is_vcp_suspending(struct mtk_vcp_device *vcp)
+{
+	return vcp->vcp_cluster->is_suspending;
+}
+
 int wait_core_hart_shutdown(struct mtk_vcp_device *vcp,
 			    enum vcp_core_id core_id)
 {
@@ -269,6 +274,92 @@ int wait_core_hart_shutdown(struct mtk_vcp_device *vcp,
 	return 0;
 }
 
+void vcp_wait_core_stop(struct mtk_vcp_device *vcp, enum vcp_core_id core_id)
+{
+	u32 status;
+	u32 stop_ctrl;
+	u32 num_harts;
+	int ret;
+
+	if (core_id >= VCP_CORE_TOTAL) {
+		dev_err(vcp->dev, "%s, Invalid core id %d\n", __func__, core_id);
+		return;
+	}
+
+	num_harts = vcp->vcp_cluster->hart_count[core_id];
+
+	/* Build stop control mask based on number of harts */
+	stop_ctrl = B_CORE_GATED | B_HART0_HALT | B_CORE_AXIS_BUSY;
+	if (num_harts > 1)
+		stop_ctrl |= B_HART1_HALT;
+
+	if (core_id == VCP_ID) {
+		ret = readl_poll_timeout(vcp->vcp_cluster->cfg + R_CORE0_STATUS,
+					 status,
+					 (status & stop_ctrl) == (stop_ctrl & ~B_CORE_AXIS_BUSY),
+					 USEC_PER_MSEC,
+					 CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+		if (ret)
+			dev_err(vcp->dev, "VCP core stop timeout, status 0x%x\n", status);
+	} else if (core_id == MMUP_ID) {
+		ret = readl_poll_timeout(vcp->vcp_cluster->cfg + R_CORE1_STATUS,
+					 status,
+					 (status & stop_ctrl) == (stop_ctrl & ~B_CORE_AXIS_BUSY),
+					 USEC_PER_MSEC,
+					 CORE_HART_SHUTDOWN_TIMEOUT_MS * USEC_PER_MSEC);
+		if (ret)
+			dev_err(vcp->dev, "MMUP core stop timeout, status 0x%x\n", status);
+	}
+}
+
+static bool vcp_get_suspend_resume_status(struct mtk_vcp_device *vcp)
+{
+	if (vcp->vcp_cluster->core_nums > MMUP_ID)
+		return !!(readl(vcp->vcp_cluster->cfg_sec + R_GPR3_SEC) & VCP_AP_SUSPEND) &&
+		       !!(readl(vcp->vcp_cluster->cfg_sec + R_GPR2_SEC) & MMUP_AP_SUSPEND);
+
+	return !!(readl(vcp->vcp_cluster->cfg_sec + R_GPR3_SEC) & VCP_AP_SUSPEND);
+}
+
+void vcp_wait_suspend_resume(struct mtk_vcp_device *vcp, bool suspend)
+{
+	bool status;
+	int ret;
+
+	if (suspend) {
+		writel(B_CORE0_SUSPEND, vcp->vcp_cluster->cfg_core + AP_R_GPR2);
+		writel(SUSPEND_MAGIC, vcp->vcp_cluster->cfg + VCP_C0_GPR0_SUSPEND_RESUME);
+		if (vcp->vcp_cluster->core_nums > MMUP_ID) {
+			writel(B_CORE1_SUSPEND, vcp->vcp_cluster->cfg_core + AP_R_GPR3);
+			writel(SUSPEND_MAGIC, vcp->vcp_cluster->cfg + VCP_C1_GPR0_SUSPEND_RESUME);
+		}
+	} else {
+		writel(B_CORE0_RESUME, vcp->vcp_cluster->cfg_core + AP_R_GPR2);
+		writel(RESUME_MAGIC, vcp->vcp_cluster->cfg + VCP_C0_GPR0_SUSPEND_RESUME);
+		if (vcp->vcp_cluster->core_nums > MMUP_ID) {
+			writel(B_CORE1_RESUME, vcp->vcp_cluster->cfg_core + AP_R_GPR3);
+			writel(RESUME_MAGIC, vcp->vcp_cluster->cfg + VCP_C1_GPR0_SUSPEND_RESUME);
+		}
+	}
+
+	writel(B_GIPC4_SETCLR_3, vcp->vcp_cluster->cfg_core + R_GIPC_IN_SET);
+
+	ret = read_poll_timeout(vcp_get_suspend_resume_status,
+				status, (status == suspend),
+				USEC_PER_MSEC,
+				SUSPEND_WAIT_TIMEOUT_MS * USEC_PER_MSEC,
+				false, vcp);
+	if (ret)
+		dev_err(vcp->dev, "vcp %s timeout GPIC 0x%x 0x%x 0x%x 0x%x flag 0x%x 0x%x\n",
+			suspend ? "suspend" : "resume",
+			readl(vcp->vcp_cluster->cfg_core + R_GIPC_IN_SET),
+			readl(vcp->vcp_cluster->cfg_core + R_GIPC_IN_CLR),
+			readl(vcp->vcp_cluster->cfg_core + AP_R_GPR2),
+			readl(vcp->vcp_cluster->cfg_core + AP_R_GPR3),
+			readl(vcp->vcp_cluster->cfg_sec + R_GPR2_SEC),
+			readl(vcp->vcp_cluster->cfg_sec + R_GPR3_SEC));
+}
+
 void vcp_register_notify(struct mtk_vcp_device *vcp,
 			 enum vcp_feature_id id,
 			 struct notifier_block *nb)
@@ -438,6 +529,16 @@ static int vcp_enable_pm_clk(struct mtk_vcp_device *vcp, enum vcp_feature_id id)
 	bool suspend_status;
 	int ret;
 
+	ret = read_poll_timeout(is_vcp_suspending,
+				suspend_status, !suspend_status,
+				USEC_PER_MSEC,
+				SUSPEND_WAIT_TIMEOUT_MS * USEC_PER_MSEC,
+				false, vcp);
+	if (ret) {
+		dev_err(vcp->dev, "%s blocked by vcp suspend\n", __func__);
+		return ret;
+	}
+
 	if (vcp->vcp_cluster->feature_enable[id]) {
 		dev_err(vcp->dev, "%s feature(id=%d) already enabled\n",
 			__func__, id);
@@ -465,6 +566,16 @@ static int vcp_disable_pm_clk(struct mtk_vcp_device *vcp, enum vcp_feature_id id
 	bool suspend_status;
 	int ret;
 
+	ret = read_poll_timeout(is_vcp_suspending,
+				suspend_status, !suspend_status,
+				USEC_PER_MSEC,
+				SUSPEND_WAIT_TIMEOUT_MS * USEC_PER_MSEC,
+				false, vcp);
+	if (ret) {
+		dev_err(vcp->dev, "%s blocked by vcp suspend\n", __func__);
+		return ret;
+	}
+
 	if (!vcp->vcp_cluster->feature_enable[id]) {
 		dev_err(vcp->dev, "%s feature(id=%d) already disabled\n",
 			__func__, id);
diff --git a/drivers/remoteproc/mtk_vcp_common.h b/drivers/remoteproc/mtk_vcp_common.h
index 1238a165cac4..f193e2f66796 100644
--- a/drivers/remoteproc/mtk_vcp_common.h
+++ b/drivers/remoteproc/mtk_vcp_common.h
@@ -16,9 +16,12 @@
 #define VCP_READY_TIMEOUT_MS 3000
 #define VCP_IPI_DEV_READY_TIMEOUT 1000
 #define CORE_HART_SHUTDOWN_TIMEOUT_MS 10
+#define SUSPEND_WAIT_TIMEOUT_MS 100
 
 /* VCP platform definition */
 #define DMA_MAX_MASK_BIT 33
+#define RESUME_MAGIC 0x12345678
+#define SUSPEND_MAGIC 0x87654321
 #define PIN_OUT_C_SIZE_SLEEP_0 2
 
 /* VCP load image definition */
@@ -271,5 +274,8 @@ int vcp_register_feature(struct mtk_vcp_device *vcp,
 int vcp_deregister_feature(struct mtk_vcp_device *vcp,
 			   enum vcp_feature_id id);
 
+bool is_vcp_suspending(struct mtk_vcp_device *vcp);
 int wait_core_hart_shutdown(struct mtk_vcp_device *vcp, enum vcp_core_id core_id);
+void vcp_wait_core_stop(struct mtk_vcp_device *vcp, enum vcp_core_id core_id);
+void vcp_wait_suspend_resume(struct mtk_vcp_device *vcp, bool suspend);
 #endif
diff --git a/drivers/remoteproc/mtk_vcp_rproc.c b/drivers/remoteproc/mtk_vcp_rproc.c
index 2f320849fe15..b27bf1b6f668 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.c
+++ b/drivers/remoteproc/mtk_vcp_rproc.c
@@ -11,6 +11,7 @@
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/suspend.h>
 #include <linux/remoteproc.h>
 
 #include "mtk_vcp_common.h"
@@ -71,6 +72,66 @@ struct mtk_ipi_device *vcp_get_ipidev(struct mtk_vcp_device *vcp)
 }
 EXPORT_SYMBOL_GPL(vcp_get_ipidev);
 
+static int mtk_vcp_suspend(struct device *dev)
+{
+	struct mtk_vcp_device *vcp = platform_get_drvdata(to_platform_device(dev));
+	u32 f_id;
+	int ret;
+
+	vcp_extern_notify(VCP_ID, VCP_EVENT_SUSPEND);
+	vcp_extern_notify(MMUP_ID, VCP_EVENT_SUSPEND);
+
+	for (f_id = RTOS_FEATURE_ID + 1; f_id < NUM_FEATURE_ID; f_id++) {
+		if (vcp->vcp_cluster->feature_enable[f_id]) {
+			dev_err(vcp->dev, "%s, Feature %d still active\n", __func__, f_id);
+			return -EBUSY;
+		}
+	}
+
+	if (!vcp->vcp_cluster->is_suspending) {
+		vcp->vcp_cluster->is_suspending = true;
+		vcp->vcp_cluster->vcp_ready[VCP_ID] = false;
+		vcp->vcp_cluster->vcp_ready[MMUP_ID] = false;
+
+		flush_workqueue(vcp->vcp_cluster->vcp_workqueue);
+
+		vcp_wait_suspend_resume(vcp, true);
+		vcp_wait_core_stop(vcp, VCP_ID);
+		vcp_wait_core_stop(vcp, MMUP_ID);
+
+		ret = pm_runtime_put_sync(dev);
+		if (ret < 0) {
+			dev_err(dev, "%s, Failed to suspend: %d\n", __func__, ret);
+			vcp->vcp_cluster->is_suspending = false;
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int mtk_vcp_resume(struct device *dev)
+{
+	struct mtk_vcp_device *vcp = platform_get_drvdata(to_platform_device(dev));
+	int ret;
+
+	if (vcp->vcp_cluster->is_suspending) {
+		ret = pm_runtime_get_sync(dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(dev);
+			return ret;
+		}
+
+		vcp_wait_suspend_resume(vcp, false);
+	}
+	vcp->vcp_cluster->is_suspending = false;
+
+	vcp_extern_notify(MMUP_ID, VCP_EVENT_RESUME);
+	vcp_extern_notify(VCP_ID, VCP_EVENT_RESUME);
+
+	return 0;
+}
+
 static int mtk_vcp_start(struct rproc *rproc)
 {
 	struct mtk_vcp_device *vcp = rproc->priv;
@@ -491,6 +552,11 @@ static const struct mtk_vcp_of_data mt8196_of_data = {
 	},
 };
 
+static const struct dev_pm_ops mtk_vcp_rproc_pm_ops = {
+	.suspend_noirq = mtk_vcp_suspend,
+	.resume_noirq = mtk_vcp_resume,
+};
+
 static const struct of_device_id mtk_vcp_of_match[] = {
 	{ .compatible = "mediatek,mt8196-vcp", .data = &mt8196_of_data},
 	{}
@@ -504,6 +570,7 @@ static struct platform_driver mtk_vcp_device = {
 	.driver = {
 		.name = "mtk-vcp",
 		.of_match_table = mtk_vcp_of_match,
+		.pm = pm_ptr(&mtk_vcp_rproc_pm_ops),
 	},
 };
 
diff --git a/drivers/remoteproc/mtk_vcp_rproc.h b/drivers/remoteproc/mtk_vcp_rproc.h
index 64a25287dc5c..a1ac6c7efd08 100644
--- a/drivers/remoteproc/mtk_vcp_rproc.h
+++ b/drivers/remoteproc/mtk_vcp_rproc.h
@@ -23,6 +23,7 @@
  * @msg_vcp_ready1: core1 ready ipi msg data
  * @slp_ipi_ack_data: sleep ipi msg data
  * @feature_enable: feature status count data
+ * @is_suspending: suspend status flag
  * @vcp_ready: vcp core status flag
  * @share_mem_iova: shared memory iova base
  * @share_mem_size: shared memory size
@@ -46,6 +47,7 @@ struct mtk_vcp_of_cluster {
 	u32 msg_vcp_ready1;
 	u32 slp_ipi_ack_data;
 	bool feature_enable[NUM_FEATURE_ID];
+	bool is_suspending;
 	bool vcp_ready[VCP_CORE_TOTAL];
 	dma_addr_t share_mem_iova;
 	size_t share_mem_size;
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v4 7/7] MAINTAINERS: Add entry for MediaTek VCP remoteproc driver
  2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
                   ` (5 preceding siblings ...)
  2026-04-27 11:04 ` [PATCH v4 6/7] remoteproc: mediatek: vcp: Add vcp suspend and resume feature Xiangzhi Tang
@ 2026-04-27 11:04 ` Xiangzhi Tang
  6 siblings, 0 replies; 10+ messages in thread
From: Xiangzhi Tang @ 2026-04-27 11:04 UTC (permalink / raw)
  To: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Xiangzhi Tang
  Cc: linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu, Xiangzhi Tang

Add maintainer entry for the MediaTek Video Companion Processor
(VCP) remoteproc driver, including the device tree bindings,
firmware IPC interface, remoteproc driver, and public headers.

Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
---
 MAINTAINERS | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 62b89d0013d2..4e27fcca28f8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3065,6 +3065,20 @@ N:	mtk
 N:	mt[2678]
 K:	mediatek
 
+ARM/MEDIATEK VCP REMOTEPROC DRIVER
+M:	Xiangzhi Tang <xiangzhi.tang@mediatek.com>
+M:	Matthias Brugger <matthias.bgg@gmail.com>
+M:	AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
+L:	linux-remoteproc@vger.kernel.org
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
+S:	Maintained
+F:	Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
+F:	drivers/firmware/mtk-vcp-ipc.c
+F:	drivers/remoteproc/mtk_vcp*
+F:	include/linux/firmware/mediatek/mtk-vcp-ipc.h
+F:	include/linux/remoteproc/mtk_vcp_public.h
+
 ARM/Mediatek USB3 PHY DRIVER
 M:	Chunfeng Yun <chunfeng.yun@mediatek.com>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
-- 
2.46.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding
  2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
@ 2026-04-27 12:39   ` Rob Herring (Arm)
  2026-04-27 13:51   ` Rob Herring
  1 sibling, 0 replies; 10+ messages in thread
From: Rob Herring (Arm) @ 2026-04-27 12:39 UTC (permalink / raw)
  To: Xiangzhi Tang
  Cc: Hailong Fan, Krzysztof Kozlowski, linux-mediatek, linux-kernel,
	devicetree, Project_Global_Chrome_Upstream_Group, Justin Yeh,
	Conor Dooley, Xiangzhi Tang, AngeloGioacchino Del Regno,
	linux-remoteproc, linux-arm-kernel, Vince-WL Liu, Huayu Zong,
	Jarried Lin, Bjorn Andersson, Matthias Brugger, Mathieu Poirier


On Mon, 27 Apr 2026 19:04:40 +0800, Xiangzhi Tang wrote:
> Add device tree binding for the MediaTek Video Companion Processor
> (VCP), a RISC-V based coprocessor used for video processing and
> multimedia tasks on mt8196 and future MediaTek SoCs.
> 
> The VCP is a heterogeneous multi-core processor that can contain
> multiple RISC-V cores with different hart (hardware thread)
> configurations. Key features:
> 
> - Supports both single-core and multi-core VCP configurations
> - Each core can have 1 or 2 harts (hardware threads)
> - Shared SRAM memory space partitioned among cores
> - Communication via 5 dedicated mailbox channels for IPI messaging
> - Integrated with SoC IOMMU for multimedia memory management
> - Boot and power management coordinated with ARM Trusted Firmware
> 
> The binding defines both the top-level VCP device (with mailboxes,
> interrupts, and power domains) and child nodes for individual VCP
> cores (with SRAM allocation and hart configuration).
> 
> Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
> ---
>  .../remoteproc/mediatek,mt8196-vcp.yaml       | 166 ++++++++++++++++++
>  1 file changed, 166 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.example.dts:26:18: fatal error: dt-bindings/power/mt8196-power.h: No such file or directory
   26 |         #include <dt-bindings/power/mt8196-power.h>
      |                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [scripts/Makefile.dtbs:140: Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1635: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260427111446.22955-2-xiangzhi.tang@mediatek.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding
  2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
  2026-04-27 12:39   ` Rob Herring (Arm)
@ 2026-04-27 13:51   ` Rob Herring
  1 sibling, 0 replies; 10+ messages in thread
From: Rob Herring @ 2026-04-27 13:51 UTC (permalink / raw)
  To: Xiangzhi Tang
  Cc: Bjorn Andersson, Mathieu Poirier, Krzysztof Kozlowski,
	Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
	linux-remoteproc, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Project_Global_Chrome_Upstream_Group, Hailong Fan,
	Huayu Zong, Jarried Lin, Justin Yeh, Vince-WL Liu

On Mon, Apr 27, 2026 at 07:04:40PM +0800, Xiangzhi Tang wrote:
> Add device tree binding for the MediaTek Video Companion Processor
> (VCP), a RISC-V based coprocessor used for video processing and
> multimedia tasks on mt8196 and future MediaTek SoCs.
> 
> The VCP is a heterogeneous multi-core processor that can contain
> multiple RISC-V cores with different hart (hardware thread)
> configurations. Key features:
> 
> - Supports both single-core and multi-core VCP configurations
> - Each core can have 1 or 2 harts (hardware threads)
> - Shared SRAM memory space partitioned among cores
> - Communication via 5 dedicated mailbox channels for IPI messaging
> - Integrated with SoC IOMMU for multimedia memory management
> - Boot and power management coordinated with ARM Trusted Firmware
> 
> The binding defines both the top-level VCP device (with mailboxes,
> interrupts, and power domains) and child nodes for individual VCP
> cores (with SRAM allocation and hart configuration).
> 
> Signed-off-by: Xiangzhi Tang <xiangzhi.tang@mediatek.com>
> ---
>  .../remoteproc/mediatek,mt8196-vcp.yaml       | 166 ++++++++++++++++++
>  1 file changed, 166 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
> 
> diff --git a/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml b/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
> new file mode 100644
> index 000000000000..8ecb643cbdc5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/remoteproc/mediatek,mt8196-vcp.yaml
> @@ -0,0 +1,166 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/remoteproc/mediatek,mt8196-vcp.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: MediaTek Video Companion Processor (VCP)
> +
> +maintainers:
> +  - Xiangzhi Tang <xiangzhi.tang@mediatek.com>
> +
> +description:
> +  This binding provides support for the MediaTek Video Companion Processor
> +  (VCP), a Risc-V coprocessor found on some MediaTek SoCs.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - mediatek,mt8196-vcp
> +
> +  reg:
> +    items:
> +      - description: sram base
> +      - description: cfg group IO
> +      - description: cfg core group IO
> +      - description: cfg sec group IO
> +
> +  reg-names:
> +    items:
> +      - const: sram
> +      - const: cfg
> +      - const: cfg-core
> +      - const: cfg-sec
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  mboxes:
> +    maxItems: 5

You have to define what each one is.

> +
> +  mbox-names:
> +    items:
> +      - const: mbox0
> +      - const: mbox1
> +      - const: mbox2
> +      - const: mbox3
> +      - const: mbox4

name plus an index is not useful.

> +
> +  power-domains:
> +    maxItems: 1
> +
> +  iommus:
> +    description:
> +      Using MediaTek IOMMU to apply larb ports for Multimedia Memory
> +      Management Unit and address translation.
> +    maxItems: 1
> +
> +  memory-region:
> +    maxItems: 1
> +
> +patternProperties:
> +  "^vcp@[a-f0-9]+$":
> +    type: object
> +    description:

You need '>' for paragraphs.

> +      The MediaTek VCP integrated to SoC might be a multi-core version.
> +      The other cores are represented as child nodes of the boot core.
> +      There are some integration differences for the IP like the usage of
> +      address translator for translating SoC bus addresses into address
> +      space for the processor.
> +
> +      The SRAM is shared by all cores, each VCP core only using a piece of
> +      SRAM memory. The power of SRAM should be enabled before booting VCP cores.
> +      The size of SRAM varies on different SoCs.
> +
> +      The VCP cores have differences on different SoCs for Hart support.
> +
> +    properties:
> +      compatible:
> +        enum:
> +          - mediatek,vcp-core
> +
> +      reg:
> +        description: The base address and size of SRAM.
> +        maxItems: 1

This is memory mapped, right? If so you need 'ranges' in the parent 
node.

> +
> +      reg-names:
> +        const: sram
> +
> +      mediatek,vcp-core-harts:
> +        $ref: /schemas/types.yaml#/definitions/uint32
> +        description: Number of harts in this VCP core.
> +        enum: [1, 2]
> +
> +      mediatek,vcp-core-sram-offset:
> +        $ref: /schemas/types.yaml#/definitions/uint32
> +        description:
> +          Offset of the allocated SRAM memory for this VCP core.

Why do you have this and SRAM for the core defined in reg? It should 
only be in reg.

> +
> +    required:
> +      - compatible
> +      - reg
> +      - reg-names
> +      - mediatek,vcp-core-harts
> +      - mediatek,vcp-core-sram-offset
> +
> +    additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +  - reg-names
> +  - interrupts
> +  - mboxes
> +  - mbox-names
> +  - power-domains
> +  - iommus
> +  - memory-region
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/power/mt8196-power.h>
> +
> +    vcp: vcp@31800000 {

Drop unused labels.

> +        compatible = "mediatek,mt8196-vcp";
> +        reg = <0x31800000 0x60000>,
> +              <0x31a04000 0xa000>,
> +              <0x31bd0000 0x1000>,
> +              <0x31a70020 0x100>;
> +        reg-names = "sram",
> +                    "cfg",
> +                    "cfg-core",
> +                    "cfg-sec";
> +
> +        interrupts = <GIC_SPI 787 IRQ_TYPE_LEVEL_HIGH 0>;
> +
> +        mboxes = <&vcp_mailbox0>,
> +                 <&vcp_mailbox1>,
> +                 <&vcp_mailbox2>,
> +                 <&vcp_mailbox3>,
> +                 <&vcp_mailbox4>;
> +        mbox-names = "mbox0", "mbox1", "mbox2", "mbox3", "mbox4";
> +
> +        power-domains = <&scpsys MT8196_POWER_DOMAIN_MM_PROC_DORMANT>;
> +        iommus = <&mm_smmu 160>;
> +        memory-region = <&vcp_resv_mem>;
> +
> +        vcp@0 {
> +            compatible = "mediatek,vcp-core";
> +            reg = <0x0 0x31000>;
> +            reg-names = "sram";
> +            mediatek,vcp-core-harts = <2>;
> +            mediatek,vcp-core-sram-offset = <0x0>;
> +        };
> +
> +        vcp@31000 {
> +            compatible = "mediatek,vcp-core";
> +            reg = <0x31000 0x60000>;
> +            reg-names = "sram";
> +            mediatek,vcp-core-harts = <1>;
> +            mediatek,vcp-core-sram-offset = <0x31000>;
> +        };
> +    };
> -- 
> 2.46.0
> 


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-04-27 13:51 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-27 11:04 [PATCH 0/7] Add MediaTek VCP remoteproc driver support Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 1/7] dt-bindings: remoteproc: Add MediaTek mt8196 VCP binding Xiangzhi Tang
2026-04-27 12:39   ` Rob Herring (Arm)
2026-04-27 13:51   ` Rob Herring
2026-04-27 11:04 ` [PATCH v4 2/7] remoteproc: mediatek: Add VCP remoteproc driver Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 3/7] firmware: mediatek: Add VCP IPC protocol driver Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 4/7] remoteproc: mediatek: Add VCP IPI mailbox initialization Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 5/7] remoteproc: mediatek: Add VCP ipi communication sync mechanism Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 6/7] remoteproc: mediatek: vcp: Add vcp suspend and resume feature Xiangzhi Tang
2026-04-27 11:04 ` [PATCH v4 7/7] MAINTAINERS: Add entry for MediaTek VCP remoteproc driver Xiangzhi Tang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox