Merge tag 'mips-pull-2022-07-13' of https://source.denx.de/u-boot/custodians/u-boot-mips

- MIPS: add drivers and board support for Mediatek MT7621 SoC
diff --git a/MAINTAINERS b/MAINTAINERS
index 2515ce7..b85629d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1032,15 +1032,23 @@
 S:	Maintained
 F:	arch/mips/mach-mtmips/
 F:	arch/mips/dts/mt7620.dtsi
+F:	arch/mips/dts/mt7621.dtsi
 F:	arch/mips/dts/mt7620-u-boot.dtsi
+F:	arch/mips/dts/mt7621-u-boot.dtsi
 F:	include/configs/mt7620.h
+F:	include/configs/mt7621.h
 F:	include/dt-bindings/clock/mt7620-clk.h
+F:	include/dt-bindings/clock/mt7621-clk.h
 F:	include/dt-bindings/clock/mt7628-clk.h
 F:	include/dt-bindings/reset/mt7620-reset.h
+F:	include/dt-bindings/reset/mt7621-reset.h
 F:	include/dt-bindings/reset/mt7628-reset.h
 F:	drivers/clk/mtmips/
 F:	drivers/pinctrl/mtmips/
 F:	drivers/gpio/mt7620_gpio.c
+F:	drivers/mtd/nand/raw/mt7621_nand.c
+F:	drivers/mtd/nand/raw/mt7621_nand.h
+F:	drivers/mtd/nand/raw/mt7621_nand_spl.c
 F:	drivers/net/mt7620-eth.c
 F:	drivers/phy/mt7620-usb-phy.c
 F:	drivers/reset/reset-mtmips.c
diff --git a/arch/mips/Makefile b/arch/mips/Makefile
index 32c436f..a007914 100644
--- a/arch/mips/Makefile
+++ b/arch/mips/Makefile
@@ -4,6 +4,11 @@
 
 ifeq ($(CONFIG_SPL_BUILD),y)
 head-$(CONFIG_ARCH_JZ47XX) := arch/mips/mach-jz47xx/start.o
+head-$(CONFIG_SOC_MT7621) := arch/mips/mach-mtmips/mt7621/spl/start.o
+endif
+
+ifeq ($(CONFIG_TPL_BUILD),y)
+head-$(CONFIG_SOC_MT7621) := arch/mips/mach-mtmips/mt7621/tpl/start.o
 endif
 
 libs-y += arch/mips/cpu/
diff --git a/arch/mips/cpu/u-boot-spl.lds b/arch/mips/cpu/u-boot-spl.lds
index 194398b..310a5c5 100644
--- a/arch/mips/cpu/u-boot-spl.lds
+++ b/arch/mips/cpu/u-boot-spl.lds
@@ -13,7 +13,9 @@
 
 	. = ALIGN(4);
 	.text : {
+		__text_start = .;
 		*(.text*)
+		__text_end = .;
 	} > .spl_mem
 
 	. = ALIGN(4);
@@ -36,6 +38,7 @@
 
 	. = ALIGN(4);
 	__image_copy_end = .;
+	__image_copy_len = __image_copy_end - __text_start;
 
 	_image_binary_end = .;
 
diff --git a/arch/mips/dts/Makefile b/arch/mips/dts/Makefile
index 95144b2..1b17911 100644
--- a/arch/mips/dts/Makefile
+++ b/arch/mips/dts/Makefile
@@ -16,6 +16,8 @@
 dtb-$(CONFIG_BOARD_HUAWEI_HG556A) += huawei,hg556a.dtb
 dtb-$(CONFIG_BOARD_MT7620_RFB) += mediatek,mt7620-rfb.dtb
 dtb-$(CONFIG_BOARD_MT7620_MT7530_RFB) += mediatek,mt7620-mt7530-rfb.dtb
+dtb-$(CONFIG_BOARD_MT7621_RFB) += mediatek,mt7621-rfb.dtb
+dtb-$(CONFIG_BOARD_MT7621_NAND_RFB) += mediatek,mt7621-nand-rfb.dtb
 dtb-$(CONFIG_BOARD_MT7628_RFB) += mediatek,mt7628-rfb.dtb
 dtb-$(CONFIG_BOARD_GARDENA_SMART_GATEWAY_MT7688) += gardena-smart-gateway-mt7688.dtb
 dtb-$(CONFIG_BOARD_LINKIT_SMART_7688) += linkit-smart-7688.dtb
