The DMA masters of a display pipeline reach DRAM through an SMI local
arbiter, and the larbs themselves through the smi_common block behind
them, whose clocks must be ungated as well. Drive it with a second
driver in the same file, found through the "mediatek,smi" phandle of the
larb, and reference count both: several masters share a larb, and
several larbs share an smi_common. A larb whose smi_common has no driver
here, or no node, is not an error: its clocks are already on.

U-Boot has no IOMMU driver, so those masters address DRAM directly: add
a helper switching one larb port to physical addressing, which the
overlay calls once its larb is ungated.

Four compatibles are added:

 - "mediatek,mt8186-smi-larb", the generic fallback the MT8365 larbs
   carry behind their SoC specific string, so a single entry covers
   every SoC reusing that larb generation,
 - "mediatek,mt8366-smi-larb", which the MT8366 larbs carry on their own
   as that SoC is not upstream yet. They are the MT8188 IP, so they
   reuse its data, and with it the secure monitor call taking the larb
   ports out of the IOMMU,
 - "mediatek,mt8365-smi-common" and "mediatek,mt8366-smi-common" for
   the smi_common driver. Neither node declares a generic fallback, so
   both SoC specific strings have to be listed.

Signed-off-by: Julien Stephan <[email protected]>
---
 drivers/memory/mtk-smi.c | 157 +++++++++++++++++++++++++++++++++++++++++++----
 include/mtk_smi.h        |  20 +++++-
 2 files changed, 161 insertions(+), 16 deletions(-)

diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c
index 4531d416861..28f026c3ed9 100644
--- a/drivers/memory/mtk-smi.c
+++ b/drivers/memory/mtk-smi.c
@@ -1,24 +1,28 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * MediaTek SMI local arbiter (larb) driver
+ * MediaTek SMI local arbiter (larb) and smi_common driver
  *
  * Copyright (c) 2026 BayLibre, SAS.
  * Author: Julien Stephan <[email protected]>
  */
 
+#include <asm/io.h>
 #include <clk.h>
 #include <dm.h>
 #include <dm/device_compat.h>
 #include <errno.h>
 #include <linux/arm-smccc.h>
+#include <linux/bitops.h>
 #include <mtk_smi.h>
 
+#define SMI_LARB_NONSEC_CON(port)      (0x380 + 4 * (port))
+#define MMU_EN                                 BIT(0)
+
 /*
- * On the newer larbs the mmu_en bits of SMI_LARB_NONSEC_CON live in the
- * secure world, where "the mmu_en bits of LARB_NONSEC_CON have no effect",
- * as the Linux driver puts it. Which ports of a larb go through the IOMMU
- * is told to the secure monitor instead; U-Boot sets up no page table, so
- * it asks for none, and the DMA masters use physical addresses.
+ * On the newer larbs the mmu_en bits above live in the secure world, where
+ * "the mmu_en bits of LARB_NONSEC_CON have no effect", as the Linux driver
+ * puts it. Which ports of a larb go through the IOMMU is told to the secure
+ * monitor instead; U-Boot sets up no page table, so it asks for none.
  */
 #define MTK_SIP_SMC_CONVENTION         (IS_ENABLED(CONFIG_ARM64) ? \
                                         ARM_SMCCC_SMC_64 : ARM_SMCCC_SMC_32)
@@ -28,6 +32,24 @@
                                                           ARM_SMCCC_OWNER_SIP, 
0x514)
 #define IOMMU_ATF_CMD_CONFIG_SMI_LARB  0
 
