This driver (ported from the Linux kernel) implements 1-byte wide read
and write operations for the SPMI-PMIF bus found on MediaTek SoCs. It
currently supports MT8195 and MT8196.

Signed-off-by: Arnaud Ferraris <[email protected]>
---
 drivers/spmi/Kconfig         |   7 +
 drivers/spmi/Makefile        |   1 +
 drivers/spmi/spmi-mtk-pmif.c | 462 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 470 insertions(+)

diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
index f8972371150..cab82e83c24 100644
--- a/drivers/spmi/Kconfig
+++ b/drivers/spmi/Kconfig
@@ -27,6 +27,13 @@ config SPL_SPMI_MSM
          Support SPMI bus implementation found on Qualcomm Snapdragon SoCs
          in SPL.
 
+config SPMI_MTK_PMIF
+       bool "Support MediaTek SPMI-PMIF bus"
+       depends on SPMI
+       help
+         Support SPMI bus implementation found on MediaTek SoCs. This
+         driver currently support the MT8195 and MT8196 SoCs.
+
 config SPMI_SANDBOX
        bool "Support for Sandbox SPMI bus"
        depends on SPMI
diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile
index 8cb93c820e0..016a8e6b734 100644
--- a/drivers/spmi/Makefile
+++ b/drivers/spmi/Makefile
@@ -4,4 +4,5 @@
 
 obj-$(CONFIG_$(PHASE_)SPMI)  += spmi-uclass.o
 obj-$(CONFIG_$(PHASE_)SPMI_MSM) += spmi-msm.o
+obj-$(CONFIG_SPMI_MTK_PMIF) += spmi-mtk-pmif.o
 obj-$(CONFIG_SPMI_SANDBOX) += spmi-sandbox.o