diff --git a/arch/mips/dts/mediatek,mt7621-nand-rfb.dts b/arch/mips/dts/mediatek,mt7621-nand-rfb.dts
new file mode 100644
index 0000000..67ba298
--- /dev/null
+++ b/arch/mips/dts/mediatek,mt7621-nand-rfb.dts
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+/dts-v1/;
+
+#include "mt7621.dtsi"
+
+/ {
+	compatible = "mediatek,mt7621-nand-rfb", "mediatek,mt7621-soc";
+	model = "MediaTek MT7621 RFB (NAND)";
+
+	aliases {
+		serial0 = &uart0;
+	};
+
+	chosen {
+		stdout-path = &uart0;
+	};
+};
+
+&pinctrl {
+	state_default: pin_state {
+		nand {
+			groups = "spi", "sdxc";
+			function = "nand";
+		};
+
+		gpios {
+			groups = "i2c", "uart3", "pcie reset";
+			function = "gpio";
+		};
+
+		wdt {
+			groups = "wdt";
+			function = "wdt rst";
+		};
+
+		jtag {
+			groups = "jtag";
+			function = "jtag";
+		};
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&gpio {
+	status = "okay";
+};
+
+&eth {
+	status = "okay";
+};
+
+&ssusb {
+	status = "okay";
+};
+
+&u3phy {
+	status = "okay";
+};
diff --git a/arch/mips/dts/mediatek,mt7621-rfb.dts b/arch/mips/dts/mediatek,mt7621-rfb.dts
new file mode 100644
index 0000000..ff7eaf0
--- /dev/null
+++ b/arch/mips/dts/mediatek,mt7621-rfb.dts
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+/dts-v1/;
+
+#include "mt7621.dtsi"
+
+/ {
+	compatible = "mediatek,mt7621-rfb", "mediatek,mt7621-soc";
+	model = "MediaTek MT7621 RFB (SPI-NOR)";
+
+	aliases {
+		serial0 = &uart0;
+		spi0 = &spi;
+	};
+
+	chosen {
+		stdout-path = &uart0;
+	};
+};
+
+&pinctrl {
+	state_default: pin_state {
+		gpios {
+			groups = "i2c", "uart3", "pcie reset";
+			function = "gpio";
+		};
+
+		wdt {
+			groups = "wdt";
+			function = "wdt rst";
+		};
+
+		jtag {
+			groups = "jtag";
+			function = "jtag";
+		};
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&gpio {
+	status = "okay";
+};
+
+&spi {
+	status = "okay";
+	num-cs = <2>;
+
+	spi-flash@0 {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		compatible = "jedec,spi-nor";
+		spi-max-frequency = <25000000>;
+		reg = <0>;
+	};
+};
+
+&eth {
+	status = "okay";
+};
+
+&mmc {
+	cap-sd-highspeed;
+
+	status = "okay";
+};
+
+&ssusb {
+	status = "okay";
+};
+
+&u3phy {
+	status = "okay";
+};
diff --git a/arch/mips/dts/mt7621-u-boot.dtsi b/arch/mips/dts/mt7621-u-boot.dtsi
new file mode 100644
index 0000000..c5a8aa3
--- /dev/null
+++ b/arch/mips/dts/mt7621-u-boot.dtsi
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <linux/stringify.h>
+
+/ {
+	binman: binman {
+		multiple-images;
+	};
+};
+
+&sysc {
+	u-boot,dm-pre-reloc;
+};
+
+&reboot {
+	u-boot,dm-pre-reloc;
+};
+
+&clkctrl {
+	u-boot,dm-pre-reloc;
+};
+
+&rstctrl {
+	u-boot,dm-pre-reloc;
+};
+
+&pinctrl {
+	u-boot,dm-pre-reloc;
+};
+
+&uart0 {
+	u-boot,dm-pre-reloc;
+};
+
+&uart1 {
+	u-boot,dm-pre-reloc;
+};
+
+&uart2 {
+	u-boot,dm-pre-reloc;
+};
+
+&binman {
+	u-boot-spl-ddr {
+		align = <4>;
+		align-size = <4>;
+		filename = "u-boot-spl-ddr.bin";
+		pad-byte = <0xff>;
+
+		u-boot-spl {
+			align-end = <4>;
+			filename = "u-boot-spl.bin";
+		};
+
+		stage_bin {
+			filename = "mt7621_stage_sram.bin";
+			type = "blob-ext";
+		};
+	};
+
+	spl-img {
+		filename = "u-boot-spl-ddr.img";
+
+		mkimage {
+#ifdef CONFIG_MT7621_BOOT_FROM_NAND
+			args = "-T", "mtk_image", "-n", "mt7621=1",
+			       "-a", __stringify(CONFIG_SPL_TEXT_BASE),
+			       "-e", __stringify(CONFIG_SPL_TEXT_BASE);
+#else
+			args = "-A", "mips", "-T", "standalone", "-O", "u-boot",
+			       "-C", "none", "-n", "MT7621 U-Boot SPL",
+			       "-a", __stringify(CONFIG_SPL_TEXT_BASE),
+			       "-e", __stringify(CONFIG_SPL_TEXT_BASE);
+#endif
+
+			blob {
+				filename = "u-boot-spl-ddr.bin";
+			};
+		};
+	};
+
+	mt7621-uboot {
+		filename = "u-boot-mt7621.bin";
+		pad-byte = <0xff>;
+
+#ifndef CONFIG_MT7621_BOOT_FROM_NAND
+		u-boot-tpl {
+			align-end = <4>;
+			filename = "u-boot-tpl.bin";
+		};
+#endif
+
+		spl {
+#ifdef CONFIG_MT7621_BOOT_FROM_NAND
+			align-end = <0x1000>;
+#endif
+			filename = "u-boot-spl-ddr.img";
+			type = "blob";
+		};
+
+		u-boot {
+			filename = "u-boot-lzma.img";
+			type = "blob";
+		};
+	};
+};
diff --git a/arch/mips/dts/mt7621.dtsi b/arch/mips/dts/mt7621.dtsi
new file mode 100644
index 0000000..c32b609
--- /dev/null
+++ b/arch/mips/dts/mt7621.dtsi
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <dt-bindings/clock/mt7621-clk.h>
+#include <dt-bindings/reset/mt7621-reset.h>
+#include <dt-bindings/phy/phy.h>
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+	compatible = "mediatek,mt7621-soc";
+
+	cpus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		cpu@0 {
+			device_type = "cpu";
+			compatible = "mips,mips1004Kc";
+			reg = <0>;
+		};
+
+		cpu@1 {
+			device_type = "cpu";
+			compatible = "mips,mips1004Kc";
+			reg = <1>;
+		};
+	};
+
+	clk48m: clk48m {
+		compatible = "fixed-clock";
+
+		clock-frequency = <48000000>;
+
+		#clock-cells = <0>;
+	};
+
+	clk50m: clk50m {
+		compatible = "fixed-clock";
+
+		clock-frequency = <50000000>;
+
+		#clock-cells = <0>;
+	};
+
+	sysc: sysctrl@1e000000 {
+		compatible = "mediatek,mt7621-sysc", "syscon";
+		reg = <0x1e000000 0x100>;
+
+		clkctrl: clock-controller@1e000030 {
+			compatible = "mediatek,mt7621-clk";
+			mediatek,memc = <&memc>;
+
+			#clock-cells = <1>;
+		};
+	};
+
+	rstctrl: reset-controller@1e000034 {
+		compatible = "mediatek,mtmips-reset";
+		reg = <0x1e000034 0x4>;
+		#reset-cells = <1>;
+	};
+
+	reboot: resetctl-reboot {
+		compatible = "resetctl-reboot";
+
+		resets = <&rstctrl RST_SYS>;
+		reset-names = "sysreset";
+	};
+
+	memc: memctrl@1e005000 {
+		compatible = "mediatek,mt7621-memc", "syscon";
+		reg = <0x1e005000 0x1000>;
+	};
+
+	pinctrl: pinctrl@1e000060 {
+		compatible = "mediatek,mt7621-pinctrl";
+		reg = <0x1e000048 0x30>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&state_default>;
+
+		state_default: pin_state {
+		};
+
+		uart1_pins: uart1_pins {
+			groups = "uart1";
+			function = "uart";
+		};
+
+		uart2_pins: uart2_pins {
+			groups = "uart2";
+			function = "uart";
+		};
+
+		uart3_pins: uart3_pins {
+			groups = "uart3";
+			function = "uart";
+		};
+
+		sdxc_pins: sdxc_pins {
+			groups = "sdxc";
+			function = "sdxc";
+		};
+
+		spi_pins: spi_pins {
+			groups = "spi";
+			function = "spi";
+		};
+
+		eth_pins: eth_pins {
+			mdio_pins {
+				groups = "mdio";
+				function = "mdio";
+			};
+
+			rgmii1_pins {
+				groups = "rgmii1";
+				function = "rgmii";
+			};
+
+			esw_pins {
+				groups = "esw int";
+				function = "esw int";
+			};
+
+			mdio_pconf {
+				groups = "mdio";
+				drive-strength = <2>;
+			};
+		};
+	};
+
+	watchdog: watchdog@1e000100 {
+		compatible = "mediatek,mt7621-wdt";
+		reg = <0x1e000100 0x40>;
+
+		resets = <&rstctrl RST_TIMER>;
+		reset-names = "wdt";
+
+		status = "disabled";
+	};
+
+	gpio: gpio@1e000600 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		compatible = "mtk,mt7621-gpio";
+		reg = <0x1e000600 0x100>;
+
+		resets = <&rstctrl RST_PIO>;
+		reset-names = "pio";
+
+		gpio0: bank@0 {
+			reg = <0>;
+			compatible = "mtk,mt7621-gpio-bank";
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+
+		gpio1: bank@1 {
+			reg = <1>;
+			compatible = "mtk,mt7621-gpio-bank";
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+
+		gpio2: bank@2 {
+			reg = <2>;
+			compatible = "mtk,mt7621-gpio-bank";
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+	};
+
+	spi: spi@1e000b00 {
+		compatible = "ralink,mt7621-spi";
+		reg = <0x1e000b00 0x40>;
+
+		status = "disabled";
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&spi_pins>;
+
+		resets = <&rstctrl RST_SPI>;
+		reset-names = "spi";
+
+		clocks = <&clkctrl MT7621_CLK_SPI>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+	};
+
+	uart0: uart1@1e000c00 {
+		compatible = "mediatek,hsuart", "ns16550a";
+		reg = <0x1e000c00 0x100>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&uart1_pins>;
+
+		clocks = <&clkctrl MT7621_CLK_UART1>;
+
+		resets = <&rstctrl RST_UART1>;
+
+		reg-shift = <2>;
+	};
+
+	uart1: uart2@1e000d00 {
+		compatible = "mediatek,hsuart", "ns16550a";
+		reg = <0x1e000d00 0x100>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&uart2_pins>;
+
+		clocks = <&clkctrl MT7621_CLK_UART2>;
+
+		resets = <&rstctrl RST_UART2>;
+
+		reg-shift = <2>;
+
+		status = "disabled";
+	};
+
+	uart2: uart3@1e000e00 {
+		compatible = "mediatek,hsuart", "ns16550a";
+		reg = <0x1e000e00 0x100>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&uart3_pins>;
+
+		clocks = <&clkctrl MT7621_CLK_UART3>;
+
+		resets = <&rstctrl RST_UART3>;
+
+		reg-shift = <2>;
+
+		status = "disabled";
+	};
+
+	eth: eth@1e100000 {
+		compatible = "mediatek,mt7621-eth";
+		reg = <0x1e100000 0x20000>;
+		mediatek,ethsys = <&sysc>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&eth_pins>;
+
+		resets = <&rstctrl RST_FE>, <&rstctrl RST_GMAC>, <&rstctrl RST_MCM>;
+		reset-names = "fe", "gmac", "mcm";
+
+		clocks = <&clkctrl MT7621_CLK_GDMA>,
+			 <&clkctrl MT7621_CLK_ETH>;
+		clock-names = "gmac", "fe";
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		mediatek,gmac-id = <0>;
+		phy-mode = "rgmii";
+		mediatek,switch = "mt7530";
+		mediatek,mcm;
+
+		fixed-link {
+			speed = <1000>;
+			full-duplex;
+		};
+	};
+
+	mmc: mmc@1e130000 {
+		compatible = "mediatek,mt7621-mmc";
+		reg = <0x1e130000 0x4000>;
+
+		status = "disabled";
+
+		bus-width = <4>;
+		builtin-cd = <1>;
+		r_smpl = <1>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&sdxc_pins>;
+
+		clocks = <&clk50m>, <&clkctrl MT7621_CLK_SHXC>;
+		clock-names = "source", "hclk";
+
+		resets = <&rstctrl RST_SDXC>;
+	};
+
+	ssusb: usb@1e1c0000 {
+		compatible = "mediatek,mt7621-xhci", "mediatek,mtk-xhci";
+		reg = <0x1e1c0000 0x1000>, <0x1e1d0700 0x100>;
+		reg-names = "mac", "ippc";
+
+		clocks = <&clk48m>, <&clk48m>;
+		clock-names = "sys_ck", "ref_ck";
+
+		phys = <&u2port0 PHY_TYPE_USB2>,
+		       <&u3port0 PHY_TYPE_USB3>,
+		       <&u2port1 PHY_TYPE_USB2>;
+
+		status = "disabled";
+	};
+
+	u3phy: usb-phy@1e1d0000 {
+		compatible = "mediatek,mt7621-u3phy",
+			     "mediatek,generic-tphy-v1";
+		reg = <0x1e1d0000 0x700>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges;
+		status = "disabled";
+
+		u2port0: usb-phy@1e1d0800 {
+			reg = <0x1e1d0800 0x0100>;
+			#phy-cells = <1>;
+			clocks = <&clk48m>;
+			clock-names = "ref";
+		};
+
+		u3port0: usb-phy@1e1d0900 {
+			reg = <0x1e1d0900 0x0100>;
+			#phy-cells = <1>;
+		};
+
+		u2port1: usb-phy@1e1d1000 {
+			reg = <0x1e1d1000 0x0100>;
+			#phy-cells = <1>;
+			clocks = <&clk48m>;
+			clock-names = "ref";
+		};
+	};
+
+	i2c: i2c@1e000900 {
+		compatible = "i2c-gpio";
+
+		status = "disabled";
+
+		i2c-gpio,delay-us = <3>;
+
+		gpios = <&gpio0 3 1>, /* PIN3 as SDA */
+			<&gpio0 4 1>; /* PIN4 as CLK */
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+	};
+};
diff --git a/arch/mips/include/asm/cm.h b/arch/mips/include/asm/cm.h
index 99ddbcc..5cc8c09 100644
--- a/arch/mips/include/asm/cm.h
+++ b/arch/mips/include/asm/cm.h
@@ -8,9 +8,23 @@
 #define __MIPS_ASM_CM_H__
 
 /* Global Control Register (GCR) offsets */
+#define GCR_CONFIG			0x0000
 #define GCR_BASE			0x0008
 #define GCR_BASE_UPPER			0x000c
+#define GCR_CONTROL			0x0010
+#define GCR_ACCESS			0x0020
 #define GCR_REV				0x0030
+#define GCR_GIC_BASE			0x0080
+#define GCR_CPC_BASE			0x0088
+#define GCR_REG0_BASE			0x0090
+#define GCR_REG0_MASK			0x0098
+#define GCR_REG1_BASE			0x00a0
+#define GCR_REG1_MASK			0x00a8
+#define GCR_REG2_BASE			0x00b0
+#define GCR_REG2_MASK			0x00b8
+#define GCR_REG3_BASE			0x00c0
+#define GCR_REG3_MASK			0x00c8
+#define GCR_CPC_STATUS			0x00f0
 #define GCR_L2_CONFIG			0x0130
 #define GCR_L2_TAG_ADDR			0x0600
 #define GCR_L2_TAG_ADDR_UPPER		0x0604
@@ -19,10 +33,59 @@
 #define GCR_L2_DATA			0x0610
 #define GCR_L2_DATA_UPPER		0x0614
 #define GCR_Cx_COHERENCE		0x2008
+#define GCR_Cx_OTHER			0x2018
+#define GCR_Cx_ID			0x2028
+#define GCR_CO_COHERENCE		0x4008
+
+/* GCR_CONFIG fields */
+#define GCR_CONFIG_NUM_CLUSTERS_SHIFT	23
+#define GCR_CONFIG_NUM_CLUSTERS		(0x7f << 23)
+#define GCR_CONFIG_NUMIOCU_SHIFT	8
+#define GCR_CONFIG_NUMIOCU		(0xff << 8)
+#define GCR_CONFIG_PCORES_SHIFT		0
+#define GCR_CONFIG_PCORES		(0xff << 0)
+
+/* GCR_BASE fields */
+#define GCR_BASE_SHIFT			15
+#define CCA_DEFAULT_OVR_SHIFT		5
+#define CCA_DEFAULT_OVR_MASK		(0x7 << 5)
+#define CCA_DEFAULT_OVREN		(0x1 << 4)
+#define CM_DEFAULT_TARGET_SHIFT		0
+#define CM_DEFAULT_TARGET_MASK		(0x3 << 0)
+
+/* GCR_CONTROL fields */
+#define GCR_CONTROL_SYNCCTL		(0x1 << 16)
 
 /* GCR_REV CM versions */
 #define GCR_REV_CM3			0x0800
 
+/* GCR_GIC_BASE fields */
+#define GCR_GIC_BASE_ADDRMASK_SHIFT	7
+#define GCR_GIC_BASE_ADDRMASK		(0x1ffffff << 7)
+#define GCR_GIC_EN			(0x1 << 0)
+
+/* GCR_CPC_BASE fields */
+#define GCR_CPC_BASE_ADDRMASK_SHIFT	15
+#define GCR_CPC_BASE_ADDRMASK		(0x1ffff << 15)
+#define GCR_CPC_EN			(0x1 << 0)
+
+/* GCR_REGn_MASK fields */
+#define GCR_REGn_MASK_ADDRMASK_SHIFT	16
+#define GCR_REGn_MASK_ADDRMASK		(0xffff << 16)
+#define GCR_REGn_MASK_CCAOVR_SHIFT	5
+#define GCR_REGn_MASK_CCAOVR		(0x7 << 5)
+#define GCR_REGn_MASK_CCAOVREN		(1 << 4)
+#define GCR_REGn_MASK_DROPL2		(1 << 2)
+#define GCR_REGn_MASK_CMTGT_SHIFT	0
+#define GCR_REGn_MASK_CMTGT		(0x3 << 0)
+#define  GCR_REGn_MASK_CMTGT_DISABLED	0x0
+#define  GCR_REGn_MASK_CMTGT_MEM	0x1
+#define  GCR_REGn_MASK_CMTGT_IOCU0	0x2
+#define  GCR_REGn_MASK_CMTGT_IOCU1	0x3
+
+/* GCR_CPC_STATUS fields */
+#define GCR_CPC_EX			(0x1 << 0)
+
 /* GCR_L2_CONFIG fields */
 #define GCR_L2_CONFIG_ASSOC_SHIFT	0
 #define GCR_L2_CONFIG_ASSOC_BITS	8
@@ -36,6 +99,10 @@
 #define GCR_Cx_COHERENCE_DOM_EN		(0xff << 0)
 #define GCR_Cx_COHERENCE_EN		(0x1 << 0)
 
+/* GCR_Cx_OTHER fields */
+#define GCR_Cx_OTHER_CORENUM_SHIFT	16
+#define GCR_Cx_OTHER_CORENUM		(0xffff << 16)
+
 #ifndef __ASSEMBLY__
 
 #include <asm/io.h>
diff --git a/arch/mips/include/asm/mipsmtregs.h b/arch/mips/include/asm/mipsmtregs.h
new file mode 100644
index 0000000..ba82e2b
--- /dev/null
+++ b/arch/mips/include/asm/mipsmtregs.h
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * MT regs definitions, follows on from mipsregs.h
+ * Copyright (C) 2004 - 2005 MIPS Technologies, Inc.  All rights reserved.
+ * Elizabeth Clarke et. al.
+ *
+ */
+#ifndef _ASM_MIPSMTREGS_H
+#define _ASM_MIPSMTREGS_H
+
+#include <asm/mipsregs.h>
+
+/*
+ * Macros for use in assembly language code
+ */
+
+#define CP0_MVPCONTROL		$0, 1
+#define CP0_MVPCONF0		$0, 2
+#define CP0_MVPCONF1		$0, 3
+#define CP0_VPECONTROL		$1, 1
+#define CP0_VPECONF0		$1, 2
+#define CP0_VPECONF1		$1, 3
+#define CP0_YQMASK		$1, 4
+#define CP0_VPESCHEDULE		$1, 5
+#define CP0_VPESCHEFBK		$1, 6
+#define CP0_TCSTATUS		$2, 1
+#define CP0_TCBIND		$2, 2
+#define CP0_TCRESTART		$2, 3
+#define CP0_TCHALT		$2, 4
+#define CP0_TCCONTEXT		$2, 5
+#define CP0_TCSCHEDULE		$2, 6
+#define CP0_TCSCHEFBK		$2, 7
+#define CP0_SRSCONF0		$6, 1
+#define CP0_SRSCONF1		$6, 2
+#define CP0_SRSCONF2		$6, 3
+#define CP0_SRSCONF3		$6, 4
+#define CP0_SRSCONF4		$6, 5
+
+/* MVPControl fields */
+#define MVPCONTROL_EVP		(_ULCAST_(1))
+
+#define MVPCONTROL_VPC_SHIFT	1
+#define MVPCONTROL_VPC		(_ULCAST_(1) << MVPCONTROL_VPC_SHIFT)
+
+#define MVPCONTROL_STLB_SHIFT	2
+#define MVPCONTROL_STLB		(_ULCAST_(1) << MVPCONTROL_STLB_SHIFT)
+
+/* MVPConf0 fields */
+#define MVPCONF0_PTC_SHIFT	0
+#define MVPCONF0_PTC		(_ULCAST_(0xff))
+#define MVPCONF0_PVPE_SHIFT	10
+#define MVPCONF0_PVPE		(_ULCAST_(0xf) << MVPCONF0_PVPE_SHIFT)
+#define MVPCONF0_TCA_SHIFT	15
+#define MVPCONF0_TCA		(_ULCAST_(1) << MVPCONF0_TCA_SHIFT)
+#define MVPCONF0_PTLBE_SHIFT	16
+#define MVPCONF0_PTLBE		(_ULCAST_(0x3ff) << MVPCONF0_PTLBE_SHIFT)
+#define MVPCONF0_TLBS_SHIFT	29
+#define MVPCONF0_TLBS		(_ULCAST_(1) << MVPCONF0_TLBS_SHIFT)
+#define MVPCONF0_M_SHIFT	31
+#define MVPCONF0_M		(_ULCAST_(0x1) << MVPCONF0_M_SHIFT)
+
+/* config3 fields */
+#define CONFIG3_MT_SHIFT	2
+#define CONFIG3_MT		(_ULCAST_(1) << CONFIG3_MT_SHIFT)
+
+/* VPEControl fields (per VPE) */
+#define VPECONTROL_TARGTC	(_ULCAST_(0xff))
+
+#define VPECONTROL_TE_SHIFT	15
+#define VPECONTROL_TE		(_ULCAST_(1) << VPECONTROL_TE_SHIFT)
+#define VPECONTROL_EXCPT_SHIFT	16
+#define VPECONTROL_EXCPT	(_ULCAST_(0x7) << VPECONTROL_EXCPT_SHIFT)
+
+/* Thread Exception Codes for EXCPT field */
+#define THREX_TU		0
+#define THREX_TO		1
+#define THREX_IYQ		2
+#define THREX_GSX		3
+#define THREX_YSCH		4
+#define THREX_GSSCH		5
+
+#define VPECONTROL_GSI_SHIFT	20
+#define VPECONTROL_GSI		(_ULCAST_(1) << VPECONTROL_GSI_SHIFT)
+#define VPECONTROL_YSI_SHIFT	21
+#define VPECONTROL_YSI		(_ULCAST_(1) << VPECONTROL_YSI_SHIFT)
+
+/* VPEConf0 fields (per VPE) */
+#define VPECONF0_VPA_SHIFT	0
+#define VPECONF0_VPA		(_ULCAST_(1) << VPECONF0_VPA_SHIFT)
+#define VPECONF0_MVP_SHIFT	1
+#define VPECONF0_MVP		(_ULCAST_(1) << VPECONF0_MVP_SHIFT)
+#define VPECONF0_XTC_SHIFT	21
+#define VPECONF0_XTC		(_ULCAST_(0xff) << VPECONF0_XTC_SHIFT)
+
+/* VPEConf1 fields (per VPE) */
+#define VPECONF1_NCP1_SHIFT	0
+#define VPECONF1_NCP1		(_ULCAST_(0xff) << VPECONF1_NCP1_SHIFT)
+#define VPECONF1_NCP2_SHIFT	10
+#define VPECONF1_NCP2		(_ULCAST_(0xff) << VPECONF1_NCP2_SHIFT)
+#define VPECONF1_NCX_SHIFT	20
+#define VPECONF1_NCX		(_ULCAST_(0xff) << VPECONF1_NCX_SHIFT)
+
+/* TCStatus fields (per TC) */
+#define TCSTATUS_TASID		(_ULCAST_(0xff))
+#define TCSTATUS_IXMT_SHIFT	10
+#define TCSTATUS_IXMT		(_ULCAST_(1) << TCSTATUS_IXMT_SHIFT)
+#define TCSTATUS_TKSU_SHIFT	11
+#define TCSTATUS_TKSU		(_ULCAST_(3) << TCSTATUS_TKSU_SHIFT)
+#define TCSTATUS_A_SHIFT	13
+#define TCSTATUS_A		(_ULCAST_(1) << TCSTATUS_A_SHIFT)
+#define TCSTATUS_DA_SHIFT	15
+#define TCSTATUS_DA		(_ULCAST_(1) << TCSTATUS_DA_SHIFT)
+#define TCSTATUS_DT_SHIFT	20
+#define TCSTATUS_DT		(_ULCAST_(1) << TCSTATUS_DT_SHIFT)
+#define TCSTATUS_TDS_SHIFT	21
+#define TCSTATUS_TDS		(_ULCAST_(1) << TCSTATUS_TDS_SHIFT)
+#define TCSTATUS_TSST_SHIFT	22
+#define TCSTATUS_TSST		(_ULCAST_(1) << TCSTATUS_TSST_SHIFT)
+#define TCSTATUS_RNST_SHIFT	23
+#define TCSTATUS_RNST		(_ULCAST_(3) << TCSTATUS_RNST_SHIFT)
+/* Codes for RNST */
+#define TC_RUNNING		0
+#define TC_WAITING		1
+#define TC_YIELDING		2
+#define TC_GATED		3
+
+#define TCSTATUS_TMX_SHIFT	27
+#define TCSTATUS_TMX		(_ULCAST_(1) << TCSTATUS_TMX_SHIFT)
+/* TCStatus TCU bits can use same definitions/offsets as CU bits in Status */
+
+/* TCBind */
+#define TCBIND_CURVPE_SHIFT	0
+#define TCBIND_CURVPE		(_ULCAST_(0xf))
+
+#define TCBIND_CURTC_SHIFT	21
+
+#define TCBIND_CURTC		(_ULCAST_(0xff) << TCBIND_CURTC_SHIFT)
+
+/* TCHalt */
+#define TCHALT_H		(_ULCAST_(1))
+
+#endif
diff --git a/arch/mips/include/asm/system.h b/arch/mips/include/asm/system.h
index 79e6388..89a2ac2 100644
--- a/arch/mips/include/asm/system.h
+++ b/arch/mips/include/asm/system.h
@@ -282,4 +282,24 @@
 	: "=&r"(tmp));
 }
 
+#ifdef CONFIG_SYS_NONCACHED_MEMORY
+/* 1MB granularity */
+#define MMU_SECTION_SHIFT	20
+#define MMU_SECTION_SIZE	(1 << MMU_SECTION_SHIFT)
+
+/**
+ * noncached_init() - Initialize non-cached memory region
+ *
+ * Initialize non-cached memory area. This memory region will be typically
+ * located right below the malloc() area and be accessed from KSEG1.
+ *
+ * It is called during the generic post-relocation init sequence.
+ *
+ * Return: 0 if OK
+ */
+int noncached_init(void);
+
+phys_addr_t noncached_alloc(size_t size, size_t align);
+#endif /* CONFIG_SYS_NONCACHED_MEMORY */
+
 #endif /* _ASM_SYSTEM_H */
diff --git a/arch/mips/lib/cache.c b/arch/mips/lib/cache.c
index ec652f0..d23b38d 100644
--- a/arch/mips/lib/cache.c
+++ b/arch/mips/lib/cache.c
@@ -6,6 +6,7 @@
 
 #include <common.h>
 #include <cpu_func.h>
+#include <malloc.h>
 #include <asm/cache.h>
 #include <asm/cacheops.h>
 #include <asm/cm.h>
@@ -197,3 +198,45 @@
 	/* ensure the pipeline doesn't contain now-invalid instructions */
 	instruction_hazard_barrier();
 }
+
+#ifdef CONFIG_SYS_NONCACHED_MEMORY
+static unsigned long noncached_start;
+static unsigned long noncached_end;
+static unsigned long noncached_next;
+
+void noncached_set_region(void)
+{
+}
+
+int noncached_init(void)
+{
+	phys_addr_t start, end;
+	size_t size;
+
+	/* If this calculation changes, update board_f.c:reserve_noncached() */
+	end = ALIGN(mem_malloc_start, MMU_SECTION_SIZE) - MMU_SECTION_SIZE;
+	size = ALIGN(CONFIG_SYS_NONCACHED_MEMORY, MMU_SECTION_SIZE);
+	start = end - size;
+
+	debug("mapping memory %pa-%pa non-cached\n", &start, &end);
+
+	noncached_start = start;
+	noncached_end = end;
+	noncached_next = start;
+
+	return 0;
+}
+
+phys_addr_t noncached_alloc(size_t size, size_t align)
+{
+	phys_addr_t next = ALIGN(noncached_next, align);
+
+	if (next >= noncached_end || (noncached_end - next) < size)
+		return 0;
+
+	debug("allocated %zu bytes of uncached memory @%pa\n", size, &next);
+	noncached_next = next + size;
+
+	return CKSEG1ADDR(next);
+}
+#endif /* CONFIG_SYS_NONCACHED_MEMORY */
diff --git a/arch/mips/mach-mtmips/Kconfig b/arch/mips/mach-mtmips/Kconfig
index 151b004..d46be50 100644
--- a/arch/mips/mach-mtmips/Kconfig
+++ b/arch/mips/mach-mtmips/Kconfig
@@ -9,6 +9,7 @@
 
 config SYS_SOC
 	default "mt7620" if SOC_MT7620
+	default "mt7621" if SOC_MT7621
 	default "mt7628" if SOC_MT7628
 
 config SYS_DCACHE_SIZE
@@ -18,25 +19,45 @@
 	default 32
 
 config SYS_ICACHE_SIZE
-	default 65536
+	default 65536 if SOC_MT7620 || SOC_MT7628
+	default 32768 if SOC_MT7621
 
 config SYS_ICACHE_LINE_SIZE
 	default 32
 
+config SYS_SCACHE_LINE_SIZE
+	default 32 if SOC_MT7621
+
 config SYS_TEXT_BASE
-	default 0x9c000000 if !SPL
-	default 0x80200000 if SPL
+	default 0x9c000000 if !SPL && !SOC_MT7621
+	default 0x80200000 if SPL || SOC_MT7621
 
 config SPL_TEXT_BASE
-	default 0x9c000000
+	default 0x9c000000 if !SOC_MT7621
+	default 0x80100000 if SOC_MT7621
+
+config SPL_SIZE_LIMIT
+	default 0x30000 if SOC_MT7621
+
+config TPL_TEXT_BASE
+	default 0xbfc00000 if SOC_MT7621
+
+config TPL_MAX_SIZE
+	default 4096 if SOC_MT7621
 
 config SPL_PAYLOAD
 	default "u-boot-lzma.img" if SPL_LZMA
 
 config BUILD_TARGET
-	default "u-boot-with-spl.bin" if SPL
+	default "u-boot-with-spl.bin" if SPL && !SOC_MT7621
+	default "u-boot-lzma.img" if SOC_MT7621
 	default "u-boot.bin"
 
+config MAX_MEM_SIZE
+	int
+	default 256 if SOC_MT7620 || SOC_MT7628
+	default 512 if SOC_MT7621
+
 choice
 	prompt "MediaTek MIPS SoC select"
 
@@ -55,6 +76,23 @@
 	help
 	  This supports MediaTek MT7620.
 
+config SOC_MT7621
+	bool "MT7621"
+	select MIPS_CM
+	select MIPS_L2_CACHE
+	select SYS_CACHE_SHIFT_5
+	select SYS_MIPS_CACHE_INIT_RAM_LOAD
+	select PINCTRL_MT7621
+	select MTK_SERIAL
+	select REGMAP
+	select SYSCON
+	select BINMAN
+	select SUPPORT_TPL
+	select SPL_LOADER_SUPPORT if SPL
+	select SPL_INIT_STACK_WITHOUT_MALLOC_F if SPL
+	help
+	  This supports MediaTek MT7621.
+
 config SOC_MT7628
 	bool "MT7628"
 	select SYS_CACHE_SHIFT_5
@@ -80,6 +118,7 @@
 endchoice
 
 source "arch/mips/mach-mtmips/mt7620/Kconfig"
+source "arch/mips/mach-mtmips/mt7621/Kconfig"
 source "arch/mips/mach-mtmips/mt7628/Kconfig"
 
 endmenu
diff --git a/arch/mips/mach-mtmips/Makefile b/arch/mips/mach-mtmips/Makefile
index 4909b47..19f1e07 100644
--- a/arch/mips/mach-mtmips/Makefile
+++ b/arch/mips/mach-mtmips/Makefile
@@ -1,9 +1,13 @@
 # SPDX-License-Identifier: GPL-2.0+
 
 obj-y += cpu.o
+
+ifneq ($(CONFIG_SOC_MT7621),y)
 obj-y += ddr_init.o
 obj-y += ddr_cal.o
 obj-$(CONFIG_SPL_BUILD) += spl.o
+endif
 
 obj-$(CONFIG_SOC_MT7620) += mt7620/
+obj-$(CONFIG_SOC_MT7621) += mt7621/
 obj-$(CONFIG_SOC_MT7628) += mt7628/
diff --git a/arch/mips/mach-mtmips/cpu.c b/arch/mips/mach-mtmips/cpu.c
index a4b5cff..f1e9022 100644
--- a/arch/mips/mach-mtmips/cpu.c
+++ b/arch/mips/mach-mtmips/cpu.c
@@ -16,7 +16,7 @@
 
 int dram_init(void)
 {
-	gd->ram_size = get_ram_size((void *)KSEG1, SZ_256M);
+	gd->ram_size = get_ram_size((void *)KSEG1, CONFIG_MAX_MEM_SIZE << 20);
 
 	return 0;
 }
diff --git a/arch/mips/mach-mtmips/mt7621/Kconfig b/arch/mips/mach-mtmips/mt7621/Kconfig
new file mode 100644
index 0000000..008a28f
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/Kconfig
@@ -0,0 +1,115 @@
+
+if SOC_MT7621
+
+menu "CPU & DDR configuration"
+
+config MT7621_CPU_FREQ
+	int "CPU Frequency (MHz)"
+	range 400 1200
+	default 880
+
+choice
+	prompt "DRAM Frequency"
+	default MT7621_DRAM_FREQ_1200
+
+config MT7621_DRAM_FREQ_400
+	bool "400MHz"
+
+config MT7621_DRAM_FREQ_800
+	bool "800MHz"
+
+config MT7621_DRAM_FREQ_1066
+	bool "1066MHz"
+
+config MT7621_DRAM_FREQ_1200
+	bool "1200MHz"
+
+endchoice
+
+choice
+	prompt "DDR2 timing parameters"
+	default MT7621_DRAM_DDR2_1024M
+
+config MT7621_DRAM_DDR2_512M
+	bool "64MB"
+
+config MT7621_DRAM_DDR2_1024M
+	bool "128MB"
+
+config MT7621_DRAM_DDR2_512M_W9751G6KB_A02_1066MHZ
+	bool "W9751G6KB_A02 @ 1066MHz (64MB)"
+
+config MT7621_DRAM_DDR2_1024M_W971GG6KB25_800MHZ
+	bool "W971GG6KB25 @ 800MHz (128MB)"
+
+config MT7621_DRAM_DDR2_1024M_W971GG6KB18_1066MHZ
+	bool "W971GG6KB18 @ 1066MHz (128MB)"
+
+endchoice
+
+choice
+	prompt "DDR3 timing parameters"
+	default MT7621_DRAM_DDR3_2048M
+
+config MT7621_DRAM_DDR3_1024M
+	bool "128MB"
+
+config MT7621_DRAM_DDR3_1024M_KGD
+	bool "128MB KGD (MT7621DA)"
+
+config MT7621_DRAM_DDR3_2048M
+	bool "256MB"
+
+config MT7621_DRAM_DDR3_4096M
+	bool "512MB"
+
+endchoice
+
+endmenu
+
+config DEBUG_UART_BOARD_INIT
+	default y
+
+config MT7621_BOOT_FROM_NAND
+	bool "Boot from NAND"
+	help
+	  Select this if u-boot will boot from NAND flash. When booting from
+	  NAND, SPL will be loaded by bootrom directly and no TPL is needed.
+
+choice
+	prompt "Board select"
+
+config BOARD_MT7621_RFB
+	bool "MediaTek MT7621 RFB (SPI-NOR)"
+	help
+	  The reference design of MT7621A (WS3010) booting from SPI-NOR flash.
+	  The board can be configured with DDR2 (64MiB~256MiB) or DDR3
+	  (128MiB~512MiB). The board has 16 MiB SPI-NOR flash, built-in MT7530
+	  GbE switch, 1 UART, 1 USB 2.0 host, 1 USB 3.0 host, 1 SDXC, 3 PCIe
+	  sockets, 1 RGMII to external GbE PHY, 2 audio jacks (in/out),
+	  JTAG pins and expansion GPIO pins.
+
+config BOARD_MT7621_NAND_RFB
+	bool "MediaTek MT7621 RFB (NAND)"
+	help
+	  The reference design of MT7621A (WS3010) booting from NAND flash.
+	  The board can be configured with DDR2 (64MiB~256MiB) or DDR3
+	  (128MiB~512MiB). The board has 128 MiB parallel NAND flash, built-in
+	  MT7530 GbE switch, 1 UART, 1 USB 2.0 host, 1 USB 3.0 host, 3 PCIe
+	  sockets, 1 RGMII to external GbE PHY, 2 audio jacks (in/out),
+	  JTAG pins and expansion GPIO pins.
+
+endchoice
+
+config SYS_CONFIG_NAME
+	string "Board configuration name"
+	default "mt7621" if  BOARD_MT7621_RFB || BOARD_MT7621_NAND_RFB
+
+config SYS_BOARD
+	string "Board name"
+	default "mt7621" if  BOARD_MT7621_RFB || BOARD_MT7621_NAND_RFB
+
+config SYS_VENDOR
+	default "mediatek" if BOARD_MT7621_RFB || BOARD_MT7621_NAND_RFB
+
+endif
diff --git a/arch/mips/mach-mtmips/mt7621/Makefile b/arch/mips/mach-mtmips/mt7621/Makefile
new file mode 100644
index 0000000..bf1b0bb
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += init.o
+obj-y += serial.o
+
+ifeq ($(CONFIG_SPL_BUILD),y)
+ifeq ($(CONFIG_TPL_BUILD),y)
+obj-y += tpl/
+else
+obj-y += spl/
+endif
+
+obj-y += sram_init.o
+endif
diff --git a/arch/mips/mach-mtmips/mt7621/init.c b/arch/mips/mach-mtmips/mt7621/init.c
new file mode 100644
index 0000000..d21848a
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/init.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/uclass.h>
+#include <dt-bindings/clock/mt7621-clk.h>
+#include <asm/global_data.h>
+#include <linux/io.h>
+#include <linux/bitfield.h>
+#include "mt7621.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static const char *const boot_mode[(CHIP_MODE_M >> CHIP_MODE_S) + 1] = {
+	[1] = "NAND 2K+64",
+	[2] = "SPI-NOR 3-Byte Addr",
+	[3] = "SPI-NOR 4-Byte Addr",
+	[10] = "NAND 2K+128",
+	[11] = "NAND 4K+128",
+	[12] = "NAND 4K+256",
+};
+
+int print_cpuinfo(void)
+{
+	void __iomem *sysc = ioremap_nocache(SYSCTL_BASE, SYSCTL_SIZE);
+	u32 val, ver, eco, pkg, core, dram, chipmode;
+	u32 cpu_clk, ddr_clk, bus_clk, xtal_clk;
+	struct udevice *clkdev;
+	const char *bootdev;
+	struct clk clk;
+	int ret;
+
+	val = readl(sysc + SYSCTL_CHIP_REV_ID_REG);
+	ver = FIELD_GET(VER_ID_M, val);
+	eco = FIELD_GET(ECO_ID_M, val);
+	pkg = FIELD_GET(PKG_ID, val);
+	core = FIELD_GET(CPU_ID, val);
+
+	val = readl(sysc + SYSCTL_SYSCFG0_REG);
+	dram = FIELD_GET(DRAM_TYPE, val);
+	chipmode = FIELD_GET(CHIP_MODE_M, val);
+
+	bootdev = boot_mode[chipmode];
+	if (!bootdev)
+		bootdev = "Unsupported boot mode";
+
+	printf("CPU:   MediaTek MT7621%c ver %u, eco %u\n",
+	       core ? (pkg ? 'A' : 'N') : 'S', ver, eco);
+
+	printf("Boot:  DDR%u, %s\n", dram ? 2 : 3, bootdev);
+
+	ret = uclass_get_device_by_driver(UCLASS_CLK, DM_DRIVER_GET(mt7621_clk),
+					  &clkdev);
+	if (ret)
+		return ret;
+
+	clk.dev = clkdev;
+
+	clk.id = MT7621_CLK_CPU;
+	cpu_clk = clk_get_rate(&clk);
+
+	clk.id = MT7621_CLK_BUS;
+	bus_clk = clk_get_rate(&clk);
+
+	clk.id = MT7621_CLK_DDR;
+	ddr_clk = clk_get_rate(&clk);
+
+	clk.id = MT7621_CLK_XTAL;
+	xtal_clk = clk_get_rate(&clk);
+
+	/* Set final timer frequency */
+	if (cpu_clk)
+		gd->arch.timer_freq = cpu_clk / 2;
+
+	printf("Clock: CPU: %uMHz, DDR: %uMT/s, Bus: %uMHz, XTAL: %uMHz\n",
+	       cpu_clk / 1000000, ddr_clk / 500000, bus_clk / 1000000,
+	       xtal_clk / 1000000);
+
+	return 0;
+}
+
+unsigned long get_xtal_mhz(void)
+{
+	void __iomem *sysc = ioremap_nocache(SYSCTL_BASE, SYSCTL_SIZE);
+	u32 bs, xtal_sel;
+
+	bs = readl(sysc + SYSCTL_SYSCFG0_REG);
+	xtal_sel = FIELD_GET(XTAL_MODE_SEL_M, bs);
+
+	if (xtal_sel <= 2)
+		return 20;
+	else if (xtal_sel <= 5)
+		return 40;
+	else
+		return 25;
+}
+
+static void xhci_config_40mhz(void __iomem *usbh)
+{
+	writel(FIELD_PREP(SSUSB_MAC3_SYS_CK_GATE_MASK_TIME_M, 0x20) |
+	       FIELD_PREP(SSUSB_MAC2_SYS_CK_GATE_MASK_TIME_M, 0x20) |
+	       FIELD_PREP(SSUSB_MAC3_SYS_CK_GATE_MODE_M, 2) |
+	       FIELD_PREP(SSUSB_MAC2_SYS_CK_GATE_MODE_M, 2) | 0x10,
+	       usbh + SSUSB_MAC_CK_CTRL_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_PREDIV_PE1D_M, 2) |
+	       FIELD_PREP(SSUSB_PLL_PREDIV_U3_M, 1) |
+	       FIELD_PREP(SSUSB_PLL_FBKDI_M, 4),
+	       usbh + DA_SSUSB_U3PHYA_10_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_FBKDIV_PE2H_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_PE1D_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_PE1H_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_U3_M, 0x1e),
+	       usbh + DA_SSUSB_PLL_FBKDIV_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_PCW_NCPO_U3_M, 0x1e400000),
+	       usbh + DA_SSUSB_PLL_PCW_NCPO_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_DELTA1_PE1H_M, 0x25) |
+	       FIELD_PREP(SSUSB_PLL_SSC_DELTA1_U3_M, 0x73),
+	       usbh + DA_SSUSB_PLL_SSC_DELTA1_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_DELTA_U3_M, 0x71) |
+	       FIELD_PREP(SSUSB_PLL_SSC_DELTA1_PE2D_M, 0x4a),
+	       usbh + DA_SSUSB_U3PHYA_21_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_PRD_M, 0x140),
+	       usbh + SSUSB_U3PHYA_9_REG);
+
+	writel(FIELD_PREP(SSUSB_SYSPLL_PCW_NCPO_M, 0x11c00000),
+	       usbh + SSUSB_U3PHYA_3_REG);
+
+	writel(FIELD_PREP(SSUSB_PCIE_CLKDRV_AMP_M, 4) |
+	       FIELD_PREP(SSUSB_SYSPLL_FBSEL_M, 1) |
+	       FIELD_PREP(SSUSB_SYSPLL_PREDIV_M, 1),
+	       usbh + SSUSB_U3PHYA_1_REG);
+
+	writel(FIELD_PREP(SSUSB_SYSPLL_FBDIV_M, 0x12) |
+	       SSUSB_SYSPLL_VCO_DIV_SEL | SSUSB_SYSPLL_FPEN |
+	       SSUSB_SYSPLL_MONCK_EN | SSUSB_SYSPLL_VOD_EN,
+	       usbh + SSUSB_U3PHYA_2_REG);
+
+	writel(SSUSB_EQ_CURSEL | FIELD_PREP(SSUSB_RX_DAC_MUX_M, 8) |
+	       FIELD_PREP(SSUSB_PCIE_SIGDET_VTH_M, 1) |
+	       FIELD_PREP(SSUSB_PCIE_SIGDET_LPF_M, 1),
+	       usbh + SSUSB_U3PHYA_11_REG);
+
+	writel(FIELD_PREP(SSUSB_RING_OSC_CNTEND_M, 0x1ff) |
+	       FIELD_PREP(SSUSB_XTAL_OSC_CNTEND_M, 0x7f) |
+	       SSUSB_RING_BYPASS_DET,
+	       usbh + SSUSB_B2_ROSC_0_REG);
+
+	writel(FIELD_PREP(SSUSB_RING_OSC_FRC_RECAL_M, 3) |
+	       SSUSB_RING_OSC_FRC_SEL,
+	       usbh + SSUSB_B2_ROSC_1_REG);
+}
+
+static void xhci_config_25mhz(void __iomem *usbh)
+{
+	writel(FIELD_PREP(SSUSB_MAC3_SYS_CK_GATE_MASK_TIME_M, 0x20) |
+	       FIELD_PREP(SSUSB_MAC2_SYS_CK_GATE_MASK_TIME_M, 0x20) |
+	       FIELD_PREP(SSUSB_MAC3_SYS_CK_GATE_MODE_M, 2) |
+	       FIELD_PREP(SSUSB_MAC2_SYS_CK_GATE_MODE_M, 2) | 0x10,
+	       usbh + SSUSB_MAC_CK_CTRL_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_PREDIV_PE1D_M, 2) |
+	       FIELD_PREP(SSUSB_PLL_FBKDI_M, 4),
+	       usbh + DA_SSUSB_U3PHYA_10_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_FBKDIV_PE2H_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_PE1D_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_PE1H_M, 0x18) |
+	       FIELD_PREP(SSUSB_PLL_FBKDIV_U3_M, 0x19),
+	       usbh + DA_SSUSB_PLL_FBKDIV_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_PCW_NCPO_U3_M, 0x18000000),
+	       usbh + DA_SSUSB_PLL_PCW_NCPO_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_DELTA1_PE1H_M, 0x25) |
+	       FIELD_PREP(SSUSB_PLL_SSC_DELTA1_U3_M, 0x4a),
+	       usbh + DA_SSUSB_PLL_SSC_DELTA1_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_DELTA_U3_M, 0x48) |
+	       FIELD_PREP(SSUSB_PLL_SSC_DELTA1_PE2D_M, 0x4a),
+	       usbh + DA_SSUSB_U3PHYA_21_REG);
+
+	writel(FIELD_PREP(SSUSB_PLL_SSC_PRD_M, 0x190),
+	       usbh + SSUSB_U3PHYA_9_REG);
+
+	writel(FIELD_PREP(SSUSB_SYSPLL_PCW_NCPO_M, 0xe000000),
+	       usbh + SSUSB_U3PHYA_3_REG);
+
+	writel(FIELD_PREP(SSUSB_PCIE_CLKDRV_AMP_M, 4) |
+	       FIELD_PREP(SSUSB_SYSPLL_FBSEL_M, 1),
+	       usbh + SSUSB_U3PHYA_1_REG);
+
+	writel(FIELD_PREP(SSUSB_SYSPLL_FBDIV_M, 0xf) |
+	       SSUSB_SYSPLL_VCO_DIV_SEL | SSUSB_SYSPLL_FPEN |
+	       SSUSB_SYSPLL_MONCK_EN | SSUSB_SYSPLL_VOD_EN,
+	       usbh + SSUSB_U3PHYA_2_REG);
+
+	writel(SSUSB_EQ_CURSEL | FIELD_PREP(SSUSB_RX_DAC_MUX_M, 8) |
+	       FIELD_PREP(SSUSB_PCIE_SIGDET_VTH_M, 1) |
+	       FIELD_PREP(SSUSB_PCIE_SIGDET_LPF_M, 1),
+	       usbh + SSUSB_U3PHYA_11_REG);
+
+	writel(FIELD_PREP(SSUSB_RING_OSC_CNTEND_M, 0x1ff) |
+	       FIELD_PREP(SSUSB_XTAL_OSC_CNTEND_M, 0x7f) |
+	       SSUSB_RING_BYPASS_DET,
+	       usbh + SSUSB_B2_ROSC_0_REG);
+
+	writel(FIELD_PREP(SSUSB_RING_OSC_FRC_RECAL_M, 3) |
+	       SSUSB_RING_OSC_FRC_SEL,
+	       usbh + SSUSB_B2_ROSC_1_REG);
+}
+
+void lowlevel_init(void)
+{
+	void __iomem *usbh = ioremap_nocache(SSUSB_BASE, SSUSB_SIZE);
+	u32 xtal = get_xtal_mhz();
+
+	/* Setup USB xHCI */
+	if (xtal == 40)
+		xhci_config_40mhz(usbh);
+	else if (xtal == 25)
+		xhci_config_25mhz(usbh);
+}
+
+ulong notrace get_tbclk(void)
+{
+	return gd->arch.timer_freq;
+}
+
+void _machine_restart(void)
+{
+	void __iomem *sysc = ioremap_nocache(SYSCTL_BASE, SYSCTL_SIZE);
+
+	while (1)
+		writel(SYS_RST, sysc + SYSCTL_RSTCTL_REG);
+}
diff --git a/arch/mips/mach-mtmips/mt7621/mt7621.h b/arch/mips/mach-mtmips/mt7621/mt7621.h
new file mode 100644
index 0000000..916cc99
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/mt7621.h
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT7621_H_
+#define _MT7621_H_
+
+#define SYSCTL_BASE				0x1e000000
+#define SYSCTL_SIZE				0x100
+#define TIMER_BASE				0x1e000100
+#define TIMER_SIZE				0x100
+#define RBUS_BASE				0x1e000400
+#define RBUS_SIZE				0x100
+#define GPIO_BASE				0x1e000600
+#define GPIO_SIZE				0x100
+#define DMA_CFG_ARB_BASE			0x1e000800
+#define DMA_CFG_ARB_SIZE			0x100
+#define SPI_BASE				0x1e000b00
+#define SPI_SIZE				0x100
+#define UART1_BASE				0x1e000c00
+#define UART1_SIZE				0x100
+#define UART2_BASE				0x1e000d00
+#define UART2_SIZE				0x100
+#define UART3_BASE				0x1e000e00
+#define UART3_SIZE				0x100
+#define NFI_BASE				0x1e003000
+#define NFI_SIZE				0x800
+#define NFI_ECC_BASE				0x1e003800
+#define NFI_ECC_SIZE				0x800
+#define DRAMC_BASE				0x1e005000
+#define DRAMC_SIZE				0x1000
+#define FE_BASE					0x1e100000
+#define FE_SIZE					0xe000
+#define GMAC_BASE				0x1e110000
+#define GMAC_SIZE				0x8000
+#define SSUSB_BASE				0x1e1c0000
+#define SSUSB_SIZE				0x40000
+
+ /* GIC Base Address */
+#define MIPS_GIC_BASE				0x1fbc0000
+
+ /* CPC Base Address */
+#define MIPS_CPC_BASE				0x1fbf0000
+
+ /* Flash Memory-mapped Base Address */
+#define FLASH_MMAP_BASE				0x1fc00000
+#define TPL_INFO_OFFSET				0x40
+#define TPL_INFO_MAGIC				0x31323637 /* Magic "7621" */
+
+/* SRAM */
+#define FE_SRAM_BASE1				0x8000
+#define FE_SRAM_BASE2				0xa000
+
+/* SYSCTL_BASE */
+#define SYSCTL_CHIP_REV_ID_REG			0x0c
+#define CPU_ID					0x20000
+#define PKG_ID					0x10000
+#define VER_ID_S				8
+#define VER_ID_M				0xf00
+#define ECO_ID_S				0
+#define ECO_ID_M				0x0f
+
+#define SYSCTL_SYSCFG0_REG			0x10
+#define XTAL_MODE_SEL_S				6
+#define XTAL_MODE_SEL_M				0x1c0
+#define DRAM_TYPE				0x10
+#define CHIP_MODE_S				0
+#define CHIP_MODE_M				0x0f
+
+#define BOOT_SRAM_BASE_REG			0x20
+
+#define SYSCTL_CLKCFG0_REG			0x2c
+#define CPU_CLK_SEL_S				30
+#define CPU_CLK_SEL_M				0xc0000000
+#define MPLL_CFG_SEL_S				23
+#define MPLL_CFG_SEL_M				0x800000
+
+#define SYSCTL_RSTCTL_REG			0x34
+#define MCM_RST					0x04
+#define SYS_RST					0x01
+
+#define SYSCTL_CUR_CLK_STS_REG			0x44
+#define CUR_CPU_FDIV_S				8
+#define CUR_CPU_FDIV_M				0x1f00
+#define CUR_CPU_FFRAC_S				0
+#define CUR_CPU_FFRAC_M				0x1f
+
+#define SYSCTL_GPIOMODE_REG			0x60
+#define UART2_MODE_S				5
+#define UART2_MODE_M				0x60
+#define UART3_MODE_S				3
+#define UART3_MODE_M				0x18
+#define UART1_MODE				0x02
+
+/* RBUS_BASE */
+#define RBUS_DYN_CFG0_REG			0x0010
+#define CPU_FDIV_S				8
+#define CPU_FDIV_M				0x1f00
+#define CPU_FFRAC_S				0
+#define CPU_FFRAC_M				0x1f
+
+/* DMA_CFG_ARB_BASE */
+#define DMA_ROUTE_REG				0x000c
+
+/* SPI_BASE */
+#define SPI_SPACE_REG				0x003c
+#define FS_SLAVE_SEL_S				12
+#define FS_SLAVE_SEL_M				0x70000
+#define FS_CLK_SEL_S				0
+#define FS_CLK_SEL_M				0xfff
+
+/* FE_BASE */
+#define FE_RST_GLO_REG				0x0004
+#define FE_PSE_RAM				0x04
+#define FE_PSE_MEM_EN				0x02
+#define FE_PSE_RESET				0x01
+
+/* SSUSB_BASE */
+#define SSUSB_MAC_CK_CTRL_REG			0x10784
+#define SSUSB_MAC3_SYS_CK_GATE_MASK_TIME_S	16
+#define SSUSB_MAC3_SYS_CK_GATE_MASK_TIME_M	0xff0000
+#define SSUSB_MAC2_SYS_CK_GATE_MASK_TIME_S	8
+#define SSUSB_MAC2_SYS_CK_GATE_MASK_TIME_M	0xff00
+#define SSUSB_MAC3_SYS_CK_GATE_MODE_S		2
+#define SSUSB_MAC3_SYS_CK_GATE_MODE_M		0x0c
+#define SSUSB_MAC2_SYS_CK_GATE_MODE_S		0
+#define SSUSB_MAC2_SYS_CK_GATE_MODE_M		0x03
+
+#define SSUSB_B2_ROSC_0_REG			0x10a40
+#define SSUSB_RING_OSC_CNTEND_S			23
+#define SSUSB_RING_OSC_CNTEND_M			0xff800000
+#define SSUSB_XTAL_OSC_CNTEND_S			16
+#define SSUSB_XTAL_OSC_CNTEND_M			0x7f0000
+#define SSUSB_RING_BYPASS_DET			0x01
+
+#define SSUSB_B2_ROSC_1_REG			0x10a44
+#define SSUSB_RING_OSC_FRC_RECAL_S		17
+#define SSUSB_RING_OSC_FRC_RECAL_M		0x60000
+#define SSUSB_RING_OSC_FRC_SEL			0x01
+
+#define SSUSB_U3PHYA_1_REG			0x10b04
+#define SSUSB_PCIE_CLKDRV_AMP_S			27
+#define SSUSB_PCIE_CLKDRV_AMP_M			0x38000000
+#define SSUSB_SYSPLL_FBSEL_S			2
+#define SSUSB_SYSPLL_FBSEL_M			0x0c
+#define SSUSB_SYSPLL_PREDIV_S			0
+#define SSUSB_SYSPLL_PREDIV_M			0x03
+
+#define SSUSB_U3PHYA_2_REG			0x10b08
+#define SSUSB_SYSPLL_FBDIV_S			24
+#define SSUSB_SYSPLL_FBDIV_M			0x7f000000
+#define SSUSB_SYSPLL_VCO_DIV_SEL		0x200000
+#define SSUSB_SYSPLL_FPEN			0x2000
+#define SSUSB_SYSPLL_MONCK_EN			0x1000
+#define SSUSB_SYSPLL_VOD_EN			0x200
+
+#define SSUSB_U3PHYA_3_REG			0x10b10
+#define SSUSB_SYSPLL_PCW_NCPO_S			1
+#define SSUSB_SYSPLL_PCW_NCPO_M			0xfffffffe
+
+#define SSUSB_U3PHYA_9_REG			0x10b24
+#define SSUSB_PLL_SSC_PRD_S			0
+#define SSUSB_PLL_SSC_PRD_M			0xffff
+
+#define SSUSB_U3PHYA_11_REG			0x10b2c
+#define SSUSB_EQ_CURSEL				0x1000000
+#define SSUSB_RX_DAC_MUX_S			19
+#define SSUSB_RX_DAC_MUX_M			0xf80000
+#define SSUSB_PCIE_SIGDET_VTH_S			5
+#define SSUSB_PCIE_SIGDET_VTH_M			0x60
+#define SSUSB_PCIE_SIGDET_LPF_S			3
+#define SSUSB_PCIE_SIGDET_LPF_M			0x18
+
+#define DA_SSUSB_PLL_FBKDIV_REG			0x10c1c
+#define SSUSB_PLL_FBKDIV_PE2H_S			24
+#define SSUSB_PLL_FBKDIV_PE2H_M			0x7f000000
+#define SSUSB_PLL_FBKDIV_PE1D_S			16
+#define SSUSB_PLL_FBKDIV_PE1D_M			0x7f0000
+#define SSUSB_PLL_FBKDIV_PE1H_S			8
+#define SSUSB_PLL_FBKDIV_PE1H_M			0x7f00
+#define SSUSB_PLL_FBKDIV_U3_S			0
+#define SSUSB_PLL_FBKDIV_U3_M			0x7f
+
+#define DA_SSUSB_U3PHYA_10_REG			0x10c20
+#define SSUSB_PLL_PREDIV_PE1D_S			18
+#define SSUSB_PLL_PREDIV_PE1D_M			0xc0000
+#define SSUSB_PLL_PREDIV_U3_S			8
+#define SSUSB_PLL_PREDIV_U3_M			0x300
+#define SSUSB_PLL_FBKDI_S			0
+#define SSUSB_PLL_FBKDI_M			0x07
+
+#define DA_SSUSB_PLL_PCW_NCPO_REG		0x10c24
+#define SSUSB_PLL_PCW_NCPO_U3_S			0
+#define SSUSB_PLL_PCW_NCPO_U3_M			0x7fffffff
+
+#define DA_SSUSB_PLL_SSC_DELTA1_REG		0x10c38
+#define SSUSB_PLL_SSC_DELTA1_PE1H_S		16
+#define SSUSB_PLL_SSC_DELTA1_PE1H_M		0xffff0000
+#define SSUSB_PLL_SSC_DELTA1_U3_S		0
+#define SSUSB_PLL_SSC_DELTA1_U3_M		0xffff
+
+#define DA_SSUSB_U3PHYA_21_REG			0x10c40
+#define SSUSB_PLL_SSC_DELTA_U3_S		16
+#define SSUSB_PLL_SSC_DELTA_U3_M		0xffff0000
+#define SSUSB_PLL_SSC_DELTA1_PE2D_S		0
+#define SSUSB_PLL_SSC_DELTA1_PE2D_M		0xffff
+
+/* MT7621 specific CM values */
+
+/* GCR_REGx_BASE */
+#define GCR_REG0_BASE_VALUE			0x1c000000
+#define GCR_REG1_BASE_VALUE			0x60000000
+#define GCR_REG2_BASE_VALUE			0x1c000000
+#define GCR_REG3_BASE_VALUE			0x1c000000
+
+/* GCR_REGx_MASK */
+#define GCR_REG0_MASK_VALUE			0x0000fc00 /* 64M Bus */
+#define GCR_REG1_MASK_VALUE			0x0000f000 /* 256M PCI Mem */
+#define GCR_REG2_MASK_VALUE			0x0000fc00 /* unused */
+#define GCR_REG3_MASK_VALUE			0x0000fc00 /* unused */
+
+#ifndef __ASSEMBLY__
+unsigned long get_xtal_mhz(void);
+#endif
+
+#endif /* _MT7621_H_ */
diff --git a/arch/mips/mach-mtmips/mt7621/serial.c b/arch/mips/mach-mtmips/mt7621/serial.c
new file mode 100644
index 0000000..0ccc71d
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/serial.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/io.h>
+#include <asm/addrspace.h>
+#include "mt7621.h"
+
+void board_debug_uart_init(void)
+{
+	void __iomem *base = ioremap_nocache(SYSCTL_BASE, SYSCTL_SIZE);
+
+#if CONFIG_DEBUG_UART_BASE == 0xbe000c00 /* KSEG1ADDR(UART1_BASE) */
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART1_MODE);
+#elif CONFIG_DEBUG_UART_BASE == 0xbe000d00 /* KSEG1ADDR(UART2_BASE) */
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART2_MODE_M);
+#elif CONFIG_DEBUG_UART_BASE == 0xbe000e00 /* KSEG1ADDR(UART3_BASE) */
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART3_MODE_M);
+#endif
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/Makefile b/arch/mips/mach-mtmips/mt7621/spl/Makefile
new file mode 100644
index 0000000..ebe54e7
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/Makefile
@@ -0,0 +1,9 @@
+
+extra-y += start.o
+
+obj-y += spl.o
+obj-y += cps.o
+obj-y += dram.o
+obj-y += serial.o
+obj-y += launch.o
+obj-y += launch_ll.o
diff --git a/arch/mips/mach-mtmips/mt7621/spl/cps.c b/arch/mips/mach-mtmips/mt7621/spl/cps.c
new file mode 100644
index 0000000..779e646
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/cps.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/io.h>
+#include <asm/addrspace.h>
+#include <asm/mipsregs.h>
+#include <asm/cm.h>
+#include <linux/bitfield.h>
+#include "../mt7621.h"
+
+/* GIC Shared Register Bases */
+#define GIC_SH_POL_BASE		0x100
+#define GIC_SH_TRIG_BASE	0x180
+#define GIC_SH_RMASK_BASE	0x300
+#define GIC_SH_SMASK_BASE	0x380
+#define GIC_SH_MASK_BASE	0x400
+#define GIC_SH_PEND_BASE	0x480
+#define GIC_SH_MAP_PIN_BASE	0x500
+#define GIC_SH_MAP_VPE_BASE	0x2000
+
+/* GIC Registers */
+#define GIC_SH_POL31_0		(GIC_SH_POL_BASE + 0x00)
+#define GIC_SH_POL63_32		(GIC_SH_POL_BASE + 0x04)
+
+#define GIC_SH_TRIG31_0		(GIC_SH_TRIG_BASE + 0x00)
+#define GIC_SH_TRIG63_32	(GIC_SH_TRIG_BASE + 0x04)
+
+#define GIC_SH_RMASK31_0	(GIC_SH_RMASK_BASE + 0x00)
+#define GIC_SH_RMASK63_32	(GIC_SH_RMASK_BASE + 0x04)
+
+#define GIC_SH_SMASK31_0	(GIC_SH_SMASK_BASE + 0x00)
+#define GIC_SH_SMASK63_32	(GIC_SH_SMASK_BASE + 0x04)
+
+#define GIC_SH_MAP_PIN(n)	(GIC_SH_MAP_PIN_BASE + (n) * 4)
+
+#define GIC_SH_MAP_VPE(n, v)	(GIC_SH_MAP_VPE_BASE + (n) * 0x20 + ((v) / 32) * 4)
+#define GIC_SH_MAP_VPE31_0(n)	GIC_SH_MAP_VPE(n, 0)
+
+/* GIC_SH_MAP_PIN fields */
+#define GIC_MAP_TO_PIN		BIT(31)
+#define GIC_MAP_TO_NMI		BIT(30)
+#define GIC_MAP			GENMASK(5, 0)
+#define GIC_MAP_SHIFT		0
+
+static void cm_init(void __iomem *cm_base)
+{
+	u32 gcrcfg, num_cores;
+
+	gcrcfg = readl(cm_base + GCR_CONFIG);
+	num_cores = FIELD_GET(GCR_CONFIG_PCORES, gcrcfg) + 1;
+
+	writel((1 << num_cores) - 1, cm_base + GCR_ACCESS);
+
+	writel(GCR_REG0_BASE_VALUE, cm_base + GCR_REG0_BASE);
+	writel(GCR_REG1_BASE_VALUE, cm_base + GCR_REG1_BASE);
+	writel(GCR_REG2_BASE_VALUE, cm_base + GCR_REG2_BASE);
+	writel(GCR_REG3_BASE_VALUE, cm_base + GCR_REG3_BASE);
+
+	clrsetbits_32(cm_base + GCR_REG0_MASK,
+		      GCR_REGn_MASK_ADDRMASK | GCR_REGn_MASK_CMTGT,
+		      FIELD_PREP(GCR_REGn_MASK_ADDRMASK, GCR_REG0_MASK_VALUE) |
+		      GCR_REGn_MASK_CMTGT_IOCU0);
+
+	clrsetbits_32(cm_base + GCR_REG1_MASK,
+		      GCR_REGn_MASK_ADDRMASK | GCR_REGn_MASK_CMTGT,
+		      FIELD_PREP(GCR_REGn_MASK_ADDRMASK, GCR_REG1_MASK_VALUE) |
+		      GCR_REGn_MASK_CMTGT_IOCU0);
+
+	clrsetbits_32(cm_base + GCR_REG2_MASK,
+		      GCR_REGn_MASK_ADDRMASK | GCR_REGn_MASK_CMTGT,
+		      FIELD_PREP(GCR_REGn_MASK_ADDRMASK, GCR_REG2_MASK_VALUE) |
+		      GCR_REGn_MASK_CMTGT_IOCU0);
+
+	clrsetbits_32(cm_base + GCR_REG3_MASK,
+		      GCR_REGn_MASK_ADDRMASK | GCR_REGn_MASK_CMTGT,
+		      FIELD_PREP(GCR_REGn_MASK_ADDRMASK, GCR_REG3_MASK_VALUE) |
+		      GCR_REGn_MASK_CMTGT_IOCU0);
+
+	clrbits_32(cm_base + GCR_BASE, CM_DEFAULT_TARGET_MASK);
+	setbits_32(cm_base + GCR_CONTROL, GCR_CONTROL_SYNCCTL);
+}
+
+static void gic_init(void)
+{
+	void __iomem *gic_base = (void *)KSEG1ADDR(MIPS_GIC_BASE);
+	int i;
+
+	/* Interrupt 0..5: Level Trigger, Active High */
+	writel(0, gic_base + GIC_SH_TRIG31_0);
+	writel(0x3f, gic_base + GIC_SH_RMASK31_0);
+	writel(0x3f, gic_base + GIC_SH_POL31_0);
+	writel(0x3f, gic_base + GIC_SH_SMASK31_0);
+
+	/* Interrupt 56..63: Edge Trigger, Rising Edge */
+	/* Hardcoded to set up the last 8 external interrupts for IPI. */
+	writel(0xff000000, gic_base + GIC_SH_TRIG63_32);
+	writel(0xff000000, gic_base + GIC_SH_RMASK63_32);
+	writel(0xff000000, gic_base + GIC_SH_POL63_32);
+	writel(0xff000000, gic_base + GIC_SH_SMASK63_32);
+
+	/* Map interrupt source to particular hardware interrupt pin */
+	/* source {0,1,2,3,4,5} -> pin {0,0,4,3,0,5} */
+	writel(GIC_MAP_TO_PIN | 0, gic_base + GIC_SH_MAP_PIN(0));
+	writel(GIC_MAP_TO_PIN | 0, gic_base + GIC_SH_MAP_PIN(1));
+	writel(GIC_MAP_TO_PIN | 4, gic_base + GIC_SH_MAP_PIN(2));
+	writel(GIC_MAP_TO_PIN | 3, gic_base + GIC_SH_MAP_PIN(3));
+	writel(GIC_MAP_TO_PIN | 0, gic_base + GIC_SH_MAP_PIN(4));
+	writel(GIC_MAP_TO_PIN | 5, gic_base + GIC_SH_MAP_PIN(5));
+
+	/* source 56~59 -> pin 1, 60~63 -> pin 2 */
+	writel(GIC_MAP_TO_PIN | 1, gic_base + GIC_SH_MAP_PIN(56));
+	writel(GIC_MAP_TO_PIN | 1, gic_base + GIC_SH_MAP_PIN(57));
+	writel(GIC_MAP_TO_PIN | 1, gic_base + GIC_SH_MAP_PIN(58));
+	writel(GIC_MAP_TO_PIN | 1, gic_base + GIC_SH_MAP_PIN(59));
+	writel(GIC_MAP_TO_PIN | 2, gic_base + GIC_SH_MAP_PIN(60));
+	writel(GIC_MAP_TO_PIN | 2, gic_base + GIC_SH_MAP_PIN(61));
+	writel(GIC_MAP_TO_PIN | 2, gic_base + GIC_SH_MAP_PIN(62));
+	writel(GIC_MAP_TO_PIN | 2, gic_base + GIC_SH_MAP_PIN(63));
+
+	/* Interrupt map to VPE (bit mask) */
+	for (i = 0; i < 32; i++)
+		writel(BIT(0), gic_base + GIC_SH_MAP_VPE31_0(i));
+
+	/*
+	 * Direct GIC_int 56..63 to vpe 0..3
+	 * MIPS Linux convention that last 16 interrupts implemented be set
+	 * aside for IPI signaling.
+	 * The actual interrupts are tied low and software sends interrupts
+	 * via GIC_SH_WEDGE writes.
+	 */
+	for (i = 0; i < 4; i++) {
+		writel(BIT(i), gic_base + GIC_SH_MAP_VPE31_0(i + 56));
+		writel(BIT(i), gic_base + GIC_SH_MAP_VPE31_0(i + 60));
+	}
+}
+
+void mt7621_cps_init(void)
+{
+	void __iomem *cm_base = (void *)KSEG1ADDR(CONFIG_MIPS_CM_BASE);
+
+	/* Enable GIC */
+	writel(MIPS_GIC_BASE | GCR_GIC_EN, cm_base + GCR_GIC_BASE);
+
+	/* Enable CPC */
+	writel(MIPS_CPC_BASE | GCR_CPC_EN, cm_base + GCR_CPC_BASE);
+
+	gic_init();
+	cm_init(cm_base);
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/dram.c b/arch/mips/mach-mtmips/mt7621/spl/dram.c
new file mode 100644
index 0000000..100adfb
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/dram.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <vsprintf.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/byteorder.h>
+#include <asm/addrspace.h>
+#include <linux/string.h>
+#include "../mt7621.h"
+#include "dram.h"
+
+static const u32 ddr2_act[DDR_PARAM_SIZE] = {
+#if defined(CONFIG_MT7621_DRAM_DDR2_512M)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x22174441,
+	0x00000000, 0xF0748661, 0x40001273, 0x9F0A0481,
+	0x0304692F, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000000, 0x07100000,
+	0x00001B63, 0x00002000, 0x00004000, 0x00006000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+#elif defined(CONFIG_MT7621_DRAM_DDR2_512M_W9751G6KB_A02_1066MHZ)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x33484584,
+	0x00000000, 0xF07486A1, 0x50001273, 0x9F010481,
+	0x0304693F, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000010, 0x07100000,
+	0x00001F73, 0x00002000, 0x00004000, 0x00006000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+#elif defined(CONFIG_MT7621_DRAM_DDR2_1024M_W971GG6KB25_800MHZ)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x22174430,
+	0x01000000, 0xF0748661, 0x40001273, 0x9F0F0481,
+	0x0304692F, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000000, 0x07100000,
+	0x00001B63, 0x00002000, 0x00004000, 0x00006000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+#elif defined(CONFIG_MT7621_DRAM_DDR2_1024M_W971GG6KB18_1066MHZ)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x33484584,
+	0x01000000, 0xF07486A1, 0x50001273, 0x9F070481,
+	0x0304693F, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000010, 0x07100000,
+	0x00001F73, 0x00002000, 0x00004000, 0x00006000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+#else /* CONFIG_MT7621_DRAM_DDR2_1024M */
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x22174441,
+	0x01000000, 0xF0748661, 0x40001273, 0x9F0F0481,
+	0x0304692F, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000000, 0x07100000,
+	0x00001B63, 0x00002000, 0x00004000, 0x00006000,
+	0x00000000, 0x00000000, 0x00000000, 0x00000000,
+#endif
+};
+
+static const u32 ddr3_act[DDR_PARAM_SIZE] = {
+#if defined(CONFIG_MT7621_DRAM_DDR3_1024M)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x44694683,
+	0x01000000, 0xF07486A1, 0xC287221D, 0x9F060481,
+	0x03046948, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000210, 0x07100000,
+	0x00001B61, 0x00002040, 0x00004010, 0x00006000,
+	0x0C000000, 0x07070000, 0x00000000, 0x00000000,
+#elif defined(CONFIG_MT7621_DRAM_DDR3_4096M)
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x44694683,
+	0x01000000, 0xF07486A1, 0xC287221D, 0x9F0F0481,
+	0x03046948, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000240, 0x07100000,
+	0x00001B61, 0x00002040, 0x00004010, 0x00006000,
+	0x0C000000, 0x07070000, 0x00000000, 0x00000000,
+#elif defined(CONFIG_MT7621_DRAM_DDR3_1024M_KGD)
+	0xFF00FF00, 0xFF00FF00, 0x00000007, 0x44694683,
+	0x01000000, 0xF07406A1, 0xC287221D, 0x9F060481,
+	0x03046923, 0x152f2842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000210, 0x07100000,
+	0x00001B61, 0x00002040, 0x00004010, 0x00006000,
+	0x0C000000, 0x07070000, 0x000C0000, 0x00000000,
+#else /* CONFIG_MT7621_DRAM_DDR3_2048M */
+	0xAA00AA00, 0xAA00AA00, 0x00000007, 0x44694673,
+	0x01000000, 0xF07486A1, 0xC287221D, 0x9F050481,
+	0x03046948, 0x15602842, 0x00008888, 0x88888888,
+	0x00000000, 0x00000000, 0x00000220, 0x07100000,
+	0x00001B61, 0x00002040, 0x00004010, 0x00006000,
+	0x0C000000, 0x07070000, 0x00000000, 0x00000000,
+#endif
+};
+
+#if defined(CONFIG_MT7621_DRAM_FREQ_400)
+#define DDR_FREQ_PARAM		0x41000000
+#elif defined(CONFIG_MT7621_DRAM_FREQ_1066)
+#define DDR_FREQ_PARAM		0x21000000
+#elif defined(CONFIG_MT7621_DRAM_FREQ_1200)
+#define DDR_FREQ_PARAM		0x11000000
+#else /* CONFIG_MT7621_DRAM_FREQ_800 */
+#define DDR_FREQ_PARAM		0x31000000
+#endif
+
+#define RG_MEPL_FBDIV_S		4
+#define RG_MEPL_FBDIV_M		0x7f
+
+static inline void word_copy(u32 *dest, const u32 *src, u32 count)
+{
+	u32 i;
+
+	for (i = 0; i < count; i++)
+		dest[i] = src[i];
+}
+
+static u32 calc_cpu_pll_val(void)
+{
+	u32 div, baseval, fb;
+
+	div = get_xtal_mhz();
+
+	if (div == 40) {
+		div /= 2;
+		baseval = 0xc0005802;
+	} else {
+		baseval = 0xc0004802;
+	}
+
+	fb = CONFIG_MT7621_CPU_FREQ / div - 1;
+	if (fb > RG_MEPL_FBDIV_M)
+		fb = RG_MEPL_FBDIV_M;
+
+	return baseval | (fb << RG_MEPL_FBDIV_S);
+}
+
+void prepare_stage_bin(void)
+{
+	u32 stage_size;
+
+	const struct stage_header *stock_stage_bin =
+		(const struct stage_header *)__image_copy_end;
+
+	struct stage_header *new_stage_bin =
+		(struct stage_header *)STAGE_LOAD_ADDR;
+
+	if (be32_to_cpu(stock_stage_bin->ep) != STAGE_LOAD_ADDR)
+		panic("Invalid DDR stage binary blob\n");
+
+	stage_size = be32_to_cpu(stock_stage_bin->stage_size);
+
+	word_copy((u32 *)new_stage_bin, (const u32 *)stock_stage_bin,
+		  (stage_size + sizeof(u32) - 1) / sizeof(u32));
+
+	word_copy(new_stage_bin->ddr2_act, ddr2_act, DDR_PARAM_SIZE);
+	word_copy(new_stage_bin->ddr3_act, ddr3_act, DDR_PARAM_SIZE);
+
+	new_stage_bin->cpu_pll_cfg = calc_cpu_pll_val();
+	new_stage_bin->ddr_pll_cfg = DDR_FREQ_PARAM;
+	new_stage_bin->baudrate = CONFIG_BAUDRATE;
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/dram.h b/arch/mips/mach-mtmips/mt7621/spl/dram.h
new file mode 100644
index 0000000..7322c58
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/dram.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT7621_DRAM_H_
+#define _MT7621_DRAM_H_
+
+#define STAGE_LOAD_ADDR			0xBE108800
+
+#ifndef __ASSEMBLY__
+#include <linux/types.h>
+
+#define DDR_PARAM_SIZE			24
+
+struct stage_header {
+	u32 jump_insn[2];
+	u32 ep;
+	u32 stage_size;
+	u32 has_stage2;
+	u32 next_ep;
+	u32 next_size;
+	u32 next_offset;
+	u32 cpu_pll_cfg;
+	u32 ddr_pll_cfg;
+	u32 reserved2[6];
+	char build_tag[32];
+	u32 ddr3_act[DDR_PARAM_SIZE];
+	u32 padding1[2];
+	u32 ddr2_act[DDR_PARAM_SIZE];
+	u32 padding2[2];
+	u32 baudrate;
+	u32 padding3;
+};
+#endif
+
+#endif /* _MT7621_DRAM_H_ */
diff --git a/arch/mips/mach-mtmips/mt7621/spl/launch.c b/arch/mips/mach-mtmips/mt7621/spl/launch.c
new file mode 100644
index 0000000..37c20a5
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/launch.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/io.h>
+#include <asm/cm.h>
+#include <asm/sections.h>
+#include <asm/addrspace.h>
+#include <asm/mipsmtregs.h>
+#include <linux/sizes.h>
+#include <time.h>
+#include <cpu_func.h>
+#include "launch.h"
+#include "../mt7621.h"
+
+/* Cluster Power Controller (CPC) offsets */
+#define CPC_CL_OTHER			0x2010
+#define CPC_CO_CMD			0x4000
+
+/* CPC_CL_OTHER fields */
+#define CPC_CL_OTHER_CORENUM_SHIFT	16
+#define CPC_CL_OTHER_CORENUM		GENMASK(23, 16)
+
+/* CPC_CO_CMD */
+#define PWR_UP				3
+
+#define NUM_CORES			2
+#define NUM_CPUS			4
+#define WAIT_CPUS_TIMEOUT		4000
+
+static void copy_launch_wait_code(void)
+{
+	memset((void *)KSEG1, 0, SZ_4K);
+
+	memcpy((void *)KSEG1ADDR(LAUNCH_WAITCODE),
+	       &launch_wait_code_start,
+	       &launch_wait_code_end - &launch_wait_code_start);
+
+	invalidate_dcache_range(KSEG0, SZ_4K);
+}
+
+static void bootup_secondary_core(void)
+{
+	void __iomem *cpcbase = (void __iomem *)KSEG1ADDR(MIPS_CPC_BASE);
+	int i;
+
+	for (i = 1; i < NUM_CORES; i++) {
+		writel(i << CPC_CL_OTHER_CORENUM_SHIFT, cpcbase + CPC_CL_OTHER);
+		writel(PWR_UP, cpcbase + CPC_CO_CMD);
+	}
+}
+
+void secondary_cpu_init(void)
+{
+	void __iomem *sysc = (void __iomem *)KSEG1ADDR(SYSCTL_BASE);
+	u32 i, dual_core = 0, cpuready = 1, cpumask = 0x03;
+	ulong wait_tick;
+	struct cpulaunch_t *c;
+
+	/* Copy LAUNCH wait code used by other VPEs */
+	copy_launch_wait_code();
+
+	dual_core = readl(sysc + SYSCTL_CHIP_REV_ID_REG) & CPU_ID;
+
+	if (dual_core) {
+		/* Bootup secondary core for MT7621A */
+		cpumask = 0x0f;
+
+		/* Make BootROM/TPL redirect Core1's bootup flow to our entry point */
+		writel((uintptr_t)&_start, sysc + BOOT_SRAM_BASE_REG);
+
+		bootup_secondary_core();
+	}
+
+	/* Join the coherent domain */
+	join_coherent_domain(dual_core ? 2 : 1);
+
+	/* Bootup Core0/VPE1 */
+	boot_vpe1();
+
+	/* Wait for all CPU ready */
+	wait_tick = get_timer(0) + WAIT_CPUS_TIMEOUT;
+
+	while (time_before(get_timer(0), wait_tick)) {
+		/* CPU0 is obviously ready */
+		for (i = 1; i < NUM_CPUS; i++) {
+			c = (struct cpulaunch_t *)(KSEG0ADDR(CPULAUNCH) +
+						   (i << LOG2CPULAUNCH));
+
+			if (c->flags & LAUNCH_FREADY)
+				cpuready |= BIT(i);
+		}
+
+		if ((cpuready & cpumask) == cpumask)
+			break;
+	}
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/launch.h b/arch/mips/mach-mtmips/mt7621/spl/launch.h
new file mode 100644
index 0000000..f34250d
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/launch.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _LAUNCH_H_
+#define _LAUNCH_H_
+
+#ifndef __ASSEMBLY__
+
+struct cpulaunch_t {
+	unsigned long pc;
+	unsigned long gp;
+	unsigned long sp;
+	unsigned long a0;
+	unsigned long _pad[3]; /* pad to cache line size to avoid thrashing */
+	unsigned long flags;
+};
+
+extern char launch_wait_code_start;
+extern char launch_wait_code_end;
+
+void join_coherent_domain(int ncores);
+void boot_vpe1(void);
+
+#else
+
+#define	LAUNCH_PC		0
+#define	LAUNCH_GP		4
+#define	LAUNCH_SP		8
+#define	LAUNCH_A0		12
+#define	LAUNCH_FLAGS		28
+
+#endif
+
+#define LOG2CPULAUNCH		5
+
+#define LAUNCH_FREADY		1
+#define LAUNCH_FGO		2
+#define LAUNCH_FGONE		4
+
+#define LAUNCH_WAITCODE		0x00000d00
+#define SCRLAUNCH		0x00000e00
+#define CPULAUNCH		0x00000f00
+#define NCPULAUNCH		8
+
+/* Polling period in count cycles for secondary CPU's */
+#define LAUNCHPERIOD		10000
+
+#endif /* _LAUNCH_H_ */
diff --git a/arch/mips/mach-mtmips/mt7621/spl/launch_ll.S b/arch/mips/mach-mtmips/mt7621/spl/launch_ll.S
new file mode 100644
index 0000000..32d28c7
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/launch_ll.S
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/cm.h>
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/cacheops.h>
+#include <asm/mipsregs.h>
+#include <asm/addrspace.h>
+#include <asm/mipsmtregs.h>
+#include "launch.h"
+
+	.macro cache_loop	curr, end, line_sz, op
+10:	cache		\op, 0(\curr)
+	PTR_ADDU	\curr, \curr, \line_sz
+	bne		\curr, \end, 10b
+	.endm
+
+	.set	mt
+
+/*
+ * Join the coherent domain
+ * a0 = number of cores
+ */
+LEAF(join_coherent_domain)
+	/*
+	 * Enable coherence and allow interventions from all other cores.
+	 * (Write access enabled via GCR_ACCESS by core 0.)
+	 */
+	li	t1, 1
+	sll	t1, a0
+	addiu	t1, -1
+
+	li	t0, KSEG1ADDR(CONFIG_MIPS_CM_BASE)
+	sw	t1, GCR_Cx_COHERENCE(t0)
+	ehb
+
+	move	t2, zero
+
+_next_coherent_core:
+	sll	t1, t2, GCR_Cx_OTHER_CORENUM_SHIFT
+	sw	t1, GCR_Cx_OTHER(t0)
+
+_busy_wait_coherent_core:
+	lw	t1, GCR_CO_COHERENCE(t0)
+	beqz	t1, _busy_wait_coherent_core
+
+	addiu	t2, 1
+	bne	t2, a0, _next_coherent_core
+
+	jr	ra
+	END(join_coherent_domain)
+
+/*
+ * All VPEs other than VPE0 will go here.
+ */
+LEAF(launch_vpe_entry)
+	mfc0	t0, CP0_EBASE
+	and	t0, t0, MIPS_EBASE_CPUNUM
+
+	/* per-VPE cpulaunch_t */
+	li	a0, KSEG0ADDR(CPULAUNCH)
+	sll	t1, t0, LOG2CPULAUNCH
+	addu	a0, t1
+
+	/* Set CPU online flag */
+	li	t0, LAUNCH_FREADY
+	sw	t0, LAUNCH_FLAGS(a0)
+
+	/* Enable count interrupt in mask, but do not enable interrupts */
+	mfc0	t0, CP0_STATUS
+	ori	t0, STATUSF_IP7
+	mtc0	t0, CP0_STATUS
+
+	/* VPEs executing in wait code do not need a stack */
+	li	t9, KSEG0ADDR(LAUNCH_WAITCODE)
+	jr	t9
+	END(launch_vpe_entry)
+
+/*
+ * This function will not be executed in place.
+ * It will be copied into memory, and VPEs other than VPE0 will be
+ * started to run into this in-memory function.
+ */
+LEAF(launch_wait_code)
+	.globl	launch_wait_code_start
+launch_wait_code_start:
+
+	move	t0, a0
+
+start_poll:
+	/* Poll CPU go flag */
+	mtc0	zero, CP0_COUNT
+	li	t1, LAUNCHPERIOD
+	mtc0	t1, CP0_COMPARE
+
+time_wait:
+	/* Software wait */
+	mfc0	t2, CP0_COUNT
+	subu	t2, t1
+	bltz	t2, time_wait
+
+	/* Check the launch flag */
+	lw	t3, LAUNCH_FLAGS(t0)
+	and	t3, LAUNCH_FGO
+	beqz	t3, start_poll
+
+	/* Reset the counter and interrupts to give naive clients a chance */
+	mfc0	t1, CP0_STATUS
+	ins	t1, zero, STATUSB_IP7, 1
+	mtc0	t1, CP0_STATUS
+
+	mfc0	t1, CP0_COUNT
+	subu	t1, 1
+	mtc0	t1, CP0_COMPARE
+
+	/* Jump to kernel */
+	lw	t9, LAUNCH_PC(t0)
+	lw	gp, LAUNCH_GP(t0)
+	lw	sp, LAUNCH_SP(t0)
+	lw	a0, LAUNCH_A0(t0)
+	move	a1, zero
+	move	a2, zero
+	move	a3, zero
+	ori	t3, LAUNCH_FGONE
+	sw	t3, LAUNCH_FLAGS(t0)
+
+	jr	t9
+
+	.globl	launch_wait_code_end
+launch_wait_code_end:
+	END(launch_wait_code)
+
+/*
+ * Core1 will go here.
+ */
+LEAF(launch_core_entry)
+	/* Disable caches */
+	bal	mips_cache_disable
+
+	/* Initialize L1 cache only */
+	li	a0, CONFIG_SYS_ICACHE_SIZE
+	li	a1, CONFIG_SYS_ICACHE_LINE_SIZE
+	li	a2, CONFIG_SYS_DCACHE_SIZE
+	li	a3, CONFIG_SYS_DCACHE_LINE_SIZE
+
+	mtc0	zero, CP0_TAGLO
+	mtc0	zero, CP0_TAGLO, 2
+	ehb
+
+	/*
+	 * Initialize the I-cache first,
+	 */
+	li		t0, KSEG0
+	addu		t1, t0, a0
+	/* clear tag to invalidate */
+	cache_loop	t0, t1, a1, INDEX_STORE_TAG_I
+#ifdef CONFIG_SYS_MIPS_CACHE_INIT_RAM_LOAD
+	/* fill once, so data field parity is correct */
+	PTR_LI		t0, KSEG0
+	cache_loop	t0, t1, a1, FILL
+	/* invalidate again - prudent but not strictly necessary */
+	PTR_LI		t0, KSEG0
+	cache_loop	t0, t1, a1, INDEX_STORE_TAG_I
+#endif
+
+	/*
+	 * then initialize D-cache.
+	 */
+	PTR_LI		t0, KSEG0
+	PTR_ADDU	t1, t0, a2
+	/* clear all tags */
+	cache_loop	t0, t1, a3, INDEX_STORE_TAG_D
+#ifdef CONFIG_SYS_MIPS_CACHE_INIT_RAM_LOAD
+	/* load from each line (in cached space) */
+	PTR_LI		t0, KSEG0
+2:	LONG_L		zero, 0(t0)
+	PTR_ADDU	t0, a3
+	 bne		t0, t1, 2b
+	/* clear all tags */
+	PTR_LI		t0, KSEG0
+	cache_loop	t0, t1, a3, INDEX_STORE_TAG_D
+#endif
+
+	/* Set Cache Mode */
+	mfc0	t0, CP0_CONFIG
+	li	t1, CONF_CM_CACHABLE_COW
+	ins	t0, t1, 0, 3
+	mtc0	t0, CP0_CONFIG
+
+	/* Join the coherent domain */
+	li	a0, 2
+	bal	join_coherent_domain
+
+	/* Bootup Core0/VPE1 */
+	bal	boot_vpe1
+
+	b	launch_vpe_entry
+	END(launch_core_entry)
+
+/*
+ * Bootup VPE1.
+ * This subroutine must be executed from VPE0 with VPECONF0[MVP] already set.
+ */
+LEAF(boot_vpe1)
+	mfc0	t0, CP0_MVPCONF0
+
+	/* a0 = number of TCs - 1 */
+	ext	a0, t0, MVPCONF0_PTC_SHIFT, 8
+	beqz	a0, _vpe1_init_done
+
+	/* a1 = number of VPEs - 1 */
+	ext	a1, t0, MVPCONF0_PVPE_SHIFT, 4
+	beqz	a1, _vpe1_init_done
+
+	/* a2 = current TC No. */
+	move	a2, zero
+
+	/* Enter VPE Configuration State */
+	mfc0	t0, CP0_MVPCONTROL
+	or	t0, MVPCONTROL_VPC
+	mtc0	t0, CP0_MVPCONTROL
+	ehb
+
+_next_tc:
+	/* Set the TC number to be used on MTTR and MFTR instructions */
+	mfc0	t0, CP0_VPECONTROL
+	ins	t0, a2, 0, 8
+	mtc0	t0, CP0_VPECONTROL
+	ehb
+
+	/* TC0 is already bound */
+	beqz	a2, _next_vpe
+
+	/* Halt current TC */
+	li	t0, TCHALT_H
+	mttc0	t0, CP0_TCHALT
+	ehb
+
+	/* If there is spare TC, bind it to the last VPE (VPE[a1]) */
+	slt	t1, a1, a2
+	bnez	t1, _vpe_bind_tc
+	 move	t1, a1
+
+	/* Set Exclusive TC for active TC */
+	mftc0	t0, CP0_VPECONF0
+	ins	t0, a2, VPECONF0_XTC_SHIFT, 8
+	mttc0	t0, CP0_VPECONF0
+
+	move	t1, a2
+_vpe_bind_tc:
+	/* Bind TC to a VPE */
+	mftc0	t0, CP0_TCBIND
+	ins	t0, t1, TCBIND_CURVPE_SHIFT, 4
+	mttc0	t0, CP0_TCBIND
+
+	/*
+	 * Set up CP0_TCSTATUS register:
+	 * Disable Coprocessor Usable bits
+	 * Disable MDMX/DSP ASE
+	 * Clear Dirty TC
+	 * not dynamically allocatable
+	 * not allocated
+	 * Kernel mode
+	 * interrupt exempt
+	 * ASID 0
+	 */
+	li	t0, TCSTATUS_IXMT
+	mttc0	t0, CP0_TCSTATUS
+
+_next_vpe:
+	slt	t1, a1, a2
+	bnez	t1, _done_vpe	# No more VPEs
+
+	/* Disable TC multi-threading */
+	mftc0	t0, CP0_VPECONTROL
+	ins	t0, zero, VPECONTROL_TE_SHIFT, 1
+	mttc0	t0, CP0_VPECONTROL
+
+	/* Skip following configuration for TC0 */
+	beqz	a2, _done_vpe
+
+	/* Deactivate VPE, set Master VPE */
+	mftc0	t0, CP0_VPECONF0
+	ins	t0, zero, VPECONF0_VPA_SHIFT, 1
+	or	t0, VPECONF0_MVP
+	mttc0	t0, CP0_VPECONF0
+
+	mfc0	t0, CP0_STATUS
+	mttc0	t0, CP0_STATUS
+
+	mttc0	zero, CP0_EPC
+	mttc0	zero, CP0_CAUSE
+
+	mfc0	t0, CP0_CONFIG
+	mttc0	t0, CP0_CONFIG
+
+	/*
+	 * VPE1 of each core can execute cached as its L1 I$ has already
+	 * been initialized.
+	 * and the L2$ has been initialized or "disabled" via CCA override.
+	 */
+	PTR_LA	t0, _start
+	mttc0	t0, CP0_TCRESTART
+
+	/* Unset Interrupt Exempt, set Activate Thread */
+	mftc0	t0, CP0_TCSTATUS
+	ins	t0, zero, TCSTATUS_IXMT_SHIFT, 1
+	ori	t0, TCSTATUS_A
+	mttc0	t0, CP0_TCSTATUS
+
+	/* Resume TC */
+	mttc0	zero, CP0_TCHALT
+
+	/* Activate VPE */
+	mftc0	t0, CP0_VPECONF0
+	ori	t0, VPECONF0_VPA
+	mttc0	t0, CP0_VPECONF0
+
+_done_vpe:
+	addu	a2, 1
+	sltu	t0, a0, a2
+	beqz	t0, _next_tc
+
+	mfc0	t0, CP0_MVPCONTROL
+	/* Enable all activated VPE to execute */
+	ori	t0, MVPCONTROL_EVP
+	/* Exit VPE Configuration State */
+	ins	t0, zero, MVPCONTROL_VPC_SHIFT, 1
+	mtc0	t0, CP0_MVPCONTROL
+	ehb
+
+_vpe1_init_done:
+	jr	ra
+	END(boot_vpe1)
diff --git a/arch/mips/mach-mtmips/mt7621/spl/serial.c b/arch/mips/mach-mtmips/mt7621/spl/serial.c
new file mode 100644
index 0000000..5cf093a
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/serial.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/io.h>
+#include "../mt7621.h"
+
+void mtmips_spl_serial_init(void)
+{
+#ifdef CONFIG_SPL_SERIAL
+	void __iomem *base = ioremap_nocache(SYSCTL_BASE, SYSCTL_SIZE);
+
+#if CONFIG_CONS_INDEX == 1
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART1_MODE);
+#elif CONFIG_CONS_INDEX == 2
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART2_MODE_M);
+#elif CONFIG_CONS_INDEX == 3
+	clrbits_32(base + SYSCTL_GPIOMODE_REG, UART3_MODE_M);
+#endif /* CONFIG_CONS_INDEX */
+#endif /* CONFIG_SPL_SERIAL */
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/spl.c b/arch/mips/mach-mtmips/mt7621/spl/spl.c
new file mode 100644
index 0000000..91eebc6
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/spl.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <init.h>
+#include <image.h>
+#include <vsprintf.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+#include <asm/addrspace.h>
+#include <asm/byteorder.h>
+#include <asm/global_data.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+#include <mach/serial.h>
+#include "../mt7621.h"
+#include "dram.h"
+#include <spl.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct tpl_info {
+	u32 magic;
+	u32 size;
+};
+
+void set_timer_freq_simple(void)
+{
+	u32 div = get_xtal_mhz();
+
+	/* Round down cpu freq */
+	gd->arch.timer_freq = rounddown(CONFIG_MT7621_CPU_FREQ, div) * 500000;
+}
+
+void __noreturn board_init_f(ulong dummy)
+{
+	spl_init();
+
+#ifdef CONFIG_SPL_SERIAL
+	/*
+	 * mtmips_spl_serial_init() is useful if debug uart is enabled,
+	 * or DM based serial is not enabled.
+	 */
+	mtmips_spl_serial_init();
+	preloader_console_init();
+#endif
+
+	board_init_r(NULL, 0);
+}
+
+void board_boot_order(u32 *spl_boot_list)
+{
+#ifdef CONFIG_MT7621_BOOT_FROM_NAND
+	spl_boot_list[0] = BOOT_DEVICE_NAND;
+#else
+	spl_boot_list[0] = BOOT_DEVICE_NOR;
+#endif
+}
+
+unsigned long spl_nor_get_uboot_base(void)
+{
+	const struct tpl_info *tpli;
+	const image_header_t *hdr;
+	u32 addr;
+
+	addr = FLASH_MMAP_BASE + TPL_INFO_OFFSET;
+	tpli = (const struct tpl_info *)KSEG1ADDR(addr);
+
+	if (tpli->magic == TPL_INFO_MAGIC) {
+		addr = FLASH_MMAP_BASE + tpli->size;
+		hdr = (const image_header_t *)KSEG1ADDR(addr);
+
+		if (image_get_magic(hdr) == IH_MAGIC) {
+			addr += sizeof(*hdr) + image_get_size(hdr);
+			return KSEG1ADDR(addr);
+		}
+	}
+
+	panic("Unable to locate SPL payload\n");
+	return 0;
+}
+
+uint32_t spl_nand_get_uboot_raw_page(void)
+{
+	const struct stage_header *sh = (const struct stage_header *)&_start;
+	u32 addr;
+
+	addr = image_get_header_size() + be32_to_cpu(sh->stage_size);
+	addr = ALIGN(addr, SZ_4K);
+
+	return addr;
+}
diff --git a/arch/mips/mach-mtmips/mt7621/spl/start.S b/arch/mips/mach-mtmips/mt7621/spl/start.S
new file mode 100644
index 0000000..3cad356
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/spl/start.S
@@ -0,0 +1,226 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm-offsets.h>
+#include <config.h>
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/mipsregs.h>
+#include <asm/cacheops.h>
+#include <asm/addrspace.h>
+#include <asm/mipsmtregs.h>
+#include <asm/cm.h>
+#include "../mt7621.h"
+#include "dram.h"
+
+#ifndef CONFIG_SYS_INIT_SP_ADDR
+#define CONFIG_SYS_INIT_SP_ADDR	(CONFIG_SYS_SDRAM_BASE + \
+				CONFIG_SYS_INIT_SP_OFFSET)
+#endif
+
+#define SP_ADDR_TEMP		0xbe10dff0
+
+	.macro init_wr sel
+	MTC0	zero, CP0_WATCHLO,\sel
+	mtc0	t1, CP0_WATCHHI,\sel
+	.endm
+
+	.macro setup_stack_gd
+	li	t0, -16
+	PTR_LI	t1, CONFIG_SYS_INIT_SP_ADDR
+	and	sp, t1, t0		# force 16 byte alignment
+	PTR_SUBU \
+		sp, sp, GD_SIZE		# reserve space for gd
+	and	sp, sp, t0		# force 16 byte alignment
+	move	k0, sp			# save gd pointer
+#if CONFIG_VAL(SYS_MALLOC_F_LEN) && \
+    !CONFIG_IS_ENABLED(INIT_STACK_WITHOUT_MALLOC_F)
+	li	t2, CONFIG_VAL(SYS_MALLOC_F_LEN)
+	PTR_SUBU \
+		sp, sp, t2		# reserve space for early malloc
+	and	sp, sp, t0		# force 16 byte alignment
+#endif
+	move	fp, sp
+
+	/* Clear gd */
+	move	t0, k0
+1:
+	PTR_S	zero, 0(t0)
+	PTR_ADDIU t0, PTRSIZE
+	blt	t0, t1, 1b
+	 nop
+
+#if CONFIG_VAL(SYS_MALLOC_F_LEN) && \
+    !CONFIG_IS_ENABLED(INIT_STACK_WITHOUT_MALLOC_F)
+	PTR_S	sp, GD_MALLOC_BASE(k0)	# gd->malloc_base offset
+#endif
+	.endm
+
+	.set	noreorder
+
+ENTRY(_start)
+	b	1f
+	 mtc0	zero, CP0_COUNT
+
+	/* Stage header required by BootROM */
+	.org	0x8
+	.word	0		# ep, filled by mkimage
+	.word	0		# stage_size, filled by mkimage
+	.word	0		# has_stage2
+	.word	0		# next_ep
+	.word	0		# next_size
+	.word	0		# next_offset
+
+1:
+	/* Init CP0 Status */
+	mfc0	t0, CP0_STATUS
+	and	t0, ST0_IMPL
+	or	t0, ST0_BEV | ST0_ERL
+	mtc0	t0, CP0_STATUS
+	ehb
+
+	/* Clear Watch Status bits and disable watch exceptions */
+	li	t1, 0x7		# Clear I, R and W conditions
+	init_wr	0
+	init_wr	1
+	init_wr	2
+	init_wr	3
+
+	/* Clear WP, IV and SW interrupts */
+	mtc0	zero, CP0_CAUSE
+
+	/* Clear timer interrupt (CP0_COUNT cleared on branch to 'reset') */
+	mtc0	zero, CP0_COMPARE
+
+	/* VPE1 goes to wait code directly */
+	mfc0	t0, CP0_TCBIND
+	andi	t0, TCBIND_CURVPE
+	bnez	t0, launch_vpe_entry
+	 nop
+
+	/* Core1 goes to specific launch entry */
+	PTR_LI	t0, KSEG1ADDR(CONFIG_MIPS_CM_BASE)
+	lw	t1, GCR_Cx_ID(t0)
+	bnez	t1, launch_core_entry
+	 nop
+
+	/* MT7530 reset */
+	li	t0, KSEG1ADDR(SYSCTL_BASE)
+	lw	t1, SYSCTL_RSTCTL_REG(t0)
+	ori	t1, MCM_RST
+	sw	t1, SYSCTL_RSTCTL_REG(t0)
+
+	/* Disable DMA route for PSE SRAM set by BootROM */
+	PTR_LI	t0, KSEG1ADDR(DMA_CFG_ARB_BASE)
+	sw	zero, DMA_ROUTE_REG(t0)
+
+	/* Set CPU clock to 500MHz (Required if boot from NAND) */
+	li	t0, KSEG1ADDR(SYSCTL_BASE)
+	lw	t1, SYSCTL_CLKCFG0_REG(t0)
+	ins	t1, zero, 30, 2		# CPU_CLK_SEL
+	sw	t1, SYSCTL_CLKCFG0_REG(t0)
+
+	/* Set CPU clock divider to 1/1 */
+	li	t0, KSEG1ADDR(RBUS_BASE)
+	li	t1, 0x101
+	sw	t1, RBUS_DYN_CFG0_REG(t0)
+
+	/* (Re-)initialize the SRAM */
+	bal	mips_sram_init
+	 nop
+
+	/* Set up temporary stack */
+	li	sp, SP_ADDR_TEMP
+
+	/* Setup full CPS */
+	bal	mips_cm_map
+	 nop
+
+	bal	mt7621_cps_init
+	 nop
+
+	/* Prepare for CPU/DDR initialization binary blob */
+	bal	prepare_stage_bin
+	 nop
+
+	/* Call CPU/DDR initialization binary blob */
+	li	t9, STAGE_LOAD_ADDR
+	jalr	t9
+	 nop
+
+	/* Switch CPU PLL source */
+	li	t0, KSEG1ADDR(SYSCTL_BASE)
+	lw	t1, SYSCTL_CLKCFG0_REG(t0)
+	li	t2, 1
+	ins	t1, t2, CPU_CLK_SEL_S, 2
+	sw	t1, SYSCTL_CLKCFG0_REG(t0)
+
+	/*
+	 * Currently SPL is running on locked L2 cache (on KSEG0).
+	 * To reset the entire cache, we have to writeback SPL to DRAM first.
+	 * Cache flush won't work here. Use memcpy instead.
+	 */
+
+	la	a0, __text_start
+	move	a1, a0
+	la	a2, __image_copy_end
+	sub	a2, a2, a1
+	li	a3, 5
+	ins	a0, a3, 29, 3	# convert to KSEG1
+
+	bal	memcpy
+	 nop
+
+	/* Disable caches */
+	bal	mips_cache_disable
+	 nop
+
+	/* Reset caches */
+	bal	mips_cache_reset
+	 nop
+
+	/* Disable SRAM */
+	li	t0, KSEG1ADDR(FE_BASE)
+	li	t1, FE_PSE_RESET
+	sw	t1, FE_RST_GLO_REG(t0)
+
+	/* Clear the .bss section */
+	la	a0, __bss_start
+	la	a1, __bss_end
+1:	sw	zero, 0(a0)
+	addiu	a0, 4
+	ble	a0, a1, 1b
+	 nop
+
+	/* Set up initial stack and global data */
+	setup_stack_gd
+
+#if CONFIG_IS_ENABLED(INIT_STACK_WITHOUT_MALLOC_F)
+	/* Set malloc base */
+	li	t0, (CONFIG_SYS_INIT_SP_ADDR + 15) & (~15)
+	PTR_S	t0, GD_MALLOC_BASE(k0)	# gd->malloc_base offset
+#endif
+
+#if defined(CONFIG_DEBUG_UART) && defined(CONFIG_SPL_SERIAL)
+	/* Earliest point to set up debug uart */
+	bal	debug_uart_init
+	 nop
+#endif
+
+	/* Setup timer */
+	bal	set_timer_freq_simple
+	 nop
+
+	/* Bootup secondary CPUs */
+	bal	secondary_cpu_init
+	 nop
+
+	move	a0, zero		# a0 <-- boot_flags = 0
+	bal board_init_f
+	 move	ra, zero
+
+	END(_start)
diff --git a/arch/mips/mach-mtmips/mt7621/sram_init.S b/arch/mips/mach-mtmips/mt7621/sram_init.S
new file mode 100644
index 0000000..03b9eab
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/sram_init.S
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm/addrspace.h>
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include "mt7621.h"
+
+LEAF(mips_sram_init)
+	li	t0, KSEG1ADDR(FE_BASE)
+	li	t1, FE_PSE_RESET
+	sw	t1, FE_RST_GLO_REG(t0)
+
+	li	t1, (FE_PSE_RAM | FE_PSE_MEM_EN)
+	sw	t1, FE_RST_GLO_REG(t0)
+
+	jr	ra
+	END(mips_sram_init)
diff --git a/arch/mips/mach-mtmips/mt7621/tpl/Makefile b/arch/mips/mach-mtmips/mt7621/tpl/Makefile
new file mode 100644
index 0000000..471ad74
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/tpl/Makefile
@@ -0,0 +1,4 @@
+
+extra-y += start.o
+
+obj-y += tpl.o
diff --git a/arch/mips/mach-mtmips/mt7621/tpl/start.S b/arch/mips/mach-mtmips/mt7621/tpl/start.S
new file mode 100644
index 0000000..19b09f7
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/tpl/start.S
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <asm-offsets.h>
+#include <config.h>
+#include <asm/asm.h>
+#include <asm/regdef.h>
+#include <asm/addrspace.h>
+#include <asm/mipsregs.h>
+#include <asm/cm.h>
+#include "../mt7621.h"
+
+#define SP_ADDR_TEMP		0xbe10dff0
+
+	.set noreorder
+
+	.macro init_wr sel
+	MTC0	zero, CP0_WATCHLO,\sel
+	mtc0	t1, CP0_WATCHHI,\sel
+	.endm
+
+	.macro uhi_mips_exception
+	move	k0, t9		# preserve t9 in k0
+	move	k1, a0		# preserve a0 in k1
+	li	t9, 15		# UHI exception operation
+	li	a0, 0		# Use hard register context
+	sdbbp	1		# Invoke UHI operation
+	.endm
+
+ENTRY(_start)
+	b	reset
+	 mtc0	zero, CP0_COUNT
+
+	/*
+	 * Store TPL size here.
+	 * This will be used by SPL to locate u-boot payload.
+	 */
+	.org	TPL_INFO_OFFSET
+	.word	TPL_INFO_MAGIC
+	.word	__image_copy_len
+
+	/* Exception vector */
+	.org 0x200
+	/* TLB refill, 32 bit task */
+	uhi_mips_exception
+
+	.org 0x280
+	/* XTLB refill, 64 bit task */
+	uhi_mips_exception
+
+	.org 0x300
+	/* Cache error exception */
+	uhi_mips_exception
+
+	.org 0x380
+	/* General exception */
+	uhi_mips_exception
+
+	.org 0x400
+	/* Catch interrupt exceptions */
+	uhi_mips_exception
+
+	.org 0x480
+	/* EJTAG debug exception */
+1:	b	1b
+	 nop
+
+	.org 0x500
+
+reset:
+	/* Set KSEG0 to Uncached */
+	mfc0	t0, CP0_CONFIG
+	ins	t0, zero, 0, 3
+	ori	t0, t0, CONF_CM_UNCACHED
+	mtc0	t0, CP0_CONFIG
+	ehb
+
+	/* Check for CPU number */
+	mfc0	t0, CP0_EBASE
+	and	t0, t0, MIPS_EBASE_CPUNUM
+	beqz	t0, 1f
+	 nop
+
+	/* Secondary core goes to specified SPL entry address */
+	li	t0, KSEG1ADDR(SYSCTL_BASE)
+	lw	t0, BOOT_SRAM_BASE_REG(t0)
+	jr	t0
+	 nop
+
+	/* Init CP0 Status */
+1:	mfc0	t0, CP0_STATUS
+	and	t0, ST0_IMPL
+	or	t0, ST0_BEV | ST0_ERL
+	mtc0	t0, CP0_STATUS
+	 nop
+
+	/* Clear Watch Status bits and disable watch exceptions */
+	li	t1, 0x7		# Clear I, R and W conditions
+	init_wr	0
+	init_wr	1
+	init_wr	2
+	init_wr	3
+
+	/* Clear WP, IV and SW interrupts */
+	mtc0	zero, CP0_CAUSE
+
+	/* Clear timer interrupt (CP0_COUNT cleared on branch to 'reset') */
+	mtc0	zero, CP0_COMPARE
+
+	/* Setup basic CPS */
+	bal	mips_cm_map
+	 nop
+
+	li	t0, KSEG1ADDR(CONFIG_MIPS_CM_BASE)
+	li	t1, GCR_REG0_BASE_VALUE
+	sw	t1, GCR_REG0_BASE(t0)
+
+	li	t1, ((GCR_REG0_MASK_VALUE << GCR_REGn_MASK_ADDRMASK_SHIFT) | \
+		    GCR_REGn_MASK_CMTGT_IOCU0)
+	sw	t1, GCR_REG0_MASK(t0)
+
+	lw	t1, GCR_BASE(t0)
+	ins	t1, zero, 0, 2		# CM_DEFAULT_TARGET
+	sw	t1, GCR_BASE(t0)
+
+	lw	t1, GCR_CONTROL(t0)
+	li	t2, GCR_CONTROL_SYNCCTL
+	or	t1, t1, t2
+	sw	t1, GCR_CONTROL(t0)
+
+	/* Increase SPI frequency */
+	li	t0, KSEG1ADDR(SPI_BASE)
+	li	t1, 5
+	sw	t1, SPI_SPACE_REG(t0)
+
+	/* Set CPU clock to 500MHz */
+	li	t0, KSEG1ADDR(SYSCTL_BASE)
+	lw	t1, SYSCTL_CLKCFG0_REG(t0)
+	ins	t1, zero, 30, 2		# CPU_CLK_SEL
+	sw	t1, SYSCTL_CLKCFG0_REG(t0)
+
+	/* Set CPU clock divider to 1/1 */
+	li	t0, KSEG1ADDR(RBUS_BASE)
+	li	t1, 0x101
+	sw	t1, RBUS_DYN_CFG0_REG(t0)
+
+	/* Initialize the SRAM */
+	bal	mips_sram_init
+	 nop
+
+	/* Set up initial stack */
+	li	sp, SP_ADDR_TEMP
+
+	bal	tpl_main
+	 nop
+
+	END(_start)
diff --git a/arch/mips/mach-mtmips/mt7621/tpl/tpl.c b/arch/mips/mach-mtmips/mt7621/tpl/tpl.c
new file mode 100644
index 0000000..2a82890
--- /dev/null
+++ b/arch/mips/mach-mtmips/mt7621/tpl/tpl.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <image.h>
+#include <asm/system.h>
+#include <asm/sections.h>
+#include <asm/cacheops.h>
+#include <asm/mipsregs.h>
+#include <asm/cm.h>
+
+#define INDEX_STORE_DATA_SD	0x0f
+
+typedef void __noreturn (*image_entry_noargs_t)(void);
+
+/*
+ * Lock L2 cache and fill data
+ * Assume that data is 4-byte aligned and start_addr/size is 32-byte aligned
+ */
+static void fill_lock_l2cache(uintptr_t dataptr, ulong start_addr, ulong size)
+{
+	ulong slsize = CONFIG_SYS_DCACHE_LINE_SIZE;
+	ulong end_addr = start_addr + size;
+	const u32 *data = (u32 *)dataptr;
+	ulong i, addr;
+	u32 val;
+
+	/* Clear WSC & SPR bit in ErrCtl */
+	val = read_c0_ecc();
+	val &= 0xcfffffff;
+	write_c0_ecc(val);
+	execution_hazard_barrier();
+
+	for (addr = start_addr; addr < end_addr; addr += slsize) {
+		/* Set STagLo to lock cache line */
+		write_c0_staglo((addr & 0x1ffff800) | 0xa0);
+		mips_cache(INDEX_STORE_TAG_SD, (void *)addr);
+
+		/* Fill data */
+		for (i = 0; i < slsize; i += 8) {
+			val = *data++;
+			__write_32bit_c0_register($28, 5, val); /* sdtaglo */
+			val = *data++;
+			__write_32bit_c0_register($29, 5, val); /* sdtaghi */
+			mips_cache(INDEX_STORE_DATA_SD, (void *)(addr + i));
+		}
+	}
+
+	sync();
+}
+
+/* A simple function to initialize MT7621's cache */
+static void mt7621_cache_init(void)
+{
+	void __iomem *cm_base = (void *)KSEG1ADDR(CONFIG_MIPS_CM_BASE);
+	ulong lsize = CONFIG_SYS_DCACHE_LINE_SIZE;
+	ulong addr;
+	u32 val;
+
+	/* Enable CCA override. Set to uncached */
+	val = readl(cm_base + GCR_BASE);
+	val &= ~CCA_DEFAULT_OVR_MASK;
+	val |= CCA_DEFAULT_OVREN | (2 << CCA_DEFAULT_OVR_SHIFT);
+	writel(val, cm_base + GCR_BASE);
+
+	/* Initialize L1 I-Cache */
+	write_c0_taglo(0);
+	write_c0_taghi(0);
+
+	for (addr = 0; addr < CONFIG_SYS_ICACHE_SIZE; addr += lsize)
+		mips_cache(INDEX_STORE_TAG_I, (void *)addr);
+
+	/* Initialize L1 D-Cache */
+	write_c0_dtaglo(0);
+	__write_32bit_c0_register($29, 2, 0); /* dtaghi */
+
+	for (addr = 0; addr < CONFIG_SYS_DCACHE_SIZE; addr += lsize)
+		mips_cache(INDEX_STORE_TAG_D, (void *)addr);
+
+	/* Initialize L2 Cache */
+	write_c0_staglo(0);
+	__write_32bit_c0_register($29, 4, 0); /* staghi */
+
+	for (addr = 0; addr < (256 << 10); addr += lsize)
+		mips_cache(INDEX_STORE_TAG_SD, (void *)addr);
+
+	/* Dsiable CCA override */
+	val = readl(cm_base + GCR_BASE);
+	val &= ~(CCA_DEFAULT_OVR_MASK | CCA_DEFAULT_OVREN);
+	writel(val, cm_base + GCR_BASE);
+
+	/* Set KSEG0 to non-coherent cached (important!) */
+	val = read_c0_config();
+	val &= ~CONF_CM_CMASK;
+	val |= CONF_CM_CACHABLE_NONCOHERENT;
+	write_c0_config(val);
+	execution_hazard_barrier();
+
+	/* Again, invalidate L1 D-Cache */
+	for (addr = 0; addr < CONFIG_SYS_DCACHE_SIZE; addr += lsize)
+		mips_cache(INDEX_WRITEBACK_INV_D, (void *)addr);
+
+	/* Invalidate L1 I-Cache */
+	for (addr = 0; addr < CONFIG_SYS_ICACHE_SIZE; addr += lsize)
+		mips_cache(INDEX_INVALIDATE_I, (void *)addr);
+
+	/* Disable L2 cache bypass */
+	val = read_c0_config2();
+	val &= ~MIPS_CONF_IMPL;
+	write_c0_config2(val);
+	execution_hazard_barrier();
+}
+
+void __noreturn tpl_main(void)
+{
+	const image_header_t *hdr = (const image_header_t *)__image_copy_end;
+	image_entry_noargs_t image_entry;
+	u32 loadaddr, size;
+	uintptr_t data;
+
+	/* Initialize the cache first */
+	mt7621_cache_init();
+
+	if (image_get_magic(hdr) != IH_MAGIC)
+		goto failed;
+
+	loadaddr = image_get_load(hdr);
+	size = image_get_size(hdr);
+	image_entry = (image_entry_noargs_t)image_get_ep(hdr);
+
+	/* Load TPL image to L2 cache */
+	data = (uintptr_t)__image_copy_end + sizeof(struct image_header);
+	fill_lock_l2cache(data, loadaddr, size);
+
+	/* Jump to SPL */
+	image_entry();
+
+failed:
+	for (;;)
+		;
+}
diff --git a/board/mediatek/mt7621/MAINTAINERS b/board/mediatek/mt7621/MAINTAINERS
new file mode 100644
index 0000000..f83141c
--- /dev/null
+++ b/board/mediatek/mt7621/MAINTAINERS
@@ -0,0 +1,8 @@
+MT7621_RFB BOARD
+M:	Weijie Gao <weijie.gao@mediatek.com>
+S:	Maintained
+F:	board/mediatek/mt7621
+F:	configs/mt7621_rfb_defconfig
+F:	configs/mt7621_nand_rfb_defconfig
+F:	arch/mips/dts/mediatek,mt7621-rfb.dts
+F:	arch/mips/dts/mediatek,mt7621-nand-rfb.dts
diff --git a/board/mediatek/mt7621/Makefile b/board/mediatek/mt7621/Makefile
new file mode 100644
index 0000000..db129c5
--- /dev/null
+++ b/board/mediatek/mt7621/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-y += board.o
diff --git a/board/mediatek/mt7621/board.c b/board/mediatek/mt7621/board.c
new file mode 100644
index 0000000..0496f3f
--- /dev/null
+++ b/board/mediatek/mt7621/board.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
diff --git a/common/spl/Kconfig b/common/spl/Kconfig
index 931619c..908d7d1 100644
--- a/common/spl/Kconfig
+++ b/common/spl/Kconfig
@@ -884,7 +884,7 @@
 	  expose the cmd_ctrl() interface.
 
 config SPL_NAND_BASE
