The SMI local arbiters (larbs) gate a multimedia subsystem's access to DRAM. On the MT8188 the display data path feeds through the VDO1 larbs (larb2 and larb3), whose clocks must be ungated for the HDMI pipeline to reach the framebuffer.
There is no dedicated SMI larb driver upstream, as Linux handles the larbs through its SMI/IOMMU framework (drivers/memory/mtk-smi.c). Add a minimal U-Boot driver that ungates the clocks of one larb, and export mtk_smi_larb_enable() and mtk_smi_larb_disable() so that the DMA master behind a larb can tie it to its own lifetime. The larbs are not video devices: they serve every multimedia master (display, video codec, camera, MDP), so the driver lives in drivers/memory with its own Kconfig symbol rather than being built as part of one master's driver. Every "mediatek,mt8188-smi-larb" node binds, which is cheap, and a larb is only probed once a master asks for it. Signed-off-by: Julien Stephan <[email protected]> --- drivers/memory/Kconfig | 12 ++++ drivers/memory/Makefile | 1 + drivers/memory/mtk-smi.c | 164 +++++++++++++++++++++++++++++++++++++++++++++++ include/mtk_smi.h | 37 +++++++++++ 4 files changed, 214 insertions(+) diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 5f029fd3e70..738b32aa83d 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -20,6 +20,18 @@ config ATMEL_EBI driver. Doesn't provide an access to EBI controller. Select this option to enable the NAND flash controller driver +config MTK_SMI + bool "MediaTek SoC Memory Controller driver" + depends on ARCH_MEDIATEK && DM + select ARM_SMCCC + select MEMORY + help + Enable support for the SMI local arbiters (larbs) found on + MediaTek SoCs such as the MT8188. A larb gates the DRAM access of + the multimedia masters behind it: this driver ungates its clocks + on behalf of these masters and, where the secure world owns the + port configuration, asks it to take the larb out of the IOMMU. + config SANDBOX_MEMORY bool "Enable Sandbox Memory Controller driver" depends on SANDBOX && MEMORY diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index d5f5b073672..f33d8acd5b2 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -1,6 +1,7 @@ obj-$(CONFIG_ATMEL_EBI) += atmel_ebi.o obj-$(CONFIG_MEMORY) += memory-uclass.o +obj-$(CONFIG_MTK_SMI) += mtk-smi.o obj-$(CONFIG_SANDBOX_MEMORY) += memory-sandbox.o obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o obj-$(CONFIG_STM32_OMM) += stm32_omm.o diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c new file mode 100644 index 00000000000..4531d416861 --- /dev/null +++ b/drivers/memory/mtk-smi.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MediaTek SMI local arbiter (larb) driver + * + * Copyright (c) 2026 BayLibre, SAS. + * Author: Julien Stephan <[email protected]> + */ + +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <errno.h> +#include <linux/arm-smccc.h> +#include <mtk_smi.h> + +/* + * 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. + */ +#define MTK_SIP_SMC_CONVENTION (IS_ENABLED(CONFIG_ARM64) ? \ + ARM_SMCCC_SMC_64 : ARM_SMCCC_SMC_32) + +#define MTK_SIP_KERNEL_IOMMU_CONTROL ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + MTK_SIP_SMC_CONVENTION, \ + ARM_SMCCC_OWNER_SIP, 0x514) +#define IOMMU_ATF_CMD_CONFIG_SMI_LARB 0 + +/** + * struct mtk_smi_larb_data - per-SoC larb data + * @port_sec_ctl: the port mmu_en bits are controlled by the secure world + */ +struct mtk_smi_larb_data { + bool port_sec_ctl; +}; + +/** + * struct mtk_smi_larb_priv - private data of an SMI larb + * @clk_bulk: clocks of the larb + * @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; + const struct mtk_smi_larb_data *data; + unsigned int enable_count; +}; + +static const struct mtk_smi_larb_data mt8188_larb_data = { + .port_sec_ctl = true, +}; + +static int mtk_smi_larb_bypass_iommu(struct udevice *dev) +{ + struct arm_smccc_res res; + u32 larb_id; + int ret; + + ret = dev_read_u32(dev, "mediatek,larb-id", &larb_id); + if (ret) { + dev_err(dev, "cannot read mediatek,larb-id: %d\n", ret); + return ret; + } + + /* the fourth argument is the mask of the ports using the IOMMU */ + arm_smccc_smc(MTK_SIP_KERNEL_IOMMU_CONTROL, IOMMU_ATF_CMD_CONFIG_SMI_LARB, + larb_id, 0, 0, 0, 0, 0, &res); + if (res.a0) { + dev_err(dev, "cannot take larb %u out of the iommu: %ld\n", + larb_id, res.a0); + return -EIO; + } + + return 0; +} + +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 */ + 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); + goto err; + } + + /* as in Linux, only talk to the secure monitor once the larb is on */ + if (priv->data && priv->data->port_sec_ctl) { + ret = mtk_smi_larb_bypass_iommu(dev); + if (ret) + goto err_disable_clks; + } + + return 0; + +err_disable_clks: + clk_disable_bulk(&priv->clk_bulk); +err: + priv->enable_count = 0; + + return ret; +} + +int mtk_smi_larb_disable(struct udevice *dev) +{ + struct mtk_smi_larb_priv *priv = dev_get_priv(dev); + int ret; + + if (!priv->enable_count) + return -EINVAL; + + if (--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_larb_probe(struct udevice *dev) +{ + struct mtk_smi_larb_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; + } + + priv->data = (const struct mtk_smi_larb_data *)dev_get_driver_data(dev); + + return 0; +} + +static const struct udevice_id mtk_smi_larb_ids[] = { + { + .compatible = "mediatek,mt8188-smi-larb", + .data = (ulong)&mt8188_larb_data, + }, + { } +}; + +U_BOOT_DRIVER(mtk_smi_larb) = { + .name = "mtk_smi_larb", + .id = UCLASS_MEMORY, + .of_match = mtk_smi_larb_ids, + .probe = mtk_smi_larb_probe, + .priv_auto = sizeof(struct mtk_smi_larb_priv), +}; diff --git a/include/mtk_smi.h b/include/mtk_smi.h new file mode 100644 index 00000000000..b0166b37538 --- /dev/null +++ b/include/mtk_smi.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * MediaTek SMI local arbiter (larb) API + * + * Copyright (c) 2026 BayLibre, SAS. + * Author: Julien Stephan <[email protected]> + */ + +#ifndef _MTK_SMI_H +#define _MTK_SMI_H + +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. + * + * Return: 0 on success, or a negative error code + */ +int mtk_smi_larb_enable(struct udevice *dev); + +/** + * mtk_smi_larb_disable() - gate an SMI local arbiter + * @dev: larb device + * + * Drop one reference taken by mtk_smi_larb_enable(), and disable the larb + * clocks 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); + +#endif /* _MTK_SMI_H */ -- 2.55.0
