diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig
index a08b4288b4..93deaef809 100644
--- a/drivers/power/domain/Kconfig
+++ b/drivers/power/domain/Kconfig
@@ -23,6 +23,13 @@ config IMX8_POWER_DOMAIN
           Enable support for manipulating NXP i.MX8 on-SoC power domains via IPC
           requests to the SCU.
 
+config MTK_POWER_DOMAIN
+	bool "Enable the MediaTek power domain driver"
+	depends on POWER_DOMAIN && ARCH_MEDIATEK
+	help
+	  Enable support for manipulating MediaTek power domains via MMIO
+	  mapped registers.
+
 config MESON_GX_VPU_POWER_DOMAIN
 	bool "Enable Amlogic Meson GX VPU power domain driver"
 	depends on ARCH_MESON
diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile
index b08d18f7ac..695aafe17d 100644
--- a/drivers/power/domain/Makefile
+++ b/drivers/power/domain/Makefile
@@ -6,6 +6,7 @@
 obj-$(CONFIG_$(SPL_)POWER_DOMAIN) += power-domain-uclass.o
 obj-$(CONFIG_BCM6328_POWER_DOMAIN) += bcm6328-power-domain.o
 obj-$(CONFIG_IMX8_POWER_DOMAIN) += imx8-power-domain.o
+obj-$(CONFIG_MTK_POWER_DOMAIN) += mtk-power-domain.o
 obj-$(CONFIG_MESON_GX_VPU_POWER_DOMAIN) += meson-gx-pwrc-vpu.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain.o
 obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain-test.o
