On 26-09-24 18:02:49, Arnaud Ferraris wrote:
> 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>

Do you really need all of these?

> +
> +#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

PMIF_MAX_CLKS and SPMI_OP_ST_BUSY are unused. Same for PMIF_CMD_REG_0,
PMIF_CMD_REG and PMIF_CMD_EXT_REG above. Please drop the unused defines.

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

The bus array isn't used, since the bus instances are allocated separately
in bind(). spmi_ver and spmimst_regs also don't seem to have any users.
Can you drop the unused fields and the unused SPMI register table?

> +
> +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);

Are the upper bits guaranteed to be zero for a single-byte read? Otherwise,
this can return a value outside 0..255, or even a negative value which the
caller will interpret as an error. I'd mask the result with 0xff here.

> +     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);

devfdt_remap_addr_name() returns NULL when the address cannot be resolved,
not an ERR_PTR(). These checks won't catch that, so probe can succeed with
an invalid MMIO base. Please check for NULL and return an error instead.

Also, spmimst_base is never used after this. Do we need to map it at all?
With the IS_ERR()/PTR_ERR() calls gone, linux/err.h can go too.
> +
> +     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);

Please propagate the bind error instead of just printing it. As it stands,
we continue and return success even if a child bus failed to bind.

Reply via email to