-	depends on SPL_NAND_DRIVERS
+	depends on SPL_NAND_SUPPORT
 	bool "Use Base NAND Driver"
 	help
 	  Include nand_base.c in the SPL.
diff --git a/common/spl/spl_legacy.c b/common/spl/spl_legacy.c
index 2ec7154..ae8731c 100644
--- a/common/spl/spl_legacy.c
+++ b/common/spl/spl_legacy.c
@@ -88,15 +88,29 @@
 	/* Read header into local struct */
 	load->read(load, header, sizeof(hdr), &hdr);
 
+	/*
+	 * If the payload is compressed, the decompressed data should be
+	 * directly write to its load address.
+	 */
+	if (spl_image_get_comp(&hdr) != IH_COMP_NONE)
+		spl_image->flags |= SPL_COPY_PAYLOAD_ONLY;
+
 	ret = spl_parse_image_header(spl_image, bootdev, &hdr);
 	if (ret)
 		return ret;
 
-	dataptr = header + sizeof(hdr);
-
 	/* Read image */
 	switch (spl_image_get_comp(&hdr)) {
 	case IH_COMP_NONE:
+		dataptr = header;
+
+		/*
+		 * Image header will be skipped only if SPL_COPY_PAYLOAD_ONLY
+		 * is set
+		 */
+		if (spl_image->flags & SPL_COPY_PAYLOAD_ONLY)
+			dataptr += sizeof(hdr);
+
 		load->read(load, dataptr, spl_image->size,
 			   (void *)(unsigned long)spl_image->load_addr);
 		break;
@@ -104,6 +118,9 @@
 	case IH_COMP_LZMA:
 		lzma_len = LZMA_LEN;
 
+		/* dataptr points to compressed payload  */
+		dataptr = header + sizeof(hdr);
+
 		debug("LZMA: Decompressing %08lx to %08lx\n",
 		      dataptr, spl_image->load_addr);
 		src = malloc(spl_image->size);
diff --git a/common/spl/spl_nand.c b/common/spl/spl_nand.c
index 82a10ff..7b7579a 100644
--- a/common/spl/spl_nand.c
+++ b/common/spl/spl_nand.c
@@ -56,6 +56,21 @@
 	return size / load->bl_len;
 }
 
