diff options
Diffstat (limited to 'drivers/memory/tegra/mc.c')
-rw-r--r-- | drivers/memory/tegra/mc.c | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index fe3c44e7e1d1..4e3a886816a4 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -13,6 +13,9 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/sort.h> + +#include <soc/tegra/fuse.h> #include "mc.h" @@ -48,6 +51,9 @@ #define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff #define MC_EMEM_ARB_MISC0 0xd8 +#define MC_EMEM_ADR_CFG 0x54 +#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0) + static const struct of_device_id tegra_mc_of_match[] = { #ifdef CONFIG_ARCH_TEGRA_3x_SOC { .compatible = "nvidia,tegra30-mc", .data = &tegra30_mc_soc }, @@ -91,6 +97,130 @@ static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc) return 0; } +void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate) +{ + unsigned int i; + struct tegra_mc_timing *timing = NULL; + + for (i = 0; i < mc->num_timings; i++) { + if (mc->timings[i].rate == rate) { + timing = &mc->timings[i]; + break; + } + } + + if (!timing) { + dev_err(mc->dev, "no memory timing registered for rate %lu\n", + rate); + return; + } + + for (i = 0; i < mc->soc->num_emem_regs; ++i) + mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]); +} + +unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc) +{ + u8 dram_count; + + dram_count = mc_readl(mc, MC_EMEM_ADR_CFG); + dram_count &= MC_EMEM_ADR_CFG_EMEM_NUMDEV; + dram_count++; + + return dram_count; +} + +static int load_one_timing(struct tegra_mc *mc, + struct tegra_mc_timing *timing, + struct device_node *node) +{ + int err; + u32 tmp; + + err = of_property_read_u32(node, "clock-frequency", &tmp); + if (err) { + dev_err(mc->dev, + "timing %s: failed to read rate\n", node->name); + return err; + } + + timing->rate = tmp; + timing->emem_data = devm_kcalloc(mc->dev, mc->soc->num_emem_regs, + sizeof(u32), GFP_KERNEL); + if (!timing->emem_data) + return -ENOMEM; + + err = of_property_read_u32_array(node, "nvidia,emem-configuration", + timing->emem_data, + mc->soc->num_emem_regs); + if (err) { + dev_err(mc->dev, + "timing %s: failed to read EMEM configuration\n", + node->name); + return err; + } + + return 0; +} + +static int load_timings(struct tegra_mc *mc, struct device_node *node) +{ + struct device_node *child; + struct tegra_mc_timing *timing; + int child_count = of_get_child_count(node); + int i = 0, err; + + mc->timings = devm_kcalloc(mc->dev, child_count, sizeof(*timing), + GFP_KERNEL); + if (!mc->timings) + return -ENOMEM; + + mc->num_timings = child_count; + + for_each_child_of_node(node, child) { + timing = &mc->timings[i++]; + + err = load_one_timing(mc, timing, child); + if (err) + return err; + } + + return 0; +} + +static int tegra_mc_setup_timings(struct tegra_mc *mc) +{ + struct device_node *node; + u32 ram_code, node_ram_code; + int err; + + ram_code = tegra_read_ram_code(); + + mc->num_timings = 0; + + for_each_child_of_node(mc->dev->of_node, node) { + err = of_property_read_u32(node, "nvidia,ram-code", + &node_ram_code); + if (err || (node_ram_code != ram_code)) { + of_node_put(node); + continue; + } + + err = load_timings(mc, node); + if (err) + return err; + of_node_put(node); + break; + } + + if (mc->num_timings == 0) + dev_warn(mc->dev, + "no memory timings for RAM code %u registered\n", + ram_code); + + return 0; +} + static const char *const status_names[32] = { [ 1] = "External interrupt", [ 6] = "EMEM address decode error", @@ -248,6 +378,12 @@ static int tegra_mc_probe(struct platform_device *pdev) return err; } + err = tegra_mc_setup_timings(mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup timings: %d\n", err); + return err; + } + if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU)) { mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc); if (IS_ERR(mc->smmu)) { |