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/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;
+}