+static ulong spl_nand_legacy_read(struct spl_load_info *load, ulong offs,
+				  ulong size, void *dst)
+{
+	int err;
+
+	debug("%s: offs %lx, size %lx, dst %p\n",
+	      __func__, offs, size, dst);
+
+	err = nand_spl_load_image(offs, size, dst);
+	if (err)
+		return 0;
+
+	return size;
+}
+
 struct mtd_info * __weak nand_get_mtd(void)
 {
 	return NULL;
@@ -93,6 +108,18 @@
 		load.bl_len = bl_len;
 		load.read = spl_nand_fit_read;
 		return spl_load_imx_container(spl_image, &load, offset / bl_len);
+	} else if (IS_ENABLED(CONFIG_SPL_LEGACY_IMAGE_FORMAT) &&
+		   image_get_magic(header) == IH_MAGIC) {
+		struct spl_load_info load;
+
+		debug("Found legacy image\n");
+		load.dev = NULL;
+		load.priv = NULL;
+		load.filename = NULL;
+		load.bl_len = 1;
+		load.read = spl_nand_legacy_read;
+
+		return spl_load_legacy_img(spl_image, bootdev, &load, offset);
 	} else {
 		err = spl_parse_image_header(spl_image, bootdev, header);
 		if (err)
diff --git a/configs/mt7621_nand_rfb_defconfig b/configs/mt7621_nand_rfb_defconfig
new file mode 100644
index 0000000..0ad4849
--- /dev/null
+++ b/configs/mt7621_nand_rfb_defconfig
@@ -0,0 +1,89 @@
+CONFIG_MIPS=y
+CONFIG_SYS_MALLOC_LEN=0x100000
+CONFIG_SPL_LIBCOMMON_SUPPORT=y
+CONFIG_SPL_LIBGENERIC_SUPPORT=y
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x1000
+CONFIG_DEFAULT_DEVICE_TREE="mediatek,mt7621-nand-rfb"
+CONFIG_SPL_SERIAL=y
+CONFIG_SPL_SYS_MALLOC_F_LEN=0x100000
+CONFIG_SPL=y
+CONFIG_DEBUG_UART_BASE=0xbe000c00
+CONFIG_DEBUG_UART_CLOCK=50000000
+CONFIG_SYS_LOAD_ADDR=0x83000000
+CONFIG_ARCH_MTMIPS=y
+CONFIG_SOC_MT7621=y
+CONFIG_MT7621_BOOT_FROM_NAND=y
+CONFIG_BOARD_MT7621_NAND_RFB=y
+# CONFIG_MIPS_CACHE_SETUP is not set
+# CONFIG_MIPS_CACHE_DISABLE is not set
+CONFIG_RESTORE_EXCEPTION_VECTOR_BASE=y
+CONFIG_MIPS_BOOT_FDT=y
+CONFIG_DEBUG_UART=y
+CONFIG_FIT=y
+# CONFIG_ARCH_FIXUP_FDT_MEMORY is not set
+CONFIG_SYS_CONSOLE_INFO_QUIET=y
+CONFIG_SPL_MAX_SIZE=0x30000
+CONFIG_SPL_BSS_START_ADDR=0x80140000
+CONFIG_SPL_BSS_MAX_SIZE=0x80000
+CONFIG_SPL_SYS_MALLOC_SIMPLE=y
+CONFIG_SPL_NAND_SUPPORT=y
+CONFIG_SPL_NAND_BASE=y
+CONFIG_SPL_NAND_IDENT=y
+# CONFIG_BOOTM_NETBSD is not set
+# CONFIG_BOOTM_PLAN9 is not set
+# CONFIG_BOOTM_RTEMS is not set
+# CONFIG_BOOTM_VXWORKS is not set
+CONFIG_SYS_BOOTM_LEN=0x2000000
+# CONFIG_CMD_ELF is not set
+# CONFIG_CMD_XIMG is not set
+# CONFIG_CMD_CRC32 is not set
+# CONFIG_CMD_DM is not set
+# CONFIG_CMD_FLASH is not set
+CONFIG_CMD_GPIO=y
+# CONFIG_CMD_LOADS is not set
+CONFIG_CMD_MMC=y
+CONFIG_CMD_MTD=y
+CONFIG_CMD_PART=y
+# CONFIG_CMD_PINMUX is not set
+CONFIG_CMD_USB=y
+# CONFIG_CMD_NFS is not set
+CONFIG_CMD_FAT=y
+CONFIG_CMD_FS_GENERIC=y
+# CONFIG_SPL_DOS_PARTITION is not set
+# CONFIG_ISO_PARTITION is not set
+CONFIG_EFI_PARTITION=y
+# CONFIG_SPL_EFI_PARTITION is not set
+CONFIG_PARTITION_TYPE_GUID=y
+CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_NET_RANDOM_ETHADDR=y
+# CONFIG_I2C is not set
+# CONFIG_INPUT is not set
+CONFIG_MMC=y
+# CONFIG_MMC_QUIRKS is not set
+# CONFIG_MMC_HW_PARTITIONING is not set
+CONFIG_MMC_MTK=y
+CONFIG_MTD=y
+CONFIG_DM_MTD=y
+CONFIG_MTD_RAW_NAND=y
+CONFIG_NAND_MT7621=y
+CONFIG_SYS_NAND_ONFI_DETECTION=y
+CONFIG_SYS_NAND_U_BOOT_LOCATIONS=y
+CONFIG_SYS_NAND_U_BOOT_OFFS=0x0
+CONFIG_MEDIATEK_ETH=y
+CONFIG_PHY=y
+CONFIG_PHY_MTK_TPHY=y
+CONFIG_DEBUG_UART_SHIFT=2
+CONFIG_SYSRESET=y
+CONFIG_SYSRESET_RESETCTL=y
+CONFIG_USB=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MTK=y
+CONFIG_USB_STORAGE=y
+CONFIG_WDT=y
+CONFIG_WDT_MT7621=y
+CONFIG_FAT_WRITE=y
+# CONFIG_BINMAN_FDT is not set
+CONFIG_LZMA=y
+# CONFIG_GZIP is not set
+CONFIG_SPL_LZMA=y
diff --git a/configs/mt7621_rfb_defconfig b/configs/mt7621_rfb_defconfig
new file mode 100644
index 0000000..fa66364
--- /dev/null
+++ b/configs/mt7621_rfb_defconfig
@@ -0,0 +1,86 @@
+CONFIG_MIPS=y
+CONFIG_SYS_MALLOC_LEN=0x100000
+CONFIG_SPL_LIBCOMMON_SUPPORT=y
+CONFIG_SPL_LIBGENERIC_SUPPORT=y
+CONFIG_NR_DRAM_BANKS=1
+CONFIG_ENV_SIZE=0x1000
+CONFIG_DEFAULT_DEVICE_TREE="mediatek,mt7621-rfb"
+CONFIG_SPL_SERIAL=y
+CONFIG_SPL_SYS_MALLOC_F_LEN=0x40000
+CONFIG_SPL=y
+CONFIG_DEBUG_UART_BASE=0xbe000c00
+CONFIG_DEBUG_UART_CLOCK=50000000
+CONFIG_SYS_LOAD_ADDR=0x83000000
+CONFIG_ARCH_MTMIPS=y
+CONFIG_SOC_MT7621=y
+# CONFIG_MIPS_CACHE_SETUP is not set
+# CONFIG_MIPS_CACHE_DISABLE is not set
+CONFIG_RESTORE_EXCEPTION_VECTOR_BASE=y
+CONFIG_MIPS_BOOT_FDT=y
+CONFIG_DEBUG_UART=y
+CONFIG_TPL_SYS_MALLOC_F_LEN=0x1000
+CONFIG_FIT=y
+# CONFIG_ARCH_FIXUP_FDT_MEMORY is not set
+CONFIG_SYS_CONSOLE_INFO_QUIET=y
+CONFIG_SPL_MAX_SIZE=0x30000
+CONFIG_SPL_BSS_START_ADDR=0x80140000
+CONFIG_SPL_BSS_MAX_SIZE=0x80000
+CONFIG_SPL_SYS_MALLOC_SIMPLE=y
+CONFIG_SPL_NOR_SUPPORT=y
+CONFIG_TPL=y
+# CONFIG_TPL_FRAMEWORK is not set
+# CONFIG_BOOTM_NETBSD is not set
+# CONFIG_BOOTM_PLAN9 is not set
+# CONFIG_BOOTM_RTEMS is not set
+# CONFIG_BOOTM_VXWORKS is not set
+CONFIG_SYS_BOOTM_LEN=0x2000000
+# CONFIG_CMD_ELF is not set
+# CONFIG_CMD_XIMG is not set
+# CONFIG_CMD_CRC32 is not set
+# CONFIG_CMD_DM is not set
+CONFIG_CMD_GPIO=y
+# CONFIG_CMD_LOADS is not set
+CONFIG_CMD_MMC=y
+CONFIG_CMD_PART=y
+# CONFIG_CMD_PINMUX is not set
+CONFIG_CMD_SPI=y
+# CONFIG_CMD_NFS is not set
+CONFIG_DOS_PARTITION=y
+# CONFIG_SPL_DOS_PARTITION is not set
+# CONFIG_ISO_PARTITION is not set
+CONFIG_EFI_PARTITION=y
+# CONFIG_SPL_EFI_PARTITION is not set
+CONFIG_PARTITION_TYPE_GUID=y
+CONFIG_SYS_RELOC_GD_ENV_ADDR=y
+CONFIG_NET_RANDOM_ETHADDR=y
+# CONFIG_I2C is not set
+# CONFIG_INPUT is not set
+CONFIG_MMC=y
+# CONFIG_MMC_QUIRKS is not set
+# CONFIG_MMC_HW_PARTITIONING is not set
+CONFIG_MMC_MTK=y
+CONFIG_SF_DEFAULT_SPEED=20000000
+CONFIG_SPI_FLASH_BAR=y
+CONFIG_SPI_FLASH_EON=y
+CONFIG_SPI_FLASH_GIGADEVICE=y
+CONFIG_SPI_FLASH_ISSI=y
+CONFIG_SPI_FLASH_MACRONIX=y
+CONFIG_SPI_FLASH_SPANSION=y
+CONFIG_SPI_FLASH_STMICRO=y
+CONFIG_SPI_FLASH_WINBOND=y
+CONFIG_SPI_FLASH_XMC=y
+CONFIG_SPI_FLASH_XTX=y
+CONFIG_MEDIATEK_ETH=y
+CONFIG_PHY=y
+CONFIG_PHY_MTK_TPHY=y
+CONFIG_DEBUG_UART_SHIFT=2
+CONFIG_SPI=y
+CONFIG_MT7621_SPI=y
+CONFIG_SYSRESET=y
+CONFIG_SYSRESET_RESETCTL=y
+CONFIG_WDT=y
+CONFIG_WDT_MT7621=y
+# CONFIG_BINMAN_FDT is not set
+CONFIG_LZMA=y
+# CONFIG_GZIP is not set
+CONFIG_SPL_LZMA=y
diff --git a/doc/board/index.rst b/doc/board/index.rst
index f90a9ca..01b99f9 100644
--- a/doc/board/index.rst
+++ b/doc/board/index.rst
@@ -23,6 +23,7 @@
    highbank/index
    intel/index
    kontron/index
+   mediatek/index
    microchip/index
    nokia/index
    nxp/index
diff --git a/doc/board/mediatek/index.rst b/doc/board/mediatek/index.rst
new file mode 100644
index 0000000..38cd8cb
--- /dev/null
+++ b/doc/board/mediatek/index.rst
@@ -0,0 +1,9 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Mediatek
+=========
+
+.. toctree::
+   :maxdepth: 2
+
+   mt7621
diff --git a/doc/board/mediatek/mt7621.rst b/doc/board/mediatek/mt7621.rst
new file mode 100644
index 0000000..1662255
--- /dev/null
+++ b/doc/board/mediatek/mt7621.rst
@@ -0,0 +1,48 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+mt7621_rfb/mt7621_nand_rfb
+==========================
+
+U-Boot for the MediaTek MT7621 boards
+
+Quick Start
+-----------
+
+- Get the DDR initialization binary blob
+- Configure CPU and DDR parameters
+- Build U-Boot
+
+Get the DDR initialization binary blob
+--------------------------------------
+
+Download one from:
+ - https://raw.githubusercontent.com/mtk-openwrt/mt7621-lowlevel-preloader/master/mt7621_stage_sram.bin
+ - https://raw.githubusercontent.com/mtk-openwrt/mt7621-lowlevel-preloader/master/mt7621_stage_sram_noprint.bin
+
+mt7621_stage_sram_noprint.bin has removed all output logs. To use this one,
+download and rename it to mt7621_stage_sram.bin
+
+Put the binary blob to the u-boot build directory.
+
+Configure CPU and DDR parameters
+--------------------------------
+
+menuconfig > MIPS architecture > MediaTek MIPS platforms > CPU & DDR configuration
+
+Select the correct DDR timing parameters for your board. The size shown here
+must match the DDR size of you board.
+
+The frequency of CPU and DDR can also be adjusted.
+
+Build U-Boot
+------------
+
+.. code-block:: bash
+
+   $ export CROSS_COMPILE=mipsel-linux-
+   $ make O=build mt7621_rfb_defconfig # or mt7621_nand_rfb_defconfig
+   $ cp mt7621_stage_sram.bin ./build/mt7621_stage_sram.bin
+   $ # or cp mt7621_stage_sram_noprint.bin ./build/mt7621_stage_sram.bin
+   $ make O=build
+
+Burn the u-boot-mt7621.bin to the SPI-NOR or NAND flash.
diff --git a/drivers/clk/mtmips/Makefile b/drivers/clk/mtmips/Makefile
index 732e7f2..ee8b5af 100644
--- a/drivers/clk/mtmips/Makefile
+++ b/drivers/clk/mtmips/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_SOC_MT7620) += clk-mt7620.o
+obj-$(CONFIG_SOC_MT7621) += clk-mt7621.o
 obj-$(CONFIG_SOC_MT7628) += clk-mt7628.o
