/* * arch/arm/mach-tegra/mc.c * * Copyright (C) 2010 Google, Inc. * Copyright (C) 2011-2018, NVIDIA Corporation. All rights reserved. * * Author: * Erik Gilling * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "mc: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MC_CLIENT_HOTRESET_CTRL 0x200 #define MC_CLIENT_HOTRESET_STAT 0x204 #define MC_CLIENT_HOTRESET_CTRL_1 0x970 #define MC_CLIENT_HOTRESET_STAT_1 0x974 #define MC_LATENCY_ALLOWANCE_BASE MC_LATENCY_ALLOWANCE_AFI_0 #define MSSNVLINK_CYA_DESIGN_MODES 0x3c #define MSS_NVLINK_L3_ALLOC_HINT (1 << 2) static DEFINE_SPINLOCK(tegra_mc_lock); int mc_channels; void __iomem *mc; void __iomem *mc_regs[MC_MAX_CHANNELS]; unsigned int mssnvlink_hubs; void __iomem *mssnvlink_regs[MC_MAX_MSSNVLINK_HUBS]; u32 tegra_mc_readl(u32 reg) { return mc_readl(reg); } EXPORT_SYMBOL(tegra_mc_readl); void tegra_mc_writel(u32 val, u32 reg) { mc_writel(val, reg); } EXPORT_SYMBOL(tegra_mc_writel); /* * Return carveout info for @co in @inf. If @nr is non-NULL then the number of * carveouts are also place in @*nr. If both @inf and @nr are NULL then the * validity of @co is checked and that is it. */ int mc_get_carveout_info(struct mc_carveout_info *inf, int *nr, enum carveout_desc co) { #define MC_SECURITY_CARVEOUT(carveout, infop) \ do { \ (infop)->desc = co; \ (infop)->base = mc_readl(carveout ## _BOM) | \ ((u64)mc_readl(carveout ## _BOM_HI) & 0x3) << 32; \ (infop)->size = mc_readl(carveout ## _SIZE_128KB); \ (infop)->size <<= 17; /* Convert to bytes. */ \ } while (0) if (!mc) { WARN(1, "Reading carveout info before MC init'ed!\n"); return 0; } if (co >= MC_NR_CARVEOUTS) return -EINVAL; if (nr) *nr = MC_NR_CARVEOUTS; if (!inf) return 0; switch (co) { case MC_SECURITY_CARVEOUT1: #ifdef MC_SECURITY_CARVEOUT1_BOM MC_SECURITY_CARVEOUT(MC_SECURITY_CARVEOUT1, inf); break; #else return -ENODEV; #endif case MC_SECURITY_CARVEOUT2: #ifdef MC_SECURITY_CARVEOUT2_BOM MC_SECURITY_CARVEOUT(MC_SECURITY_CARVEOUT2, inf); break; #else return -ENODEV; #endif case MC_SECURITY_CARVEOUT4: #ifdef MC_SECURITY_CARVEOUT4_BOM MC_SECURITY_CARVEOUT(MC_SECURITY_CARVEOUT4, inf); break; #else return -ENODEV; #endif default: return -EINVAL; } return 0; } EXPORT_SYMBOL(mc_get_carveout_info); /* * API to convert BW in bytes/s to clock frequency. * * bw: Bandwidth to convert. It can be in any unit - the resulting frequency * will be in the same unit as passed. E.g KBps leads to KHz. */ unsigned long tegra_emc_bw_to_freq_req(unsigned long bw) { return bwmgr_bw_to_freq(bw); } EXPORT_SYMBOL_GPL(tegra_emc_bw_to_freq_req); /* * API to convert EMC clock frequency into theoretical available BW. This * does not account for a realistic utilization of the EMC bus. That is the * various overheads (refresh, bank commands, etc) that a real system sees * are not computed. * * freq: Frequency to convert. Like tegra_emc_bw_to_freq_req() it will work * on any passed order of ten. The result will be on the same order. */ unsigned long tegra_emc_freq_req_to_bw(unsigned long freq) { return bwmgr_freq_to_bw(freq); } EXPORT_SYMBOL_GPL(tegra_emc_freq_req_to_bw); #define HOTRESET_READ_COUNT 5 static bool tegra_stable_hotreset_check(u32 stat_reg, u32 *stat) { int i; u32 cur_stat; u32 prv_stat; unsigned long flags; spin_lock_irqsave(&tegra_mc_lock, flags); prv_stat = mc_readl(stat_reg); for (i = 0; i < HOTRESET_READ_COUNT; i++) { cur_stat = mc_readl(stat_reg); if (cur_stat != prv_stat) { spin_unlock_irqrestore(&tegra_mc_lock, flags); return false; } } *stat = cur_stat; spin_unlock_irqrestore(&tegra_mc_lock, flags); return true; } int tegra_mc_flush(int id) { u32 rst_ctrl, rst_stat; u32 rst_ctrl_reg, rst_stat_reg; unsigned long flags; unsigned int timeout; bool ret; if (!mc) return 0; if (id < 32) { rst_ctrl_reg = MC_CLIENT_HOTRESET_CTRL; rst_stat_reg = MC_CLIENT_HOTRESET_STAT; } else { id %= 32; rst_ctrl_reg = MC_CLIENT_HOTRESET_CTRL_1; rst_stat_reg = MC_CLIENT_HOTRESET_STAT_1; } spin_lock_irqsave(&tegra_mc_lock, flags); rst_ctrl = mc_readl(rst_ctrl_reg); rst_ctrl |= (1 << id); mc_writel(rst_ctrl, rst_ctrl_reg); spin_unlock_irqrestore(&tegra_mc_lock, flags); timeout = 0; do { bool exit = false; udelay(10); rst_stat = 0; ret = tegra_stable_hotreset_check(rst_stat_reg, &rst_stat); timeout++; /* keep lower timeout if we are running in qt or fpga */ exit |= (timeout > 100) && (tegra_platform_is_qt() || tegra_platform_is_fpga()); /* otherwise have huge timeout (~1s) */ exit |= timeout > 100000; if (exit) { WARN(1, "%s flush %d timeout\n", __func__, id); return -ETIMEDOUT; } if (!ret) continue; } while (!(rst_stat & (1 << id))); return 0; } EXPORT_SYMBOL(tegra_mc_flush); int tegra_mc_flush_done(int id) { u32 rst_ctrl; u32 rst_ctrl_reg, rst_stat_reg; unsigned long flags; if (!mc) return 0; if (id < 32) { rst_ctrl_reg = MC_CLIENT_HOTRESET_CTRL; rst_stat_reg = MC_CLIENT_HOTRESET_STAT; } else { id %= 32; rst_ctrl_reg = MC_CLIENT_HOTRESET_CTRL_1; rst_stat_reg = MC_CLIENT_HOTRESET_STAT_1; } spin_lock_irqsave(&tegra_mc_lock, flags); rst_ctrl = mc_readl(rst_ctrl_reg); rst_ctrl &= ~(1 << id); mc_writel(rst_ctrl, rst_ctrl_reg); spin_unlock_irqrestore(&tegra_mc_lock, flags); return 0; } EXPORT_SYMBOL(tegra_mc_flush_done); /* * Map an MC register space. Each MC has a set of register ranges which must * be parsed. The first starting address in the set of ranges is returned as * it is expected that the DT file has the register ranges in ascending * order. * * device 0 = global channel. * device n = specific channel device-1, e.g device = 1 ==> channel 0. */ static void __iomem *tegra_mc_map_regs(struct platform_device *pdev, int device) { struct resource res; const void *prop; void __iomem *regs; void __iomem *regs_start = NULL; u32 reg_ranges; int i, start; prop = of_get_property(pdev->dev.of_node, "reg-ranges", NULL); if (!prop) { pr_err("Failed to get MC MMIO region\n"); pr_err(" device = %d: missing reg-ranges\n", device); return NULL; } reg_ranges = be32_to_cpup(prop); start = device * reg_ranges; for (i = 0; i < reg_ranges; i++) { regs = of_iomap(pdev->dev.of_node, start + i); if (!regs) { pr_err("Failed to get MC MMIO region\n"); pr_err(" device = %d, range = %u\n", device, i); return NULL; } if (i == 0) regs_start = regs; } if (of_address_to_resource(pdev->dev.of_node, start, &res)) return NULL; pr_info("mapped MMIO address: 0x%p -> 0x%lx\n", regs_start, (unsigned long)res.start); return regs_start; } /* * Map mssnvlink igpu hubs. In t19x, 4 igpu links supported */ static void enable_mssnvlinks(struct platform_device *pdev) { struct device_node *dn = NULL; void __iomem *regs; int ret = 0, i; u32 reg_val; /* MSSNVLINK support is available in silicon or fpga only */ if (!tegra_platform_is_silicon()) return; dn = of_get_next_child(pdev->dev.of_node, NULL); if (!dn) { mssnvlink_hubs = UINT_MAX; dev_info(&pdev->dev, "No mssnvlink node\n"); return; } ret = of_property_read_u32(dn, "mssnvlink_hubs", &mssnvlink_hubs); if (ret) { dev_err(&pdev->dev, " property missing in %s\n", pdev->dev.of_node->name); ret = -EINVAL; goto err_out; } if (mssnvlink_hubs > MC_MAX_MSSNVLINK_HUBS || mssnvlink_hubs < 1) { pr_err("Invalid number of mssnvlink hubs: %d\n", mssnvlink_hubs); ret = -EINVAL; goto err_out; } for (i = 0; i < mssnvlink_hubs; i++) { regs = of_iomap(dn, i); if (!regs) { dev_err(&pdev->dev, "Failed to get MSSNVLINK aperture: %d\n", i); ret = PTR_ERR(regs); goto err_out; } mssnvlink_regs[i] = regs; reg_val = __raw_readl(regs + MSSNVLINK_CYA_DESIGN_MODES); reg_val |= MSS_NVLINK_L3_ALLOC_HINT; __raw_writel(reg_val, regs + MSSNVLINK_CYA_DESIGN_MODES); } err_out: WARN_ON(ret); } __weak const struct of_device_id tegra_mc_of_ids[] = { { .compatible = "nvidia,tegra-mc" }, { .compatible = "nvidia,tegra-t18x-mc" }, { } }; /* * MC driver init. */ static int tegra_mc_probe(struct platform_device *pdev) { #if defined(CONFIG_TEGRA_MC_EARLY_ACK) u32 reg; #endif int i; const void *prop; struct dentry *mc_debugfs_dir = NULL; struct tegra_mc_data *mc_data; const struct of_device_id *match; if (!pdev->dev.of_node) return -EINVAL; match = of_match_device(tegra_mc_of_ids, &pdev->dev); if (!match) { pr_err("Missing DT entry!\n"); return -EINVAL; } mc_data = (struct tegra_mc_data *)match->data; /* * Channel count. */ prop = of_get_property(pdev->dev.of_node, "channels", NULL); if (!prop) mc_channels = 1; else mc_channels = be32_to_cpup(prop); if (mc_channels > MC_MAX_CHANNELS || mc_channels < 1) { pr_err("Invalid number of memory channels: %d\n", mc_channels); return -EINVAL; } /* * IO mem. */ mc = tegra_mc_map_regs(pdev, 0); if (!mc) return -ENOMEM; /* Populate the rest of the channels... */ if (mc_channels > 1) { for (i = 1; i <= mc_channels; i++) { mc_regs[i - 1] = tegra_mc_map_regs(pdev, i); if (!mc_regs[i - 1]) return -ENOMEM; } } else { /* Make channel 0 the same as the MC broadcast range. */ mc_regs[0] = mc; } enable_mssnvlinks(pdev); #if defined(CONFIG_TEGRA_MC_EARLY_ACK) reg = mc_readl(MC_EMEM_ARB_OVERRIDE); reg |= 3; #if defined(CONFIG_TEGRA_ERRATA_1157520) if (tegra_revision == TEGRA_REVISION_A01) reg &= ~2; #endif mc_writel(reg, MC_EMEM_ARB_OVERRIDE); #endif #ifdef CONFIG_DEBUG_FS mc_debugfs_dir = debugfs_create_dir("mc", NULL); if (mc_debugfs_dir == NULL) pr_err("Failed to make debugfs node: %ld\n", PTR_ERR(mc_debugfs_dir)); #endif tegra_mcerr_init(mc_debugfs_dir, pdev); return 0; } u32 __weak tegra_get_dvfs_clk_change_latency_nsec(unsigned long emc_freq_khz) { return 2000; } static int tegra_mc_remove(struct platform_device *pdev) { return 0; } static struct platform_driver mc_driver = { .driver = { .name = "nv-tegra-mc", .of_match_table = tegra_mc_of_ids, .owner = THIS_MODULE, }, .probe = tegra_mc_probe, .remove = tegra_mc_remove, }; static int __init tegra_mc_init(void) { int ret; ret = platform_driver_register(&mc_driver); if (ret) return ret; return 0; } core_initcall(tegra_mc_init); static void __exit tegra_mc_fini(void) { } module_exit(tegra_mc_fini);