arm: mediatek: add support for MediaTek MT7987 SoC

This patch adds basic support for MediaTek MT7987 SoC.
This includes files that will initialize the SoC after boot and
its device tree.

In order to maximize the continuous usable  memory space, MT7987 has its
ATF BL31 loaded at the top of RAM. Since u-boot will also locate itself to
top of RAM, u-boot will read the actual memory region of BL31 and set
correct gd->ram_top to avoid u-boot overlapping with BL31.

As now support for mt7987 hasn't been submitted to linux kernel, all dts
filed will be put to arch/arm/dts. They'll be removed after successfully
being merged by linux kernel, and OF_UPSTREAM will also be switched on.

diff --git a/arch/arm/mach-mediatek/Kconfig b/arch/arm/mach-mediatek/Kconfig
index ff1fdee..39eea05 100644
--- a/arch/arm/mach-mediatek/Kconfig
+++ b/arch/arm/mach-mediatek/Kconfig
@@ -59,6 +59,16 @@
 	  including UART, SPI, SPI flash, USB3.0, MMC, NAND, SNFI, PWM, PCIe,
 	  Gigabit Ethernet, I2C, built-in 4x4 Wi-Fi, and PCIe.
 
+config TARGET_MT7987
+	bool "MediaTek MT7987 SoC"
+	select ARM64
+	select CPU
+	select MTK_TZ_MOVABLE
+	help
+	  The MediaTek MT7987 is a ARM64-based SoC with a quad-core Cortex-A53.
+	  including UART, I2C, PWM, SPI controller which support SPI flash,
+	  USB3.0, MMC, PCIe, SGMII and built-in Ethernet PHY.
+
 config TARGET_MT7988
 	bool "MediaTek MT7988 SoC"
 	select ARM64
@@ -122,6 +132,7 @@
 	default "mt7629" if TARGET_MT7629
 	default "mt7981" if TARGET_MT7981
 	default "mt7986" if TARGET_MT7986
+	default "mt7987" if TARGET_MT7987
 	default "mt7988" if TARGET_MT7988
 	default "mt8183" if TARGET_MT8183
 	default "mt8512" if TARGET_MT8512
@@ -139,6 +150,7 @@
 	default "mt7629" if TARGET_MT7629
 	default "mt7981" if TARGET_MT7981
 	default "mt7986" if TARGET_MT7986
+	default "mt7987" if TARGET_MT7987
 	default "mt7988" if TARGET_MT7988
 	default "mt8183" if TARGET_MT8183
 	default "mt8365" if TARGET_MT8365
@@ -150,7 +162,12 @@
 	string
 	default "media=nor" if TARGET_MT8518 || TARGET_MT8512 || TARGET_MT7629 || TARGET_MT7622
 	default "media=emmc" if TARGET_MT8516 || TARGET_MT8365 || TARGET_MT8183
-	default "media=snand;nandinfo=2k+64" if TARGET_MT7981 || TARGET_MT7986 || TARGET_MT7988
+	default "media=snand;nandinfo=2k+64" if TARGET_MT7981 || TARGET_MT7986 || TARGET_MT7987 || TARGET_MT7988
 	default "lk=1" if TARGET_MT7623
 
+config MTK_TZ_MOVABLE
+	select ARCH_MISC_INIT
+	select OF_SYSTEM_SETUP
+	bool
+
 endif
diff --git a/arch/arm/mach-mediatek/Makefile b/arch/arm/mach-mediatek/Makefile
index 3d9e468..344434c 100644
--- a/arch/arm/mach-mediatek/Makefile
+++ b/arch/arm/mach-mediatek/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier:	GPL-2.0
 
 obj-y	+= cpu.o
+obj-$(CONFIG_MTK_TZ_MOVABLE)	+= tzcfg.o
 obj-$(CONFIG_XPL_BUILD)	+= spl.o
 
 obj-$(CONFIG_MT8512) += mt8512/
@@ -9,6 +10,7 @@
 obj-$(CONFIG_TARGET_MT7629) += mt7629/
 obj-$(CONFIG_TARGET_MT7981) += mt7981/
 obj-$(CONFIG_TARGET_MT7986) += mt7986/