diff --git a/drivers/clk/mtmips/clk-mt7621.c b/drivers/clk/mtmips/clk-mt7621.c
new file mode 100644
index 0000000..03363b7
--- /dev/null
+++ b/drivers/clk/mtmips/clk-mt7621.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <dt-bindings/clock/mt7621-clk.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+
+#define SYSC_MAP_SIZE			0x100
+#define MEMC_MAP_SIZE			0x1000
+
+/* SYSC */
+#define SYSCFG0_REG			0x10
+#define XTAL_MODE_SEL			GENMASK(8, 6)
+
+#define CLKCFG0_REG			0x2c
+#define CPU_CLK_SEL			GENMASK(31, 30)
+#define PERI_CLK_SEL			BIT(4)
+
+#define CLKCFG1_REG			0x30
+
+#define CUR_CLK_STS_REG			0x44
+#define CUR_CPU_FDIV			GENMASK(12, 8)
+#define CUR_CPU_FFRAC			GENMASK(4, 0)
+
+/* MEMC */
+#define MEMPLL1_REG			0x0604
+#define RG_MEPL_DIV2_SEL		GENMASK(2, 1)
+
+#define MEMPLL6_REG			0x0618
+#define MEMPLL18_REG			0x0648
+#define RG_MEPL_PREDIV			GENMASK(13, 12)
+#define RG_MEPL_FBDIV			GENMASK(10, 4)
+
+/* Fixed 500M clock */
+#define GMPLL_CLK			500000000
+
+struct mt7621_clk_priv {
+	void __iomem *sysc_base;
+	int cpu_clk;
+	int ddr_clk;
+	int sys_clk;
+	int xtal_clk;
+};
+
+enum mt7621_clk_src {
+	CLK_SRC_CPU,
+	CLK_SRC_DDR,
+	CLK_SRC_SYS,
+	CLK_SRC_XTAL,
+	CLK_SRC_PERI,
+	CLK_SRC_125M,
+	CLK_SRC_150M,
+	CLK_SRC_250M,
+	CLK_SRC_270M,
+
+	__CLK_SRC_MAX
+};
+
+struct mt7621_clk_map {
+	u32 cgbit;
+	enum mt7621_clk_src clksrc;
+};
+
+#define CLK_MAP(_id, _cg, _src) \
+	[_id] = { .cgbit = (_cg), .clksrc = (_src) }
+
+#define CLK_MAP_SRC(_id, _src) \
+	[_id] = { .cgbit = UINT32_MAX, .clksrc = (_src) }
+
+static const struct mt7621_clk_map mt7621_clk_mappings[] = {
+	CLK_MAP_SRC(MT7621_CLK_XTAL, CLK_SRC_XTAL),
+	CLK_MAP_SRC(MT7621_CLK_CPU, CLK_SRC_CPU),
+	CLK_MAP_SRC(MT7621_CLK_BUS, CLK_SRC_SYS),
+	CLK_MAP_SRC(MT7621_CLK_50M, CLK_SRC_PERI),
+	CLK_MAP_SRC(MT7621_CLK_125M, CLK_SRC_125M),
+	CLK_MAP_SRC(MT7621_CLK_150M, CLK_SRC_150M),
+	CLK_MAP_SRC(MT7621_CLK_250M, CLK_SRC_250M),
+	CLK_MAP_SRC(MT7621_CLK_270M, CLK_SRC_270M),
+
+	CLK_MAP(MT7621_CLK_HSDMA, 5, CLK_SRC_150M),
+	CLK_MAP(MT7621_CLK_FE, 6, CLK_SRC_250M),
+	CLK_MAP(MT7621_CLK_SP_DIVTX, 7, CLK_SRC_270M),
+	CLK_MAP(MT7621_CLK_TIMER, 8, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_PCM, 11, CLK_SRC_270M),
+	CLK_MAP(MT7621_CLK_PIO, 13, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_GDMA, 14, CLK_SRC_SYS),
+	CLK_MAP(MT7621_CLK_NAND, 15, CLK_SRC_125M),
+	CLK_MAP(MT7621_CLK_I2C, 16, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_I2S, 17, CLK_SRC_270M),
+	CLK_MAP(MT7621_CLK_SPI, 18, CLK_SRC_SYS),
+	CLK_MAP(MT7621_CLK_UART1, 19, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_UART2, 20, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_UART3, 21, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_ETH, 23, CLK_SRC_PERI),
+	CLK_MAP(MT7621_CLK_PCIE0, 24, CLK_SRC_125M),
+	CLK_MAP(MT7621_CLK_PCIE1, 25, CLK_SRC_125M),
+	CLK_MAP(MT7621_CLK_PCIE2, 26, CLK_SRC_125M),
+	CLK_MAP(MT7621_CLK_CRYPTO, 29, CLK_SRC_250M),
+	CLK_MAP(MT7621_CLK_SHXC, 30, CLK_SRC_PERI),
+
+	CLK_MAP_SRC(MT7621_CLK_MAX, __CLK_SRC_MAX),
+
+	CLK_MAP_SRC(MT7621_CLK_DDR, CLK_SRC_DDR),
+};
+
+static ulong mt7621_clk_get_rate(struct clk *clk)
+{
+	struct mt7621_clk_priv *priv = dev_get_priv(clk->dev);
+	u32 val;
+
+	switch (mt7621_clk_mappings[clk->id].clksrc) {
+	case CLK_SRC_CPU:
+		return priv->cpu_clk;
+	case CLK_SRC_DDR:
+		return priv->ddr_clk;
+	case CLK_SRC_SYS:
+		return priv->sys_clk;
+	case CLK_SRC_XTAL:
+		return priv->xtal_clk;
+	case CLK_SRC_PERI:
+		val = readl(priv->sysc_base + CLKCFG0_REG);
+		if (val & PERI_CLK_SEL)
+			return priv->xtal_clk;
+		else
+			return GMPLL_CLK / 10;
+	case CLK_SRC_125M:
+		return 125000000;
+	case CLK_SRC_150M:
+		return 150000000;
+	case CLK_SRC_250M:
+		return 250000000;
+	case CLK_SRC_270M:
+		return 270000000;
+	default:
+		return 0;
+	}
+}
+
+static int mt7621_clk_enable(struct clk *clk)
+{
+	struct mt7621_clk_priv *priv = dev_get_priv(clk->dev);
+	u32 cgbit;
+
+	cgbit = mt7621_clk_mappings[clk->id].cgbit;
+	if (cgbit == UINT32_MAX)
+		return -ENOSYS;
+
+	setbits_32(priv->sysc_base + CLKCFG1_REG, BIT(cgbit));
+
+	return 0;
+}
+
+static int mt7621_clk_disable(struct clk *clk)
+{
+	struct mt7621_clk_priv *priv = dev_get_priv(clk->dev);
+	u32 cgbit;
+
+	cgbit = mt7621_clk_mappings[clk->id].cgbit;
+	if (cgbit == UINT32_MAX)
+		return -ENOSYS;
+
+	clrbits_32(priv->sysc_base + CLKCFG1_REG, BIT(cgbit));
+
+	return 0;
+}
+
+static int mt7621_clk_request(struct clk *clk)
+{
+	if (clk->id >= ARRAY_SIZE(mt7621_clk_mappings))
+		return -EINVAL;
+	return 0;
+}
+
+const struct clk_ops mt7621_clk_ops = {
+	.request = mt7621_clk_request,
+	.enable = mt7621_clk_enable,
+	.disable = mt7621_clk_disable,
+	.get_rate = mt7621_clk_get_rate,
+};
+
+static void mt7621_get_clocks(struct mt7621_clk_priv *priv, struct regmap *memc)
+{
+	u32 bs, xtal_sel, clkcfg0, cur_clk, mempll, dividx, fb;
+	u32 xtal_clk, xtal_div, ffiv, ffrac, cpu_clk, ddr_clk;
+	static const u32 xtal_div_tbl[] = {0, 1, 2, 2};
+
+	bs = readl(priv->sysc_base + SYSCFG0_REG);
+	clkcfg0 = readl(priv->sysc_base + CLKCFG0_REG);
+	cur_clk = readl(priv->sysc_base + CUR_CLK_STS_REG);
+
+	xtal_sel = FIELD_GET(XTAL_MODE_SEL, bs);
+
+	if (xtal_sel <= 2)
+		xtal_clk = 20 * 1000 * 1000;
+	else if (xtal_sel <= 5)
+		xtal_clk = 40 * 1000 * 1000;
+	else
+		xtal_clk = 25 * 1000 * 1000;
+
+	switch (FIELD_GET(CPU_CLK_SEL, clkcfg0)) {
+	case 0:
+		cpu_clk = GMPLL_CLK;
+		break;
+	case 1:
+		regmap_read(memc, MEMPLL18_REG, &mempll);
+		dividx = FIELD_GET(RG_MEPL_PREDIV, mempll);
+		fb = FIELD_GET(RG_MEPL_FBDIV, mempll);
+		xtal_div = 1 << xtal_div_tbl[dividx];
+		cpu_clk = (fb + 1) * xtal_clk / xtal_div;
+		break;
+	default:
+		cpu_clk = xtal_clk;
+	}
+
+	ffiv = FIELD_GET(CUR_CPU_FDIV, cur_clk);
+	ffrac = FIELD_GET(CUR_CPU_FFRAC, cur_clk);
+	cpu_clk = cpu_clk / ffiv * ffrac;
+
+	regmap_read(memc, MEMPLL6_REG, &mempll);
+	dividx = FIELD_GET(RG_MEPL_PREDIV, mempll);
+	fb = FIELD_GET(RG_MEPL_FBDIV, mempll);
+	xtal_div = 1 << xtal_div_tbl[dividx];
+	ddr_clk = fb * xtal_clk / xtal_div;
+
+	regmap_read(memc, MEMPLL1_REG, &bs);
+	if (!FIELD_GET(RG_MEPL_DIV2_SEL, bs))
+		ddr_clk *= 2;
+
+	priv->cpu_clk = cpu_clk;
+	priv->sys_clk = cpu_clk / 4;
+	priv->ddr_clk = ddr_clk;
+	priv->xtal_clk = xtal_clk;
+}
+
+static int mt7621_clk_probe(struct udevice *dev)
+{
+	struct mt7621_clk_priv *priv = dev_get_priv(dev);
+	struct ofnode_phandle_args args;
+	struct udevice *pdev;
+	struct regmap *memc;
+	int ret;
+
+	pdev = dev_get_parent(dev);
+	if (!pdev)
+		return -ENODEV;
+
+	priv->sysc_base = dev_remap_addr(pdev);
+	if (!priv->sysc_base)
+		return -EINVAL;
+
+	/* get corresponding memc phandle */
+	ret = dev_read_phandle_with_args(dev, "mediatek,memc", NULL, 0, 0,
+					 &args);
+	if (ret)
+		return ret;
+
+	memc = syscon_node_to_regmap(args.node);
+	if (IS_ERR(memc))
+		return PTR_ERR(memc);
+
+	mt7621_get_clocks(priv, memc);
+
+	return 0;
+}
+
+static const struct udevice_id mt7621_clk_ids[] = {
+	{ .compatible = "mediatek,mt7621-clk" },
+	{ }
+};
+
+U_BOOT_DRIVER(mt7621_clk) = {
+	.name = "mt7621-clk",
+	.id = UCLASS_CLK,
+	.of_match = mt7621_clk_ids,
+	.probe = mt7621_clk_probe,
+	.priv_auto = sizeof(struct mt7621_clk_priv),
+	.ops = &mt7621_clk_ops,
+};
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 3c73a7f..aaa152f 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -553,7 +553,7 @@
 
 config MT7621_GPIO
 	bool "MediaTek MT7621 GPIO driver"
