From 1527acf2295cf2d6e11e3e9b121d63709667e6e7 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 18 Jun 2026 02:22:02 +0800 Subject: [PATCH 1/4] memory: stm32_omm: initialize ret in stm32_omm_set_amcr stm32_omm_set_amcr() returns ret after checking whether the AMCR value matches the device tree description. On the normal matching path ret is not otherwise assigned, so initialize it to 0 before the checks. Signed-off-by: Ruoyu Wang Reviewed-by: Patrice Chotard Link: https://patch.msgid.link/20260617182202.961843-1-ruoyuw560@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/stm32_omm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/memory/stm32_omm.c b/drivers/memory/stm32_omm.c index 5d06623f3f68..2a1af229d244 100644 --- a/drivers/memory/stm32_omm.c +++ b/drivers/memory/stm32_omm.c @@ -47,7 +47,7 @@ static int stm32_omm_set_amcr(struct device *dev, bool set) struct device_node *node; struct resource res, res1; unsigned int syscon_args[2]; - int ret, idx; + int ret = 0, idx; unsigned int i, amcr, read_amcr; for (i = 0; i < omm->nb_child; i++) { From 5239692170f1815c0bbc57de432d7eb8b616f8f1 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 26 Jun 2026 18:44:23 +0530 Subject: [PATCH 2/4] memory: tegra: add multi-socket support to the memory interconnect Add support for representing each memory-controller instance (one per NUMA node / socket) as its own interconnect (ICC) provider, with its own MC client nodes, to match the hardware topology on multi-socket Tegra SoCs. Use the NUMA node ID to make client IDs globally unique across per-socket providers, since the ICC framework allocates node IDs from a single global IDR. Per-socket MC and EMC node names are also derived from dev_name() so they match the corresponding debugfs subdirectory. On single-socket platforms (NUMA_NO_NODE) the existing client IDs and node-name strings are preserved. Each socket's MC and EMC therefore get their own debugfs subdirectory under /sys/kernel/debug/{mc,emc}/. The parent directories are created on first probe. Bandwidth requests from MC clients in a socket are routed to that socket's local BPMP. Signed-off-by: Sumit Gupta Tested-by: Jon Hunter Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260626131423.3986998-1-sumitg@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/mc.c | 40 ++++++++++++---- drivers/memory/tegra/mc.h | 37 +++++++++++++++ drivers/memory/tegra/tegra186-emc.c | 71 ++++++++++++++++++++++------- 3 files changed, 123 insertions(+), 25 deletions(-) diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index ec80ea9cc173..51ad0d3e48d4 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -3,6 +3,7 @@ * Copyright (C) 2014-2026 NVIDIA CORPORATION. All rights reserved. */ +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +24,9 @@ #include "mc.h" +static DEFINE_MUTEX(tegra_mc_debugfs_root_lock); +static struct dentry *tegra_mc_debugfs_root; + static const struct of_device_id tegra_mc_of_match[] = { #ifdef CONFIG_ARCH_TEGRA_2x_SOC { .compatible = "nvidia,tegra20-mc-gart", .data = &tegra20_mc_soc }, @@ -778,7 +783,7 @@ struct icc_node *tegra_mc_icc_xlate(const struct of_phandle_args *spec, void *da struct icc_node *node; list_for_each_entry(node, &mc->provider.nodes, node_list) { - if (node->id == spec->args[0]) + if (tegra_mc_client_id_from_node(node) == spec->args[0]) return node; } @@ -834,6 +839,7 @@ const struct tegra_mc_icc_ops tegra_mc_icc_ops = { */ static int tegra_mc_interconnect_setup(struct tegra_mc *mc) { + int node_id = dev_to_node(mc->dev); struct icc_node *node; unsigned int i; int err; @@ -854,31 +860,40 @@ static int tegra_mc_interconnect_setup(struct tegra_mc *mc) icc_provider_init(&mc->provider); /* create Memory Controller node */ - node = icc_node_create(TEGRA_ICC_MC); + node = tegra_mc_icc_node_create(node_id, TEGRA_ICC_MC); if (IS_ERR(node)) return PTR_ERR(node); - node->name = "Memory Controller"; + if (node_id == NUMA_NO_NODE) + node->name = "Memory Controller"; + else + node->name = dev_name(mc->dev); + icc_node_add(node, &mc->provider); /* link Memory Controller to External Memory Controller */ - err = icc_link_create(node, TEGRA_ICC_EMC); + err = tegra_mc_icc_link_create(node, node_id, TEGRA_ICC_EMC); if (err) goto remove_nodes; for (i = 0; i < mc->soc->num_clients; i++) { /* create MC client node */ - node = icc_node_create(mc->soc->clients[i].id); + node = tegra_mc_icc_node_create(node_id, mc->soc->clients[i].id); if (IS_ERR(node)) { err = PTR_ERR(node); goto remove_nodes; } - node->name = mc->soc->clients[i].name; + if (node_id == NUMA_NO_NODE) + node->name = mc->soc->clients[i].name; + else + node->name = devm_kasprintf(mc->dev, GFP_KERNEL, "%d-%s", + node_id, mc->soc->clients[i].name); + icc_node_add(node, &mc->provider); /* link Memory Client to Memory Controller */ - err = icc_link_create(node, TEGRA_ICC_MC); + err = tegra_mc_icc_link_create(node, node_id, TEGRA_ICC_MC); if (err) goto remove_nodes; @@ -957,7 +972,16 @@ static int tegra_mc_probe(struct platform_device *pdev) if (IS_ERR(mc->regs)) return PTR_ERR(mc->regs); - mc->debugfs.root = debugfs_create_dir("mc", NULL); + scoped_guard(mutex, &tegra_mc_debugfs_root_lock) { + if (!tegra_mc_debugfs_root) + tegra_mc_debugfs_root = debugfs_create_dir("mc", NULL); + + if (dev_to_node(mc->dev) == NUMA_NO_NODE) + mc->debugfs.root = tegra_mc_debugfs_root; + else + mc->debugfs.root = debugfs_create_dir(dev_name(mc->dev), + tegra_mc_debugfs_root); + } if (mc->soc->ops && mc->soc->ops->probe) { err = mc->soc->ops->probe(mc); diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h index e94d265d7b67..01499292dd5f 100644 --- a/drivers/memory/tegra/mc.h +++ b/drivers/memory/tegra/mc.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -167,6 +168,42 @@ icc_provider_to_tegra_mc(struct icc_provider *provider) return container_of(provider, struct tegra_mc, provider); } +/* + * Compose a globally-unique ICC node ID. On single-socket + * systems (NUMA_NO_NODE), the SoC client ID is returned unchanged. + * On multi-socket systems, the NUMA node ID is encoded in the + * upper bits of the returned ID. + * + * The client ID field is sized to keep composed IDs below + * ICC_DYN_ID_START (the start of the ICC core's dynamic-ID range). + */ +#define TEGRA_MC_CLIENT_ID_BITS 12 +#define TEGRA_MC_CLIENT_ID_MASK ((1U << TEGRA_MC_CLIENT_ID_BITS) - 1) + +static inline u32 tegra_mc_get_client_id(int node_id, int id) +{ + if (node_id == NUMA_NO_NODE) + return id; + + return ((node_id + 1) << TEGRA_MC_CLIENT_ID_BITS) | id; +} + +static inline struct icc_node *tegra_mc_icc_node_create(int node_id, int id) +{ + return icc_node_create(tegra_mc_get_client_id(node_id, id)); +} + +static inline int tegra_mc_icc_link_create(struct icc_node *node, int node_id, int id) +{ + return icc_link_create(node, tegra_mc_get_client_id(node_id, id)); +} + +/* Return the SoC client ID encoded in an ICC node ID. */ +static inline u32 tegra_mc_client_id_from_node(const struct icc_node *node) +{ + return node->id & TEGRA_MC_CLIENT_ID_MASK; +} + static inline u32 mc_ch_readl(const struct tegra_mc *mc, int ch, unsigned long offset) { diff --git a/drivers/memory/tegra/tegra186-emc.c b/drivers/memory/tegra/tegra186-emc.c index f71265b303b9..e25f485e136e 100644 --- a/drivers/memory/tegra/tegra186-emc.c +++ b/drivers/memory/tegra/tegra186-emc.c @@ -3,16 +3,22 @@ * Copyright (C) 2019-2025 NVIDIA CORPORATION. All rights reserved. */ +#include #include #include +#include #include #include +#include #include #include #include #include "mc.h" +static DEFINE_MUTEX(tegra_emc_debugfs_root_lock); +static struct dentry *tegra_emc_debugfs_root; + struct tegra186_emc_dvfs { unsigned long latency; unsigned long rate; @@ -207,7 +213,17 @@ static int tegra186_emc_get_emc_dvfs_latency(struct tegra186_emc *emc) return err; } - emc->debugfs.root = debugfs_create_dir("emc", NULL); + scoped_guard(mutex, &tegra_emc_debugfs_root_lock) { + if (!tegra_emc_debugfs_root) + tegra_emc_debugfs_root = debugfs_create_dir("emc", NULL); + + if (dev_to_node(emc->dev) == NUMA_NO_NODE) + emc->debugfs.root = tegra_emc_debugfs_root; + else + emc->debugfs.root = debugfs_create_dir(dev_name(emc->dev), + tegra_emc_debugfs_root); + } + debugfs_create_file("available_rates", 0444, emc->debugfs.root, emc, &tegra186_emc_debug_available_rates_fops); debugfs_create_file("min_rate", 0644, emc->debugfs.root, emc, @@ -239,7 +255,7 @@ tegra186_emc_of_icc_xlate(const struct of_phandle_args *spec, void *data) /* External Memory is the only possible ICC route */ list_for_each_entry(node, &provider->nodes, node_list) { - if (node->id != TEGRA_ICC_EMEM) + if (tegra_mc_client_id_from_node(node) != TEGRA_ICC_EMEM) continue; return node; @@ -258,6 +274,7 @@ static int tegra186_emc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *pe static int tegra186_emc_interconnect_init(struct tegra186_emc *emc) { + int node_id = dev_to_node(emc->dev->parent); struct icc_node *node; int err; @@ -271,26 +288,36 @@ static int tegra186_emc_interconnect_init(struct tegra186_emc *emc) icc_provider_init(&emc->provider); /* create External Memory Controller node */ - node = icc_node_create(TEGRA_ICC_EMC); - if (IS_ERR(node)) - return PTR_ERR(node); - - node->name = "External Memory Controller"; - icc_node_add(node, &emc->provider); - - /* link External Memory Controller to External Memory (DRAM) */ - err = icc_link_create(node, TEGRA_ICC_EMEM); - if (err) - goto remove_nodes; - - /* create External Memory node */ - node = icc_node_create(TEGRA_ICC_EMEM); + node = tegra_mc_icc_node_create(node_id, TEGRA_ICC_EMC); if (IS_ERR(node)) { err = PTR_ERR(node); goto remove_nodes; } - node->name = "External Memory (DRAM)"; + if (node_id == NUMA_NO_NODE) + node->name = "External Memory Controller"; + else + node->name = dev_name(emc->dev); + + icc_node_add(node, &emc->provider); + + /* link External Memory Controller to External Memory (DRAM) */ + err = tegra_mc_icc_link_create(node, node_id, TEGRA_ICC_EMEM); + if (err) + goto remove_nodes; + + /* create External Memory node */ + node = tegra_mc_icc_node_create(node_id, TEGRA_ICC_EMEM); + if (IS_ERR(node)) { + err = PTR_ERR(node); + goto remove_nodes; + } + + if (node_id == NUMA_NO_NODE) + node->name = "External Memory (DRAM)"; + else + node->name = devm_kasprintf(emc->dev, GFP_KERNEL, "%d-dram", node_id); + icc_node_add(node, &emc->provider); err = icc_provider_register(&emc->provider); @@ -383,6 +410,16 @@ static void tegra186_emc_remove(struct platform_device *pdev) debugfs_remove_recursive(emc->debugfs.root); + scoped_guard(mutex, &tegra_emc_debugfs_root_lock) { + if (emc->debugfs.root == tegra_emc_debugfs_root) { + tegra_emc_debugfs_root = NULL; + } else if (tegra_emc_debugfs_root && + simple_empty(tegra_emc_debugfs_root)) { + debugfs_remove(tegra_emc_debugfs_root); + tegra_emc_debugfs_root = NULL; + } + } + mc->bpmp = NULL; tegra_bpmp_put(emc->bpmp); } From 8111c7f3723f414b7f655a3880775cbbd0e9de8e Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Fri, 3 Jul 2026 04:56:53 +0000 Subject: [PATCH 3/4] memory: tegra: Guard against NULL mc_regs in IRQ handler The per-error decode in tegra30_mc_handle_irq() dereferences mc->soc->regs unconditionally. This 'regs' structure is optional and is only used for decoding and logging MC error interrupts. The rest of the MC functionality does not depend on it. When adding support for the Tegra238 SoC, the 'regs' structure was initially omitted because it is only used for error logging. We found that this resulted in a NULL pointer dereference in IRQ context, causing a crash when an MC error interrupt fired. Although existing upstream devices will not hit this condition because 'regs' is present, guard against it to improve the robustness of the driver. Skip the decode and just clear the interrupt when mc_regs is NULL. This bypasses the error interrupt logging while keeping the remaining MC functionality intact. Signed-off-by: Ashish Mhetre Link: https://patch.msgid.link/20260703045653.395498-1-amhetre@nvidia.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/tegra/mc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index 51ad0d3e48d4..458ce99916ad 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -603,6 +603,13 @@ irqreturn_t tegra30_mc_handle_irq(int irq, void *data) if (!status) return IRQ_NONE; + if (!mc->soc->regs) { + dev_err_ratelimited(mc->dev, + "MC error interrupt 0x%08lx with no error register map, Clearing.\n", + status); + goto clear; + } + for_each_set_bit(bit, &status, 32) { const char *error = tegra_mc_status_names[bit] ?: "unknown"; const char *client = "unknown", *desc; @@ -741,6 +748,7 @@ irqreturn_t tegra30_mc_handle_irq(int irq, void *data) desc, perm); } +clear: /* clear interrupts */ if (mc->soc->num_channels) { mc_ch_writel(mc, channel, status, MC_INTSTATUS); From 96aa6902ff2bf81b9b8e0fa228494595dc2374a9 Mon Sep 17 00:00:00 2001 From: Miles Krause Date: Sat, 11 Jul 2026 22:56:14 -0400 Subject: [PATCH 4/4] memory: jz4780-nemc: Use dev_err_probe() for clock error Use dev_err_probe() to simplify clock acquisition error handling and avoid logging probe deferral as an error. No functional change. Signed-off-by: Miles Krause Link: https://patch.msgid.link/20260711-jz4780-nemc-dev-err-probe-v1-1-afb6f6a82752@gmail.com Signed-off-by: Krzysztof Kozlowski --- drivers/memory/jz4780-nemc.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/memory/jz4780-nemc.c b/drivers/memory/jz4780-nemc.c index 1a8161514d03..1bf17057cbf5 100644 --- a/drivers/memory/jz4780-nemc.c +++ b/drivers/memory/jz4780-nemc.c @@ -313,10 +313,9 @@ static int jz4780_nemc_probe(struct platform_device *pdev) writel(0, nemc->base + NEMC_NFCSR); nemc->clk = devm_clk_get(dev, NULL); - if (IS_ERR(nemc->clk)) { - dev_err(dev, "failed to get clock\n"); - return PTR_ERR(nemc->clk); - } + if (IS_ERR(nemc->clk)) + return dev_err_probe(dev, PTR_ERR(nemc->clk), + "failed to get clock\n"); ret = clk_prepare_enable(nemc->clk); if (ret) {