feat(mt8196): add SPMI driver

Add SPMI and PMIF driver for PMIC communication

Change-Id: Iad1d90381d6dad6b3e92fd9d6a3ce02fa11d15f1
Signed-off-by: Hope Wang <hope.wang@mediatek.corp-partner.google.com>
diff --git a/plat/mediatek/drivers/spmi/mt8196/platform_pmif_spmi.c b/plat/mediatek/drivers/spmi/mt8196/platform_pmif_spmi.c
new file mode 100644
index 0000000..5aab856
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/mt8196/platform_pmif_spmi.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2025, Mediatek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <lib/utils_def.h>
+
+#include <drivers/spmi/pmif_common.h>
+#include <drivers/spmi/pmif_v1/pmif.h>
+#include <drivers/spmi/spmi_common.h>
+#include <drivers/spmi/spmi_sw.h>
+#include <drivers/spmi_api.h>
+#include <lib/mtk_init/mtk_init.h>
+#include <mtk_mmap_pool.h>
+
+#define SPMI_GROUP_ID	0xB
+#define SPMI_DEBUG	0
+
+static const mmap_region_t pmif_spmi_mmap[] MTK_MMAP_SECTION = {
+	MAP_REGION_FLAT(PMIF_SPMI_M_BASE, PMIF_SPMI_SIZE,
+		MT_DEVICE | MT_RW | MT_NS),
+	MAP_REGION_FLAT(SPMI_MST_M_BASE, SPMI_MST_SIZE,
+		MT_DEVICE | MT_RW | MT_SECURE),
+	MAP_REGION_FLAT(PMIF_SPMI_P_BASE, PMIF_SPMI_SIZE,
+		MT_DEVICE | MT_RW | MT_NS),
+	{0}
+};
+DECLARE_MTK_MMAP_REGIONS(pmif_spmi_mmap);
+
+static uint16_t mt6xxx_spmi_regs[] = {
+	[SPMI_OP_ST_CTRL]   = 0x0000,
+	[SPMI_GRP_ID_EN]    = 0x0004,
+	[SPMI_OP_ST_STA]    = 0x0008,
+	[SPMI_MST_SAMPL]    = 0x000C,
+	[SPMI_MST_REQ_EN]   = 0x0010,
+	[SPMI_RCS_CTRL]     = 0x0014,
+	[SPMI_SLV_3_0_EINT] = 0x0020,
+	[SPMI_SLV_7_4_EINT] = 0x0024,
+	[SPMI_SLV_B_8_EINT] = 0x0028,
+	[SPMI_SLV_F_C_EINT] = 0x002C,
+	[SPMI_REC_CTRL]     = 0x0040,
+	[SPMI_REC0]         = 0x0044,
+	[SPMI_REC1]         = 0x0048,
+	[SPMI_REC2]         = 0x004C,
+	[SPMI_REC3]         = 0x0050,
+	[SPMI_REC4]         = 0x0054,
+	[SPMI_REC_CMD_DEC]  = 0x005C,
+	[SPMI_DEC_DBG]      = 0x00F8,
+	[SPMI_MST_DBG]      = 0x00FC,
+};
+
+static uint16_t mt6xxx_regs[] = {
+	[PMIF_INIT_DONE]                = 0x0000,
+	[PMIF_INF_EN]                   = 0x0024,
+	[PMIF_ARB_EN]                   = 0x0150,
+	[PMIF_IRQ_EVENT_EN_0]           = 0x0420,
+	[PMIF_IRQ_FLAG_0]               = 0x0428,
+	[PMIF_IRQ_CLR_0]                = 0x042C,
+	[PMIF_IRQ_EVENT_EN_2]           = 0x0440,
+	[PMIF_IRQ_FLAG_2]               = 0x0448,
+	[PMIF_IRQ_CLR_2]                = 0x044C,
+	[PMIF_WDT_CTRL]                 = 0x0470,
+	[PMIF_WDT_EVENT_EN_1]           = 0x047C,
+	[PMIF_WDT_FLAG_1]               = 0x0480,
+	[PMIF_SWINF_2_ACC]              = 0x0880,
+	[PMIF_SWINF_2_WDATA_31_0]       = 0x0884,
+	[PMIF_SWINF_2_WDATA_63_32]      = 0x0888,
+	[PMIF_SWINF_2_RDATA_31_0]       = 0x0894,
+	[PMIF_SWINF_2_RDATA_63_32]      = 0x0898,
+	[PMIF_SWINF_2_VLD_CLR]          = 0x08A4,
+	[PMIF_SWINF_2_STA]              = 0x08A8,
+	[PMIF_SWINF_3_ACC]              = 0x08C0,
+	[PMIF_SWINF_3_WDATA_31_0]       = 0x08C4,
+	[PMIF_SWINF_3_WDATA_63_32]      = 0x08C8,
+	[PMIF_SWINF_3_RDATA_31_0]       = 0x08D4,
+	[PMIF_SWINF_3_RDATA_63_32]      = 0x08D8,
+	[PMIF_SWINF_3_VLD_CLR]          = 0x08E4,
+	[PMIF_SWINF_3_STA]              = 0x08E8,
+	/* hw mpu */
+	[PMIF_PMIC_ALL_RGN_EN_1]        = 0x09B0,
+	[PMIF_PMIC_ALL_RGN_EN_2]        = 0x0D30,
+	[PMIF_PMIC_ALL_RGN_0_START]     = 0x09B4,
+	[PMIF_PMIC_ALL_RGN_0_END]       = 0x09B8,
+	[PMIF_PMIC_ALL_RGN_1_START]     = 0x09BC,
+	[PMIF_PMIC_ALL_RGN_1_END]       = 0x09C0,
+	[PMIF_PMIC_ALL_RGN_2_START]     = 0x09C4,
+	[PMIF_PMIC_ALL_RGN_2_END]       = 0x09C8,
+	[PMIF_PMIC_ALL_RGN_3_START]     = 0x09CC,
+	[PMIF_PMIC_ALL_RGN_3_END]       = 0x09D0,
+	[PMIF_PMIC_ALL_RGN_31_START]    = 0x0D34,
+	[PMIF_PMIC_ALL_RGN_31_END]      = 0x0D38,
+	[PMIF_PMIC_ALL_INVLD_SLVID]     = 0x0AAC,
+	[PMIF_PMIC_ALL_RGN_0_PER0]      = 0x0AB0,
+	[PMIF_PMIC_ALL_RGN_0_PER1]      = 0x0AB4,
+	[PMIF_PMIC_ALL_RGN_1_PER0]      = 0x0AB8,
+	[PMIF_PMIC_ALL_RGN_2_PER0]      = 0x0AC0,
+	[PMIF_PMIC_ALL_RGN_3_PER0]      = 0x0AC8,
+	[PMIF_PMIC_ALL_RGN_31_PER0]     = 0x0DB4,
+	[PMIF_PMIC_ALL_RGN_31_PER1]     = 0x0DB8,
+	[PMIF_PMIC_ALL_RGN_OTHERS_PER0] = 0x0BA8,
+	[PMIF_PMIC_ALL_RGN_OTHERS_PER1] = 0x0BAC,
+};
+
+struct pmif pmif_spmi_arb[] = {
+	{
+		.base = (void *)PMIF_SPMI_M_BASE,
+		.regs = mt6xxx_regs,
+		.spmimst_base = (void *)SPMI_MST_M_BASE,
+		.spmimst_regs = mt6xxx_spmi_regs,
+		.mstid = SPMI_MASTER_0,
+		.read_cmd = pmif_spmi_read_cmd,
+		.write_cmd = pmif_spmi_write_cmd,
+	}, {
+		.base = (void *)PMIF_SPMI_M_BASE,
+		.regs = mt6xxx_regs,
+		.spmimst_base = (void *)SPMI_MST_M_BASE,
+		.spmimst_regs = mt6xxx_spmi_regs,
+		.mstid = SPMI_MASTER_1,
+		.read_cmd = pmif_spmi_read_cmd,
+		.write_cmd = pmif_spmi_write_cmd,
+	}, {
+		.base = (void *)PMIF_SPMI_P_BASE,
+		.regs = mt6xxx_regs,
+		.spmimst_base = (void *)SPMI_MST_P_BASE,
+		.spmimst_regs = mt6xxx_spmi_regs,
+		.mstid = SPMI_MASTER_P_1,
+		.read_cmd = pmif_spmi_read_cmd,
+		.write_cmd = pmif_spmi_write_cmd,
+	},
+};
+
+static struct spmi_device spmi_dev[] = {
+	{
+		.slvid = SPMI_SLAVE_4,
+		.grpiden = 0x1 << SPMI_GROUP_ID,
+		.mstid = SPMI_MASTER_1,
+		.hwcid_addr = 0x09,
+		.hwcid_val = 0x63,
+		.swcid_addr = 0x0B,
+		.swcid_val = 0x63,
+		.wpk_key_addr = 0x3A7,
+		.wpk_key_val = 0x9C,
+		.wpk_key_h_val = 0x9C,
+		.tma_key_addr = 0x39E,
+		.tma_key_val = 0x9C,
+		.tma_key_h_val = 0x9C,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_1],
+	}, {
+		.slvid = SPMI_SLAVE_9,
+		.grpiden = 0x1 << SPMI_GROUP_ID,
+		.mstid = SPMI_MASTER_1,
+		.hwcid_addr = 0x09,
+		.hwcid_val = 0x85,
+		.swcid_addr = 0x0B,
+		.swcid_val = 0x85,
+		.wpk_key_addr = 0x3AA,
+		.wpk_key_val = 0x30,
+		.wpk_key_h_val = 0x63,
+		.tma_key_addr = 0x39E,
+		.tma_key_val = 0x7A,
+		.tma_key_h_val = 0x99,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_1],
+	}, {
+		.slvid = SPMI_SLAVE_5,
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_1,/* spmi-m */
+		.hwcid_addr = 0x09,
+		.hwcid_val = 0x73,
+		.swcid_addr = 0x0B,
+		.swcid_val = 0x73,
+		.wpk_key_addr = 0x3A7,
+		.wpk_key_val = 0x8C,
+		.wpk_key_h_val = 0x9C,
+		.tma_key_addr = 0x39E,
+		.tma_key_val = 0x8C,
+		.tma_key_h_val = 0x9C,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_1],
+	}, {
+		.slvid = SPMI_SLAVE_14, /* MT6379 */
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_1,/* spmi-m */
+		.hwcid_addr = 0x00,
+		.hwcid_val = 0x70,
+		.hwcid_mask = 0xF0,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_1],
+	}, {
+		.slvid = SPMI_SLAVE_6, /* MT6316 */
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_P_1,/* spmi-m */
+		.hwcid_addr = 0x209,
+		.hwcid_val = 0x16,
+		.swcid_addr = 0x20B,
+		.swcid_val = 0x16,
+		.wpk_key_addr = 0x3B1,
+		.wpk_key_val = 0xE9,
+		.wpk_key_h_val = 0xE6,
+		.tma_key_addr = 0x3A8,
+		.tma_key_val = 0xE9,
+		.tma_key_h_val = 0xE6,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_P_1],
+	}, {
+		.slvid = SPMI_SLAVE_7,
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_P_1,/* spmi-m */
+		.hwcid_addr = 0x209,
+		.hwcid_val = 0x16,
+		.swcid_addr = 0x20B,
+		.swcid_val = 0x16,
+		.wpk_key_addr = 0x3B1,
+		.wpk_key_val = 0xE9,
+		.wpk_key_h_val = 0xE6,
+		.tma_key_addr = 0x3A8,
+		.tma_key_val = 0xE9,
+		.tma_key_h_val = 0xE6,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_P_1],
+	}, {
+		.slvid = SPMI_SLAVE_8,
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_P_1,/* spmi-m */
+		.hwcid_addr = 0x209,
+		.hwcid_val = 0x16,
+		.swcid_addr = 0x20B,
+		.swcid_val = 0x16,
+		.wpk_key_addr = 0x3B1,
+		.wpk_key_val = 0xE9,
+		.wpk_key_h_val = 0xE6,
+		.tma_key_addr = 0x3A8,
+		.tma_key_val = 0xE9,
+		.tma_key_h_val = 0xE6,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_P_1],
+	}, {
+		.slvid = SPMI_SLAVE_15,
+		.grpiden = 0x800,
+		.mstid = SPMI_MASTER_P_1,/* spmi-m */
+		.hwcid_addr = 0x209,
+		.hwcid_val = 0x16,
+		.swcid_addr = 0x20B,
+		.swcid_val = 0x16,
+		.wpk_key_addr = 0x3B1,
+		.wpk_key_val = 0xE9,
+		.wpk_key_h_val = 0xE6,
+		.tma_key_addr = 0x3A8,
+		.tma_key_val = 0xE9,
+		.tma_key_h_val = 0xE6,
+		.pmif_arb = &pmif_spmi_arb[SPMI_MASTER_P_1],
+	},
+};
+
+#if SPMI_DEBUG
+static void spmi_read_check(struct spmi_device *dev)
+{
+	uint8_t rdata = 0;
+
+	spmi_ext_register_readl(dev, dev->hwcid_addr, &rdata, 1);
+
+	if (dev->hwcid_mask) {
+		if ((rdata & dev->hwcid_mask) == (dev->hwcid_val & dev->hwcid_mask))
+			SPMI_INFO("%s pass, slvid:%d rdata = 0x%x\n", __func__,
+				dev->slvid, rdata);
+		else
+			SPMI_ERR("%s fail, slvid:%d rdata = 0x%x\n", __func__,
+				dev->slvid, rdata);
+	} else {
+		if (rdata == dev->hwcid_val)
+			SPMI_INFO("%s pass, slvid:%d rdata = 0x%x\n", __func__,
+				dev->slvid, rdata);
+		else
+			SPMI_ERR("%s fail, slvid:%d rdata = 0x%x\n", __func__,
+				dev->slvid, rdata);
+	}
+}
+
+void spmi_test(void)
+{
+	for (int k = 0; k < ARRAY_SIZE(spmi_dev); k++)
+		spmi_read_check(&spmi_dev[k]);
+}
+#endif
+
+int platform_pmif_spmi_init(void)
+{
+	spmi_device_register(spmi_dev, ARRAY_SIZE(spmi_dev));
+
+#if SPMI_DEBUG
+	spmi_test();
+#endif
+	return 0;
+}
+MTK_ARCH_INIT(platform_pmif_spmi_init);
diff --git a/plat/mediatek/drivers/spmi/pmif_common.c b/plat/mediatek/drivers/spmi/pmif_common.c
new file mode 100644
index 0000000..5e45f0b
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/pmif_common.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <lib/mmio.h>
+#include <lib/spinlock.h>
+
+#include <pmif.h>
+#include "pmif_common.h"
+#include "spmi_common.h"
+#include "spmi_sw.h"
+
+#define PMIF_CMD_REG_0			0
+#define PMIF_CMD_REG			1
+#define PMIF_CMD_EXT_REG		2
+#define PMIF_CMD_EXT_REG_LONG		3
+#define PMIF_READ_CMD_MIN		0x60
+#define PMIF_READ_CMD_MAX		0x7F
+#define PMIF_READ_CMD_EXT_MIN		0x20
+#define PMIF_READ_CMD_EXT_MAX		0x2F
+#define PMIF_READ_CMD_EXT_LONG_MIN	0x38
+#define PMIF_READ_CMD_EXT_LONG_MAX	0x3F
+#define PMIF_WRITE_CMD_MIN		0x40
+#define PMIF_WRITE_CMD_MAX		0x5F
+#define PMIF_WRITE_CMD_EXT_MAX		0xF
+#define PMIF_WRITE_CMD_EXT_LONG_MIN	0x30
+#define PMIF_WRITE_CMD_EXT_LONG_MAX	0x37
+#define PMIF_WRITE_CMD_0_MIN		0x80
+
+/* macro for SWINF_FSM */
+#define SWINF_FSM_IDLE			0x00
+#define SWINF_FSM_REQ			0x02
+#define SWINF_FSM_WFDLE			0x04
+#define SWINF_FSM_WFVLDCLR		0x06
+
+#define GET_SWINF_FSM(x)		(((x) >> 1) & 0x7)
+#define GET_PMIF_INIT_DONE(x)		(((x) >> 15) & 0x1)
+#define TIMEOUT_WAIT_IDLE_US		10000 /* 10ms */
+
+#define PMIF_RW_CMD_SET(opc, rw, sid, bc, addr)	\
+	(((opc) << 30) | ((rw) << 29) | ((sid) << 24) | ((bc) << 16) | (addr))
+
+static spinlock_t pmif_lock;
+
+struct pmif *get_pmif_controller(int inf, int mstid)
+{
+	return &pmif_spmi_arb[mstid];
+}
+
+static int pmif_check_idle(int mstid)
+{
+	struct pmif *arb = get_pmif_controller(PMIF_SPMI, mstid);
+	unsigned int reg_rdata, offset = 0;
+
+	do {
+		offset = arb->regs[PMIF_SWINF_3_STA];
+		reg_rdata = mmio_read_32((uintptr_t)(arb->base + offset));
+	} while (GET_SWINF_FSM(reg_rdata) != SWINF_FSM_IDLE);
+
+	return 0;
+}
+
+static int pmif_check_vldclr(int mstid)
+{
+	struct pmif *arb = get_pmif_controller(PMIF_SPMI, mstid);
+	unsigned int reg_rdata, offset = 0;
+
+	do {
+		offset = arb->regs[PMIF_SWINF_3_STA];
+		reg_rdata = mmio_read_32((uintptr_t)(arb->base + offset));
+	} while (GET_SWINF_FSM(reg_rdata) != SWINF_FSM_WFVLDCLR);
+
+	return 0;
+}
+
+int pmif_spmi_read_cmd(struct pmif *arb, uint8_t opc, uint8_t sid,
+		       uint16_t addr, uint8_t *buf, uint8_t len)
+{
+	int ret;
+	uint32_t offset = 0, data = 0;
+	uint8_t bc = len - 1;
+
+	if (sid > SPMI_MAX_SLAVE_ID || len > PMIF_BYTECNT_MAX)
+		return -EINVAL;
+
+	/* Check the opcode */
+	if (opc >= PMIF_READ_CMD_MIN && opc <= PMIF_READ_CMD_MAX)
+		opc = PMIF_CMD_REG;
+	else if (opc >= PMIF_READ_CMD_EXT_MIN && opc <= PMIF_READ_CMD_EXT_MAX)
+		opc = PMIF_CMD_EXT_REG;
+	else if (opc >= PMIF_READ_CMD_EXT_LONG_MIN && opc <= PMIF_READ_CMD_EXT_LONG_MAX)
+		opc = PMIF_CMD_EXT_REG_LONG;
+	else
+		return -EINVAL;
+
+	spin_lock(&pmif_lock);
+
+	/* Wait for Software Interface FSM state to be IDLE. */
+	ret = pmif_check_idle(arb->mstid);
+	if (ret)
+		goto done;
+
+	/* Send the command. */
+	offset = arb->regs[PMIF_SWINF_3_ACC];
+	mmio_write_32((uintptr_t)(arb->base + offset), PMIF_RW_CMD_SET(opc, 0, sid, bc, addr));
+	/*
+	 * Wait for Software Interface FSM state to be WFVLDCLR,
+	 * read the data and clear the valid flag.
+	 */
+	ret = pmif_check_vldclr(arb->mstid);
+	if (ret)
+		goto done;
+
+	offset = arb->regs[PMIF_SWINF_3_RDATA_31_0];
+
+	data = mmio_read_32((uintptr_t)(arb->base + offset));
+	memcpy(buf, &data, (bc & 3) + 1);
+
+	offset = arb->regs[PMIF_SWINF_3_VLD_CLR];
+	mmio_write_32((uintptr_t)(arb->base + offset), 0x1);
+
+done:
+	spin_unlock(&pmif_lock);
+	return ret;
+}
+
+int pmif_spmi_write_cmd(struct pmif *arb, uint8_t opc, uint8_t sid, uint16_t addr,
+			const uint8_t *buf, uint8_t len)
+{
+	int ret;
+	uint32_t offset = 0, data = 0;
+	uint8_t bc = len - 1;
+
+	if (sid > SPMI_MAX_SLAVE_ID || len > PMIF_BYTECNT_MAX)
+		return -EINVAL;
+
+	/* Check the opcode */
+	if (opc >= PMIF_WRITE_CMD_MIN && opc <= PMIF_WRITE_CMD_MAX)
+		opc = PMIF_CMD_REG;
+	else if (opc <= PMIF_WRITE_CMD_EXT_MAX)
+		opc = PMIF_CMD_EXT_REG;
+	else if (opc >= PMIF_WRITE_CMD_EXT_LONG_MIN && opc <= PMIF_WRITE_CMD_EXT_LONG_MAX)
+		opc = PMIF_CMD_EXT_REG_LONG;
+	else if (opc >= PMIF_WRITE_CMD_0_MIN)
+		opc = PMIF_CMD_REG_0;
+	else
+		return -EINVAL;
+
+	spin_lock(&pmif_lock);
+
+	/* Wait for Software Interface FSM state to be IDLE. */
+	ret = pmif_check_idle(arb->mstid);
+	if (ret)
+		goto done;
+
+	/* Set the write data. */
+	offset = arb->regs[PMIF_SWINF_3_WDATA_31_0];
+	memcpy(&data, buf, (bc & 3) + 1);
+	mmio_write_32((uintptr_t)(arb->base + offset), data);
+	/* Send the command. */
+	offset = arb->regs[PMIF_SWINF_3_ACC];
+	mmio_write_32((uintptr_t)(arb->base + offset), PMIF_RW_CMD_SET(opc, 1, sid, bc, addr));
+
+done:
+	spin_unlock(&pmif_lock);
+	return ret;
+}
diff --git a/plat/mediatek/drivers/spmi/pmif_common.h b/plat/mediatek/drivers/spmi/pmif_common.h
new file mode 100644
index 0000000..bdb2722
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/pmif_common.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef PMIF_COMMON_H
+#define PMIF_COMMON_H
+
+#include <stdint.h>
+
+enum {
+	PMIF_CMD_REG_0,
+	PMIF_CMD_REG,
+	PMIF_CMD_EXT_REG,
+	PMIF_CMD_EXT_REG_LONG,
+};
+
+struct pmif {
+	void *base;
+	uint16_t *regs;
+	void *spmimst_base;
+	uint16_t *spmimst_regs;
+	uint32_t mstid;
+	int (*read_cmd)(struct pmif *arb, uint8_t opc, uint8_t sid, uint16_t addr, uint8_t *buf,
+			uint8_t len);
+	int (*write_cmd)(struct pmif *arb, uint8_t opc, uint8_t sid, uint16_t addr,
+			 const uint8_t *buf, uint8_t len);
+};
+
+enum {
+	PMIF_SPMI,
+	PMIF_SPI
+};
+
+int pmif_spmi_read_cmd(struct pmif *arb, uint8_t opc, uint8_t sid, uint16_t addr, uint8_t *buf,
+		       uint8_t len);
+int pmif_spmi_write_cmd(struct pmif *arb, uint8_t opc, uint8_t sid, uint16_t addr,
+			const uint8_t *buf, uint8_t len);
+struct pmif *get_pmif_controller(int inf, int mstid);
+extern struct pmif pmif_spmi_arb[];
+#endif
diff --git a/plat/mediatek/drivers/spmi/pmif_v1/pmif.h b/plat/mediatek/drivers/spmi/pmif_v1/pmif.h
new file mode 100644
index 0000000..b136262
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/pmif_v1/pmif.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2025, Mediatek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef PMIF_H
+#define PMIF_H
+
+#include <stdint.h>
+
+#include <platform_def.h>
+
+#include <drivers/spmi/pmif_common.h>
+#include <drivers/spmi/spmi_common.h>
+
+enum pmif_regs {
+	PMIF_INIT_DONE,
+	PMIF_INF_EN,
+	PMIF_ARB_EN,
+	PMIF_IRQ_EVENT_EN_0,
+	PMIF_IRQ_FLAG_0,
+	PMIF_IRQ_CLR_0,
+	PMIF_IRQ_EVENT_EN_2,
+	PMIF_IRQ_FLAG_2,
+	PMIF_IRQ_CLR_2,
+	PMIF_WDT_CTRL,
+	PMIF_WDT_EVENT_EN_1,
+	PMIF_WDT_FLAG_1,
+	PMIF_SWINF_2_ACC,
+	PMIF_SWINF_2_WDATA_31_0,
+	PMIF_SWINF_2_WDATA_63_32,
+	PMIF_SWINF_2_RDATA_31_0,
+	PMIF_SWINF_2_RDATA_63_32,
+	PMIF_SWINF_2_VLD_CLR,
+	PMIF_SWINF_2_STA,
+	PMIF_SWINF_3_ACC,
+	PMIF_SWINF_3_WDATA_31_0,
+	PMIF_SWINF_3_WDATA_63_32,
+	PMIF_SWINF_3_RDATA_31_0,
+	PMIF_SWINF_3_RDATA_63_32,
+	PMIF_SWINF_3_VLD_CLR,
+	PMIF_SWINF_3_STA,
+	/* HW MPU */
+	PMIF_PMIC_ALL_RGN_EN_1,
+	PMIF_PMIC_ALL_RGN_EN_2,
+	PMIF_PMIC_ALL_RGN_0_START,
+	PMIF_PMIC_ALL_RGN_0_END,
+	PMIF_PMIC_ALL_RGN_1_START,
+	PMIF_PMIC_ALL_RGN_1_END,
+	PMIF_PMIC_ALL_RGN_2_START,
+	PMIF_PMIC_ALL_RGN_2_END,
+	PMIF_PMIC_ALL_RGN_3_START,
+	PMIF_PMIC_ALL_RGN_3_END,
+	PMIF_PMIC_ALL_RGN_31_START,
+	PMIF_PMIC_ALL_RGN_31_END,
+	PMIF_PMIC_ALL_INVLD_SLVID,
+	PMIF_PMIC_ALL_RGN_0_PER0,
+	PMIF_PMIC_ALL_RGN_0_PER1,
+	PMIF_PMIC_ALL_RGN_1_PER0,
+	PMIF_PMIC_ALL_RGN_2_PER0,
+	PMIF_PMIC_ALL_RGN_3_PER0,
+	PMIF_PMIC_ALL_RGN_31_PER0,
+	PMIF_PMIC_ALL_RGN_31_PER1,
+	PMIF_PMIC_ALL_RGN_OTHERS_PER0,
+	PMIF_PMIC_ALL_RGN_OTHERS_PER1,
+};
+#endif
diff --git a/plat/mediatek/drivers/spmi/rules.mk b/plat/mediatek/drivers/spmi/rules.mk
new file mode 100644
index 0000000..ce6f465
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/rules.mk
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2025, MediaTek Inc. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+LOCAL_DIR := $(call GET_LOCAL_DIR)
+
+MODULE := spmi
+
+PLAT_INCLUDES += -I${MTK_PLAT}/drivers/spmi/pmif_v1
+
+#Add your source code here
+LOCAL_SRCS-y := ${LOCAL_DIR}/pmif_common.c
+LOCAL_SRCS-y += ${LOCAL_DIR}/spmi_common.c
+LOCAL_SRCS-y += ${LOCAL_DIR}/${MTK_SOC}/platform_pmif_spmi.c
+
+#Epilogue, build as module
+$(eval $(call MAKE_MODULE,$(MODULE),$(LOCAL_SRCS-y),$(MTK_BL)))
diff --git a/plat/mediatek/drivers/spmi/spmi_common.c b/plat/mediatek/drivers/spmi/spmi_common.c
new file mode 100644
index 0000000..c9b05ec
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/spmi_common.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+
+#include <lib/mmio.h>
+#include <platform_def.h>
+
+#include "spmi_common.h"
+#include "spmi_sw.h"
+
+/* SPMI Commands */
+#define SPMI_CMD_EXT_WRITE			0x00
+#define SPMI_CMD_EXT_READ			0x20
+#define SPMI_CMD_EXT_WRITEL			0x30
+#define SPMI_CMD_EXT_READL			0x38
+#define SPMI_CMD_WRITE				0x40
+#define SPMI_CMD_READ				0x60
+#define SPMI_CMD_ZERO_WRITE			0x80
+#define SPMI_READ_ADDR_MAX			0x1F
+
+static struct spmi_device *spmi_dev[SPMI_MAX_SLAVE_ID];
+
+int spmi_register_zero_write(struct spmi_device *dev, uint8_t data)
+{
+	return dev->pmif_arb->write_cmd(dev->pmif_arb, SPMI_CMD_ZERO_WRITE,
+					dev->slvid, 0, &data, 1);
+}
+
+int spmi_register_read(struct spmi_device *dev, uint8_t addr, uint8_t *buf)
+{
+	/* 5-bit register address */
+	if (addr > SPMI_READ_ADDR_MAX)
+		return -EINVAL;
+
+	return dev->pmif_arb->read_cmd(dev->pmif_arb, SPMI_CMD_READ, dev->slvid, addr, buf, 1);
+}
+
+int spmi_register_write(struct spmi_device *dev, uint8_t addr, uint8_t data)
+{
+	/* 5-bit register address */
+	if (addr > SPMI_READ_ADDR_MAX)
+		return -EINVAL;
+
+	return dev->pmif_arb->write_cmd(dev->pmif_arb, SPMI_CMD_WRITE,
+					dev->slvid, addr, &data, 1);
+}
+
+int spmi_ext_register_read(struct spmi_device *dev, uint8_t addr, uint8_t *buf,
+			   uint8_t len)
+{
+	/* 8-bit register address, up to 16 bytes */
+	if (len == 0 || len > 16)
+		return -EINVAL;
+
+	return dev->pmif_arb->read_cmd(dev->pmif_arb, SPMI_CMD_EXT_READ,
+				       dev->slvid, addr, buf, len);
+}
+
+int spmi_ext_register_write(struct spmi_device *dev, uint8_t addr,
+			    const uint8_t *buf, uint8_t len)
+{
+	/* 8-bit register address, up to 16 bytes */
+	if (len == 0 || len > 16)
+		return -EINVAL;
+
+	return dev->pmif_arb->write_cmd(dev->pmif_arb, SPMI_CMD_EXT_WRITE,
+					dev->slvid, addr, buf, len);
+}
+
+int spmi_ext_register_readl(struct spmi_device *dev, uint16_t addr,
+			    uint8_t *buf, uint8_t len)
+{
+	/* 8-bit register address, up to 16 bytes */
+	if (len == 0 || len > 16)
+		return -EINVAL;
+
+	return dev->pmif_arb->read_cmd(dev->pmif_arb, SPMI_CMD_EXT_READL,
+				       dev->slvid, addr, buf, len);
+}
+
+int spmi_ext_register_writel(struct spmi_device *dev, uint16_t addr,
+			     const uint8_t *buf, uint8_t len)
+{
+	/* 8-bit register address, up to 16 bytes */
+	if (len == 0 || len > 16)
+		return -EINVAL;
+
+	return dev->pmif_arb->write_cmd(dev->pmif_arb, SPMI_CMD_EXT_WRITEL,
+					dev->slvid, addr, buf, len);
+}
+
+int spmi_ext_register_readl_field(struct spmi_device *dev, uint16_t addr,
+				  uint8_t *buf, uint16_t mask, uint16_t shift)
+{
+	int ret;
+	uint8_t rdata = 0;
+
+	ret = dev->pmif_arb->read_cmd(dev->pmif_arb, SPMI_CMD_EXT_READL,
+				      dev->slvid, addr, &rdata, 1);
+	if (!ret)
+		*buf = (rdata >> shift) & mask;
+
+	return ret;
+}
+
+int spmi_ext_register_writel_field(struct spmi_device *dev, uint16_t addr,
+				   uint8_t data, uint16_t mask, uint16_t shift)
+{
+	int ret;
+	uint8_t tmp = 0;
+
+	ret = spmi_ext_register_readl(dev, addr, &tmp, 1);
+	if (ret)
+		return ret;
+
+	tmp &= ~(mask << shift);
+	tmp |= (data << shift);
+	return dev->pmif_arb->write_cmd(dev->pmif_arb, SPMI_CMD_EXT_WRITEL,
+					dev->slvid, addr, &tmp, 1);
+}
+
+struct spmi_device *get_spmi_device(int mstid, int slvid)
+{
+	if (slvid >= SPMI_MAX_SLAVE_ID || slvid < 0) {
+		SPMI_ERR("failed to get spmi_device with slave id %d\n", slvid);
+		return NULL;
+	}
+	return spmi_dev[slvid];
+}
+
+int spmi_device_register(struct spmi_device *platform_spmi_dev, unsigned int num_devs)
+{
+	int i;
+
+	if (!platform_spmi_dev || num_devs == 0)
+		return -EINVAL;
+
+	for (i = 0; i < num_devs; i++) {
+		if (platform_spmi_dev[i].slvid >= SPMI_MAX_SLAVE_ID ||
+		    platform_spmi_dev[i].slvid < 0) {
+			SPMI_INFO("invalid slave id %d\n", platform_spmi_dev[i].slvid);
+			continue;
+		}
+		if (!spmi_dev[platform_spmi_dev[i].slvid])
+			spmi_dev[platform_spmi_dev[i].slvid] = &platform_spmi_dev[i];
+		else {
+			SPMI_INFO("duplicated slave id %d\n", platform_spmi_dev[i].slvid);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int spmi_ctrl_op_st(int mstid, unsigned int grpiden, unsigned int sid,
+		unsigned int cmd)
+{
+	struct pmif *arb = get_pmif_controller(PMIF_SPMI, mstid);
+	unsigned int rdata = 0x0;
+	uintptr_t spmi_grp_id_en_addr =
+		(uintptr_t)(arb->spmimst_base + arb->spmimst_regs[SPMI_GRP_ID_EN]);
+	uintptr_t spmi_op_st_ctrl_addr =
+		(uintptr_t)(arb->spmimst_base + arb->spmimst_regs[SPMI_OP_ST_CTRL]);
+	uintptr_t spmi_op_st_sta_addr =
+		(uintptr_t)(arb->spmimst_base + arb->spmimst_regs[SPMI_OP_ST_STA]);
+
+	/* gid is 0x800 */
+	mmio_write_32(spmi_grp_id_en_addr, grpiden);
+
+	if (grpiden == (1 << SPMI_GROUP_ID))
+		mmio_write_32(spmi_op_st_ctrl_addr, (cmd << 0x4) | SPMI_GROUP_ID);
+	else
+		mmio_write_32(spmi_op_st_ctrl_addr, (cmd << 0x4) | sid);
+
+	SPMI_INFO("%s 0x%x\n", __func__, mmio_read_32(spmi_op_st_ctrl_addr));
+
+	do {
+		rdata = mmio_read_32(spmi_op_st_sta_addr);
+		SPMI_INFO("%s 0x%x\n", __func__, rdata);
+
+		if (((rdata >> 0x1) & SPMI_OP_ST_NACK) == SPMI_OP_ST_NACK) {
+			SPMI_ERR("SPMI_OP_ST_NACK occurs! OP_ST_STA = 0x%x\n", rdata);
+			break;
+		}
+	} while ((rdata & SPMI_OP_ST_BUSY) == SPMI_OP_ST_BUSY);
+
+	return 0;
+}
+
+int spmi_command_shutdown(int mstid, struct spmi_device *dev, unsigned int grpiden)
+{
+	if (grpiden != (1 << SPMI_GROUP_ID))
+		dev->slvid = grpiden;
+
+	return spmi_ctrl_op_st(mstid, grpiden, dev->slvid, SPMI_SHUTDOWN);
+}
diff --git a/plat/mediatek/drivers/spmi/spmi_common.h b/plat/mediatek/drivers/spmi/spmi_common.h
new file mode 100644
index 0000000..16850b1
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/spmi_common.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2025, Mediatek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SPMI_COMMON_H
+#define SPMI_COMMON_H
+
+#include <stdint.h>
+
+#include <platform_def.h>
+#include "pmif_common.h"
+
+/* Read/write byte limitation */
+#define PMIF_BYTECNT_MAX	2
+
+#define SPMI_GROUP_ID		0xB
+
+/* enum marco for cmd/channel */
+enum spmi_master {
+	SPMI_MASTER_0 = 0,
+	SPMI_MASTER_1,
+	SPMI_MASTER_P_1,
+	SPMI_MASTER_MAX
+};
+
+enum spmi_slave {
+	SPMI_SLAVE_0 = 0,
+	SPMI_SLAVE_1,
+	SPMI_SLAVE_2,
+	SPMI_SLAVE_3,
+	SPMI_SLAVE_4,
+	SPMI_SLAVE_5,
+	SPMI_SLAVE_6,
+	SPMI_SLAVE_7,
+	SPMI_SLAVE_8,
+	SPMI_SLAVE_9,
+	SPMI_SLAVE_10,
+	SPMI_SLAVE_11,
+	SPMI_SLAVE_12,
+	SPMI_SLAVE_13,
+	SPMI_SLAVE_14,
+	SPMI_SLAVE_15,
+	SPMI_MAX_SLAVE_ID
+};
+
+enum slv_type {
+	BUCK_CPU,
+	BUCK_GPU,
+	BUCK_MD,
+	BUCK_RF,
+	MAIN_PMIC,
+	BUCK_VPU,
+	SUB_PMIC,
+	CLOCK_PMIC,
+	SECOND_PMIC,
+	SLV_TYPE_MAX
+};
+
+enum slv_type_id {
+	BUCK_RF_ID = 1,
+	BUCK_MD_ID = 3,
+	MAIN_PMIC_ID = 5,
+	BUCK_CPU_ID = 6,
+	BUCK_GPU_ID = 7,
+	BUCK_VPU_ID,
+	SUB_PMIC_ID = 10,
+	CLOCK_PMIC_ID = 11,
+	SECOND_PMIC_ID = 12,
+	SLV_TYPE_ID_MAX
+};
+
+enum {
+	SPMI_OP_ST_BUSY = 1,
+	SPMI_OP_ST_ACK = 0,
+	SPMI_OP_ST_NACK = 1
+};
+
+struct spmi_device {
+	int slvid;
+	int grpiden;
+	enum slv_type type;
+	enum slv_type_id type_id;
+	int mstid;
+	uint16_t hwcid_addr;
+	uint8_t hwcid_val;
+	uint16_t hwcid_mask;
+	uint16_t swcid_addr;
+	uint8_t swcid_val;
+	uint16_t wpk_key_addr;
+	uint16_t wpk_key_val;
+	uint16_t wpk_key_h_val;
+	uint16_t tma_key_addr;
+	uint16_t tma_key_val;
+	uint16_t tma_key_h_val;
+	uint16_t rcs_en_addr;
+	uint16_t rcs_slvid_addr;
+	struct pmif *pmif_arb;
+};
+
+int spmi_command_shutdown(int mstid, struct spmi_device *dev, unsigned int grpiden);
+#endif
diff --git a/plat/mediatek/drivers/spmi/spmi_sw.h b/plat/mediatek/drivers/spmi/spmi_sw.h
new file mode 100644
index 0000000..fae2704
--- /dev/null
+++ b/plat/mediatek/drivers/spmi/spmi_sw.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025, Mediatek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SPMI_SW_H
+#define SPMI_SW_H
+
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <mt_timer.h>
+
+enum spmi_regs {
+	SPMI_OP_ST_CTRL,
+	SPMI_GRP_ID_EN,
+	SPMI_OP_ST_STA,
+	SPMI_MST_SAMPL,
+	SPMI_MST_REQ_EN,
+	/* RCS support */
+	SPMI_RCS_CTRL,
+	SPMI_SLV_3_0_EINT,
+	SPMI_SLV_7_4_EINT,
+	SPMI_SLV_B_8_EINT,
+	SPMI_SLV_F_C_EINT,
+	SPMI_REC_CTRL,
+	SPMI_REC0,
+	SPMI_REC1,
+	SPMI_REC2,
+	SPMI_REC3,
+	SPMI_REC4,
+	SPMI_REC_CMD_DEC,
+	SPMI_DEC_DBG,
+	SPMI_MST_DBG
+};
+
+/* DEBUG MARCO */
+#define SPMITAG			"[SPMI] "
+#define SPMI_ERR(fmt, arg...)	ERROR(SPMITAG fmt, ##arg)
+#define SPMI_ERRL(fmt, arg...)	ERROR(fmt, ##arg)
+#define SPMI_INFO(fmt, arg...)	INFO(SPMITAG fmt, ##arg)
+
+#define wait_us(cond, timeout)			\
+({						\
+	uint64_t __now, __end, __ret;		\
+						\
+	__end = sched_clock() + timeout;	\
+	for (;;) {				\
+		if (cond) {			\
+			__ret = timeout;	\
+			break;			\
+		}				\
+		__now = sched_clock();		\
+		if (__end <= __now) {		\
+			__ret = 0;		\
+			break;			\
+		}				\
+	}					\
+	__ret;					\
+})
+
+enum {
+	SPMI_RESET = 0,
+	SPMI_SLEEP,
+	SPMI_SHUTDOWN,
+	SPMI_WAKEUP
+};
+
+#endif
diff --git a/plat/mediatek/include/drivers/spmi_api.h b/plat/mediatek/include/drivers/spmi_api.h
new file mode 100644
index 0000000..25b16cf
--- /dev/null
+++ b/plat/mediatek/include/drivers/spmi_api.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025, MediaTek Inc. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SPMI_API_H
+#define SPMI_API_H
+
+#include <stdint.h>
+
+#include <drivers/spmi/spmi_common.h>
+
+/* external API */
+int spmi_register_zero_write(struct spmi_device *dev, uint8_t data);
+int spmi_register_read(struct spmi_device *dev, uint8_t addr, uint8_t *buf);
+int spmi_register_write(struct spmi_device *dev, uint8_t addr, uint8_t data);
+int spmi_ext_register_read(struct spmi_device *dev, uint8_t addr, uint8_t *buf,
+			   uint8_t len);
+int spmi_ext_register_write(struct spmi_device *dev, uint8_t addr,
+			    const uint8_t *buf, uint8_t len);
+int spmi_ext_register_readl(struct spmi_device *dev, uint16_t addr,
+			    uint8_t *buf, uint8_t len);
+int spmi_ext_register_writel(struct spmi_device *dev, uint16_t addr,
+			     const uint8_t *buf, uint8_t len);
+int spmi_ext_register_readl_field(struct spmi_device *dev, uint16_t addr,
+				  uint8_t *buf, uint16_t mask, uint16_t shift);
+int spmi_ext_register_writel_field(struct spmi_device *dev, uint16_t addr,
+				   uint8_t data, uint16_t mask, uint16_t shift);
+struct spmi_device *get_spmi_device(int mstid, int slvid);
+int spmi_device_register(struct spmi_device *platform_spmi_dev, int num_devs);
+
+#endif
diff --git a/plat/mediatek/mt8196/include/platform_def.h b/plat/mediatek/mt8196/include/platform_def.h
index 6b6416a..a19fad7 100644
--- a/plat/mediatek/mt8196/include/platform_def.h
+++ b/plat/mediatek/mt8196/include/platform_def.h
@@ -95,6 +95,20 @@
 #define UART_BAUDRATE	(115200)
 
 /*******************************************************************************
+ * PMIF address
+ ******************************************************************************/
+#define PMIF_SPMI_M_BASE	(IO_PHYS + 0x0C01A000)
+#define PMIF_SPMI_P_BASE	(IO_PHYS + 0x0C018000)
+#define PMIF_SPMI_SIZE		0x1000
+
+/*******************************************************************************
+ * SPMI address
+ ******************************************************************************/
+#define SPMI_MST_M_BASE		(IO_PHYS + 0x0C01C000)
+#define SPMI_MST_P_BASE		(IO_PHYS + 0x0C01C800)
+#define SPMI_MST_SIZE		0x1000
+
+/*******************************************************************************
  * Infra IOMMU related constants
  ******************************************************************************/
 #define INFRACFG_AO_BASE	(IO_PHYS + 0x00001000)
diff --git a/plat/mediatek/mt8196/plat_config.mk b/plat/mediatek/mt8196/plat_config.mk
index a793f1b..a983de3 100644
--- a/plat/mediatek/mt8196/plat_config.mk
+++ b/plat/mediatek/mt8196/plat_config.mk
@@ -51,7 +51,7 @@
 CONFIG_MTK_PMIC_LOWPOWER := y
 CONFIG_MTK_PMIC_SHUTDOWN_CFG := y
 CONFIG_MTK_PMIC_SPT_SUPPORT := n
-
+CONFIG_MTK_SPMI := y
 PMIC_CHIP := mt6363
 
 ENABLE_FEAT_AMU := 1
diff --git a/plat/mediatek/mt8196/platform.mk b/plat/mediatek/mt8196/platform.mk
index 7e1633e..0432e56 100644
--- a/plat/mediatek/mt8196/platform.mk
+++ b/plat/mediatek/mt8196/platform.mk
@@ -34,6 +34,7 @@
 MODULES-y += $(MTK_PLAT)/helpers
 MODULES-y += $(MTK_PLAT)/topology
 MODULES-$(CONFIG_MTK_PMIC) += $(MTK_PLAT)/drivers/pmic
+MODULES-$(CONFIG_MTK_SPMI) += $(MTK_PLAT)/drivers/spmi
 
 ifneq ($(MTKLIB_PATH),)
 LDFLAGS += -L $(dir $(MTKLIB_PATH))