diff --git a/drivers/spmi/spmi-mtk-pmif.c b/drivers/spmi/spmi-mtk-pmif.c
new file mode 100644
index 00000000000..0bc8309b447
--- /dev/null
+++ b/drivers/spmi/spmi-mtk-pmif.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 MediaTek Inc.
+ * Copyright (c) 2025-2026 Collabora Ltd
+ * Authors: AngeloGioacchino Del Regno 
<[email protected]>
+ *          Arnaud Ferraris <[email protected]>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <malloc.h>
+#include <regmap.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/processor.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/time.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/devres.h>
+#include <inttypes.h>
+#include <spmi/spmi.h>
+
+#define SWINF_IDLE     0x00
+#define SWINF_WFVLDCLR 0x06
+
+#define GET_SWINF(x)   (((x) >> 1) & 0x7)
+
+#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_DELAY_US   2
+#define PMIF_TIMEOUT_US (10 * 1000)
+
+#define PMIF_CHAN_OFFSET 0x5
+
+#define PMIF_MAX_BUSES 2
+#define PMIF_MAX_CLKS  3
+
+#define SPMI_OP_ST_BUSY 1
+
+struct ch_reg {
+       u32 ch_sta;
+       u32 wdata;
+       u32 rdata;
+       u32 ch_send;
+       u32 ch_rdy;
+};
+
+struct pmif_data {
+       const u32       *regs;
+       const u32       *spmimst_regs;
+       u32     soc_chan;
+       u8      spmi_ver;
+       u32     num_spmi_buses;
+};
+
+struct pmif_bus {
+       void __iomem    *base;
+       void __iomem    *spmimst_base;
+       struct pmif *arb;
+       struct clk_bulk clks;
+};
+
+struct pmif {
+       struct pmif_bus bus[PMIF_MAX_BUSES];
+       struct ch_reg   chan;
+       const struct pmif_data *data;
+};
+
+enum pmif_regs {
+       PMIF_INIT_DONE,
+       PMIF_INF_EN,
+       PMIF_ARB_EN,
+       PMIF_CMDISSUE_EN,
+       PMIF_TIMER_CTRL,
+       PMIF_SPI_MODE_CTRL,
+       PMIF_IRQ_EVENT_EN_0,
+       PMIF_IRQ_FLAG_0,
+       PMIF_IRQ_CLR_0,
+       PMIF_IRQ_EVENT_EN_1,
+       PMIF_IRQ_FLAG_1,
+       PMIF_IRQ_CLR_1,
+       PMIF_IRQ_EVENT_EN_2,
+       PMIF_IRQ_FLAG_2,
+       PMIF_IRQ_CLR_2,
+       PMIF_IRQ_EVENT_EN_3,
+       PMIF_IRQ_FLAG_3,
+       PMIF_IRQ_CLR_3,
+       PMIF_IRQ_EVENT_EN_4,
+       PMIF_IRQ_FLAG_4,
+       PMIF_IRQ_CLR_4,
+       PMIF_WDT_EVENT_EN_0,
+       PMIF_WDT_FLAG_0,
+       PMIF_WDT_EVENT_EN_1,
+       PMIF_WDT_FLAG_1,
+       PMIF_SWINF_0_STA,
+       PMIF_SWINF_0_WDATA_31_0,
+       PMIF_SWINF_0_RDATA_31_0,
+       PMIF_SWINF_0_ACC,
+       PMIF_SWINF_0_VLD_CLR,
+       PMIF_SWINF_1_STA,
+       PMIF_SWINF_1_WDATA_31_0,
+       PMIF_SWINF_1_RDATA_31_0,
+       PMIF_SWINF_1_ACC,
+       PMIF_SWINF_1_VLD_CLR,
+       PMIF_SWINF_2_STA,
+       PMIF_SWINF_2_WDATA_31_0,
+       PMIF_SWINF_2_RDATA_31_0,
+       PMIF_SWINF_2_ACC,
+       PMIF_SWINF_2_VLD_CLR,
+       PMIF_SWINF_3_STA,
+       PMIF_SWINF_3_WDATA_31_0,
+       PMIF_SWINF_3_RDATA_31_0,
+       PMIF_SWINF_3_ACC,
+       PMIF_SWINF_3_VLD_CLR,
+};
+
+static const u32 mt8195_regs[] = {
+       [PMIF_INIT_DONE] = 0x0000,
+       [PMIF_INF_EN] = 0x0024,
+       [PMIF_ARB_EN] = 0x0150,
+       [PMIF_CMDISSUE_EN] = 0x03B8,
+       [PMIF_TIMER_CTRL] = 0x03E4,
+       [PMIF_SPI_MODE_CTRL] = 0x0408,
+       [PMIF_IRQ_EVENT_EN_0] = 0x0420,
+       [PMIF_IRQ_FLAG_0] = 0x0428,
+       [PMIF_IRQ_CLR_0] = 0x042C,
+       [PMIF_IRQ_EVENT_EN_1] = 0x0430,
+       [PMIF_IRQ_FLAG_1] = 0x0438,
+       [PMIF_IRQ_CLR_1] = 0x043C,
+       [PMIF_IRQ_EVENT_EN_2] = 0x0440,
+       [PMIF_IRQ_FLAG_2] = 0x0448,
+       [PMIF_IRQ_CLR_2] = 0x044C,
+       [PMIF_IRQ_EVENT_EN_3] = 0x0450,
+       [PMIF_IRQ_FLAG_3] = 0x0458,
+       [PMIF_IRQ_CLR_3] = 0x045C,
+       [PMIF_IRQ_EVENT_EN_4] = 0x0460,
+       [PMIF_IRQ_FLAG_4] = 0x0468,
+       [PMIF_IRQ_CLR_4] = 0x046C,
+       [PMIF_WDT_EVENT_EN_0] = 0x0474,
+       [PMIF_WDT_FLAG_0] = 0x0478,
+       [PMIF_WDT_EVENT_EN_1] = 0x047C,
+       [PMIF_WDT_FLAG_1] = 0x0480,
+       [PMIF_SWINF_0_ACC] = 0x0800,
+       [PMIF_SWINF_0_WDATA_31_0] = 0x0804,
+       [PMIF_SWINF_0_RDATA_31_0] = 0x0814,
+       [PMIF_SWINF_0_VLD_CLR] = 0x0824,
+       [PMIF_SWINF_0_STA] = 0x0828,
+       [PMIF_SWINF_1_ACC] = 0x0840,
+       [PMIF_SWINF_1_WDATA_31_0] = 0x0844,
+       [PMIF_SWINF_1_RDATA_31_0] = 0x0854,
+       [PMIF_SWINF_1_VLD_CLR] = 0x0864,
+       [PMIF_SWINF_1_STA] = 0x0868,
+       [PMIF_SWINF_2_ACC] = 0x0880,
+       [PMIF_SWINF_2_WDATA_31_0] = 0x0884,
+       [PMIF_SWINF_2_RDATA_31_0] = 0x0894,
+       [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_RDATA_31_0] = 0x08D4,
+       [PMIF_SWINF_3_VLD_CLR] = 0x08E4,
+       [PMIF_SWINF_3_STA] = 0x08E8,
+};
+
+enum spmi_regs {
+       SPMI_OP_ST_CTRL,
+       SPMI_GRP_ID_EN,
+       SPMI_OP_ST_STA,
+       SPMI_MST_SAMPL,
+       SPMI_MST_REQ_EN,
+       SPMI_REC_CTRL,
+       SPMI_REC0,
+       SPMI_REC1,
+       SPMI_REC2,
+       SPMI_REC3,
+       SPMI_REC4,
+       SPMI_MST_DBG,
+
+       /* MT8195 spmi regs */
+       SPMI_MST_RCS_CTRL,
+       SPMI_SLV_3_0_EINT,
+       SPMI_SLV_7_4_EINT,
+       SPMI_SLV_B_8_EINT,
+       SPMI_SLV_F_C_EINT,
+       SPMI_REC_CMD_DEC,
+       SPMI_DEC_DBG,
+};
+
+static const u32 mt8195_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_MST_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 u32 pmif_readl(struct pmif *arb, struct pmif_bus *pbus, enum pmif_regs 
reg)
+{
+       return readl(pbus->base + arb->data->regs[reg]);
+}
+
+static void pmif_writel(struct pmif *arb, struct pmif_bus *pbus,
+                       u32 val, enum pmif_regs reg)
+{
+       writel(val, pbus->base + arb->data->regs[reg]);
+}
+
+static bool pmif_is_fsm_vldclr(struct pmif *arb, struct pmif_bus *pbus)
+{
+       u32 reg_rdata;
+
+       reg_rdata = pmif_readl(arb, pbus, arb->chan.ch_sta);
+
+       return GET_SWINF(reg_rdata) == SWINF_WFVLDCLR;
+}
+
+static int pmif_spmi_read(struct udevice *dev, int sid, int pid, int reg)
+{
+       struct pmif_bus *pbus = dev_get_priv(dev);
+       struct pmif *arb = pbus->arb;
+       struct ch_reg *inf_reg;
+       int ret, data;
+       u32 cmd;
+
+       /* Check for argument validation. */
+       if (sid & ~0xf) {
+               dev_err(dev, "exceed the max slv id\n");
+               return -EINVAL;
+       }
+
+       /* Wait for Software Interface FSM state to be IDLE. */
+       inf_reg = &arb->chan;
+       ret = readl_poll_sleep_timeout(pbus->base + 
arb->data->regs[inf_reg->ch_sta],
+                                      data, GET_SWINF(data) == SWINF_IDLE,
+                                      PMIF_DELAY_US, PMIF_TIMEOUT_US);
+       if (ret < 0) {
+               /* set channel ready if the data has transferred */
+               if (pmif_is_fsm_vldclr(arb, pbus))
+                       pmif_writel(arb, pbus, 1, inf_reg->ch_rdy);
+               dev_err(dev, "failed to wait for SWINF_IDLE\n");
+               return ret;
+       }
+
+       /* Send the command. */
+       cmd = (PMIF_CMD_EXT_REG_LONG << 30) | (sid << 24) | reg;
+       pmif_writel(arb, pbus, cmd, inf_reg->ch_send);
+
+       /*
+        * Wait for Software Interface FSM state to be WFVLDCLR,
+        * read the data and clear the valid flag.
+        */
+       ret = readl_poll_sleep_timeout(pbus->base + 
arb->data->regs[inf_reg->ch_sta],
+                                      data, GET_SWINF(data) == SWINF_WFVLDCLR,
+                                      PMIF_DELAY_US, PMIF_TIMEOUT_US);
+       if (ret < 0) {
+               dev_err(dev, "failed to wait for SWINF_WFVLDCLR\n");
+               return ret;
+       }
+
+       data = (int)pmif_readl(arb, pbus, inf_reg->rdata);
+       pmif_writel(arb, pbus, 1, inf_reg->ch_rdy);
+
+       return data;
+}
+
+static int pmif_spmi_write(struct udevice *dev, int sid, int pid, int reg, u8 
val)
+{
+       struct pmif_bus *pbus = dev_get_priv(dev);
+       struct pmif *arb = pbus->arb;
+       struct ch_reg *inf_reg;
+       int ret;
+       u32 data, cmd;
+
+       /* Check for argument validation. */
+       if (unlikely(sid & ~0xf)) {
+               dev_err(dev, "exceed the max slv id\n");
+               return -EINVAL;
+       }
+
+       /* Wait for Software Interface FSM state to be IDLE. */
+       inf_reg = &arb->chan;
+       ret = readl_poll_sleep_timeout(pbus->base + 
arb->data->regs[inf_reg->ch_sta],
+                                      data, GET_SWINF(data) == SWINF_IDLE,
+                                      PMIF_DELAY_US, PMIF_TIMEOUT_US);
+       if (ret < 0) {
+               /* set channel ready if the data has transferred */
+               if (pmif_is_fsm_vldclr(arb, pbus))
+                       pmif_writel(arb, pbus, 1, inf_reg->ch_rdy);
+               dev_err(dev, "failed to wait for SWINF_IDLE\n");
+               return ret;
+       }
+
+       pmif_writel(arb, pbus, (u32)val, inf_reg->wdata);
+
+       /* Send the command. */
+       cmd = (PMIF_CMD_EXT_REG_LONG << 30) | BIT(29) | (sid << 24) | reg;
+       pmif_writel(arb, pbus, cmd, inf_reg->ch_send);
+
+       return 0;
+}
+
+static const struct pmif_data mt8195_pmif_arb = {
+       .regs = mt8195_regs,
+       .spmimst_regs = mt8195_spmi_regs,
+       .soc_chan = 2,
+};
+
+static const struct pmif_data mt8196_pmif_arb = {
+       .regs = mt8195_regs,
+       .spmimst_regs = mt8195_spmi_regs,
+       .soc_chan = 2,
+       .spmi_ver = 2,
+       .num_spmi_buses = 2,
+};
+
+static int mtk_spmi_probe(struct udevice *dev)
+{
+       struct pmif_bus *pbus = dev_get_priv(dev);
+       int ret;
+
+       if (!pbus) {
+               dev_info(dev, "Not probing a non-bus spmi device\n");
+               return 0;
+       }
+
+       pbus->base = devfdt_remap_addr_name(dev, "pmif");
+       if (IS_ERR(pbus->base))
+               return PTR_ERR(pbus->base);
+
+       pbus->spmimst_base = devfdt_remap_addr_name(dev, "spmimst");
+       if (IS_ERR(pbus->spmimst_base))
+               return PTR_ERR(pbus->spmimst_base);
+
+       ret = clk_get_bulk(dev, &pbus->clks);
+       if (ret < 0) {
+               dev_err(dev, "Failed to get clocks\n");
+               return ret;
+       }
+
+       ret = clk_enable_bulk(&pbus->clks);
+       if (ret) {
+               dev_err(dev, "Failed to enable clocks\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct udevice_id mtk_spmi_match_table[] = {
+       {
+               .compatible = "mediatek,mt8195-spmi",
+               .data = (unsigned long)&mt8195_pmif_arb,
+       }, {
+               .compatible = "mediatek,mt8196-spmi",
+               .data = (unsigned long)&mt8196_pmif_arb,
+       }, {
+               /* sentinel */
+       },
+};
+
+static int mtk_spmi_bind(struct udevice *dev)
+{
+       struct pmif_data *drv_data = (struct pmif_data 
*)dev_get_driver_data(dev);
+       ofnode child, node = dev_ofnode(dev);
+       int ret = 0;
+
+       if (!drv_data) {
+               dev_err(dev, "Cannot get drv_data\n");
+               return -EINVAL;
+       }
+
+       if (ofnode_name_eq(node, "spmi")) {
+               struct pmif_bus *bus = devm_kzalloc(dev, sizeof(struct 
pmif_bus), GFP_KERNEL);
+
+               if (!bus)
+                       return -ENOMEM;
+               bus->arb = dev_get_plat(dev->parent);
+               dev_set_priv(dev, bus);
+       } else {
+               struct pmif *arb;
+               u32 chan_offset;
+
+               arb = devm_kzalloc(dev, sizeof(struct pmif), GFP_KERNEL);
+               if (!arb)
+                       return -ENOMEM;
+               dev_set_plat(dev, arb);
+
+               arb->data = drv_data;
+               chan_offset = PMIF_CHAN_OFFSET * arb->data->soc_chan;
+               arb->chan.ch_sta = PMIF_SWINF_0_STA + chan_offset;
+               arb->chan.wdata = PMIF_SWINF_0_WDATA_31_0 + chan_offset;
+               arb->chan.rdata = PMIF_SWINF_0_RDATA_31_0 + chan_offset;
+               arb->chan.ch_send = PMIF_SWINF_0_ACC + chan_offset;
+               arb->chan.ch_rdy = PMIF_SWINF_0_VLD_CLR + chan_offset;
+
+               if (arb->data->num_spmi_buses) {
+                       ofnode_for_each_subnode(child, node) {
+                               if (!ofnode_name_eq(child, "spmi"))
+                                       continue;
+
+                               /* Bind the subnode to this driver */
+                               ret = device_bind_with_driver_data(dev, 
dev->driver,
+                                                                  
ofnode_get_name(child),
+                                                                  
dev->driver_data,
+                                                                  child,
+                                                                  NULL);
+                               if (ret == -ENODEV)
+                                       printf("Driver '%s' refuses to bind\n",
+                                              dev->driver->name);
+
+                               if (ret)
+                                       printf("Error binding driver '%s': 
%d\n",
+                                              dev->driver->name, ret);
+                       }
+               } else {
+                       struct pmif_bus *bus = devm_kzalloc(dev,
+                                                           sizeof(struct 
pmif_bus),
+                                                           GFP_KERNEL);
+
+                       if (!bus)
+                               return -ENOMEM;
+                       bus->arb = arb;
+                       dev_set_priv(dev, bus);
+               }
+       }
+
+       return 0;
+}
+
+struct dm_spmi_ops mtk_spmi_ops = {
+       .read = pmif_spmi_read,
+       .write = pmif_spmi_write,
+};
+
+U_BOOT_DRIVER(mtk_spmi_driver) = {
+       .name = "spmi-mtk",
+       .id = UCLASS_SPMI,
+       .ops = &mtk_spmi_ops,
+       .bind = mtk_spmi_bind,
+       .probe = mtk_spmi_probe,
+       .of_match = mtk_spmi_match_table,
+};

-- 
2.53.0

Reply via email to