+obj-$(CONFIG_TARGET_MT7987) += mt7987/
 obj-$(CONFIG_TARGET_MT7988) += mt7988/
 obj-$(CONFIG_TARGET_MT8183) += mt8183/
 obj-$(CONFIG_TARGET_MT8365) += mt8365/
diff --git a/arch/arm/mach-mediatek/mt7987/Makefile b/arch/arm/mach-mediatek/mt7987/Makefile
new file mode 100644
index 0000000..007eb4a
--- /dev/null
+++ b/arch/arm/mach-mediatek/mt7987/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier:	GPL-2.0
+
+obj-y += init.o
+obj-y += lowlevel_init.o
diff --git a/arch/arm/mach-mediatek/mt7987/init.c b/arch/arm/mach-mediatek/mt7987/init.c
new file mode 100644
index 0000000..8b26829
--- /dev/null
+++ b/arch/arm/mach-mediatek/mt7987/init.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 MediaTek Inc.
+ * Author: Sam Shih <sam.shih@mediatek.com>
+ */
+
+#include <fdtdec.h>
+#include <init.h>
+#include <asm/armv8/mmu.h>
+#include <asm/global_data.h>
+#include <asm/u-boot.h>
+#include <asm/system.h>
+#include <linux/sizes.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+int dram_init(void)
+{
+	int ret;
+
+	ret = fdtdec_setup_mem_size_base();
+	if (ret)
+		return ret;
+
+	gd->ram_size = get_ram_size((void *)gd->ram_base, SZ_4G);
+
+	return 0;
+}
+
+int dram_init_banksize(void)
+{
+	gd->bd->bi_dram[0].start = gd->ram_base;
+	gd->bd->bi_dram[0].size = gd->ram_size;
+
+	return 0;
+}
+
+void reset_cpu(ulong addr)
+{
+	psci_system_reset();
+}
+
+static struct mm_region mt7987_mem_map[] = {
+	{
+		/* DDR */
+		.virt = 0x40000000UL,
+		.phys = 0x40000000UL,
+		.size = 0x200000000ULL,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_OUTER_SHARE,
+	}, {
+		.virt = 0x00000000UL,
+		.phys = 0x00000000UL,
+		.size = 0x40000000UL,
+		.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
+			 PTE_BLOCK_NON_SHARE |
+			 PTE_BLOCK_PXN | PTE_BLOCK_UXN
+	}, {
+		0,
+	}
+};
+
+struct mm_region *mem_map = mt7987_mem_map;
diff --git a/arch/arm/mach-mediatek/mt7987/lowlevel_init.S b/arch/arm/mach-mediatek/mt7987/lowlevel_init.S
new file mode 100644
index 0000000..2f97255
--- /dev/null
+++ b/arch/arm/mach-mediatek/mt7987/lowlevel_init.S
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 MediaTek Inc.
+ * Author: Sam Shih <sam.shih@mediatek.com>
+ */
+
+/*
+ * Switch from AArch64 EL2 to AArch32 EL2
+ * @param inputs:
+ * x0: argument, zero
+ * x1: machine nr
+ * x2: fdt address
+ * x3: input argument
+ * x4: kernel entry point
+ * @param outputs for secure firmware:
+ * x0: function id
+ * x1: kernel entry point
+ * x2: machine nr
+ * x3: fdt address
+*/
+
+.global armv8_el2_to_aarch32
+armv8_el2_to_aarch32:
+	mov     x3, x2
+	mov     x2, x1
+	mov     x1, x4
+	mov	x4, #0
+	ldr x0, =0x82000200
+	SMC #0
+	ret
diff --git a/arch/arm/mach-mediatek/tzcfg.c b/arch/arm/mach-mediatek/tzcfg.c
new file mode 100644
index 0000000..71982ba
--- /dev/null
+++ b/arch/arm/mach-mediatek/tzcfg.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 MediaTek Inc.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <asm/global_data.h>
+#include <linux/kernel.h>
+#include <linux/arm-smccc.h>
+#include <linux/sizes.h>
+#include <command.h>
+#include <fdtdec.h>
+#include <fdt_support.h>
+#include <lmb.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define MTK_SIP_GET_BL31_REGION		0x82000300
+#define MTK_SIP_GET_BL32_REGION		0x82000301
+
+#define BL31_DEFAULT_ADDR		0x43000000
+#define BL31_DEFAULT_SIZE		0x80000
+
+#define TZ_REGION_MAX_SIZE		SZ_16M
+#define U_BOOT_MIN_SIZE			SZ_4M
+#define U_BOOT_MIN_STACK_SIZE		SZ_1M
+#define REGION_ALIGNMENT		SZ_64K
+
+struct tz_reserved_region {
+	phys_addr_t addr;
+	phys_addr_t size;
+};
+
+static bool fix_tz_region(struct tz_reserved_region region[],
+			  uint32_t used_regions)
+{
+	phys_addr_t size;
+
+	if (region[0].addr + region[0].size > gd->ram_top) {
+		if (region[0].addr >= gd->ram_top) {
+			debug("Discarded region 0x%08llx, size 0x%llx\n",
+			      region[0].addr, region[0].size);
+
+			if (used_regions > 1)
+				region[0] = region[1];
+
+			return true;
+		}
+
+		size = gd->ram_top - region[0].addr;
+
+		debug("Truncated region 0x%08llx, size 0x%llx -> 0x%llx\n",
+		      region[0].addr, region[0].size, size);
+
+		region[0].size = size;
+	}
+
+	return false;
+}
+
+phys_addr_t board_get_usable_ram_top(phys_size_t total_size)
+{
+	phys_addr_t uboot_ram_top, pstore_size, uboot_size = 0;
+	struct tz_reserved_region region[2], tmp;
+	phys_addr_t top_addr, low_addr;
+	struct arm_smccc_res res;
+	u32 used_regions = 1;
+
+	/* BL31 region */
+	arm_smccc_smc(MTK_SIP_GET_BL31_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+	if (res.a0) {
+		/* Assume PIE is not enabled for BL31 */
+		region[0].addr = BL31_DEFAULT_ADDR;
+		region[0].size = BL31_DEFAULT_SIZE;
+	} else {
+		region[0].addr = res.a1;
+		region[0].size = res.a2;
+	}
+
+	debug("BL31 @ 0x%08llx, size 0x%llx\n", region[0].addr,
+	      region[0].size);
+
+	/* BL32 region is optional */
+	arm_smccc_smc(MTK_SIP_GET_BL32_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+	if (!res.a0 && res.a1 && res.a2) {
+		region[used_regions].addr = res.a1;
+		region[used_regions].size = res.a2;
+
+		debug("BL32 @ 0x%08llx, size 0x%llx\n",
+		      region[used_regions].addr, region[used_regions].size);
+
+		used_regions++;
+	}
+
+	if (used_regions == 2) {
+		if (region[0].addr < region[1].addr) {
+			/* Make sure region[0] is higher than region[1] */
+			tmp = region[0];
+			region[0] = region[1];
+			region[1] = tmp;
+		}
+
+		top_addr = region[0].addr + region[0].size;
+		low_addr = min_t(phys_addr_t, region[0].addr, region[1].addr);
+
+		if (top_addr - low_addr <= TZ_REGION_MAX_SIZE) {
+			/* Merge region if they're overlapped or close enough */
+			region[0].size = top_addr - low_addr;
+			region[0].addr = low_addr;
+
+			debug("Merged region @ 0x%08llx, size 0x%llx\n",
+			      region[0].addr, region[0].size);
+
+			used_regions = 1;
+		}
+	}
+
+	debug("Effective memory @ 0x%08zx, size 0x%llx\n", gd->ram_base,
+	      gd->ram_top - gd->ram_base);
+
+	/* Discard/fix region which is outside the effective memory */
+	if (fix_tz_region(region, used_regions)) {
+		used_regions--;
+
+		if (used_regions) {
+			if (fix_tz_region(region, used_regions))
+				used_regions--;
+		}
+	}
+
+	/* Size needed for u-boot & pstore */
+#if IS_ENABLED(CONFIG_CMD_PSTORE)
+	/* pstore will be placed under ram top */
+	pstore_size = (CONFIG_CMD_PSTORE_MEM_SIZE + REGION_ALIGNMENT - 1) &
+		      ~(REGION_ALIGNMENT - 1);
+	/* u-boot will be placed under pstore */
+	uboot_size += pstore_size;
+#endif
+
+	uboot_size += max_t(uintptr_t, U_BOOT_MIN_SIZE, total_size);
+	uboot_size += U_BOOT_MIN_STACK_SIZE + REGION_ALIGNMENT - 1;
+	uboot_size &= ~(REGION_ALIGNMENT - 1);
+
+	uboot_ram_top = gd->ram_top & ~(REGION_ALIGNMENT - 1);
+
+	if (!used_regions ||
+	    (uboot_ram_top - region[0].addr - region[0].size >= uboot_size)) {
+		/* No reserved region present,
+		 * or gap between high region and ram top is large enough
+		 */
+		uboot_ram_top -= pstore_size;
+		return uboot_ram_top;
+	}
+
+	uboot_ram_top = region[0].addr & ~(REGION_ALIGNMENT - 1);
+
+	if (used_regions == 2 &&
+	    (uboot_ram_top - region[1].addr - region[1].size >= uboot_size)) {
+		/* Gap between high region and low region is large enough */
+		uboot_ram_top -= pstore_size;
+		return uboot_ram_top;
+	}
+
+	uboot_ram_top = region[used_regions - 1].addr & ~(REGION_ALIGNMENT - 1);
+
+	/* Under low region */
+	uboot_ram_top -= pstore_size;
+	return uboot_ram_top;
+}
+
+int arch_misc_init(void)
+{
+	struct arm_smccc_res res;
+
+	/*
+	 * Since board_get_usable_ram_top is be called before arch_misc_init,
+	 * there's no need to check the result
+	 */
+	arm_smccc_smc(MTK_SIP_GET_BL31_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+	lmb_reserve(res.a1, res.a2, LMB_NOMAP);
+
+	arm_smccc_smc(MTK_SIP_GET_BL32_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+	if (!res.a0 && res.a1 && res.a2)
+		lmb_reserve(res.a1, res.a2, LMB_NOMAP);
+
+#if IS_ENABLED(CONFIG_CMD_PSTORE)
+	char cmd[64];
+
+	/* Override default pstore address */
+	snprintf(cmd, sizeof(cmd), "pstore set 0x%llx 0x%x", gd->ram_top,
+		 CONFIG_CMD_PSTORE_MEM_SIZE);
+	run_command(cmd, 0);
+#endif
+
+	return 0;
+}
+
+/* For board-level setup */
+__weak int mtk_ft_system_setup(void *blob, struct bd_info *bd)
+{
+	return 0;
+}
+
+int ft_system_setup(void *blob, struct bd_info *bd)
+{
+	struct arm_smccc_res res;
+	struct fdt_memory mem;
+	int ret;
+
+	arm_smccc_smc(MTK_SIP_GET_BL31_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+
+	mem.start = res.a1;
+	mem.end = res.a1 + res.a2 - 1;
+
+	ret = fdtdec_add_reserved_memory(blob, "secmon", &mem, NULL, 0, NULL,
+					 FDTDEC_RESERVED_MEMORY_NO_MAP);
+	if (ret < 0) {
+		log_err("Failed to add reserved-memory for BL31: %s\n",
+			fdt_strerror(ret));
+		return ret;
+	}
+
+	arm_smccc_smc(MTK_SIP_GET_BL32_REGION, 0, 0, 0, 0, 0, 0, 0, &res);
+	if (!res.a0 && res.a1 && res.a2) {
+		mem.start = res.a1;
+		mem.end = res.a1 + res.a2 - 1;
+
+		ret = fdtdec_add_reserved_memory(blob, "trustzone", &mem, NULL,
+						 0, NULL,
+						 FDTDEC_RESERVED_MEMORY_NO_MAP);
+		if (ret < 0) {
+			log_err("Failed to add reserved-memory for BL32: %s\n",
+				fdt_strerror(ret));
+			return ret;
+		}
+	}
+
+	return mtk_ft_system_setup(blob, bd);
+}