-	depends on DM_GPIO && SOC_MT7628
+	depends on DM_GPIO && (SOC_MT7621 || SOC_MT7628)
 	default y
 	help
 	  Say yes here to support MediaTek MT7621 compatible GPIOs.
diff --git a/drivers/mmc/mtk-sd.c b/drivers/mmc/mtk-sd.c
index 97182ff..e61e8cf 100644
--- a/drivers/mmc/mtk-sd.c
+++ b/drivers/mmc/mtk-sd.c
@@ -1761,6 +1761,18 @@
 	.default_pad_dly = true,
 };
 
+static const struct msdc_compatible mt7621_compat = {
+	.clk_div_bits = 8,
+	.pad_tune0 = false,
+	.async_fifo = true,
+	.data_tune = true,
+	.busy_check = false,
+	.stop_clk_fix = false,
+	.enhance_rx = false,
+	.builtin_pad_ctrl = true,
+	.default_pad_dly = true,
+};
+
 static const struct msdc_compatible mt7622_compat = {
 	.clk_div_bits = 12,
 	.pad_tune0 = true,
@@ -1809,6 +1821,7 @@
 
 static const struct udevice_id msdc_ids[] = {
 	{ .compatible = "mediatek,mt7620-mmc", .data = (ulong)&mt7620_compat },
+	{ .compatible = "mediatek,mt7621-mmc", .data = (ulong)&mt7621_compat },
 	{ .compatible = "mediatek,mt7622-mmc", .data = (ulong)&mt7622_compat },
 	{ .compatible = "mediatek,mt7623-mmc", .data = (ulong)&mt7623_compat },
 	{ .compatible = "mediatek,mt8512-mmc", .data = (ulong)&mt8512_compat },
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index 190300f..ce67d1a 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -526,12 +526,25 @@
 	help
 	  Enables support for NAND Flash chips on Tegra SoCs platforms.
 
+config NAND_MT7621
+	bool "Support for MediaTek MT7621 NAND flash controller"
+	depends on SOC_MT7621
+	select SYS_NAND_SELF_INIT
+	select SPL_SYS_NAND_SELF_INIT
+	imply CMD_NAND
+	help
+	  This enables NAND driver for the NAND flash controller on MediaTek
+	  MT7621 platform.
+	  The controller supports 4~12 bits correction per 512 bytes with a
+	  maximum 4KB page size.
+
 comment "Generic NAND options"
 
 config SYS_NAND_BLOCK_SIZE
 	hex "NAND chip eraseblock size"
 	depends on ARCH_SUNXI || SPL_NAND_SUPPORT || TPL_NAND_SUPPORT
-	depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && !NAND_FSL_IFC
+	depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && \
+		!NAND_FSL_IFC && !NAND_MT7621
 	help
 	  Number of data bytes in one eraseblock for the NAND chip on the
 	  board. This is the multiple of NAND_PAGE_SIZE and the number of
@@ -556,7 +569,7 @@
 	depends on ARCH_SUNXI || NAND_OMAP_GPMC || NAND_LPC32XX_SLC || \
 		SPL_NAND_SIMPLE || (NAND_MXC && SPL_NAND_SUPPORT) || \
 		(NAND_ATMEL && SPL_NAND_SUPPORT) || SPL_GENERATE_ATMEL_PMECC_HEADER
-	depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC
+	depends on !NAND_MXS && !NAND_DENALI_DT && !NAND_LPC32XX_MLC && !NAND_MT7621
 	help
 	  Number of data bytes in one page for the NAND chip on the
 	  board, not including the OOB area.
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index e3f6b90..f278f31 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -72,6 +72,7 @@
 obj-$(CONFIG_NAND_STM32_FMC2) += stm32_fmc2_nand.o
 obj-$(CONFIG_CORTINA_NAND) += cortina_nand.o
 obj-$(CONFIG_ROCKCHIP_NAND) += rockchip_nfc.o
+obj-$(CONFIG_NAND_MT7621) += mt7621_nand.o
 
 else  # minimal SPL drivers
 
@@ -80,5 +81,6 @@
 obj-$(CONFIG_NAND_MXC) += mxc_nand_spl.o
 obj-$(CONFIG_NAND_MXS) += mxs_nand_spl.o mxs_nand.o
 obj-$(CONFIG_NAND_SUNXI) += sunxi_nand_spl.o
+obj-$(CONFIG_NAND_MT7621) += mt7621_nand_spl.o mt7621_nand.o
 
 endif # drivers
diff --git a/drivers/mtd/nand/raw/mt7621_nand.c b/drivers/mtd/nand/raw/mt7621_nand.c
new file mode 100644
index 0000000..2fd8934
--- /dev/null
+++ b/drivers/mtd/nand/raw/mt7621_nand.c
@@ -0,0 +1,1205 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <log.h>
+#include <nand.h>
+#include <malloc.h>
+#include <asm/addrspace.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/sizes.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include "mt7621_nand.h"
+
+/* NFI core registers */
+#define NFI_CNFG			0x000
+#define   CNFG_OP_MODE			GENMASK(14, 12)
+#define     CNFG_OP_CUSTOM		6
+#define   CNFG_AUTO_FMT_EN		BIT(9)
+#define   CNFG_HW_ECC_EN		BIT(8)
+#define   CNFG_BYTE_RW			BIT(6)
+#define   CNFG_READ_MODE		BIT(1)
+
+#define NFI_PAGEFMT			0x004
+#define   PAGEFMT_FDM_ECC		GENMASK(15, 12)
+#define   PAGEFMT_FDM			GENMASK(11, 8)
+#define   PAGEFMT_SPARE			GENMASK(5, 4)
+#define   PAGEFMT_PAGE			GENMASK(1, 0)
+
+#define NFI_CON				0x008
+#define   CON_NFI_SEC			GENMASK(15, 12)
+#define   CON_NFI_BWR			BIT(9)
+#define   CON_NFI_BRD			BIT(8)
+#define   CON_NFI_RST			BIT(1)
+#define   CON_FIFO_FLUSH		BIT(0)
+
+#define NFI_ACCCON			0x00c
+#define   ACCCON_POECS			GENMASK(31, 28)
+#define   ACCCON_POECS_DEF		3
+#define   ACCCON_PRECS			GENMASK(27, 22)
+#define   ACCCON_PRECS_DEF		3
+#define   ACCCON_C2R			GENMASK(21, 16)
+#define   ACCCON_C2R_DEF		7
+#define   ACCCON_W2R			GENMASK(15, 12)
+#define   ACCCON_W2R_DEF		7
+#define   ACCCON_WH			GENMASK(11, 8)
+#define   ACCCON_WH_DEF			15
+#define   ACCCON_WST			GENMASK(7, 4)
+#define   ACCCON_WST_DEF		15
+#define   ACCCON_WST_MIN		3
+#define   ACCCON_RLT			GENMASK(3, 0)
+#define   ACCCON_RLT_DEF		15
+#define   ACCCON_RLT_MIN		3
+
+#define NFI_CMD				0x020
+
+#define NFI_ADDRNOB			0x030
+#define   ADDR_ROW_NOB			GENMASK(6, 4)
+#define   ADDR_COL_NOB			GENMASK(2, 0)
+
+#define NFI_COLADDR			0x034
+#define NFI_ROWADDR			0x038
+
+#define NFI_STRDATA			0x040
+#define   STR_DATA			BIT(0)
+
+#define NFI_CNRNB			0x044
+#define   CB2R_TIME			GENMASK(7, 4)
+#define   STR_CNRNB			BIT(0)
+
+#define NFI_DATAW			0x050
+#define NFI_DATAR			0x054
+
+#define NFI_PIO_DIRDY			0x058
+#define   PIO_DIRDY			BIT(0)
+
+#define NFI_STA				0x060
+#define   STA_NFI_FSM			GENMASK(19, 16)
+#define     STA_FSM_CUSTOM_DATA		14
+#define   STA_BUSY			BIT(8)
+#define   STA_ADDR			BIT(1)
+#define   STA_CMD			BIT(0)
+
+#define NFI_ADDRCNTR			0x070
+#define   SEC_CNTR			GENMASK(15, 12)
+#define   SEC_ADDR			GENMASK(9, 0)
+
+#define NFI_CSEL			0x090
+#define   CSEL				GENMASK(1, 0)
+
+#define NFI_FDM0L			0x0a0
+#define NFI_FDML(n)			(0x0a0 + ((n) << 3))
+
+#define NFI_FDM0M			0x0a4
+#define NFI_FDMM(n)			(0x0a4 + ((n) << 3))
+
+#define NFI_MASTER_STA			0x210
+#define   MAS_ADDR			GENMASK(11, 9)
+#define   MAS_RD			GENMASK(8, 6)
+#define   MAS_WR			GENMASK(5, 3)
+#define   MAS_RDDLY			GENMASK(2, 0)
+
+/* ECC engine registers */
+#define ECC_ENCCON			0x000
+#define   ENC_EN			BIT(0)
+
+#define ECC_ENCCNFG			0x004
+#define   ENC_CNFG_MSG			GENMASK(28, 16)
+#define   ENC_MODE			GENMASK(5, 4)
+#define     ENC_MODE_NFI		1
+#define   ENC_TNUM			GENMASK(2, 0)
+
+#define ECC_ENCIDLE			0x00c
+#define   ENC_IDLE			BIT(0)
+
+#define ECC_DECCON			0x100
+#define   DEC_EN			BIT(0)
+
+#define ECC_DECCNFG			0x104
+#define   DEC_EMPTY_EN			BIT(31)
+#define   DEC_CS			GENMASK(28, 16)
+#define   DEC_CON			GENMASK(13, 12)
+#define     DEC_CON_EL			2
+#define   DEC_MODE			GENMASK(5, 4)
+#define     DEC_MODE_NFI		1
+#define   DEC_TNUM			GENMASK(2, 0)
+
+#define ECC_DECIDLE			0x10c
+#define   DEC_IDLE			BIT(1)
+
+#define ECC_DECENUM			0x114
+#define   ERRNUM_S			2
+#define   ERRNUM_M			GENMASK(3, 0)
+
+#define ECC_DECDONE			0x118
+#define   DEC_DONE7			BIT(7)
+#define   DEC_DONE6			BIT(6)
+#define   DEC_DONE5			BIT(5)
+#define   DEC_DONE4			BIT(4)
+#define   DEC_DONE3			BIT(3)
+#define   DEC_DONE2			BIT(2)
+#define   DEC_DONE1			BIT(1)
+#define   DEC_DONE0			BIT(0)
+
+#define ECC_DECEL(n)			(0x11c + (n) * 4)
+#define   DEC_EL_ODD_S			16
+#define   DEC_EL_M			0x1fff
+#define   DEC_EL_BYTE_POS_S		3
+#define   DEC_EL_BIT_POS_M		GENMASK(2, 0)
+
+#define ECC_FDMADDR			0x13c
+
+/* ENCIDLE and DECIDLE */
+#define   ECC_IDLE			BIT(0)
+
+#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \
+	(FIELD_PREP(ACCCON_POECS, tpoecs) | \
+	 FIELD_PREP(ACCCON_PRECS, tprecs) | \
+	 FIELD_PREP(ACCCON_C2R, tc2r) | \
+	 FIELD_PREP(ACCCON_W2R, tw2r) | \
+	 FIELD_PREP(ACCCON_WH, twh) | \
+	 FIELD_PREP(ACCCON_WST, twst) | \
+	 FIELD_PREP(ACCCON_RLT, trlt))
+
+#define MASTER_STA_MASK			(MAS_ADDR | MAS_RD | MAS_WR | \
+					 MAS_RDDLY)
+#define NFI_RESET_TIMEOUT		1000000
+#define NFI_CORE_TIMEOUT		500000
+#define ECC_ENGINE_TIMEOUT		500000
+
+#define ECC_SECTOR_SIZE			512
+#define ECC_PARITY_BITS			13
+
+#define NFI_FDM_SIZE			8
+
+/* Register base */
+#define NFI_BASE			0x1e003000
+#define NFI_ECC_BASE			0x1e003800
+
+static struct mt7621_nfc nfc_dev;
+
+static const u16 mt7621_nfi_page_size[] = { SZ_512, SZ_2K, SZ_4K };
+static const u8 mt7621_nfi_spare_size[] = { 16, 26, 27, 28 };
+static const u8 mt7621_ecc_strength[] = { 4, 6, 8, 10, 12 };
+
+static inline u32 nfi_read32(struct mt7621_nfc *nfc, u32 reg)
+{
+	return readl(nfc->nfi_regs + reg);
+}
+
+static inline void nfi_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
+{
+	writel(val, nfc->nfi_regs + reg);
+}
+
+static inline u16 nfi_read16(struct mt7621_nfc *nfc, u32 reg)
+{
+	return readw(nfc->nfi_regs + reg);
+}
+
+static inline void nfi_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
+{
+	writew(val, nfc->nfi_regs + reg);
+}
+
+static inline void ecc_write16(struct mt7621_nfc *nfc, u32 reg, u16 val)
+{
+	writew(val, nfc->ecc_regs + reg);
+}
+
+static inline u32 ecc_read32(struct mt7621_nfc *nfc, u32 reg)
+{
+	return readl(nfc->ecc_regs + reg);
+}
+
+static inline void ecc_write32(struct mt7621_nfc *nfc, u32 reg, u32 val)
+{
+	return writel(val, nfc->ecc_regs + reg);
+}
+
+static inline u8 *oob_fdm_ptr(struct nand_chip *nand, int sect)
+{
+	return nand->oob_poi + sect * NFI_FDM_SIZE;
+}
+
+static inline u8 *oob_ecc_ptr(struct mt7621_nfc *nfc, int sect)
+{
+	struct nand_chip *nand = &nfc->nand;
+
+	return nand->oob_poi + nand->ecc.steps * NFI_FDM_SIZE +
+		sect * (nfc->spare_per_sector - NFI_FDM_SIZE);
+}
+
+static inline u8 *page_data_ptr(struct nand_chip *nand, const u8 *buf,
+				int sect)
+{
+	return (u8 *)buf + sect * nand->ecc.size;
+}
+
+static int mt7621_ecc_wait_idle(struct mt7621_nfc *nfc, u32 reg)
+{
+	u32 val;
+	int ret;
+
+	ret = readw_poll_timeout(nfc->ecc_regs + reg, val, val & ECC_IDLE,
+				 ECC_ENGINE_TIMEOUT);
+	if (ret) {
+		pr_warn("ECC engine timed out entering idle mode\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int mt7621_ecc_decoder_wait_done(struct mt7621_nfc *nfc, u32 sect)
+{
+	u32 val;
+	int ret;
+
+	ret = readw_poll_timeout(nfc->ecc_regs + ECC_DECDONE, val,
+				 val & (1 << sect), ECC_ENGINE_TIMEOUT);
+	if (ret) {
+		pr_warn("ECC decoder for sector %d timed out\n", sect);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static void mt7621_ecc_encoder_op(struct mt7621_nfc *nfc, bool enable)
+{
+	mt7621_ecc_wait_idle(nfc, ECC_ENCIDLE);
+	ecc_write16(nfc, ECC_ENCCON, enable ? ENC_EN : 0);
+}
+
+static void mt7621_ecc_decoder_op(struct mt7621_nfc *nfc, bool enable)
+{
+	mt7621_ecc_wait_idle(nfc, ECC_DECIDLE);
+	ecc_write16(nfc, ECC_DECCON, enable ? DEC_EN : 0);
+}
+
+static int mt7621_ecc_correct_check(struct mt7621_nfc *nfc, u8 *sector_buf,
+				    u8 *fdm_buf, u32 sect)
+{
+	struct nand_chip *nand = &nfc->nand;
+	u32 decnum, num_error_bits, fdm_end_bits;
+	u32 error_locations, error_bit_loc;
+	u32 error_byte_pos, error_bit_pos;
+	int bitflips = 0;
+	u32 i;
+
+	decnum = ecc_read32(nfc, ECC_DECENUM);
+	num_error_bits = (decnum >> (sect << ERRNUM_S)) & ERRNUM_M;
+	fdm_end_bits = (nand->ecc.size + NFI_FDM_SIZE) << 3;
+
+	if (!num_error_bits)
+		return 0;
+
+	if (num_error_bits == ERRNUM_M)
+		return -1;
+
+	for (i = 0; i < num_error_bits; i++) {
+		error_locations = ecc_read32(nfc, ECC_DECEL(i / 2));
+		error_bit_loc = (error_locations >> ((i % 2) * DEC_EL_ODD_S)) &
+				DEC_EL_M;
+		error_byte_pos = error_bit_loc >> DEC_EL_BYTE_POS_S;
+		error_bit_pos = error_bit_loc & DEC_EL_BIT_POS_M;
+
+		if (error_bit_loc < (nand->ecc.size << 3)) {
+			if (sector_buf) {
+				sector_buf[error_byte_pos] ^=
+					(1 << error_bit_pos);
+			}
+		} else if (error_bit_loc < fdm_end_bits) {
+			if (fdm_buf) {
+				fdm_buf[error_byte_pos - nand->ecc.size] ^=
+					(1 << error_bit_pos);
+			}
+		}
+
+		bitflips++;
+	}
+
+	return bitflips;
+}
+
+static int mt7621_nfc_wait_write_completion(struct mt7621_nfc *nfc,
+					    struct nand_chip *nand)
+{
+	u16 val;
+	int ret;
+
+	ret = readw_poll_timeout(nfc->nfi_regs + NFI_ADDRCNTR, val,
+				 FIELD_GET(SEC_CNTR, val) >= nand->ecc.steps,
+				 NFI_CORE_TIMEOUT);
+
+	if (ret) {
+		pr_warn("NFI core write operation timed out\n");
+		return -ETIMEDOUT;
+	}
+
+	return ret;
+}
+
+static void mt7621_nfc_hw_reset(struct mt7621_nfc *nfc)
+{
+	u32 val;
+	int ret;
+
+	/* reset all registers and force the NFI master to terminate */
+	nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
+
+	/* wait for the master to finish the last transaction */
+	ret = readw_poll_timeout(nfc->nfi_regs + NFI_MASTER_STA, val,
+				 !(val & MASTER_STA_MASK), NFI_RESET_TIMEOUT);
+	if (ret) {
+		pr_warn("Failed to reset NFI master in %dms\n",
+			NFI_RESET_TIMEOUT);
+	}
+
+	/* ensure any status register affected by the NFI master is reset */
+	nfi_write16(nfc, NFI_CON, CON_FIFO_FLUSH | CON_NFI_RST);
+	nfi_write16(nfc, NFI_STRDATA, 0);
+}
+
+static inline void mt7621_nfc_hw_init(struct mt7621_nfc *nfc)
+{
+	u32 acccon;
+
+	/*
+	 * CNRNB: nand ready/busy register
+	 * -------------------------------
+	 * 7:4: timeout register for polling the NAND busy/ready signal
+	 * 0  : poll the status of the busy/ready signal after [7:4]*16 cycles.
+	 */
+	nfi_write16(nfc, NFI_CNRNB, CB2R_TIME | STR_CNRNB);
+
+	mt7621_nfc_hw_reset(nfc);
+
+	/* Apply default access timing */
+	acccon = ACCTIMING(ACCCON_POECS_DEF, ACCCON_PRECS_DEF, ACCCON_C2R_DEF,
+			   ACCCON_W2R_DEF, ACCCON_WH_DEF, ACCCON_WST_DEF,
+			   ACCCON_RLT_DEF);
+
+	nfi_write32(nfc, NFI_ACCCON, acccon);
+}
+
+static int mt7621_nfc_send_command(struct mt7621_nfc *nfc, u8 command)
+{
+	u32 val;
+	int ret;
+
+	nfi_write32(nfc, NFI_CMD, command);
+
+	ret = readl_poll_timeout(nfc->nfi_regs + NFI_STA, val, !(val & STA_CMD),
+				 NFI_CORE_TIMEOUT);
+	if (ret) {
+		pr_warn("NFI core timed out entering command mode\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int mt7621_nfc_send_address_byte(struct mt7621_nfc *nfc, int addr)
+{
+	u32 val;
+	int ret;
+
+	nfi_write32(nfc, NFI_COLADDR, addr);
+	nfi_write32(nfc, NFI_ROWADDR, 0);
+	nfi_write16(nfc, NFI_ADDRNOB, 1);
+
+	ret = readl_poll_timeout(nfc->nfi_regs + NFI_STA, val,
+				 !(val & STA_ADDR), NFI_CORE_TIMEOUT);
+	if (ret) {
+		pr_warn("NFI core timed out entering address mode\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void mt7621_nfc_cmd_ctrl(struct mtd_info *mtd, int dat,
+				unsigned int ctrl)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	if (ctrl & NAND_ALE) {
+		mt7621_nfc_send_address_byte(nfc, dat & 0xff);
+	} else if (ctrl & NAND_CLE) {
+		mt7621_nfc_hw_reset(nfc);
+		nfi_write16(nfc, NFI_CNFG,
+			    FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM));
+		mt7621_nfc_send_command(nfc, dat);
+	}
+}
+
+static int mt7621_nfc_dev_ready(struct mtd_info *mtd)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	if (nfi_read32(nfc, NFI_STA) & STA_BUSY)
+		return 0;
+
+	return 1;
+}
+
+static void mt7621_nfc_select_chip(struct mtd_info *mtd, int chipnr)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	nfi_write16(nfc, NFI_CSEL, 0);
+}
+
+static void mt7621_nfc_wait_pio_ready(struct mt7621_nfc *nfc)
+{
+	int ret;
+	u16 val;
+
+	ret = readw_poll_timeout(nfc->nfi_regs + NFI_PIO_DIRDY, val,
+				 val & PIO_DIRDY, NFI_CORE_TIMEOUT);
+	if (ret < 0)
+		pr_err("NFI core PIO mode not ready\n");
+}
+
+static u32 mt7621_nfc_pio_read(struct mt7621_nfc *nfc, bool br)
+{
+	u32 reg, fsm;
+
+	/* after each byte read, the NFI_STA reg is reset by the hardware */
+	reg = nfi_read32(nfc, NFI_STA);
+	fsm = FIELD_GET(STA_NFI_FSM, reg);
+
+	if (fsm != STA_FSM_CUSTOM_DATA) {
+		reg = nfi_read16(nfc, NFI_CNFG);
+		reg |= CNFG_READ_MODE | CNFG_BYTE_RW;
+		if (!br)
+			reg &= ~CNFG_BYTE_RW;
+		nfi_write16(nfc, NFI_CNFG, reg);
+
+		/*
+		 * set to max sector to allow the HW to continue reading over
+		 * unaligned accesses
+		 */
+		nfi_write16(nfc, NFI_CON, CON_NFI_SEC | CON_NFI_BRD);
+
+		/* trigger to fetch data */
+		nfi_write16(nfc, NFI_STRDATA, STR_DATA);
+	}
+
+	mt7621_nfc_wait_pio_ready(nfc);
+
+	return nfi_read32(nfc, NFI_DATAR);
+}
+
+static void mt7621_nfc_read_data(struct mt7621_nfc *nfc, u8 *buf, u32 len)
+{
+	while (((uintptr_t)buf & 3) && len) {
+		*buf = mt7621_nfc_pio_read(nfc, true);
+		buf++;
+		len--;
+	}
+
+	while (len >= 4) {
+		*(u32 *)buf = mt7621_nfc_pio_read(nfc, false);
+		buf += 4;
+		len -= 4;
+	}
+
+	while (len) {
+		*buf = mt7621_nfc_pio_read(nfc, true);
+		buf++;
+		len--;
+	}
+}
+
+static void mt7621_nfc_read_data_discard(struct mt7621_nfc *nfc, u32 len)
+{
+	while (len >= 4) {
+		mt7621_nfc_pio_read(nfc, false);
+		len -= 4;
+	}
+
+	while (len) {
+		mt7621_nfc_pio_read(nfc, true);
+		len--;
+	}
+}
+
+static void mt7621_nfc_pio_write(struct mt7621_nfc *nfc, u32 val, bool bw)
+{
+	u32 reg, fsm;
+
+	reg = nfi_read32(nfc, NFI_STA);
+	fsm = FIELD_GET(STA_NFI_FSM, reg);
+
+	if (fsm != STA_FSM_CUSTOM_DATA) {
+		reg = nfi_read16(nfc, NFI_CNFG);
+		reg &= ~(CNFG_READ_MODE | CNFG_BYTE_RW);
+		if (bw)
+			reg |= CNFG_BYTE_RW;
+		nfi_write16(nfc, NFI_CNFG, reg);
+
+		nfi_write16(nfc, NFI_CON, CON_NFI_SEC | CON_NFI_BWR);
+		nfi_write16(nfc, NFI_STRDATA, STR_DATA);
+	}
+
+	mt7621_nfc_wait_pio_ready(nfc);
+	nfi_write32(nfc, NFI_DATAW, val);
+}
+
+static void mt7621_nfc_write_data(struct mt7621_nfc *nfc, const u8 *buf,
+				  u32 len)
+{
+	while (((uintptr_t)buf & 3) && len) {
+		mt7621_nfc_pio_write(nfc, *buf, true);
+		buf++;
+		len--;
+	}
+
+	while (len >= 4) {
+		mt7621_nfc_pio_write(nfc, *(const u32 *)buf, false);
+		buf += 4;
+		len -= 4;
+	}
+
+	while (len) {
+		mt7621_nfc_pio_write(nfc, *buf, true);
+		buf++;
+		len--;
+	}
+}
+
+static void mt7621_nfc_write_data_empty(struct mt7621_nfc *nfc, u32 len)
+{
+	while (len >= 4) {
+		mt7621_nfc_pio_write(nfc, 0xffffffff, false);
+		len -= 4;
+	}
+
+	while (len) {
+		mt7621_nfc_pio_write(nfc, 0xff, true);
+		len--;
+	}
+}
+
+static void mt7621_nfc_write_byte(struct mtd_info *mtd, u8 byte)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	mt7621_nfc_pio_write(nfc, byte, true);
+}
+
+static void mt7621_nfc_write_buf(struct mtd_info *mtd, const u8 *buf, int len)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	return mt7621_nfc_write_data(nfc, buf, len);
+}
+
+static u8 mt7621_nfc_read_byte(struct mtd_info *mtd)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	return mt7621_nfc_pio_read(nfc, true);
+}
+
+static void mt7621_nfc_read_buf(struct mtd_info *mtd, u8 *buf, int len)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd));
+
+	mt7621_nfc_read_data(nfc, buf, len);
+}
+
+static int mt7621_nfc_calc_ecc_strength(struct mt7621_nfc *nfc,
+					u32 avail_ecc_bytes)
+{
+	struct nand_chip *nand = &nfc->nand;
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	u32 strength;
+	int i;
+
+	strength = avail_ecc_bytes * 8 / ECC_PARITY_BITS;
+
+	/* Find the closest supported ecc strength */
+	for (i = ARRAY_SIZE(mt7621_ecc_strength) - 1; i >= 0; i--) {
+		if (mt7621_ecc_strength[i] <= strength)
+			break;
+	}
+
+	if (unlikely(i < 0)) {
+		pr_err("OOB size (%u) is not supported\n", mtd->oobsize);
+		return -EINVAL;
+	}
+
+	nand->ecc.strength = mt7621_ecc_strength[i];
+	nand->ecc.bytes = DIV_ROUND_UP(nand->ecc.strength * ECC_PARITY_BITS, 8);
+
+	pr_debug("ECC strength adjusted to %u bits\n", nand->ecc.strength);
+
+	return i;
+}
+
+static int mt7621_nfc_set_spare_per_sector(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	u32 size;
+	int i;
+
+	size = nand->ecc.bytes + NFI_FDM_SIZE;
+
+	/* Find the closest supported spare size */
+	for (i = 0; i < ARRAY_SIZE(mt7621_nfi_spare_size); i++) {
+		if (mt7621_nfi_spare_size[i] >= size)
+			break;
+	}
+
+	if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_spare_size))) {
+		pr_err("OOB size (%u) is not supported\n", mtd->oobsize);
+		return -EINVAL;
+	}
+
+	nfc->spare_per_sector = mt7621_nfi_spare_size[i];
+
+	return i;
+}
+
+static int mt7621_nfc_ecc_init(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	u32 avail_ecc_bytes, encode_block_size, decode_block_size;
+	u32 ecc_enccfg, ecc_deccfg;
+	int ecc_cap;
+
+	nand->ecc.options |= NAND_ECC_CUSTOM_PAGE_ACCESS;
+
+	nand->ecc.size = ECC_SECTOR_SIZE;
+	nand->ecc.steps = mtd->writesize / nand->ecc.size;
+
+	avail_ecc_bytes = mtd->oobsize / nand->ecc.steps - NFI_FDM_SIZE;
+
+	ecc_cap = mt7621_nfc_calc_ecc_strength(nfc, avail_ecc_bytes);
+	if (ecc_cap < 0)
+		return ecc_cap;
+
+	/* Sector + FDM */
+	encode_block_size = (nand->ecc.size + NFI_FDM_SIZE) * 8;
+	ecc_enccfg = ecc_cap | FIELD_PREP(ENC_MODE, ENC_MODE_NFI) |
+		     FIELD_PREP(ENC_CNFG_MSG, encode_block_size);
+
+	/* Sector + FDM + ECC parity bits */
+	decode_block_size = ((nand->ecc.size + NFI_FDM_SIZE) * 8) +
+			    nand->ecc.strength * ECC_PARITY_BITS;
+	ecc_deccfg = ecc_cap | FIELD_PREP(DEC_MODE, DEC_MODE_NFI) |
+		     FIELD_PREP(DEC_CS, decode_block_size) |
+		     FIELD_PREP(DEC_CON, DEC_CON_EL) | DEC_EMPTY_EN;
+
+	mt7621_ecc_encoder_op(nfc, false);
+	ecc_write32(nfc, ECC_ENCCNFG, ecc_enccfg);
+
+	mt7621_ecc_decoder_op(nfc, false);
+	ecc_write32(nfc, ECC_DECCNFG, ecc_deccfg);
+
+	return 0;
+}
+
+static int mt7621_nfc_set_page_format(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	int i, spare_size;
+	u32 pagefmt;
+
+	spare_size = mt7621_nfc_set_spare_per_sector(nfc);
+	if (spare_size < 0)
+		return spare_size;
+
+	for (i = 0; i < ARRAY_SIZE(mt7621_nfi_page_size); i++) {
+		if (mt7621_nfi_page_size[i] == mtd->writesize)
+			break;
+	}
+
+	if (unlikely(i >= ARRAY_SIZE(mt7621_nfi_page_size))) {
+		pr_err("Page size (%u) is not supported\n", mtd->writesize);
+		return -EINVAL;
+	}
+
+	pagefmt = FIELD_PREP(PAGEFMT_PAGE, i) |
+		  FIELD_PREP(PAGEFMT_SPARE, spare_size) |
+		  FIELD_PREP(PAGEFMT_FDM, NFI_FDM_SIZE) |
+		  FIELD_PREP(PAGEFMT_FDM_ECC, NFI_FDM_SIZE);
+
+	nfi_write16(nfc, NFI_PAGEFMT, pagefmt);
+
+	return 0;
+}
+
+static int mt7621_nfc_attach_chip(struct nand_chip *nand)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
+	int ret;
+
+	if (nand->options & NAND_BUSWIDTH_16) {
+		pr_err("16-bit buswidth is not supported");
+		return -EINVAL;
+	}
+
+	ret = mt7621_nfc_ecc_init(nfc);
+	if (ret)
+		return ret;
+
+	return mt7621_nfc_set_page_format(nfc);
+}
+
+static void mt7621_nfc_write_fdm(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	u32 vall, valm;
+	u8 *oobptr;
+	int i, j;
+
+	for (i = 0; i < nand->ecc.steps; i++) {
+		vall = 0;
+		valm = 0;
+		oobptr = oob_fdm_ptr(nand, i);
+
+		for (j = 0; j < 4; j++)
+			vall |= (u32)oobptr[j] << (j * 8);
+
+		for (j = 0; j < 4; j++)
+			valm |= (u32)oobptr[j + 4] << (j * 8);
+
+		nfi_write32(nfc, NFI_FDML(i), vall);
+		nfi_write32(nfc, NFI_FDMM(i), valm);
+	}
+}
+
+static void mt7621_nfc_read_sector_fdm(struct mt7621_nfc *nfc, u32 sect)
+{
+	struct nand_chip *nand = &nfc->nand;
+	u32 vall, valm;
+	u8 *oobptr;
+	int i;
+
+	vall = nfi_read32(nfc, NFI_FDML(sect));
+	valm = nfi_read32(nfc, NFI_FDMM(sect));
+	oobptr = oob_fdm_ptr(nand, sect);
+
+	for (i = 0; i < 4; i++)
+		oobptr[i] = (vall >> (i * 8)) & 0xff;
+
+	for (i = 0; i < 4; i++)
+		oobptr[i + 4] = (valm >> (i * 8)) & 0xff;
+}
+
+static int mt7621_nfc_read_page_hwecc(struct mtd_info *mtd,
+				      struct nand_chip *nand, uint8_t *buf,
+				      int oob_required, int page)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
+	int bitflips = 0, ret = 0;
+	int rc, i;
+
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
+	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
+		    CNFG_READ_MODE | CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
+
+	mt7621_ecc_decoder_op(nfc, true);
+
+	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
+		    CON_NFI_BRD);
+
+	for (i = 0; i < nand->ecc.steps; i++) {
+		if (buf)
+			mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
+					     nand->ecc.size);
+		else
+			mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
+
+		rc = mt7621_ecc_decoder_wait_done(nfc, i);
+
+		mt7621_nfc_read_sector_fdm(nfc, i);
+
+		if (rc < 0) {
+			ret = -EIO;
+			continue;
+		}
+
+		rc = mt7621_ecc_correct_check(nfc,
+			buf ? page_data_ptr(nand, buf, i) : NULL,
+			oob_fdm_ptr(nand, i), i);
+
+		if (rc < 0) {
+			pr_warn("Uncorrectable ECC error at page %d step %d\n",
+				page, i);
+			bitflips = nand->ecc.strength + 1;
+			mtd->ecc_stats.failed++;
+		} else {
+			if (rc > bitflips)
+				bitflips = rc;
+			mtd->ecc_stats.corrected += rc;
+		}
+	}
+
+	mt7621_ecc_decoder_op(nfc, false);
+
+	nfi_write16(nfc, NFI_CON, 0);
+
+	if (ret < 0)
+		return ret;
+
+	return bitflips;
+}
+
+static int mt7621_nfc_read_page_raw(struct mtd_info *mtd,
+				    struct nand_chip *nand, uint8_t *buf,
+				    int oob_required, int page)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
+	int i;
+
+	nand_read_page_op(nand, page, 0, NULL, 0);
+
+	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
+		    CNFG_READ_MODE);
+
+	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
+		    CON_NFI_BRD);
+
+	for (i = 0; i < nand->ecc.steps; i++) {
+		/* Read data */
+		if (buf)
+			mt7621_nfc_read_data(nfc, page_data_ptr(nand, buf, i),
+					     nand->ecc.size);
+		else
+			mt7621_nfc_read_data_discard(nfc, nand->ecc.size);
+
+		/* Read FDM */
+		mt7621_nfc_read_data(nfc, oob_fdm_ptr(nand, i), NFI_FDM_SIZE);
+
+		/* Read ECC parity data */
+		mt7621_nfc_read_data(nfc, oob_ecc_ptr(nfc, i),
+				     nfc->spare_per_sector - NFI_FDM_SIZE);
+	}
+
+	nfi_write16(nfc, NFI_CON, 0);
+
+	return 0;
+}
+
+static int mt7621_nfc_read_oob_hwecc(struct mtd_info *mtd,
+				     struct nand_chip *nand, int page)
+{
+	return mt7621_nfc_read_page_hwecc(mtd, nand, NULL, 1, page);
+}
+
+static int mt7621_nfc_read_oob_raw(struct mtd_info *mtd,
+				   struct nand_chip *nand, int page)
+{
+	return mt7621_nfc_read_page_raw(mtd, nand, NULL, 1, page);
+}
+
+static int mt7621_nfc_check_empty_page(struct nand_chip *nand, const u8 *buf)
+{
+	struct mtd_info *mtd = nand_to_mtd(nand);
+	u8 *oobptr;
+	u32 i, j;
+
+	if (buf) {
+		for (i = 0; i < mtd->writesize; i++)
+			if (buf[i] != 0xff)
+				return 0;
+	}
+
+	for (i = 0; i < nand->ecc.steps; i++) {
+		oobptr = oob_fdm_ptr(nand, i);
+		for (j = 0; j < NFI_FDM_SIZE; j++)
+			if (oobptr[j] != 0xff)
+				return 0;
+	}
+
+	return 1;
+}
+
+static int mt7621_nfc_write_page_hwecc(struct mtd_info *mtd,
+				       struct nand_chip *nand,
+				       const u8 *buf, int oob_required,
+				       int page)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
+
+	if (mt7621_nfc_check_empty_page(nand, buf)) {
+		/*
+		 * MT7621 ECC engine always generates parity code for input
+		 * pages, even for empty pages. Doing so will write back ECC
+		 * parity code to the oob region, which means such pages will
+		 * no longer be empty pages.
+		 *
+		 * To avoid this, stop write operation if current page is an
+		 * empty page.
+		 */
+		return 0;
+	}
+
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM) |
+		    CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN);
+
+	mt7621_ecc_encoder_op(nfc, true);
+
+	mt7621_nfc_write_fdm(nfc);
+
+	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
+		    CON_NFI_BWR);
+
+	if (buf)
+		mt7621_nfc_write_data(nfc, buf, mtd->writesize);
+	else
+		mt7621_nfc_write_data_empty(nfc, mtd->writesize);
+
+	mt7621_nfc_wait_write_completion(nfc, nand);
+
+	mt7621_ecc_encoder_op(nfc, false);
+
+	nfi_write16(nfc, NFI_CON, 0);
+
+	return nand_prog_page_end_op(nand);
+}
+
+static int mt7621_nfc_write_page_raw(struct mtd_info *mtd,
+				     struct nand_chip *nand,
+				     const u8 *buf, int oob_required,
+				     int page)
+{
+	struct mt7621_nfc *nfc = nand_get_controller_data(nand);
+	int i;
+
+	nand_prog_page_begin_op(nand, page, 0, NULL, 0);
+
+	nfi_write16(nfc, NFI_CNFG, FIELD_PREP(CNFG_OP_MODE, CNFG_OP_CUSTOM));
+
+	nfi_write16(nfc, NFI_CON, FIELD_PREP(CON_NFI_SEC, nand->ecc.steps) |
+		    CON_NFI_BWR);
+
+	for (i = 0; i < nand->ecc.steps; i++) {
+		/* Write data */
+		if (buf)
+			mt7621_nfc_write_data(nfc, page_data_ptr(nand, buf, i),
+					      nand->ecc.size);
+		else
+			mt7621_nfc_write_data_empty(nfc, nand->ecc.size);
+
+		/* Write FDM */
+		mt7621_nfc_write_data(nfc, oob_fdm_ptr(nand, i),
+				      NFI_FDM_SIZE);
+
+		/* Write dummy ECC parity data */
+		mt7621_nfc_write_data_empty(nfc, nfc->spare_per_sector -
+					    NFI_FDM_SIZE);
+	}
+
+	mt7621_nfc_wait_write_completion(nfc, nand);
+
+	nfi_write16(nfc, NFI_CON, 0);
+
+	return nand_prog_page_end_op(nand);
+}
+
+static int mt7621_nfc_write_oob_hwecc(struct mtd_info *mtd,
+				      struct nand_chip *nand, int page)
+{
+	return mt7621_nfc_write_page_hwecc(mtd, nand, NULL, 1, page);
+}
+
+static int mt7621_nfc_write_oob_raw(struct mtd_info *mtd,
+				    struct nand_chip *nand, int page)
+{
+	return mt7621_nfc_write_page_raw(mtd, nand, NULL, 1, page);
+}
+
+static int mt7621_nfc_ooblayout_free(struct mtd_info *mtd, int section,
+				     struct mtd_oob_region *oob_region)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section >= nand->ecc.steps)
+		return -ERANGE;
+
+	oob_region->length = NFI_FDM_SIZE - 1;
+	oob_region->offset = section * NFI_FDM_SIZE + 1;
+
+	return 0;
+}
+
+static int mt7621_nfc_ooblayout_ecc(struct mtd_info *mtd, int section,
+				    struct mtd_oob_region *oob_region)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+
+	if (section)
+		return -ERANGE;
+
+	oob_region->offset = NFI_FDM_SIZE * nand->ecc.steps;
+	oob_region->length = mtd->oobsize - oob_region->offset;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops mt7621_nfc_ooblayout_ops = {
+	.rfree = mt7621_nfc_ooblayout_free,
+	.ecc = mt7621_nfc_ooblayout_ecc,
+};
+
+/*
+ * This function will override the default one which is not supposed to be
+ * used for ECC syndrome based pages.
+ */
+static int mt7621_nfc_block_bad(struct mtd_info *mtd, loff_t ofs)
+{
+	struct nand_chip *nand = mtd_to_nand(mtd);
+	struct mtd_oob_ops ops;
+	int ret, i = 0;
+	u16 bad;
+
+	memset(&ops, 0, sizeof(ops));
+	ops.oobbuf = (uint8_t *)&bad;
+	ops.ooboffs = nand->badblockpos;
+	if (nand->options & NAND_BUSWIDTH_16) {
+		ops.ooboffs &= ~0x01;
+		ops.ooblen = 2;
+	} else {
+		ops.ooblen = 1;
+	}
+	ops.mode = MTD_OPS_RAW;
+
+	/* Read from first/last page(s) if necessary */
+	if (nand->bbt_options & NAND_BBT_SCANLASTPAGE)
+		ofs += mtd->erasesize - mtd->writesize;
+
+	do {
+		ret = mtd_read_oob(mtd, ofs, &ops);
+		if (ret)
+			return ret;
+
+		if (likely(nand->badblockbits == 8))
+			ret = bad != 0xFF;
+		else
+			ret = hweight8(bad) < nand->badblockbits;
+
+		i++;
+		ofs += mtd->writesize;
+	} while (!ret && (nand->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2);
+
+	return ret;
+}
+
+static void mt7621_nfc_init_chip(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	struct mtd_info *mtd;
+	int ret;
+
+	nand_set_controller_data(nand, nfc);
+
+	nand->options |= NAND_NO_SUBPAGE_WRITE;
+
+	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+	nand->ecc.read_page = mt7621_nfc_read_page_hwecc;
+	nand->ecc.read_page_raw = mt7621_nfc_read_page_raw;
+	nand->ecc.write_page = mt7621_nfc_write_page_hwecc;
+	nand->ecc.write_page_raw = mt7621_nfc_write_page_raw;
+	nand->ecc.read_oob = mt7621_nfc_read_oob_hwecc;
+	nand->ecc.read_oob_raw = mt7621_nfc_read_oob_raw;
+	nand->ecc.write_oob = mt7621_nfc_write_oob_hwecc;
+	nand->ecc.write_oob_raw = mt7621_nfc_write_oob_raw;
+
+	nand->dev_ready = mt7621_nfc_dev_ready;
+	nand->select_chip = mt7621_nfc_select_chip;
+	nand->write_byte = mt7621_nfc_write_byte;
+	nand->write_buf = mt7621_nfc_write_buf;
+	nand->read_byte = mt7621_nfc_read_byte;
+	nand->read_buf = mt7621_nfc_read_buf;
+	nand->cmd_ctrl = mt7621_nfc_cmd_ctrl;
+	nand->block_bad = mt7621_nfc_block_bad;
+
+	mtd = nand_to_mtd(nand);
+	mtd_set_ooblayout(mtd, &mt7621_nfc_ooblayout_ops);
+
+	/* Reset NFI master */
+	mt7621_nfc_hw_init(nfc);
+
+	ret = nand_scan_ident(mtd, 1, NULL);
+	if (ret)
+		return;
+
+	mt7621_nfc_attach_chip(nand);
+
+	ret = nand_scan_tail(mtd);
+	if (ret)
+		return;
+
+	nand_register(0, mtd);
+}
+
+static void mt7621_nfc_set_regs(struct mt7621_nfc *nfc)
+{
+	nfc->nfi_regs = (void __iomem *)CKSEG1ADDR(NFI_BASE);
+	nfc->ecc_regs = (void __iomem *)CKSEG1ADDR(NFI_ECC_BASE);
+}
+
+void mt7621_nfc_spl_init(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+
+	mt7621_nfc_set_regs(nfc);
+
+	nand_set_controller_data(nand, nfc);
+
+	nand->options |= NAND_NO_SUBPAGE_WRITE;
+
+	nand->ecc.mode = NAND_ECC_HW_SYNDROME;
+	nand->ecc.read_page = mt7621_nfc_read_page_hwecc;
+
+	nand->dev_ready = mt7621_nfc_dev_ready;
+	nand->select_chip = mt7621_nfc_select_chip;
+	nand->read_byte = mt7621_nfc_read_byte;
+	nand->read_buf = mt7621_nfc_read_buf;
+	nand->cmd_ctrl = mt7621_nfc_cmd_ctrl;
+
+	/* Reset NFI master */
+	mt7621_nfc_hw_init(nfc);
+}
+
+int mt7621_nfc_spl_post_init(struct mt7621_nfc *nfc)
+{
+	struct nand_chip *nand = &nfc->nand;
+	int nand_maf_id, nand_dev_id;
+	struct nand_flash_dev *type;
+
+	type = nand_get_flash_type(&nand->mtd, nand, &nand_maf_id,
+				   &nand_dev_id, NULL);
+
+	if (IS_ERR(type))
+		return PTR_ERR(type);
+
+	nand->numchips = 1;
+	nand->mtd.size = nand->chipsize;
+
+	return mt7621_nfc_attach_chip(nand);
+}
+
+void board_nand_init(void)
+{
+	mt7621_nfc_set_regs(&nfc_dev);
+	mt7621_nfc_init_chip(&nfc_dev);
+}
diff --git a/drivers/mtd/nand/raw/mt7621_nand.h b/drivers/mtd/nand/raw/mt7621_nand.h
new file mode 100644
index 0000000..af4bc55
--- /dev/null
+++ b/drivers/mtd/nand/raw/mt7621_nand.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _MT7621_NAND_H_
+#define _MT7621_NAND_H_
+
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/compiler.h>
+#include <linux/mtd/rawnand.h>
+
+struct mt7621_nfc {
+	struct nand_chip nand;
+
+	void __iomem *nfi_regs;
+	void __iomem *ecc_regs;
+
+	u32 spare_per_sector;
+};
+
+/* for SPL */
+void mt7621_nfc_spl_init(struct mt7621_nfc *nfc);
+int mt7621_nfc_spl_post_init(struct mt7621_nfc *nfc);
+
+#endif /* _MT7621_NAND_H_ */
diff --git a/drivers/mtd/nand/raw/mt7621_nand_spl.c b/drivers/mtd/nand/raw/mt7621_nand_spl.c
new file mode 100644
index 0000000..114fc8b
--- /dev/null
+++ b/drivers/mtd/nand/raw/mt7621_nand_spl.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <image.h>
+#include <malloc.h>
+#include <linux/sizes.h>
+#include <linux/delay.h>
+#include <linux/mtd/rawnand.h>
+#include "mt7621_nand.h"
+
+static struct mt7621_nfc nfc_dev;
+static u8 *buffer;
+static int nand_valid;
+
+static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
+			    int column, int page_addr)
+{
+	register struct nand_chip *chip = mtd_to_nand(mtd);
+
+	/* Command latch cycle */
+	chip->cmd_ctrl(mtd, command, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+
+	if (column != -1 || page_addr != -1) {
+		int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE;
+
+		/* Serially input address */
+		if (column != -1) {
+			chip->cmd_ctrl(mtd, column, ctrl);
+			ctrl &= ~NAND_CTRL_CHANGE;
+			if (command != NAND_CMD_READID)
+				chip->cmd_ctrl(mtd, column >> 8, ctrl);
+		}
+		if (page_addr != -1) {
+			chip->cmd_ctrl(mtd, page_addr, ctrl);
+			chip->cmd_ctrl(mtd, page_addr >> 8,
+				       NAND_NCE | NAND_ALE);
+			if (chip->options & NAND_ROW_ADDR_3)
+				chip->cmd_ctrl(mtd, page_addr >> 16,
+					       NAND_NCE | NAND_ALE);
+		}
+	}
+	chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
+
+	/*
+	 * Program and erase have their own busy handlers status, sequential
+	 * in and status need no delay.
+	 */
+	switch (command) {
+	case NAND_CMD_STATUS:
+	case NAND_CMD_READID:
+	case NAND_CMD_SET_FEATURES:
+		return;
+
+	case NAND_CMD_READ0:
+		chip->cmd_ctrl(mtd, NAND_CMD_READSTART,
+			       NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
+		chip->cmd_ctrl(mtd, NAND_CMD_NONE,
+			       NAND_NCE | NAND_CTRL_CHANGE);
+	}
+
+	/*
+	 * Apply this short delay always to ensure that we do wait tWB in
+	 * any case on any machine.
+	 */
+	ndelay(100);
+
+	nand_wait_ready(mtd);
+}
+
+static int nfc_read_page_hwecc(struct mtd_info *mtd, void *buf,
+			       unsigned int page)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
+
+	ret = chip->ecc.read_page(mtd, chip, buf, 1, page);
+	if (ret < 0 || ret > chip->ecc.strength)
+		return -1;
+
+	return 0;
+}
+
+static int nfc_read_oob_hwecc(struct mtd_info *mtd, void *buf, u32 len,
+			      unsigned int page)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	int ret;
+
+	chip->cmdfunc(mtd, NAND_CMD_READ0, 0x0, page);
+
+	ret = chip->ecc.read_page(mtd, chip, NULL, 1, page);
+	if (ret < 0)
+		return -1;
+
+	if (len > mtd->oobsize)
+		len = mtd->oobsize;
+
+	memcpy(buf, chip->oob_poi, len);
+
+	return 0;
+}
+
+static int nfc_check_bad_block(struct mtd_info *mtd, unsigned int page)
+{
+	struct nand_chip *chip = mtd_to_nand(mtd);
+	u32 pages_per_block, i = 0;
+	int ret;
+	u8 bad;
+
+	pages_per_block = 1 << (mtd->erasesize_shift - mtd->writesize_shift);
+
+	/* Read from first/last page(s) if necessary */
+	if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) {
+		page += pages_per_block - 1;
+		if (chip->bbt_options & NAND_BBT_SCAN2NDPAGE)
+			page--;
+	}
+
+	do {
+		ret = nfc_read_oob_hwecc(mtd, &bad, 1, page);
+		if (ret)
+			return ret;
+
+		ret = bad != 0xFF;
+
+		i++;
+		page++;
+	} while (!ret && (chip->bbt_options & NAND_BBT_SCAN2NDPAGE) && i < 2);
+
+	return ret;
+}
+
+int nand_spl_load_image(uint32_t offs, unsigned int size, void *dest)
+{
+	struct mt7621_nfc *nfc = &nfc_dev;
+	struct nand_chip *chip = &nfc->nand;
+	struct mtd_info *mtd = &chip->mtd;
+	u32 addr, col, page, chksz;
+	bool check_bad = true;
+
+	if (!nand_valid)
+		return -ENODEV;
+
+	while (size) {
+		if (check_bad || !(offs & mtd->erasesize_mask)) {
+			addr = offs & (~mtd->erasesize_mask);
+			page = addr >> mtd->writesize_shift;
+			if (nfc_check_bad_block(mtd, page)) {
+				/* Skip bad block */
+				if (addr >= mtd->size - mtd->erasesize)
+					return -1;
+
+				offs += mtd->erasesize;
+				continue;
+			}
+
+			check_bad = false;
+		}
+
+		col = offs & mtd->writesize_mask;
+		page = offs >> mtd->writesize_shift;
+		chksz = min(mtd->writesize - col, (uint32_t)size);
+
+		if (unlikely(chksz < mtd->writesize)) {
+			/* Not reading a full page */
+			if (nfc_read_page_hwecc(mtd, buffer, page))
+				return -1;
+
+			memcpy(dest, buffer + col, chksz);
+		} else {
+			if (nfc_read_page_hwecc(mtd, dest, page))
+				return -1;
+		}
+
+		dest += chksz;
+		offs += chksz;
+		size -= chksz;
+	}
+
+	return 0;
+}
+
+int nand_default_bbt(struct mtd_info *mtd)
+{
+	return 0;
+}
+
+unsigned long nand_size(void)
+{
+	if (!nand_valid)
+		return 0;
+
+	/* Unlikely that NAND size > 2GBytes */
+	if (nfc_dev.nand.chipsize <= SZ_2G)
+		return nfc_dev.nand.chipsize;
+
+	return SZ_2G;
+}
+
+void nand_deselect(void)
+{
+}
+
+void nand_init(void)
+{
+	struct mtd_info *mtd;
+	struct nand_chip *chip;
+
+	if (nand_valid)
+		return;
+
+	mt7621_nfc_spl_init(&nfc_dev);
+
+	chip = &nfc_dev.nand;
+	mtd = &chip->mtd;
+	chip->cmdfunc = nand_command_lp;
+
+	if (mt7621_nfc_spl_post_init(&nfc_dev))
+		return;
+
+	mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
+	mtd->writesize_shift = ffs(mtd->writesize) - 1;
+	mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
+	mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;
+
+	buffer = malloc(mtd->writesize);
+	if (!buffer)
+		return;
+
+	nand_valid = 1;
+}
diff --git a/drivers/net/mtk_eth.c b/drivers/net/mtk_eth.c
index 666ddeb..4fe7ee0 100644
--- a/drivers/net/mtk_eth.c
+++ b/drivers/net/mtk_eth.c
@@ -145,7 +145,8 @@
 enum mtk_soc {
 	SOC_MT7623,
 	SOC_MT7629,
-	SOC_MT7622
+	SOC_MT7622,
+	SOC_MT7621
 };
 
 struct mtk_eth_priv {
@@ -159,9 +160,10 @@
 
 	void __iomem *fe_base;
 	void __iomem *gmac_base;
-	void __iomem *ethsys_base;
 	void __iomem *sgmii_base;
 
+	struct regmap *ethsys_regmap;
+
 	struct mii_dev *mdio_bus;
 	int (*mii_read)(struct mtk_eth_priv *priv, u8 phy, u8 reg);
 	int (*mii_write)(struct mtk_eth_priv *priv, u8 phy, u8 reg, u16 val);
@@ -233,7 +235,12 @@
 static void mtk_ethsys_rmw(struct mtk_eth_priv *priv, u32 reg, u32 clr,
 			   u32 set)
 {
-	clrsetbits_le32(priv->ethsys_base + reg, clr, set);
+	uint val;
+
+	regmap_read(priv->ethsys_regmap, reg, &val);
+	val &= ~clr;
+	val |= set;
+	regmap_write(priv->ethsys_regmap, reg, val);
 }
 
 /* Direct MDIO clause 22/45 access via SoC */
@@ -669,12 +676,18 @@
 static int mt7530_setup(struct mtk_eth_priv *priv)
 {
 	u16 phy_addr, phy_val;
-	u32 val;
+	u32 val, txdrv;
 	int i;
 
-	/* Select 250MHz clk for RGMII mode */
-	mtk_ethsys_rmw(priv, ETHSYS_CLKCFG0_REG,
-		       ETHSYS_TRGMII_CLK_SEL362_5, 0);
+	if (priv->soc != SOC_MT7621) {
+		/* Select 250MHz clk for RGMII mode */
+		mtk_ethsys_rmw(priv, ETHSYS_CLKCFG0_REG,
+			       ETHSYS_TRGMII_CLK_SEL362_5, 0);
+
+		txdrv = 8;
+	} else {
+		txdrv = 4;
+	}
 
 	/* Modify HWTRAP first to allow direct access to internal PHYs */
 	mt753x_reg_read(priv, HWTRAP_REG, &val);
@@ -732,7 +745,8 @@
 	/* Lower Tx Driving for TRGMII path */
 	for (i = 0 ; i < NUM_TRGMII_CTRL ; i++)
 		mt753x_reg_write(priv, MT7530_TRGMII_TD_ODT(i),
-				 (8 << TD_DM_DRVP_S) | (8 << TD_DM_DRVN_S));
+				 (txdrv << TD_DM_DRVP_S) |
+				 (txdrv << TD_DM_DRVN_S));
 
 	for (i = 0 ; i < NUM_TRGMII_CTRL; i++)
 		mt753x_reg_rmw(priv, MT7530_TRGMII_RD(i), RD_TAP_M, 16);
@@ -1419,7 +1433,7 @@
 
 	priv->soc = dev_get_driver_data(dev);
 
-	pdata->iobase = dev_read_addr(dev);
+	pdata->iobase = (phys_addr_t)dev_remap_addr(dev);
 
 	/* get corresponding ethsys phandle */
 	ret = dev_read_phandle_with_args(dev, "mediatek,ethsys", NULL, 0, 0,
@@ -1427,15 +1441,9 @@
 	if (ret)
 		return ret;
 
-	regmap = syscon_node_to_regmap(args.node);
-	if (IS_ERR(regmap))
-		return PTR_ERR(regmap);
-
-	priv->ethsys_base = regmap_get_range(regmap, 0);
-	if (!priv->ethsys_base) {
-		dev_err(dev, "Unable to find ethsys\n");
-		return -ENODEV;
-	}
+	priv->ethsys_regmap = syscon_node_to_regmap(args.node);
+	if (IS_ERR(priv->ethsys_regmap))
+		return PTR_ERR(priv->ethsys_regmap);
 
 	/* Reset controllers */
 	ret = reset_get_by_name(dev, "fe", &priv->rst_fe);
@@ -1540,6 +1548,7 @@
 	{ .compatible = "mediatek,mt7629-eth", .data = SOC_MT7629 },
 	{ .compatible = "mediatek,mt7623-eth", .data = SOC_MT7623 },
 	{ .compatible = "mediatek,mt7622-eth", .data = SOC_MT7622 },
+	{ .compatible = "mediatek,mt7621-eth", .data = SOC_MT7621 },
 	{}
 };
 
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 4a3856d..c25b42c 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -266,7 +266,7 @@
 config PHY_MTK_TPHY
 	bool "MediaTek T-PHY Driver"
 	depends on PHY
-	depends on ARCH_MEDIATEK
+	depends on ARCH_MEDIATEK || SOC_MT7621
 	help
 	  MediaTek T-PHY driver supports usb2.0, usb3.0 ports, PCIe and
 	  SATA, and meanwhile supports two version T-PHY which have
diff --git a/drivers/pinctrl/mtmips/Kconfig b/drivers/pinctrl/mtmips/Kconfig
index 844d5b7..456f3ea 100644
--- a/drivers/pinctrl/mtmips/Kconfig
+++ b/drivers/pinctrl/mtmips/Kconfig
@@ -12,6 +12,15 @@
 	  The driver is controlled by a device tree node which contains
 	  the pin mux functions for each available pin groups.
 
+config PINCTRL_MT7621
+	bool "MediaTek MT7621 pin control driver"
+	select PINCTRL_MTMIPS
+	depends on SOC_MT7621 && PINCTRL_GENERIC
+	help
+	  Support pin multiplexing control on MediaTek MT7621.
+	  The driver is controlled by a device tree node which contains
+	  the pin mux functions for each available pin groups.
+
 config PINCTRL_MT7628
 	bool "MediaTek MT7628 pin control driver"
 	select PINCTRL_MTMIPS
diff --git a/drivers/pinctrl/mtmips/Makefile b/drivers/pinctrl/mtmips/Makefile
index ba945a8..8fece4f 100644
--- a/drivers/pinctrl/mtmips/Makefile
+++ b/drivers/pinctrl/mtmips/Makefile
@@ -5,4 +5,5 @@
 
 # SoC Drivers
 obj-$(CONFIG_PINCTRL_MT7620) += pinctrl-mt7620.o
+obj-$(CONFIG_PINCTRL_MT7621) += pinctrl-mt7621.o
 obj-$(CONFIG_PINCTRL_MT7628) += pinctrl-mt7628.o
diff --git a/drivers/pinctrl/mtmips/pinctrl-mt7621.c b/drivers/pinctrl/mtmips/pinctrl-mt7621.c
new file mode 100644
index 0000000..3e98a01
--- /dev/null
+++ b/drivers/pinctrl/mtmips/pinctrl-mt7621.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#include <dm.h>
+#include <dm/pinctrl.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+
+#include "pinctrl-mtmips-common.h"
+
+#define SYSC_MAP_SIZE			0x100
+
+#define PAD_UART1_GPIO0_OFS		0x00
+#define PAD_UART3_I2C_OFS		0x04
+#define PAD_UART2_JTAG_OFS		0x08
+#define PAD_PERST_WDT_OFS		0x0c
+#define PAD_RGMII2_MDIO_OFS		0x10
+#define PAD_SDXC_SPI_OFS		0x14
+#define GPIOMODE_OFS			0x18
+#define PAD_BOPT_ESWINT_OFS		0x28
+
+#define ESWINT_SHIFT			20
+#define SDXC_SHIFT			18
+#define SPI_SHIFT			16
+#define RGMII2_SHIFT			15
+#define RGMII1_SHIFT			14
+#define MDIO_SHIFT			12
+#define PERST_SHIFT			10
+#define WDT_SHIFT			8
+#define JTAG_SHIFT			7
+#define UART2_SHIFT			5
+#define UART3_SHIFT			3
+#define I2C_SHIFT			2
+#define UART1_SHIFT			1
+#define GPIO0_SHIFT			0 /* Dummy */
+
+#define GM4_MASK			3
+
+#define E4_E2_M				0x03
+#define E4_E2_S				4
+#define PULL_UP				BIT(3)
+#define PULL_DOWN			BIT(2)
+#define SMT				BIT(1)
+#define SR				BIT(0)
+
+struct mt7621_pinctrl_priv {
+	struct mtmips_pinctrl_priv mp;
+};
+
+#if CONFIG_IS_ENABLED(PINMUX)
+static const struct mtmips_pmx_func esw_int_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("esw int", 0),
+};
+
+static const struct mtmips_pmx_func sdxc_grp[] = {
+	FUNC("nand", 2),
+	FUNC("gpio", 1),
+	FUNC("sdxc", 0),
+};
+
+static const struct mtmips_pmx_func spi_grp[] = {
+	FUNC("nand", 2),
+	FUNC("gpio", 1),
+	FUNC("spi", 0),
+};
+
+static const struct mtmips_pmx_func rgmii2_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("rgmii", 0),
+};
+
+static const struct mtmips_pmx_func rgmii1_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("rgmii", 0),
+};
+
+static const struct mtmips_pmx_func mdio_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("mdio", 0),
+};
+
+static const struct mtmips_pmx_func perst_grp[] = {
+	FUNC("refclk", 2),
+	FUNC("gpio", 1),
+	FUNC("pcie reset", 0),
+};
+
+static const struct mtmips_pmx_func wdt_grp[] = {
+	FUNC("refclk", 2),
+	FUNC("gpio", 1),
+	FUNC("wdt rst", 0),
+};
+
+static const struct mtmips_pmx_func jtag_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("jtag", 0),
+};
+
+static const struct mtmips_pmx_func uart2_grp[] = {
+	FUNC("spdif", 3),
+	FUNC("pcm", 2),
+	FUNC("gpio", 1),
+	FUNC("uart", 0),
+};
+
+static const struct mtmips_pmx_func uart3_grp[] = {
+	FUNC("spdif", 3),
+	FUNC("i2s", 2),
+	FUNC("gpio", 1),
+	FUNC("uart", 0),
+};
+
+static const struct mtmips_pmx_func i2c_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("i2c", 0),
+};
+
+static const struct mtmips_pmx_func uart1_grp[] = {
+	FUNC("gpio", 1),
+	FUNC("uart", 0),
+};
+
+static const struct mtmips_pmx_func gpio0_grp[] = {
+	FUNC("gpio", 0),
+};
+
+static const struct mtmips_pmx_group mt7621_pmx_data[] = {
+	GRP_PCONF("esw int", esw_int_grp, GPIOMODE_OFS, ESWINT_SHIFT, 1,
+		  PAD_BOPT_ESWINT_OFS, 0),
+	GRP_PCONF("sdxc", sdxc_grp, GPIOMODE_OFS, SDXC_SHIFT, GM4_MASK,
+		  PAD_SDXC_SPI_OFS, 16),
+	GRP_PCONF("spi", spi_grp, GPIOMODE_OFS, SPI_SHIFT, GM4_MASK,
+		  PAD_SDXC_SPI_OFS, 0),
+	GRP_PCONF("rgmii2", rgmii2_grp, GPIOMODE_OFS, RGMII2_SHIFT, 1,
+		  PAD_RGMII2_MDIO_OFS, 16),
+	GRP("rgmii1", rgmii1_grp, GPIOMODE_OFS, RGMII1_SHIFT, 1),
+	GRP_PCONF("mdio", mdio_grp, GPIOMODE_OFS, MDIO_SHIFT, GM4_MASK,
+		  PAD_RGMII2_MDIO_OFS, 0),
+	GRP_PCONF("pcie reset", perst_grp, GPIOMODE_OFS, PERST_SHIFT, GM4_MASK,
+		  PAD_PERST_WDT_OFS, 16),
+	GRP_PCONF("wdt", wdt_grp, GPIOMODE_OFS, WDT_SHIFT, GM4_MASK,
+		  PAD_PERST_WDT_OFS, 0),
+	GRP_PCONF("jtag", jtag_grp, GPIOMODE_OFS, JTAG_SHIFT, 1,
+		  PAD_UART2_JTAG_OFS, 16),
+	GRP_PCONF("uart2", uart2_grp, GPIOMODE_OFS, UART2_SHIFT, GM4_MASK,
+		  PAD_UART2_JTAG_OFS, 0),
+	GRP_PCONF("uart3", uart3_grp, GPIOMODE_OFS, UART3_SHIFT, GM4_MASK,
+		  PAD_UART3_I2C_OFS, 16),
+	GRP_PCONF("i2c", i2c_grp, GPIOMODE_OFS, I2C_SHIFT, 1,
+		  PAD_UART3_I2C_OFS, 0),
+	GRP_PCONF("uart1", uart1_grp, GPIOMODE_OFS, UART1_SHIFT, 1,
+		  PAD_UART1_GPIO0_OFS, 16),
+	GRP_PCONF("gpio0", gpio0_grp, GPIOMODE_OFS, GPIO0_SHIFT, 1,
+		  PAD_UART1_GPIO0_OFS, 0),
+};
+
+static int mt7621_get_groups_count(struct udevice *dev)
+{
+	return ARRAY_SIZE(mt7621_pmx_data);
+}
+
+static const char *mt7621_get_group_name(struct udevice *dev,
+					 unsigned int selector)
+{
+	return mt7621_pmx_data[selector].name;
+}
+#endif /* CONFIG_IS_ENABLED(PINMUX) */
+
+#if CONFIG_IS_ENABLED(PINCONF)
+static const struct pinconf_param mt7621_conf_params[] = {
+	{ "bias-disable", PIN_CONFIG_BIAS_DISABLE, 0 },
+	{ "bias-pull-up", PIN_CONFIG_BIAS_PULL_UP, 1 },
+	{ "bias-pull-down", PIN_CONFIG_BIAS_PULL_DOWN, 1 },
+	{ "input-schmitt-enable", PIN_CONFIG_INPUT_SCHMITT_ENABLE, 1 },
+	{ "input-schmitt-disable", PIN_CONFIG_INPUT_SCHMITT_ENABLE, 0 },
+	{ "drive-strength", PIN_CONFIG_DRIVE_STRENGTH, 0 },
+	{ "slew-rate", PIN_CONFIG_SLEW_RATE, 0 },
+};
+
+static const u32 mt7621_pconf_drv_strength_tbl[] = {2, 4, 6, 8};
+
+static int mt7621_pinconf_group_set(struct udevice *dev,
+				    unsigned int group_selector,
+				    unsigned int param, unsigned int arg)
+{
+	struct mt7621_pinctrl_priv *priv = dev_get_priv(dev);
+	const struct mtmips_pmx_group *grp = &mt7621_pmx_data[group_selector];
+	u32 clr = 0, set = 0;
+	int i;
+
+	if (!grp->pconf_avail)
+		return 0;
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+		clr = PULL_UP | PULL_DOWN;
+		break;
+
+	case PIN_CONFIG_BIAS_PULL_UP:
+		clr = PULL_DOWN;
+		set = PULL_UP;
+		break;
+
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+		clr = PULL_UP;
+		set = PULL_DOWN;
+		break;
+
+	case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+		if (arg)
+			set = SMT;
+		else
+			clr = SMT;
+		break;
+
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		for (i = 0; i < ARRAY_SIZE(mt7621_pconf_drv_strength_tbl); i++)
+			if (mt7621_pconf_drv_strength_tbl[i] == arg)
+				break;
+
+		if (i >= ARRAY_SIZE(mt7621_pconf_drv_strength_tbl))
+			return -EINVAL;
+
+		clr = E4_E2_M << E4_E2_S;
+		set = i << E4_E2_S;
+		break;
+
+	case PIN_CONFIG_SLEW_RATE:
+		if (arg)
+			set = SR;
+		else
+			clr = SR;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	mtmips_pinctrl_reg_set(&priv->mp, grp->pconf_reg, grp->pconf_shift,
+			       clr, set);
+
+	return 0;
+}
+#endif
+
+static int mt7621_pinctrl_probe(struct udevice *dev)
+{
+	struct mt7621_pinctrl_priv *priv = dev_get_priv(dev);
+	int ret = 0;
+
+#if CONFIG_IS_ENABLED(PINMUX)
+	ret = mtmips_pinctrl_probe(&priv->mp, ARRAY_SIZE(mt7621_pmx_data),
+				   mt7621_pmx_data);
+#endif /* CONFIG_IS_ENABLED(PINMUX) */
+
+	return ret;
+}
+
+static int mt7621_pinctrl_of_to_plat(struct udevice *dev)
+{
+	struct mt7621_pinctrl_priv *priv = dev_get_priv(dev);
+
+	priv->mp.base = (void __iomem *)dev_remap_addr_index(dev, 0);
+
+	if (!priv->mp.base)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct pinctrl_ops mt7621_pinctrl_ops = {
+#if CONFIG_IS_ENABLED(PINMUX)
+	.get_groups_count = mt7621_get_groups_count,
+	.get_group_name = mt7621_get_group_name,
+	.get_functions_count = mtmips_get_functions_count,
+	.get_function_name = mtmips_get_function_name,
+	.pinmux_group_set = mtmips_pinmux_group_set,
+#endif /* CONFIG_IS_ENABLED(PINMUX) */
+#if CONFIG_IS_ENABLED(PINCONF)
+	.pinconf_num_params = ARRAY_SIZE(mt7621_conf_params),
+	.pinconf_params = mt7621_conf_params,
+	.pinconf_group_set = mt7621_pinconf_group_set,
+#endif /* CONFIG_IS_ENABLED(PINCONF) */
+	.set_state = pinctrl_generic_set_state,
+};
+
+static const struct udevice_id mt7621_pinctrl_ids[] = {
+	{ .compatible = "mediatek,mt7621-pinctrl" },
+	{ }
+};
+
+U_BOOT_DRIVER(mt7621_pinctrl) = {
+	.name = "mt7621-pinctrl",
+	.id = UCLASS_PINCTRL,
+	.of_match = mt7621_pinctrl_ids,
+	.of_to_plat = mt7621_pinctrl_of_to_plat,
+	.ops = &mt7621_pinctrl_ops,
+	.probe = mt7621_pinctrl_probe,
+	.priv_auto = sizeof(struct mt7621_pinctrl_priv),
+};
diff --git a/drivers/pinctrl/mtmips/pinctrl-mtmips-common.c b/drivers/pinctrl/mtmips/pinctrl-mtmips-common.c
index e361916..869b781 100644
--- a/drivers/pinctrl/mtmips/pinctrl-mtmips-common.c
+++ b/drivers/pinctrl/mtmips/pinctrl-mtmips-common.c
@@ -13,8 +13,8 @@
 
 #include "pinctrl-mtmips-common.h"
 
-static void mtmips_pinctrl_reg_set(struct mtmips_pinctrl_priv *priv,
-				   u32 reg, u32 shift, u32 mask, u32 value)
+void mtmips_pinctrl_reg_set(struct mtmips_pinctrl_priv *priv,
+			    u32 reg, u32 shift, u32 mask, u32 value)
 {
 	u32 val;
 
diff --git a/drivers/pinctrl/mtmips/pinctrl-mtmips-common.h b/drivers/pinctrl/mtmips/pinctrl-mtmips-common.h
index b51d8f0..1f1023e 100644
--- a/drivers/pinctrl/mtmips/pinctrl-mtmips-common.h
+++ b/drivers/pinctrl/mtmips/pinctrl-mtmips-common.h
@@ -22,6 +22,10 @@
 	u32 shift;
 	char mask;
 
+	int pconf_avail;
+	u32 pconf_reg;
+	u32 pconf_shift;
+
 	int nfuncs;
 	const struct mtmips_pmx_func *funcs;
 };
@@ -42,6 +46,14 @@
 	{ .name = (_name), .reg = (_reg), .shift = (_shift), .mask = (_mask), \
 	  .funcs = (_funcs), .nfuncs = ARRAY_SIZE(_funcs) }
 
+#define GRP_PCONF(_name, _funcs, _reg, _shift, _mask, _pconf_reg, _pconf_shift) \
+	{ .name = (_name), .reg = (_reg), .shift = (_shift), .mask = (_mask), \
+	  .funcs = (_funcs), .nfuncs = ARRAY_SIZE(_funcs), .pconf_avail = 1, \
+	  .pconf_reg = (_pconf_reg), .pconf_shift = (_pconf_shift) }
+
+void mtmips_pinctrl_reg_set(struct mtmips_pinctrl_priv *priv,
+			    u32 reg, u32 shift, u32 mask, u32 value);
+
 int mtmips_get_functions_count(struct udevice *dev);
 const char *mtmips_get_function_name(struct udevice *dev,
 				     unsigned int selector);
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 766d563..75b7945 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -254,7 +254,7 @@
 
 config MT7621_SPI
 	bool "MediaTek MT7621 SPI driver"
-	depends on SOC_MT7628
+	depends on SOC_MT7621 || SOC_MT7628
 	help
 	  Enable the MT7621 SPI driver. This driver can be used to access
 	  the SPI NOR flash on platforms embedding this Ralink / MediaTek
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 31ae9f7..c3b97f4 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -42,7 +42,7 @@
 
 config USB_XHCI_MTK
 	bool "Support for MediaTek on-chip xHCI USB controller"
-	depends on ARCH_MEDIATEK
+	depends on ARCH_MEDIATEK || SOC_MT7621
 	help
 	  Enables support for the on-chip xHCI controller on MediaTek SoCs.
 
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 532ada8..6043fe7 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -191,7 +191,7 @@
 
 config WDT_MT7621
 	bool "MediaTek MT7621 watchdog timer support"
-	depends on WDT && SOC_MT7628
+	depends on WDT && (SOC_MT7621 || SOC_MT7628)
 	help
 	  Select this to enable Ralink / Mediatek watchdog timer,
 	  which can be found on some MediaTek chips.
diff --git a/include/configs/mt7621.h b/include/configs/mt7621.h
new file mode 100644
index 0000000..1f68997
--- /dev/null
+++ b/include/configs/mt7621.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef __CONFIG_MT7621_H
+#define __CONFIG_MT7621_H
+
+#define CONFIG_SYS_MIPS_TIMER_FREQ	440000000
+
+#define CONFIG_SYS_SDRAM_BASE		0x80000000
+
+#define CONFIG_VERY_BIG_RAM
+#define CONFIG_MAX_MEM_MAPPED		0x1c000000
+
+#define CONFIG_SYS_INIT_SP_OFFSET	0x800000
+
+#define CONFIG_SYS_NONCACHED_MEMORY	0x100000
+
+/* MMC */
+#define MMC_SUPPORTS_TUNING
+
+/* NAND */
+#define CONFIG_SYS_MAX_NAND_DEVICE	1
+
+/* Serial SPL */
+#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_SERIAL)
+#define CONFIG_SYS_NS16550_MEM32
+#define CONFIG_SYS_NS16550_CLK		50000000
+#define CONFIG_SYS_NS16550_REG_SIZE	-4
+#define CONFIG_SYS_NS16550_COM1		0xbe000c00
+#endif
+
+/* Serial common */
+#define CONFIG_SYS_BAUDRATE_TABLE	{ 9600, 19200, 38400, 57600, 115200, \
+					  230400, 460800, 921600 }
+
+/* Dummy value */
+#define CONFIG_SYS_UBOOT_BASE		0
+
+#endif /* __CONFIG_MT7621_H */
diff --git a/include/dt-bindings/clock/mt7621-clk.h b/include/dt-bindings/clock/mt7621-clk.h
new file mode 100644
index 0000000..978c679
--- /dev/null
+++ b/include/dt-bindings/clock/mt7621-clk.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _DT_BINDINGS_MT7621_CLK_H_
+#define _DT_BINDINGS_MT7621_CLK_H_
+
+#define MT7621_CLK_XTAL		0
+#define MT7621_CLK_CPU		1
+#define MT7621_CLK_BUS		2
+#define MT7621_CLK_50M		3
+#define MT7621_CLK_125M		4
+#define MT7621_CLK_150M		5
+#define MT7621_CLK_250M		6
+#define MT7621_CLK_270M		7
+
+#define MT7621_CLK_HSDMA	8
+#define MT7621_CLK_FE		9
+#define MT7621_CLK_SP_DIVTX	10
+#define MT7621_CLK_TIMER	11
+#define MT7621_CLK_PCM		12
+#define MT7621_CLK_PIO		13
+#define MT7621_CLK_GDMA		14
+#define MT7621_CLK_NAND		15
+#define MT7621_CLK_I2C		16
+#define MT7621_CLK_I2S		17
+#define MT7621_CLK_SPI		18
+#define MT7621_CLK_UART1	19
+#define MT7621_CLK_UART2	20
+#define MT7621_CLK_UART3	21
+#define MT7621_CLK_ETH		22
+#define MT7621_CLK_PCIE0	23
+#define MT7621_CLK_PCIE1	24
+#define MT7621_CLK_PCIE2	25
+#define MT7621_CLK_CRYPTO	26
+#define MT7621_CLK_SHXC		27
+
+#define MT7621_CLK_MAX		28
+
+/* for u-boot only */
+#define MT7621_CLK_DDR		29
+
+#endif /* _DT_BINDINGS_MT7621_CLK_H_ */
diff --git a/include/dt-bindings/reset/mt7621-reset.h b/include/dt-bindings/reset/mt7621-reset.h
new file mode 100644
index 0000000..8e4341f
--- /dev/null
+++ b/include/dt-bindings/reset/mt7621-reset.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2022 MediaTek Inc. All rights reserved.
+ *
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+
+#ifndef _DT_BINDINGS_MT7621_RESET_H_
+#define _DT_BINDINGS_MT7621_RESET_H_
+
+#define RST_PPE			31
+#define RST_SDXC		30
+#define RST_CRYPTO		29
+#define RST_AUX_STCK		28
+#define RST_PCIE2		26
+#define RST_PCIE1		25
+#define RST_PCIE0		24
+#define RST_GMAC		23
+#define RST_UART3		21
+#define RST_UART2		20
+#define RST_UART1		19
+#define RST_SPI			18
+#define RST_I2S			17
+#define RST_I2C			16
+#define RST_NFI			15
+#define RST_GDMA		14
+#define RST_PIO			13
+#define RST_PCM			11
+#define RST_MC			10
+#define RST_INTC		9
+#define RST_TIMER		8
+#define RST_SPDIFTX		7
+#define RST_FE			6
+#define RST_HSDMA		5
+#define RST_MCM			2
+#define RST_SYS			0
+
+#endif /* _DT_BINDINGS_MT7621_RESET_H_ */
diff --git a/tools/mtk_image.c b/tools/mtk_image.c
index 418c5fd..de5ce4d 100644
--- a/tools/mtk_image.c
+++ b/tools/mtk_image.c
@@ -6,7 +6,9 @@
  * Author: Weijie Gao <weijie.gao@mediatek.com>
  */
 