+/*
+ * The SMI local arbiters gate a multimedia subsystem's access to DRAM. Linux
+ * handles them as part of its SMI/IOMMU framework (its mtk-smi driver);
+ * U-Boot has no such framework, so this minimal driver only ungates the
+ * clocks of a larb and of the smi_common block behind it, on behalf of the
+ * DMA master sitting in front of it.
+ */
+
+/**
+ * struct mtk_smi_common_priv - private data of an smi_common block
+ * @clk_bulk: clocks of the smi_common block
+ * @enable_count: number of larbs that enabled the smi_common block
+ */
+struct mtk_smi_common_priv {
+       struct clk_bulk clk_bulk;
+       unsigned int enable_count;
+};
+
 /**
  * struct mtk_smi_larb_data - per-SoC larb data
  * @port_sec_ctl: the port mmu_en bits are controlled by the secure world
@@ -39,20 +61,20 @@ struct mtk_smi_larb_data {
 /**
  * struct mtk_smi_larb_priv - private data of an SMI larb
  * @clk_bulk: clocks of the larb
+ * @smi_common: smi_common device behind the larb, if handled by this driver
+ * @base: larb register window
  * @data: per-SoC larb data, NULL when there is none
  * @enable_count: number of DMA masters that enabled the larb
- *
- * The SMI local arbiters gate a multimedia subsystem's access to DRAM. Linux
- * handles them as part of its SMI/IOMMU framework (drivers/memory/mtk-smi.c);
- * U-Boot has no such framework, so this minimal driver only ungates the larb
- * clocks on behalf of the DMA master sitting behind it.
  */
 struct mtk_smi_larb_priv {
        struct clk_bulk clk_bulk;
+       struct udevice *smi_common;
+       void __iomem *base;
        const struct mtk_smi_larb_data *data;
        unsigned int enable_count;
 };
 
+/* the MT8366 larbs are the MT8188 IP */
 static const struct mtk_smi_larb_data mt8188_larb_data = {
        .port_sec_ctl = true,
 };
@@ -81,19 +103,84 @@ static int mtk_smi_larb_bypass_iommu(struct udevice *dev)
        return 0;
 }
 
+static int mtk_smi_common_enable(struct udevice *dev)
+{
+       struct mtk_smi_common_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       /* several larbs sit behind one smi_common, only the first ungates it */
+       if (priv->enable_count++)
+               return 0;
+
+       ret = clk_enable_bulk(&priv->clk_bulk);
+       if (ret) {
+               dev_err(dev, "failed to enable clocks: %d\n", ret);
+               priv->enable_count = 0;
+       }
+
+       return ret;
+}
+
+static int mtk_smi_common_disable(struct udevice *dev)
+{
+       struct mtk_smi_common_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       if (!priv->enable_count || --priv->enable_count)
+               return 0;
+
+       ret = clk_disable_bulk(&priv->clk_bulk);
+       if (ret)
+               dev_err(dev, "failed to disable clocks: %d\n", ret);
+
+       return ret;
+}
+
+static int mtk_smi_common_probe(struct udevice *dev)
+{
+       struct mtk_smi_common_priv *priv = dev_get_priv(dev);
+       int ret;
+
+       ret = clk_get_bulk(dev, &priv->clk_bulk);
+       if (ret)
+               dev_err(dev, "failed to get clocks: %d\n", ret);
+
+       return ret;
+}
+
+static const struct udevice_id mtk_smi_common_ids[] = {
+       { .compatible = "mediatek,mt8365-smi-common" },
+       { .compatible = "mediatek,mt8366-smi-common" },
+       { }
+};
+
+U_BOOT_DRIVER(mtk_smi_common) = {
+       .name      = "mtk_smi_common",
+       .id        = UCLASS_MEMORY,
+       .of_match  = mtk_smi_common_ids,
+       .probe     = mtk_smi_common_probe,
+       .priv_auto = sizeof(struct mtk_smi_common_priv),
+};
+
 int mtk_smi_larb_enable(struct udevice *dev)
 {
        struct mtk_smi_larb_priv *priv = dev_get_priv(dev);
        int ret;
 
-       /* several DMA masters can sit behind the same larb */
+       /* one larb feeds several DMA masters, only the first ungates it */
        if (priv->enable_count++)
                return 0;
 
+       if (priv->smi_common) {
+               ret = mtk_smi_common_enable(priv->smi_common);
+               if (ret)
+                       goto err;
+       }
+
        ret = clk_enable_bulk(&priv->clk_bulk);
        if (ret) {
                dev_err(dev, "failed to enable clocks: %d\n", ret);
-               goto err;
+               goto err_disable_common;
        }
 
        /* as in Linux, only talk to the secure monitor once the larb is on */
@@ -107,6 +194,9 @@ int mtk_smi_larb_enable(struct udevice *dev)
 
 err_disable_clks:
        clk_disable_bulk(&priv->clk_bulk);
+err_disable_common:
+       if (priv->smi_common)
+               mtk_smi_common_disable(priv->smi_common);
 err:
        priv->enable_count = 0;
 
@@ -128,30 +218,71 @@ int mtk_smi_larb_disable(struct udevice *dev)
        if (ret)
                dev_err(dev, "failed to disable clocks: %d\n", ret);
 
+       if (priv->smi_common)
+               mtk_smi_common_disable(priv->smi_common);
+
        return ret;
 }
 