diff --git a/drivers/power/domain/mtk-power-domain.c b/drivers/power/domain/mtk-power-domain.c
new file mode 100644
index 0000000000..ed4a718023
--- /dev/null
+++ b/drivers/power/domain/mtk-power-domain.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 MediaTek Inc.
+ * Author: Ryder Lee <ryder.lee@mediatek.com>
+ */
+
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <power-domain-uclass.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/processor.h>
+#include <linux/iopoll.h>
+
+#include <dt-bindings/power/mt7629-power.h>
+
+#define SPM_EN			(0xb16 << 16 | 0x1)
+#define SPM_ETHSYS_PWR_CON	0x2e0
+#define SPM_HIF0_PWR_CON	0x2e4
+#define SPM_HIF1_PWR_CON	0x2e8
+#define SPM_PWR_STATUS		0x60c
+#define SPM_PWR_STATUS_2ND	0x610
+
+#define PWR_RST_B_BIT		BIT(0)
+#define PWR_ISO_BIT		BIT(1)
+#define PWR_ON_BIT		BIT(2)
+#define PWR_ON_2ND_BIT		BIT(3)
+#define PWR_CLK_DIS_BIT		BIT(4)
+
+#define PWR_STATUS_ETHSYS	BIT(24)
+#define PWR_STATUS_HIF0		BIT(25)
+#define PWR_STATUS_HIF1		BIT(26)
+
+/* Infrasys configuration */
+#define INFRA_TOPDCM_CTRL	0x10
+#define INFRA_TOPAXI_PROT_EN	0x220
+#define INFRA_TOPAXI_PROT_STA1	0x228
+
+#define DCM_TOP_EN		BIT(0)
+
+enum scp_domain_type {
+	SCPSYS_MT7629,
+};
+
+struct scp_domain;
+
+struct scp_domain_data {
+	struct scp_domain *scpd;
+	u32 sta_mask;
+	int ctl_offs;
+	u32 sram_pdn_bits;
+	u32 sram_pdn_ack_bits;
+	u32 bus_prot_mask;
+};
+
+struct scp_domain {
+	void __iomem *base;
+	void __iomem *infracfg;
+	enum scp_domain_type type;
+	struct scp_domain_data *data;
+};
+
+static struct scp_domain_data scp_domain_mt7629[] = {
+	[MT7629_POWER_DOMAIN_ETHSYS] = {
+		.sta_mask = PWR_STATUS_ETHSYS,
+		.ctl_offs = SPM_ETHSYS_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = (BIT(3) | BIT(17)),
+	},
+	[MT7629_POWER_DOMAIN_HIF0] = {
+		.sta_mask = PWR_STATUS_HIF0,
+		.ctl_offs = SPM_HIF0_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = GENMASK(25, 24),
+	},
+	[MT7629_POWER_DOMAIN_HIF1] = {
+		.sta_mask = PWR_STATUS_HIF1,
+		.ctl_offs = SPM_HIF1_PWR_CON,
+		.sram_pdn_bits = GENMASK(11, 8),
+		.sram_pdn_ack_bits = GENMASK(15, 12),
+		.bus_prot_mask = GENMASK(28, 26),
+	},
+};
+
+/**
+ * This function enables the bus protection bits for disabled power
+ * domains so that the system does not hang when some unit accesses the
+ * bus while in power down.
+ */
+static int mtk_infracfg_set_bus_protection(void __iomem *infracfg,
+					   u32 mask)
+{
+	u32 val;
+
+	clrsetbits_le32(infracfg + INFRA_TOPAXI_PROT_EN, mask, mask);
+
+	return readl_poll_timeout(infracfg + INFRA_TOPAXI_PROT_STA1, val,
+				  (val & mask) == mask, 100);
+}
+
+static int mtk_infracfg_clear_bus_protection(void __iomem *infracfg,
+					     u32 mask)
+{
+	u32 val;
+
+	clrbits_le32(infracfg + INFRA_TOPAXI_PROT_EN, mask);
+
+	return readl_poll_timeout(infracfg + INFRA_TOPAXI_PROT_STA1, val,
+				  !(val & mask), 100);
+}
+
+static int scpsys_domain_is_on(struct scp_domain_data *data)
+{
+	struct scp_domain *scpd = data->scpd;
+	u32 sta = readl(scpd->base + SPM_PWR_STATUS) &
+			data->sta_mask;
+	u32 sta2 = readl(scpd->base + SPM_PWR_STATUS_2ND) &
+			 data->sta_mask;
+
+	/*
+	 * A domain is on when both status bits are set. If only one is set
+	 * return an error. This happens while powering up a domain
+	 */
+	if (sta && sta2)
+		return true;
+	if (!sta && !sta2)
+		return false;
+
+	return -EINVAL;
+}
+
+static int scpsys_power_on(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data = &scpd->data[power_domain->id];
+	void __iomem *ctl_addr = scpd->base + data->ctl_offs;
+	u32 pdn_ack = data->sram_pdn_ack_bits;
+	u32 val;
+	int ret, tmp;
+
+	writel(SPM_EN, scpd->base);
+
+	val = readl(ctl_addr);
+	val |= PWR_ON_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_ON_2ND_BIT;
+	writel(val, ctl_addr);
+
+	ret = readx_poll_timeout(scpsys_domain_is_on, data, tmp, tmp > 0,
+				 100);
+	if (ret < 0)
+		return ret;
+
+	val &= ~PWR_CLK_DIS_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ISO_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_RST_B_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~data->sram_pdn_bits;
+	writel(val, ctl_addr);
+
+	ret = readl_poll_timeout(ctl_addr, tmp, !(tmp & pdn_ack), 100);
+	if (ret < 0)
+		return ret;
+
+	if (data->bus_prot_mask) {
+		ret = mtk_infracfg_clear_bus_protection(scpd->infracfg,
+							data->bus_prot_mask);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scpsys_power_off(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data = &scpd->data[power_domain->id];
+	void __iomem *ctl_addr = scpd->base + data->ctl_offs;
+	u32 pdn_ack = data->sram_pdn_ack_bits;
+	u32 val;
+	int ret, tmp;
+
+	if (data->bus_prot_mask) {
+		ret = mtk_infracfg_set_bus_protection(scpd->infracfg,
+						      data->bus_prot_mask);
+		if (ret)
+			return ret;
+	}
+
+	val = readl(ctl_addr);
+	val |= data->sram_pdn_bits;
+	writel(val, ctl_addr);
+
+	ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == pdn_ack,
+				 100);
+	if (ret < 0)
+		return ret;
+
+	val |= PWR_ISO_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_RST_B_BIT;
+	writel(val, ctl_addr);
+
+	val |= PWR_CLK_DIS_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ON_BIT;
+	writel(val, ctl_addr);
+
+	val &= ~PWR_ON_2ND_BIT;
+	writel(val, ctl_addr);
+
+	ret = readx_poll_timeout(scpsys_domain_is_on, data, tmp, !tmp, 100);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int scpsys_power_request(struct power_domain *power_domain)
+{
+	struct scp_domain *scpd = dev_get_priv(power_domain->dev);
+	struct scp_domain_data *data;
+
+	data = &scpd->data[power_domain->id];
+	data->scpd = scpd;
+
+	return 0;
+}
+
+static int scpsys_power_free(struct power_domain *power_domain)
+{
+	return 0;
+}
+
+static int mtk_power_domain_hook(struct udevice *dev)
+{
+	struct scp_domain *scpd = dev_get_priv(dev);
+
+	scpd->type = (enum scp_domain_type)dev_get_driver_data(dev);
+
+	switch (scpd->type) {
+	case SCPSYS_MT7629:
+		scpd->data = scp_domain_mt7629;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mtk_power_domain_probe(struct udevice *dev)
+{
+	struct ofnode_phandle_args args;
+	struct scp_domain *scpd = dev_get_priv(dev);
+	struct regmap *regmap;
+	struct clk_bulk bulk;
+	int err;
+
+	scpd->base = dev_read_addr_ptr(dev);
+	if (!scpd->base)
+		return -ENOENT;
+
+	err = mtk_power_domain_hook(dev);
+	if (err)
+		return err;
+
+	/* get corresponding syscon phandle */
+	err = dev_read_phandle_with_args(dev, "infracfg", NULL, 0, 0, &args);
+	if (err)
+		return err;
+
+	regmap = syscon_node_to_regmap(args.node);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	scpd->infracfg = regmap_get_range(regmap, 0);
+	if (!scpd->infracfg)
+		return -ENOENT;
+
+	/* enable Infra DCM */
+	setbits_le32(scpd->infracfg + INFRA_TOPDCM_CTRL, DCM_TOP_EN);
+
+	err = clk_get_bulk(dev, &bulk);
+	if (err)
+		return err;
+
+	return clk_enable_bulk(&bulk);
+}
+
+static const struct udevice_id mtk_power_domain_ids[] = {
+	{
+		.compatible = "mediatek,mt7629-scpsys",
+		.data = SCPSYS_MT7629,
+	},
+	{ /* sentinel */ }
+};
+
+struct power_domain_ops mtk_power_domain_ops = {
+	.free = scpsys_power_free,
+	.off = scpsys_power_off,
+	.on = scpsys_power_on,
+	.request = scpsys_power_request,
+};
+
+U_BOOT_DRIVER(mtk_power_domain) = {
+	.name = "mtk_power_domain",
+	.id = UCLASS_POWER_DOMAIN,
+	.ops = &mtk_power_domain_ops,
+	.probe = mtk_power_domain_probe,
+	.of_match = mtk_power_domain_ids,
+	.priv_auto_alloc_size = sizeof(struct scp_domain),
+};