+#include <time.h>
 #include <image.h>
+#include <u-boot/crc.h>
 #include <u-boot/sha256.h>
 #include "imagetool.h"
 #include "mtk_image.h"
@@ -251,17 +253,45 @@
 static enum brlyt_img_type hdr_media;
 static uint32_t hdr_offset;
 static int use_lk_hdr;
+static int use_mt7621_hdr;
 static bool is_arm64_image;
 
 /* LK image name */
 static char lk_name[32] = "U-Boot";
 
+/* CRC32 normal table required by MT7621 image */
+static uint32_t crc32tbl[256];
+
 /* NAND header selected by user */
 static const union nand_boot_header *hdr_nand;
 
 /* GFH header + 2 * 4KB pages of NAND */
 static char hdr_tmp[sizeof(struct gfh_header) + 0x2000];
 
+static uint32_t crc32_normal_cal(uint32_t crc, const void *data, size_t length,
+				 const uint32_t *crc32c_table)
+{
+	const uint8_t *p = data;
+
+	while (length--)
+		crc = crc32c_table[(uint8_t)((crc >> 24) ^ *p++)] ^ (crc << 8);
+
+	return crc;
+}
+
+static void crc32_normal_init(uint32_t *crc32c_table, uint32_t poly)
+{
+	uint32_t v, i, j;
+
+	for (i = 0; i < 256; i++) {
+		v = i << 24;
+		for (j = 0; j < 8; j++)
+			v = (v << 1) ^ ((v & (1 << 31)) ? poly : 0);
+
+		crc32c_table[i] = v;
+	}
+}
+
 static int mtk_image_check_image_types(uint8_t type)
 {
 	if (type == IH_TYPE_MTKIMAGE)
@@ -283,6 +313,7 @@
 	static const char *hdr_offs = "";
 	static const char *nandinfo = "";
 	static const char *lk = "";
+	static const char *mt7621 = "";
 	static const char *arm64_param = "";
 
 	key = buf;
@@ -332,6 +363,9 @@
 			if (!strcmp(key, "lk"))
 				lk = val;
 
+			if (!strcmp(key, "mt7621"))
+				mt7621 = val;
+
 			if (!strcmp(key, "lkname"))
 				snprintf(lk_name, sizeof(lk_name), "%s", val);
 
@@ -352,6 +386,13 @@
 		return 0;
 	}
 