+void mtk_smi_larb_port_set_pa(struct udevice *dev, unsigned int port)
+{
+       struct mtk_smi_larb_priv *priv = dev_get_priv(dev);
+
+       clrbits_le32(priv->base + SMI_LARB_NONSEC_CON(port), MMU_EN);
+}
+
 static int mtk_smi_larb_probe(struct udevice *dev)
 {
        struct mtk_smi_larb_priv *priv = dev_get_priv(dev);
        int ret;
 
+       priv->base = dev_remap_addr(dev);
+       if (!priv->base)
+               return -EINVAL;
+
        ret = clk_get_bulk(dev, &priv->clk_bulk);
        if (ret) {
                dev_err(dev, "failed to get clocks: %d\n", ret);
                return ret;
        }
 
+       /*
+        * Not every smi_common block is handled by this driver: where it
+        * isn't, its clocks are already ungated by the time the DMA masters
+        * run, so having no device for it is not an error.
+        */
+       ret = uclass_get_device_by_phandle(UCLASS_MEMORY, dev, "mediatek,smi",
+                                          &priv->smi_common);
+       if (ret == -ENODEV || ret == -ENOENT) {
+               dev_dbg(dev, "no smi_common device: %d\n", ret);
+               priv->smi_common = NULL;
+       } else if (ret) {
+               dev_err(dev, "failed to get smi_common device: %d\n", ret);
+               return ret;
+       }
+
        priv->data = (const struct mtk_smi_larb_data *)dev_get_driver_data(dev);
 
        return 0;
 }
 
+/*
+ * The MT8365 larbs also carry the generic mediatek,mt8186-smi-larb
+ * compatible, which is the one matched here. The MT8366 ones only have
+ * their own, as that SoC is not upstream yet.
+ */
 static const struct udevice_id mtk_smi_larb_ids[] = {
+       {
+               .compatible = "mediatek,mt8186-smi-larb",
+       },
        {
                .compatible = "mediatek,mt8188-smi-larb",
                .data = (ulong)&mt8188_larb_data,
        },
+       {
+               .compatible = "mediatek,mt8366-smi-larb",
+               .data = (ulong)&mt8188_larb_data,
+       },
        { }
 };
 
diff --git a/include/mtk_smi.h b/include/mtk_smi.h
index b0166b37538..d4a22745d5e 100644
--- a/include/mtk_smi.h
+++ b/include/mtk_smi.h
@@ -15,8 +15,11 @@ struct udevice;
  * mtk_smi_larb_enable() - ungate an SMI local arbiter
  * @dev: larb device
  *
- * Enable the clocks of the larb so that the DMA master behind it can reach
- * DRAM. Calls are refcounted, so several masters can share one larb.
+ * Enable the clocks of the smi_common block behind the larb, if any, then
+ * the larb ones, so that the DMA master in front of it can reach DRAM. On
+ * the SoCs where the secure world owns the port mmu_en bits, also ask the
+ * secure monitor to keep every port of the larb out of the IOMMU. Calls
+ * are refcounted, so several masters can share one larb.
  *
  * Return: 0 on success, or a negative error code
  */
@@ -27,11 +30,22 @@ int mtk_smi_larb_enable(struct udevice *dev);
  * @dev: larb device
  *
  * Drop one reference taken by mtk_smi_larb_enable(), and disable the larb
- * clocks when it was the last one.
+ * clocks, then the smi_common ones, when it was the last one.
  *
  * Return: 0 on success, -EINVAL if the larb is not enabled, or another
  * negative error code
  */
 int mtk_smi_larb_disable(struct udevice *dev);
 
+/**
+ * mtk_smi_larb_port_set_pa() - switch a larb port to physical addressing
+ * @dev: larb device
+ * @port: M4U port index within the larb
+ *
+ * Take the port out of the IOMMU, so that it issues physical addresses (PA).
+ * Without an IOMMU driver the DMA masters address DRAM directly, so their
+ * ports must be taken out of the translated address space.
+ */
+void mtk_smi_larb_port_set_pa(struct udevice *dev, unsigned int port);
+
 #endif /* _MTK_SMI_H */

-- 
2.55.0

Reply via email to