+	/* if user specified MT7621 image header, skip following checks */
+	if (mt7621 && mt7621[0] == '1') {
+		use_mt7621_hdr = 1;
+		free(buf);
+		return 0;
+	}
+
 	/* parse media type */
 	for (i = 0; i < ARRAY_SIZE(brom_images); i++) {
 		if (!strcmp(brom_images[i].name, media)) {
@@ -419,6 +460,13 @@
 		return 0;
 	}
 
+	if (use_mt7621_hdr) {
+		tparams->header_size = image_get_header_size();
+		tparams->hdr = &hdr_tmp;
+		memset(&hdr_tmp, 0, tparams->header_size);
+		return 0;
+	}
+
 	if (hdr_media == BRLYT_TYPE_NAND || hdr_media == BRLYT_TYPE_SNAND)
 		tparams->header_size = 2 * le16_to_cpu(hdr_nand->pagesize);
 	else
@@ -579,9 +627,90 @@
 	return 0;
 }
 
+static uint32_t crc32be_cal(const void *data, size_t length)
+{
+	uint32_t crc = 0;
+	uint8_t c;
+
+	if (crc32tbl[1] != MT7621_IH_CRC_POLYNOMIAL)
+		crc32_normal_init(crc32tbl, MT7621_IH_CRC_POLYNOMIAL);
+
+	crc = crc32_normal_cal(crc, data, length, crc32tbl);
+
+	for (; length; length >>= 8) {
+		c = length & 0xff;
+		crc = crc32_normal_cal(crc, &c, 1, crc32tbl);
+	}
+
+	return ~crc;
+}
+
+static int mtk_image_verify_mt7621_header(const uint8_t *ptr, int print)
+{
+	const image_header_t *hdr = (const image_header_t *)ptr;
+	struct mt7621_nand_header *nhdr;
+	uint32_t spl_size, crcval;
+	image_header_t header;
+	int ret;
+
+	spl_size = image_get_size(hdr);
+
+	if (spl_size > img_size) {
+		if (print)
+			printf("Incomplete SPL image\n");
+		return -1;
+	}
+
+	ret = image_check_hcrc(hdr);
+	if (!ret) {
+		if (print)
+			printf("Bad header CRC\n");
+		return -1;
+	}
+
+	ret = image_check_dcrc(hdr);
+	if (!ret) {
+		if (print)
+			printf("Bad data CRC\n");
+		return -1;
+	}
+
+	/* Copy header so we can blank CRC field for re-calculation */
+	memmove(&header, hdr, image_get_header_size());
+	image_set_hcrc(&header, 0);
+
+	nhdr = (struct mt7621_nand_header *)header.ih_name;
+	crcval = be32_to_cpu(nhdr->crc);
+	nhdr->crc = 0;
+
+	if (crcval != crc32be_cal(&header, image_get_header_size())) {
+		if (print)
+			printf("Bad NAND header CRC\n");
+		return -1;
+	}
+
+	if (print) {
+		printf("Load Address: %08x\n", image_get_load(hdr));
+
+		printf("Image Name:   %.*s\n", MT7621_IH_NMLEN,
+		       image_get_name(hdr));
+
+		if (IMAGE_ENABLE_TIMESTAMP) {
+			printf("Created:      ");
+			genimg_print_time((time_t)image_get_time(hdr));
+		}
+
+		printf("Data Size:    ");
+		genimg_print_size(image_get_data_size(hdr));
+	}
+
+	return 0;
+}
+
 static int mtk_image_verify_header(unsigned char *ptr, int image_size,
 				   struct image_tool_params *params)
 {
+	image_header_t *hdr = (image_header_t *)ptr;
 	union lk_hdr *lk = (union lk_hdr *)ptr;
 
 	/* nothing to verify for LK image header */
@@ -590,6 +719,9 @@
 
 	img_size = image_size;
 
+	if (image_get_magic(hdr) == IH_MAGIC)
+		return mtk_image_verify_mt7621_header(ptr, 0);
+
 	if (!strcmp((char *)ptr, NAND_BOOT_NAME))
 		return mtk_image_verify_nand_header(ptr, 0);
 	else
@@ -600,6 +732,7 @@
 
 static void mtk_image_print_header(const void *ptr)
 {
+	image_header_t *hdr = (image_header_t *)ptr;
 	union lk_hdr *lk = (union lk_hdr *)ptr;
 
 	if (le32_to_cpu(lk->magic) == LK_PART_MAGIC) {
@@ -610,6 +743,11 @@
 
 	printf("Image Type:   MediaTek BootROM Loadable Image\n");
 
+	if (image_get_magic(hdr) == IH_MAGIC) {
+		mtk_image_verify_mt7621_header(ptr, 1);
+		return;
+	}
+
 	if (!strcmp((char *)ptr, NAND_BOOT_NAME))
 		mtk_image_verify_nand_header(ptr, 1);
 	else
@@ -773,6 +911,45 @@
 		 filesize - 2 * le16_to_cpu(hdr_nand->pagesize) - SHA256_SUM_LEN);
 }
 
+static void mtk_image_set_mt7621_header(void *ptr, off_t filesize,
+					uint32_t loadaddr)
+{
+	image_header_t *hdr = (image_header_t *)ptr;
+	struct mt7621_stage1_header *shdr;
+	struct mt7621_nand_header *nhdr;
+	uint32_t datasize, crcval;
+
+	datasize = filesize - image_get_header_size();
+	nhdr = (struct mt7621_nand_header *)hdr->ih_name;
+	shdr = (struct mt7621_stage1_header *)(ptr + image_get_header_size());
+
+	shdr->ep = cpu_to_be32(loadaddr);
+	shdr->stage_size = cpu_to_be32(datasize);
+
+	image_set_magic(hdr, IH_MAGIC);
+	image_set_time(hdr, time(NULL));
+	image_set_size(hdr, datasize);
+	image_set_load(hdr, loadaddr);
+	image_set_ep(hdr, loadaddr);
+	image_set_os(hdr, IH_OS_U_BOOT);
+	image_set_arch(hdr, IH_ARCH_MIPS);
+	image_set_type(hdr, IH_TYPE_STANDALONE);
+	image_set_comp(hdr, IH_COMP_NONE);
+
+	crcval = crc32(0, (uint8_t *)shdr, datasize);
+	image_set_dcrc(hdr, crcval);
+
+	strncpy(nhdr->ih_name, "MT7621 NAND", MT7621_IH_NMLEN);
+
+	nhdr->ih_stage_offset = cpu_to_be32(image_get_header_size());
+
+	crcval = crc32be_cal(hdr, image_get_header_size());
+	nhdr->crc = cpu_to_be32(crcval);
+
+	crcval = crc32(0, (uint8_t *)hdr, image_get_header_size());
+	image_set_hcrc(hdr, crcval);
+}
+
 static void mtk_image_set_header(void *ptr, struct stat *sbuf, int ifd,
 				 struct image_tool_params *params)
 {
@@ -791,6 +968,11 @@
 	img_gen = true;
 	img_size = sbuf->st_size;
 
+	if (use_mt7621_hdr) {
+		mtk_image_set_mt7621_header(ptr, sbuf->st_size, params->addr);
+		return;
+	}
+
 	if (hdr_media == BRLYT_TYPE_NAND || hdr_media == BRLYT_TYPE_SNAND)
 		mtk_image_set_nand_header(ptr, sbuf->st_size, params->addr);
 	else
diff --git a/tools/mtk_image.h b/tools/mtk_image.h
index 7dda71c..d868545 100644
--- a/tools/mtk_image.h
+++ b/tools/mtk_image.h
@@ -200,4 +200,28 @@
 
 #define LK_PART_MAGIC		0x58881688
 
+/* MT7621 NAND SPL image header */
+
+#define MT7621_IH_NMLEN			12
+#define MT7621_IH_CRC_POLYNOMIAL	0x04c11db7
+
+struct mt7621_nand_header {
+	char ih_name[MT7621_IH_NMLEN];
+	uint32_t nand_ac_timing;
+	uint32_t ih_stage_offset;
+	uint32_t ih_bootloader_offset;
+	uint32_t nand_info_1_data;
+	uint32_t crc;
+};
+
+struct mt7621_stage1_header {
+	uint32_t jump_insn[2];
+	uint32_t ep;
+	uint32_t stage_size;
+	uint32_t has_stage2;
+	uint32_t next_ep;
+	uint32_t next_size;
+	uint32_t next_offset;
+};
+
 #endif /* _MTK_IMAGE_H */