diff options
| -rw-r--r-- | drivers/memory/tegra/Makefile | 3 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra124-emc.c | 185 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra186-emc.c | 293 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra186.c | 1067 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra20-emc.c | 175 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra210.c | 2 | ||||
| -rw-r--r-- | drivers/memory/tegra/tegra30-emc.c | 352 | 
7 files changed, 1928 insertions, 149 deletions
| diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index 3d23c4261104..529d10bc5650 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -13,4 +13,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o  obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o  obj-$(CONFIG_TEGRA30_EMC)  += tegra30-emc.o  obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o -obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o +obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o tegra186-emc.o +obj-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra186.o tegra186-emc.o diff --git a/drivers/memory/tegra/tegra124-emc.c b/drivers/memory/tegra/tegra124-emc.c index 464f0ceaee63..21f05240682b 100644 --- a/drivers/memory/tegra/tegra124-emc.c +++ b/drivers/memory/tegra/tegra124-emc.c @@ -467,12 +467,20 @@ struct tegra_emc {  	void __iomem *regs; +	struct clk *clk; +  	enum emc_dram_type dram_type;  	unsigned int dram_num;  	struct emc_timing last_timing;  	struct emc_timing *timings;  	unsigned int num_timings; + +	struct { +		struct dentry *root; +		unsigned long min_rate; +		unsigned long max_rate; +	} debugfs;  };  /* Timing change sequence functions */ @@ -998,38 +1006,51 @@ tegra_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code)  	return NULL;  } -/* Debugfs entry */ +/* + * debugfs interface + * + * The memory controller driver exposes some files in debugfs that can be used + * to control the EMC frequency. The top-level directory can be found here: + * + *   /sys/kernel/debug/emc + * + * It contains the following files: + * + *   - available_rates: This file contains a list of valid, space-separated + *     EMC frequencies. + * + *   - min_rate: Writing a value to this file sets the given frequency as the + *       floor of the permitted range. If this is higher than the currently + *       configured EMC frequency, this will cause the frequency to be + *       increased so that it stays within the valid range. + * + *   - max_rate: Similarily to the min_rate file, writing a value to this file + *       sets the given frequency as the ceiling of the permitted range. If + *       the value is lower than the currently configured EMC frequency, this + *       will cause the frequency to be decreased so that it stays within the + *       valid range. + */ -static int emc_debug_rate_get(void *data, u64 *rate) +static bool tegra_emc_validate_rate(struct tegra_emc *emc, unsigned long rate)  { -	struct clk *c = data; - -	*rate = clk_get_rate(c); - -	return 0; -} +	unsigned int i; -static int emc_debug_rate_set(void *data, u64 rate) -{ -	struct clk *c = data; +	for (i = 0; i < emc->num_timings; i++) +		if (rate == emc->timings[i].rate) +			return true; -	return clk_set_rate(c, rate); +	return false;  } -DEFINE_SIMPLE_ATTRIBUTE(emc_debug_rate_fops, emc_debug_rate_get, -			emc_debug_rate_set, "%lld\n"); - -static int emc_debug_supported_rates_show(struct seq_file *s, void *data) +static int tegra_emc_debug_available_rates_show(struct seq_file *s, +						void *data)  {  	struct tegra_emc *emc = s->private;  	const char *prefix = "";  	unsigned int i;  	for (i = 0; i < emc->num_timings; i++) { -		struct emc_timing *timing = &emc->timings[i]; - -		seq_printf(s, "%s%lu", prefix, timing->rate); - +		seq_printf(s, "%s%lu", prefix, emc->timings[i].rate);  		prefix = " ";  	} @@ -1038,46 +1059,126 @@ static int emc_debug_supported_rates_show(struct seq_file *s, void *data)  	return 0;  } -static int emc_debug_supported_rates_open(struct inode *inode, -					  struct file *file) +static int tegra_emc_debug_available_rates_open(struct inode *inode, +						struct file *file)  { -	return single_open(file, emc_debug_supported_rates_show, +	return single_open(file, tegra_emc_debug_available_rates_show,  			   inode->i_private);  } -static const struct file_operations emc_debug_supported_rates_fops = { -	.open = emc_debug_supported_rates_open, +static const struct file_operations tegra_emc_debug_available_rates_fops = { +	.open = tegra_emc_debug_available_rates_open,  	.read = seq_read,  	.llseek = seq_lseek,  	.release = single_release,  }; +static int tegra_emc_debug_min_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.min_rate; + +	return 0; +} + +static int tegra_emc_debug_min_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_min_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.min_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_min_rate_fops, +			tegra_emc_debug_min_rate_get, +			tegra_emc_debug_min_rate_set, "%llu\n"); + +static int tegra_emc_debug_max_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.max_rate; + +	return 0; +} + +static int tegra_emc_debug_max_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_max_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.max_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_max_rate_fops, +			tegra_emc_debug_max_rate_get, +			tegra_emc_debug_max_rate_set, "%llu\n"); +  static void emc_debugfs_init(struct device *dev, struct tegra_emc *emc)  { -	struct dentry *root, *file; -	struct clk *clk; +	unsigned int i; +	int err; -	root = debugfs_create_dir("emc", NULL); -	if (!root) { -		dev_err(dev, "failed to create debugfs directory\n"); -		return; +	emc->clk = devm_clk_get(dev, "emc"); +	if (IS_ERR(emc->clk)) { +		if (PTR_ERR(emc->clk) != -ENODEV) { +			dev_err(dev, "failed to get EMC clock: %ld\n", +				PTR_ERR(emc->clk)); +			return; +		}  	} -	clk = clk_get_sys("tegra-clk-debug", "emc"); -	if (IS_ERR(clk)) { -		dev_err(dev, "failed to get debug clock: %ld\n", PTR_ERR(clk)); +	emc->debugfs.min_rate = ULONG_MAX; +	emc->debugfs.max_rate = 0; + +	for (i = 0; i < emc->num_timings; i++) { +		if (emc->timings[i].rate < emc->debugfs.min_rate) +			emc->debugfs.min_rate = emc->timings[i].rate; + +		if (emc->timings[i].rate > emc->debugfs.max_rate) +			emc->debugfs.max_rate = emc->timings[i].rate; +	} + +	err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, +				 emc->debugfs.max_rate); +	if (err < 0) { +		dev_err(dev, "failed to set rate range [%lu-%lu] for %pC\n", +			emc->debugfs.min_rate, emc->debugfs.max_rate, +			emc->clk);  		return;  	} -	file = debugfs_create_file("rate", S_IRUGO | S_IWUSR, root, clk, -				   &emc_debug_rate_fops); -	if (!file) -		dev_err(dev, "failed to create debugfs entry\n"); +	emc->debugfs.root = debugfs_create_dir("emc", NULL); +	if (!emc->debugfs.root) { +		dev_err(dev, "failed to create debugfs directory\n"); +		return; +	} -	file = debugfs_create_file("supported_rates", S_IRUGO, root, emc, -				   &emc_debug_supported_rates_fops); -	if (!file) -		dev_err(dev, "failed to create debugfs entry\n"); +	debugfs_create_file("available_rates", S_IRUGO, emc->debugfs.root, emc, +			    &tegra_emc_debug_available_rates_fops); +	debugfs_create_file("min_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_min_rate_fops); +	debugfs_create_file("max_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_max_rate_fops);  }  static int tegra_emc_probe(struct platform_device *pdev) diff --git a/drivers/memory/tegra/tegra186-emc.c b/drivers/memory/tegra/tegra186-emc.c new file mode 100644 index 000000000000..97f26bc77ad4 --- /dev/null +++ b/drivers/memory/tegra/tegra186-emc.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2019 NVIDIA CORPORATION.  All rights reserved. + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> + +#include <soc/tegra/bpmp.h> + +struct tegra186_emc_dvfs { +	unsigned long latency; +	unsigned long rate; +}; + +struct tegra186_emc { +	struct tegra_bpmp *bpmp; +	struct device *dev; +	struct clk *clk; + +	struct tegra186_emc_dvfs *dvfs; +	unsigned int num_dvfs; + +	struct { +		struct dentry *root; +		unsigned long min_rate; +		unsigned long max_rate; +	} debugfs; +}; + +/* + * debugfs interface + * + * The memory controller driver exposes some files in debugfs that can be used + * to control the EMC frequency. The top-level directory can be found here: + * + *   /sys/kernel/debug/emc + * + * It contains the following files: + * + *   - available_rates: This file contains a list of valid, space-separated + *     EMC frequencies. + * + *   - min_rate: Writing a value to this file sets the given frequency as the + *       floor of the permitted range. If this is higher than the currently + *       configured EMC frequency, this will cause the frequency to be + *       increased so that it stays within the valid range. + * + *   - max_rate: Similarily to the min_rate file, writing a value to this file + *       sets the given frequency as the ceiling of the permitted range. If + *       the value is lower than the currently configured EMC frequency, this + *       will cause the frequency to be decreased so that it stays within the + *       valid range. + */ + +static bool tegra186_emc_validate_rate(struct tegra186_emc *emc, +				       unsigned long rate) +{ +	unsigned int i; + +	for (i = 0; i < emc->num_dvfs; i++) +		if (rate == emc->dvfs[i].rate) +			return true; + +	return false; +} + +static int tegra186_emc_debug_available_rates_show(struct seq_file *s, +						   void *data) +{ +	struct tegra186_emc *emc = s->private; +	const char *prefix = ""; +	unsigned int i; + +	for (i = 0; i < emc->num_dvfs; i++) { +		seq_printf(s, "%s%lu", prefix, emc->dvfs[i].rate); +		prefix = " "; +	} + +	seq_puts(s, "\n"); + +	return 0; +} + +static int tegra186_emc_debug_available_rates_open(struct inode *inode, +						   struct file *file) +{ +	return single_open(file, tegra186_emc_debug_available_rates_show, +			   inode->i_private); +} + +static const struct file_operations tegra186_emc_debug_available_rates_fops = { +	.open = tegra186_emc_debug_available_rates_open, +	.read = seq_read, +	.llseek = seq_lseek, +	.release = single_release, +}; + +static int tegra186_emc_debug_min_rate_get(void *data, u64 *rate) +{ +	struct tegra186_emc *emc = data; + +	*rate = emc->debugfs.min_rate; + +	return 0; +} + +static int tegra186_emc_debug_min_rate_set(void *data, u64 rate) +{ +	struct tegra186_emc *emc = data; +	int err; + +	if (!tegra186_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_min_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.min_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra186_emc_debug_min_rate_fops, +			tegra186_emc_debug_min_rate_get, +			tegra186_emc_debug_min_rate_set, "%llu\n"); + +static int tegra186_emc_debug_max_rate_get(void *data, u64 *rate) +{ +	struct tegra186_emc *emc = data; + +	*rate = emc->debugfs.max_rate; + +	return 0; +} + +static int tegra186_emc_debug_max_rate_set(void *data, u64 rate) +{ +	struct tegra186_emc *emc = data; +	int err; + +	if (!tegra186_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_max_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.max_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra186_emc_debug_max_rate_fops, +			tegra186_emc_debug_max_rate_get, +			tegra186_emc_debug_max_rate_set, "%llu\n"); + +static int tegra186_emc_probe(struct platform_device *pdev) +{ +	struct mrq_emc_dvfs_latency_response response; +	struct tegra_bpmp_message msg; +	struct tegra186_emc *emc; +	unsigned int i; +	int err; + +	emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL); +	if (!emc) +		return -ENOMEM; + +	emc->bpmp = tegra_bpmp_get(&pdev->dev); +	if (IS_ERR(emc->bpmp)) { +		err = PTR_ERR(emc->bpmp); + +		if (err != -EPROBE_DEFER) +			dev_err(&pdev->dev, "failed to get BPMP: %d\n", err); + +		return err; +	} + +	emc->clk = devm_clk_get(&pdev->dev, "emc"); +	if (IS_ERR(emc->clk)) { +		err = PTR_ERR(emc->clk); +		dev_err(&pdev->dev, "failed to get EMC clock: %d\n", err); +		return err; +	} + +	platform_set_drvdata(pdev, emc); +	emc->dev = &pdev->dev; + +	memset(&msg, 0, sizeof(msg)); +	msg.mrq = MRQ_EMC_DVFS_LATENCY; +	msg.tx.data = NULL; +	msg.tx.size = 0; +	msg.rx.data = &response; +	msg.rx.size = sizeof(response); + +	err = tegra_bpmp_transfer(emc->bpmp, &msg); +	if (err < 0) { +		dev_err(&pdev->dev, "failed to EMC DVFS pairs: %d\n", err); +		return err; +	} + +	emc->debugfs.min_rate = ULONG_MAX; +	emc->debugfs.max_rate = 0; + +	emc->num_dvfs = response.num_pairs; + +	emc->dvfs = devm_kmalloc_array(&pdev->dev, emc->num_dvfs, +				       sizeof(*emc->dvfs), GFP_KERNEL); +	if (!emc->dvfs) +		return -ENOMEM; + +	dev_dbg(&pdev->dev, "%u DVFS pairs:\n", emc->num_dvfs); + +	for (i = 0; i < emc->num_dvfs; i++) { +		emc->dvfs[i].rate = response.pairs[i].freq * 1000; +		emc->dvfs[i].latency = response.pairs[i].latency; + +		if (emc->dvfs[i].rate < emc->debugfs.min_rate) +			emc->debugfs.min_rate = emc->dvfs[i].rate; + +		if (emc->dvfs[i].rate > emc->debugfs.max_rate) +			emc->debugfs.max_rate = emc->dvfs[i].rate; + +		dev_dbg(&pdev->dev, "  %2u: %lu Hz -> %lu us\n", i, +			emc->dvfs[i].rate, emc->dvfs[i].latency); +	} + +	err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, +				 emc->debugfs.max_rate); +	if (err < 0) { +		dev_err(&pdev->dev, +			"failed to set rate range [%lu-%lu] for %pC\n", +			emc->debugfs.min_rate, emc->debugfs.max_rate, +			emc->clk); +		return err; +	} + +	emc->debugfs.root = debugfs_create_dir("emc", NULL); +	if (!emc->debugfs.root) { +		dev_err(&pdev->dev, "failed to create debugfs directory\n"); +		return 0; +	} + +	debugfs_create_file("available_rates", S_IRUGO, emc->debugfs.root, +			    emc, &tegra186_emc_debug_available_rates_fops); +	debugfs_create_file("min_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra186_emc_debug_min_rate_fops); +	debugfs_create_file("max_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra186_emc_debug_max_rate_fops); + +	return 0; +} + +static int tegra186_emc_remove(struct platform_device *pdev) +{ +	struct tegra186_emc *emc = platform_get_drvdata(pdev); + +	debugfs_remove_recursive(emc->debugfs.root); +	tegra_bpmp_put(emc->bpmp); + +	return 0; +} + +static const struct of_device_id tegra186_emc_of_match[] = { +#if defined(CONFIG_ARCH_TEGRA186_SOC) +	{ .compatible = "nvidia,tegra186-emc" }, +#endif +#if defined(CONFIG_ARCH_TEGRA194_SOC) +	{ .compatible = "nvidia,tegra194-emc" }, +#endif +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tegra186_emc_of_match); + +static struct platform_driver tegra186_emc_driver = { +	.driver = { +		.name = "tegra186-emc", +		.of_match_table = tegra186_emc_of_match, +		.suppress_bind_attrs = true, +	}, +	.probe = tegra186_emc_probe, +	.remove = tegra186_emc_remove, +}; +module_platform_driver(tegra186_emc_driver); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra186 External Memory Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/tegra/tegra186.c b/drivers/memory/tegra/tegra186.c index 441213a35930..5d53f11ca7b6 100644 --- a/drivers/memory/tegra/tegra186.c +++ b/drivers/memory/tegra/tegra186.c @@ -6,16 +6,18 @@  #include <linux/io.h>  #include <linux/module.h>  #include <linux/mod_devicetable.h> +#include <linux/of_device.h>  #include <linux/platform_device.h> +#if defined(CONFIG_ARCH_TEGRA_186_SOC)  #include <dt-bindings/memory/tegra186-mc.h> +#endif -struct tegra_mc { -	struct device *dev; -	void __iomem *regs; -}; +#if defined(CONFIG_ARCH_TEGRA_194_SOC) +#include <dt-bindings/memory/tegra194-mc.h> +#endif -struct tegra_mc_client { +struct tegra186_mc_client {  	const char *name;  	unsigned int sid;  	struct { @@ -24,7 +26,46 @@ struct tegra_mc_client {  	} regs;  }; -static const struct tegra_mc_client tegra186_mc_clients[] = { +struct tegra186_mc_soc { +	const struct tegra186_mc_client *clients; +	unsigned int num_clients; +}; + +struct tegra186_mc { +	struct device *dev; +	void __iomem *regs; + +	const struct tegra186_mc_soc *soc; +}; + +static void tegra186_mc_program_sid(struct tegra186_mc *mc) +{ +	unsigned int i; + +	for (i = 0; i < mc->soc->num_clients; i++) { +		const struct tegra186_mc_client *client = &mc->soc->clients[i]; +		u32 override, security; + +		override = readl(mc->regs + client->regs.override); +		security = readl(mc->regs + client->regs.security); + +		dev_dbg(mc->dev, "client %s: override: %x security: %x\n", +			client->name, override, security); + +		dev_dbg(mc->dev, "setting SID %u for %s\n", client->sid, +			client->name); +		writel(client->sid, mc->regs + client->regs.override); + +		override = readl(mc->regs + client->regs.override); +		security = readl(mc->regs + client->regs.security); + +		dev_dbg(mc->dev, "client %s: override: %x security: %x\n", +			client->name, override, security); +	} +} + +#if defined(CONFIG_ARCH_TEGRA_186_SOC) +static const struct tegra186_mc_client tegra186_mc_clients[] = {  	{  		.name = "ptcr",  		.sid = TEGRA186_SID_PASSTHROUGH, @@ -532,17 +573,966 @@ static const struct tegra_mc_client tegra186_mc_clients[] = {  	},  }; +static const struct tegra186_mc_soc tegra186_mc_soc = { +	.num_clients = ARRAY_SIZE(tegra186_mc_clients), +	.clients = tegra186_mc_clients, +}; +#endif + +#if defined(CONFIG_ARCH_TEGRA_194_SOC) +static const struct tegra186_mc_client tegra194_mc_clients[] = { +	{ +		.name = "ptcr", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x000, +			.security = 0x004, +		}, +	}, { +		.name = "miu7r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x008, +			.security = 0x00c, +		}, +	}, { +		.name = "miu7w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x010, +			.security = 0x014, +		}, +	}, { +		.name = "hdar", +		.sid = TEGRA194_SID_HDA, +		.regs = { +			.override = 0x0a8, +			.security = 0x0ac, +		}, +	}, { +		.name = "host1xdmar", +		.sid = TEGRA194_SID_HOST1X, +		.regs = { +			.override = 0x0b0, +			.security = 0x0b4, +		}, +	}, { +		.name = "nvencsrd", +		.sid = TEGRA194_SID_NVENC, +		.regs = { +			.override = 0x0e0, +			.security = 0x0e4, +		}, +	}, { +		.name = "satar", +		.sid = TEGRA194_SID_SATA, +		.regs = { +			.override = 0x0f8, +			.security = 0x0fc, +		}, +	}, { +		.name = "mpcorer", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x138, +			.security = 0x13c, +		}, +	}, { +		.name = "nvencswr", +		.sid = TEGRA194_SID_NVENC, +		.regs = { +			.override = 0x158, +			.security = 0x15c, +		}, +	}, { +		.name = "hdaw", +		.sid = TEGRA194_SID_HDA, +		.regs = { +			.override = 0x1a8, +			.security = 0x1ac, +		}, +	}, { +		.name = "mpcorew", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x1c8, +			.security = 0x1cc, +		}, +	}, { +		.name = "sataw", +		.sid = TEGRA194_SID_SATA, +		.regs = { +			.override = 0x1e8, +			.security = 0x1ec, +		}, +	}, { +		.name = "ispra", +		.sid = TEGRA194_SID_ISP, +		.regs = { +			.override = 0x220, +			.security = 0x224, +		}, +	}, { +		.name = "ispfalr", +		.sid = TEGRA194_SID_ISP_FALCON, +		.regs = { +			.override = 0x228, +			.security = 0x22c, +		}, +	}, { +		.name = "ispwa", +		.sid = TEGRA194_SID_ISP, +		.regs = { +			.override = 0x230, +			.security = 0x234, +		}, +	}, { +		.name = "ispwb", +		.sid = TEGRA194_SID_ISP, +		.regs = { +			.override = 0x238, +			.security = 0x23c, +		}, +	}, { +		.name = "xusb_hostr", +		.sid = TEGRA194_SID_XUSB_HOST, +		.regs = { +			.override = 0x250, +			.security = 0x254, +		}, +	}, { +		.name = "xusb_hostw", +		.sid = TEGRA194_SID_XUSB_HOST, +		.regs = { +			.override = 0x258, +			.security = 0x25c, +		}, +	}, { +		.name = "xusb_devr", +		.sid = TEGRA194_SID_XUSB_DEV, +		.regs = { +			.override = 0x260, +			.security = 0x264, +		}, +	}, { +		.name = "xusb_devw", +		.sid = TEGRA194_SID_XUSB_DEV, +		.regs = { +			.override = 0x268, +			.security = 0x26c, +		}, +	}, { +		.name = "sdmmcra", +		.sid = TEGRA194_SID_SDMMC1, +		.regs = { +			.override = 0x300, +			.security = 0x304, +		}, +	}, { +		.name = "sdmmcr", +		.sid = TEGRA194_SID_SDMMC3, +		.regs = { +			.override = 0x310, +			.security = 0x314, +		}, +	}, { +		.name = "sdmmcrab", +		.sid = TEGRA194_SID_SDMMC4, +		.regs = { +			.override = 0x318, +			.security = 0x31c, +		}, +	}, { +		.name = "sdmmcwa", +		.sid = TEGRA194_SID_SDMMC1, +		.regs = { +			.override = 0x320, +			.security = 0x324, +		}, +	}, { +		.name = "sdmmcw", +		.sid = TEGRA194_SID_SDMMC3, +		.regs = { +			.override = 0x330, +			.security = 0x334, +		}, +	}, { +		.name = "sdmmcwab", +		.sid = TEGRA194_SID_SDMMC4, +		.regs = { +			.override = 0x338, +			.security = 0x33c, +		}, +	}, { +		.name = "vicsrd", +		.sid = TEGRA194_SID_VIC, +		.regs = { +			.override = 0x360, +			.security = 0x364, +		}, +	}, { +		.name = "vicswr", +		.sid = TEGRA194_SID_VIC, +		.regs = { +			.override = 0x368, +			.security = 0x36c, +		}, +	}, { +		.name = "viw", +		.sid = TEGRA194_SID_VI, +		.regs = { +			.override = 0x390, +			.security = 0x394, +		}, +	}, { +		.name = "nvdecsrd", +		.sid = TEGRA194_SID_NVDEC, +		.regs = { +			.override = 0x3c0, +			.security = 0x3c4, +		}, +	}, { +		.name = "nvdecswr", +		.sid = TEGRA194_SID_NVDEC, +		.regs = { +			.override = 0x3c8, +			.security = 0x3cc, +		}, +	}, { +		.name = "aper", +		.sid = TEGRA194_SID_APE, +		.regs = { +			.override = 0x3c0, +			.security = 0x3c4, +		}, +	}, { +		.name = "apew", +		.sid = TEGRA194_SID_APE, +		.regs = { +			.override = 0x3d0, +			.security = 0x3d4, +		}, +	}, { +		.name = "nvjpgsrd", +		.sid = TEGRA194_SID_NVJPG, +		.regs = { +			.override = 0x3f0, +			.security = 0x3f4, +		}, +	}, { +		.name = "nvjpgswr", +		.sid = TEGRA194_SID_NVJPG, +		.regs = { +			.override = 0x3f0, +			.security = 0x3f4, +		}, +	}, { +		.name = "axiapr", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x410, +			.security = 0x414, +		}, +	}, { +		.name = "axiapw", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x418, +			.security = 0x41c, +		}, +	}, { +		.name = "etrr", +		.sid = TEGRA194_SID_ETR, +		.regs = { +			.override = 0x420, +			.security = 0x424, +		}, +	}, { +		.name = "etrw", +		.sid = TEGRA194_SID_ETR, +		.regs = { +			.override = 0x428, +			.security = 0x42c, +		}, +	}, { +		.name = "axisr", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x460, +			.security = 0x464, +		}, +	}, { +		.name = "axisw", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x468, +			.security = 0x46c, +		}, +	}, { +		.name = "eqosr", +		.sid = TEGRA194_SID_EQOS, +		.regs = { +			.override = 0x470, +			.security = 0x474, +		}, +	}, { +		.name = "eqosw", +		.sid = TEGRA194_SID_EQOS, +		.regs = { +			.override = 0x478, +			.security = 0x47c, +		}, +	}, { +		.name = "ufshcr", +		.sid = TEGRA194_SID_UFSHC, +		.regs = { +			.override = 0x480, +			.security = 0x484, +		}, +	}, { +		.name = "ufshcw", +		.sid = TEGRA194_SID_UFSHC, +		.regs = { +			.override = 0x488, +			.security = 0x48c, +		}, +	}, { +		.name = "nvdisplayr", +		.sid = TEGRA194_SID_NVDISPLAY, +		.regs = { +			.override = 0x490, +			.security = 0x494, +		}, +	}, { +		.name = "bpmpr", +		.sid = TEGRA194_SID_BPMP, +		.regs = { +			.override = 0x498, +			.security = 0x49c, +		}, +	}, { +		.name = "bpmpw", +		.sid = TEGRA194_SID_BPMP, +		.regs = { +			.override = 0x4a0, +			.security = 0x4a4, +		}, +	}, { +		.name = "bpmpdmar", +		.sid = TEGRA194_SID_BPMP, +		.regs = { +			.override = 0x4a8, +			.security = 0x4ac, +		}, +	}, { +		.name = "bpmpdmaw", +		.sid = TEGRA194_SID_BPMP, +		.regs = { +			.override = 0x4b0, +			.security = 0x4b4, +		}, +	}, { +		.name = "aonr", +		.sid = TEGRA194_SID_AON, +		.regs = { +			.override = 0x4b8, +			.security = 0x4bc, +		}, +	}, { +		.name = "aonw", +		.sid = TEGRA194_SID_AON, +		.regs = { +			.override = 0x4c0, +			.security = 0x4c4, +		}, +	}, { +		.name = "aondmar", +		.sid = TEGRA194_SID_AON, +		.regs = { +			.override = 0x4c8, +			.security = 0x4cc, +		}, +	}, { +		.name = "aondmaw", +		.sid = TEGRA194_SID_AON, +		.regs = { +			.override = 0x4d0, +			.security = 0x4d4, +		}, +	}, { +		.name = "scer", +		.sid = TEGRA194_SID_SCE, +		.regs = { +			.override = 0x4d8, +			.security = 0x4dc, +		}, +	}, { +		.name = "scew", +		.sid = TEGRA194_SID_SCE, +		.regs = { +			.override = 0x4e0, +			.security = 0x4e4, +		}, +	}, { +		.name = "scedmar", +		.sid = TEGRA194_SID_SCE, +		.regs = { +			.override = 0x4e8, +			.security = 0x4ec, +		}, +	}, { +		.name = "scedmaw", +		.sid = TEGRA194_SID_SCE, +		.regs = { +			.override = 0x4f0, +			.security = 0x4f4, +		}, +	}, { +		.name = "apedmar", +		.sid = TEGRA194_SID_APE, +		.regs = { +			.override = 0x4f8, +			.security = 0x4fc, +		}, +	}, { +		.name = "apedmaw", +		.sid = TEGRA194_SID_APE, +		.regs = { +			.override = 0x500, +			.security = 0x504, +		}, +	}, { +		.name = "nvdisplayr1", +		.sid = TEGRA194_SID_NVDISPLAY, +		.regs = { +			.override = 0x508, +			.security = 0x50c, +		}, +	}, { +		.name = "vicsrd1", +		.sid = TEGRA194_SID_VIC, +		.regs = { +			.override = 0x510, +			.security = 0x514, +		}, +	}, { +		.name = "nvdecsrd1", +		.sid = TEGRA194_SID_NVDEC, +		.regs = { +			.override = 0x518, +			.security = 0x51c, +		}, +	}, { +		.name = "miu0r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x530, +			.security = 0x534, +		}, +	}, { +		.name = "miu0w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x538, +			.security = 0x53c, +		}, +	}, { +		.name = "miu1r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x540, +			.security = 0x544, +		}, +	}, { +		.name = "miu1w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x548, +			.security = 0x54c, +		}, +	}, { +		.name = "miu2r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x570, +			.security = 0x574, +		}, +	}, { +		.name = "miu2w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x578, +			.security = 0x57c, +		}, +	}, { +		.name = "miu3r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x580, +			.security = 0x584, +		}, +	}, { +		.name = "miu3w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x588, +			.security = 0x58c, +		}, +	}, { +		.name = "miu4r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x590, +			.security = 0x594, +		}, +	}, { +		.name = "miu4w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x598, +			.security = 0x59c, +		}, +	}, { +		.name = "dpmur", +		.sid = TEGRA194_SID_PASSTHROUGH, +		.regs = { +			.override = 0x598, +			.security = 0x59c, +		}, +	}, { +		.name = "vifalr", +		.sid = TEGRA194_SID_VI_FALCON, +		.regs = { +			.override = 0x5e0, +			.security = 0x5e4, +		}, +	}, { +		.name = "vifalw", +		.sid = TEGRA194_SID_VI_FALCON, +		.regs = { +			.override = 0x5e8, +			.security = 0x5ec, +		}, +	}, { +		.name = "dla0rda", +		.sid = TEGRA194_SID_NVDLA0, +		.regs = { +			.override = 0x5f0, +			.security = 0x5f4, +		}, +	}, { +		.name = "dla0falrdb", +		.sid = TEGRA194_SID_NVDLA0, +		.regs = { +			.override = 0x5f8, +			.security = 0x5fc, +		}, +	}, { +		.name = "dla0wra", +		.sid = TEGRA194_SID_NVDLA0, +		.regs = { +			.override = 0x600, +			.security = 0x604, +		}, +	}, { +		.name = "dla0falwrb", +		.sid = TEGRA194_SID_NVDLA0, +		.regs = { +			.override = 0x608, +			.security = 0x60c, +		}, +	}, { +		.name = "dla1rda", +		.sid = TEGRA194_SID_NVDLA1, +		.regs = { +			.override = 0x610, +			.security = 0x614, +		}, +	}, { +		.name = "dla1falrdb", +		.sid = TEGRA194_SID_NVDLA1, +		.regs = { +			.override = 0x618, +			.security = 0x61c, +		}, +	}, { +		.name = "dla1wra", +		.sid = TEGRA194_SID_NVDLA1, +		.regs = { +			.override = 0x620, +			.security = 0x624, +		}, +	}, { +		.name = "dla1falwrb", +		.sid = TEGRA194_SID_NVDLA1, +		.regs = { +			.override = 0x628, +			.security = 0x62c, +		}, +	}, { +		.name = "pva0rda", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x630, +			.security = 0x634, +		}, +	}, { +		.name = "pva0rdb", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x638, +			.security = 0x63c, +		}, +	}, { +		.name = "pva0rdc", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x640, +			.security = 0x644, +		}, +	}, { +		.name = "pva0wra", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x648, +			.security = 0x64c, +		}, +	}, { +		.name = "pva0wrb", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x650, +			.security = 0x654, +		}, +	}, { +		.name = "pva0wrc", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x658, +			.security = 0x65c, +		}, +	}, { +		.name = "pva1rda", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x660, +			.security = 0x664, +		}, +	}, { +		.name = "pva1rdb", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x668, +			.security = 0x66c, +		}, +	}, { +		.name = "pva1rdc", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x670, +			.security = 0x674, +		}, +	}, { +		.name = "pva1wra", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x678, +			.security = 0x67c, +		}, +	}, { +		.name = "pva1wrb", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x680, +			.security = 0x684, +		}, +	}, { +		.name = "pva1wrc", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x688, +			.security = 0x68c, +		}, +	}, { +		.name = "rcer", +		.sid = TEGRA194_SID_RCE, +		.regs = { +			.override = 0x690, +			.security = 0x694, +		}, +	}, { +		.name = "rcew", +		.sid = TEGRA194_SID_RCE, +		.regs = { +			.override = 0x698, +			.security = 0x69c, +		}, +	}, { +		.name = "rcedmar", +		.sid = TEGRA194_SID_RCE, +		.regs = { +			.override = 0x6a0, +			.security = 0x6a4, +		}, +	}, { +		.name = "rcedmaw", +		.sid = TEGRA194_SID_RCE, +		.regs = { +			.override = 0x6a8, +			.security = 0x6ac, +		}, +	}, { +		.name = "nvenc1srd", +		.sid = TEGRA194_SID_NVENC1, +		.regs = { +			.override = 0x6b0, +			.security = 0x6b4, +		}, +	}, { +		.name = "nvenc1swr", +		.sid = TEGRA194_SID_NVENC1, +		.regs = { +			.override = 0x6b8, +			.security = 0x6bc, +		}, +	}, { +		.name = "pcie0r", +		.sid = TEGRA194_SID_PCIE0, +		.regs = { +			.override = 0x6c0, +			.security = 0x6c4, +		}, +	}, { +		.name = "pcie0w", +		.sid = TEGRA194_SID_PCIE0, +		.regs = { +			.override = 0x6c8, +			.security = 0x6cc, +		}, +	}, { +		.name = "pcie1r", +		.sid = TEGRA194_SID_PCIE1, +		.regs = { +			.override = 0x6d0, +			.security = 0x6d4, +		}, +	}, { +		.name = "pcie1w", +		.sid = TEGRA194_SID_PCIE1, +		.regs = { +			.override = 0x6d8, +			.security = 0x6dc, +		}, +	}, { +		.name = "pcie2ar", +		.sid = TEGRA194_SID_PCIE2, +		.regs = { +			.override = 0x6e0, +			.security = 0x6e4, +		}, +	}, { +		.name = "pcie2aw", +		.sid = TEGRA194_SID_PCIE2, +		.regs = { +			.override = 0x6e8, +			.security = 0x6ec, +		}, +	}, { +		.name = "pcie3r", +		.sid = TEGRA194_SID_PCIE3, +		.regs = { +			.override = 0x6f0, +			.security = 0x6f4, +		}, +	}, { +		.name = "pcie3w", +		.sid = TEGRA194_SID_PCIE3, +		.regs = { +			.override = 0x6f8, +			.security = 0x6fc, +		}, +	}, { +		.name = "pcie4r", +		.sid = TEGRA194_SID_PCIE4, +		.regs = { +			.override = 0x700, +			.security = 0x704, +		}, +	}, { +		.name = "pcie4w", +		.sid = TEGRA194_SID_PCIE4, +		.regs = { +			.override = 0x708, +			.security = 0x70c, +		}, +	}, { +		.name = "pcie5r", +		.sid = TEGRA194_SID_PCIE5, +		.regs = { +			.override = 0x710, +			.security = 0x714, +		}, +	}, { +		.name = "pcie5w", +		.sid = TEGRA194_SID_PCIE5, +		.regs = { +			.override = 0x718, +			.security = 0x71c, +		}, +	}, { +		.name = "ispfalw", +		.sid = TEGRA194_SID_ISP_FALCON, +		.regs = { +			.override = 0x720, +			.security = 0x724, +		}, +	}, { +		.name = "dla0rda1", +		.sid = TEGRA194_SID_NVDLA0, +		.regs = { +			.override = 0x748, +			.security = 0x74c, +		}, +	}, { +		.name = "dla1rda1", +		.sid = TEGRA194_SID_NVDLA1, +		.regs = { +			.override = 0x750, +			.security = 0x754, +		}, +	}, { +		.name = "pva0rda1", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x758, +			.security = 0x75c, +		}, +	}, { +		.name = "pva0rdb1", +		.sid = TEGRA194_SID_PVA0, +		.regs = { +			.override = 0x760, +			.security = 0x764, +		}, +	}, { +		.name = "pva1rda1", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x768, +			.security = 0x76c, +		}, +	}, { +		.name = "pva1rdb1", +		.sid = TEGRA194_SID_PVA1, +		.regs = { +			.override = 0x770, +			.security = 0x774, +		}, +	}, { +		.name = "pcie5r1", +		.sid = TEGRA194_SID_PCIE5, +		.regs = { +			.override = 0x778, +			.security = 0x77c, +		}, +	}, { +		.name = "nvencsrd1", +		.sid = TEGRA194_SID_NVENC, +		.regs = { +			.override = 0x780, +			.security = 0x784, +		}, +	}, { +		.name = "nvenc1srd1", +		.sid = TEGRA194_SID_NVENC1, +		.regs = { +			.override = 0x788, +			.security = 0x78c, +		}, +	}, { +		.name = "ispra1", +		.sid = TEGRA194_SID_ISP, +		.regs = { +			.override = 0x790, +			.security = 0x794, +		}, +	}, { +		.name = "pcie0r1", +		.sid = TEGRA194_SID_PCIE0, +		.regs = { +			.override = 0x798, +			.security = 0x79c, +		}, +	}, { +		.name = "nvdec1srd", +		.sid = TEGRA194_SID_NVDEC1, +		.regs = { +			.override = 0x7c8, +			.security = 0x7cc, +		}, +	}, { +		.name = "nvdec1srd1", +		.sid = TEGRA194_SID_NVDEC1, +		.regs = { +			.override = 0x7d0, +			.security = 0x7d4, +		}, +	}, { +		.name = "nvdec1swr", +		.sid = TEGRA194_SID_NVDEC1, +		.regs = { +			.override = 0x7d8, +			.security = 0x7dc, +		}, +	}, { +		.name = "miu5r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x7e0, +			.security = 0x7e4, +		}, +	}, { +		.name = "miu5w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x7e8, +			.security = 0x7ec, +		}, +	}, { +		.name = "miu6r", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x7f0, +			.security = 0x7f4, +		}, +	}, { +		.name = "miu6w", +		.sid = TEGRA194_SID_MIU, +		.regs = { +			.override = 0x7f8, +			.security = 0x7fc, +		}, +	}, +}; + +static const struct tegra186_mc_soc tegra194_mc_soc = { +	.num_clients = ARRAY_SIZE(tegra194_mc_clients), +	.clients = tegra194_mc_clients, +}; +#endif +  static int tegra186_mc_probe(struct platform_device *pdev)  { +	struct tegra186_mc *mc;  	struct resource *res; -	struct tegra_mc *mc; -	unsigned int i; -	int err = 0; +	int err;  	mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL);  	if (!mc)  		return -ENOMEM; +	mc->soc = of_device_get_match_data(&pdev->dev); +  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  	mc->regs = devm_ioremap_resource(&pdev->dev, res);  	if (IS_ERR(mc->regs)) @@ -550,46 +1540,63 @@ static int tegra186_mc_probe(struct platform_device *pdev)  	mc->dev = &pdev->dev; -	for (i = 0; i < ARRAY_SIZE(tegra186_mc_clients); i++) { -		const struct tegra_mc_client *client = &tegra186_mc_clients[i]; -		u32 override, security; - -		override = readl(mc->regs + client->regs.override); -		security = readl(mc->regs + client->regs.security); +	err = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); +	if (err < 0) +		return err; -		dev_dbg(&pdev->dev, "client %s: override: %x security: %x\n", -			client->name, override, security); - -		dev_dbg(&pdev->dev, "setting SID %u for %s\n", client->sid, -			client->name); -		writel(client->sid, mc->regs + client->regs.override); +	platform_set_drvdata(pdev, mc); +	tegra186_mc_program_sid(mc); -		override = readl(mc->regs + client->regs.override); -		security = readl(mc->regs + client->regs.security); +	return 0; +} -		dev_dbg(&pdev->dev, "client %s: override: %x security: %x\n", -			client->name, override, security); -	} +static int tegra186_mc_remove(struct platform_device *pdev) +{ +	struct tegra186_mc *mc = platform_get_drvdata(pdev); -	platform_set_drvdata(pdev, mc); +	of_platform_depopulate(mc->dev); -	return err; +	return 0;  }  static const struct of_device_id tegra186_mc_of_match[] = { -	{ .compatible = "nvidia,tegra186-mc", }, +#if defined(CONFIG_ARCH_TEGRA_186_SOC) +	{ .compatible = "nvidia,tegra186-mc", .data = &tegra186_mc_soc }, +#endif +#if defined(CONFIG_ARCH_TEGRA_194_SOC) +	{ .compatible = "nvidia,tegra194-mc", .data = &tegra194_mc_soc }, +#endif  	{ /* sentinel */ }  };  MODULE_DEVICE_TABLE(of, tegra186_mc_of_match); +static int tegra186_mc_suspend(struct device *dev) +{ +	return 0; +} + +static int tegra186_mc_resume(struct device *dev) +{ +	struct tegra186_mc *mc = dev_get_drvdata(dev); + +	tegra186_mc_program_sid(mc); + +	return 0; +} + +static const struct dev_pm_ops tegra186_mc_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(tegra186_mc_suspend, tegra186_mc_resume) +}; +  static struct platform_driver tegra186_mc_driver = {  	.driver = {  		.name = "tegra186-mc",  		.of_match_table = tegra186_mc_of_match, +		.pm = &tegra186_mc_pm_ops,  		.suppress_bind_attrs = true,  	}, -	.prevent_deferred_probe = true,  	.probe = tegra186_mc_probe, +	.remove = tegra186_mc_remove,  };  module_platform_driver(tegra186_mc_driver); diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c index 1b23b1c34476..8ae474d9bfb9 100644 --- a/drivers/memory/tegra/tegra20-emc.c +++ b/drivers/memory/tegra/tegra20-emc.c @@ -8,6 +8,7 @@  #include <linux/clk.h>  #include <linux/clk/tegra.h>  #include <linux/completion.h> +#include <linux/debugfs.h>  #include <linux/err.h>  #include <linux/interrupt.h>  #include <linux/io.h> @@ -150,6 +151,12 @@ struct tegra_emc {  	struct emc_timing *timings;  	unsigned int num_timings; + +	struct { +		struct dentry *root; +		unsigned long min_rate; +		unsigned long max_rate; +	} debugfs;  };  static irqreturn_t tegra_emc_isr(int irq, void *data) @@ -478,6 +485,171 @@ static long emc_round_rate(unsigned long rate,  	return timing->rate;  } +/* + * debugfs interface + * + * The memory controller driver exposes some files in debugfs that can be used + * to control the EMC frequency. The top-level directory can be found here: + * + *   /sys/kernel/debug/emc + * + * It contains the following files: + * + *   - available_rates: This file contains a list of valid, space-separated + *     EMC frequencies. + * + *   - min_rate: Writing a value to this file sets the given frequency as the + *       floor of the permitted range. If this is higher than the currently + *       configured EMC frequency, this will cause the frequency to be + *       increased so that it stays within the valid range. + * + *   - max_rate: Similarily to the min_rate file, writing a value to this file + *       sets the given frequency as the ceiling of the permitted range. If + *       the value is lower than the currently configured EMC frequency, this + *       will cause the frequency to be decreased so that it stays within the + *       valid range. + */ + +static bool tegra_emc_validate_rate(struct tegra_emc *emc, unsigned long rate) +{ +	unsigned int i; + +	for (i = 0; i < emc->num_timings; i++) +		if (rate == emc->timings[i].rate) +			return true; + +	return false; +} + +static int tegra_emc_debug_available_rates_show(struct seq_file *s, void *data) +{ +	struct tegra_emc *emc = s->private; +	const char *prefix = ""; +	unsigned int i; + +	for (i = 0; i < emc->num_timings; i++) { +		seq_printf(s, "%s%lu", prefix, emc->timings[i].rate); +		prefix = " "; +	} + +	seq_puts(s, "\n"); + +	return 0; +} + +static int tegra_emc_debug_available_rates_open(struct inode *inode, +						struct file *file) +{ +	return single_open(file, tegra_emc_debug_available_rates_show, +			   inode->i_private); +} + +static const struct file_operations tegra_emc_debug_available_rates_fops = { +	.open = tegra_emc_debug_available_rates_open, +	.read = seq_read, +	.llseek = seq_lseek, +	.release = single_release, +}; + +static int tegra_emc_debug_min_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.min_rate; + +	return 0; +} + +static int tegra_emc_debug_min_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_min_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.min_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_min_rate_fops, +			tegra_emc_debug_min_rate_get, +			tegra_emc_debug_min_rate_set, "%llu\n"); + +static int tegra_emc_debug_max_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.max_rate; + +	return 0; +} + +static int tegra_emc_debug_max_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_max_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.max_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_max_rate_fops, +			tegra_emc_debug_max_rate_get, +			tegra_emc_debug_max_rate_set, "%llu\n"); + +static void tegra_emc_debugfs_init(struct tegra_emc *emc) +{ +	struct device *dev = emc->dev; +	unsigned int i; +	int err; + +	emc->debugfs.min_rate = ULONG_MAX; +	emc->debugfs.max_rate = 0; + +	for (i = 0; i < emc->num_timings; i++) { +		if (emc->timings[i].rate < emc->debugfs.min_rate) +			emc->debugfs.min_rate = emc->timings[i].rate; + +		if (emc->timings[i].rate > emc->debugfs.max_rate) +			emc->debugfs.max_rate = emc->timings[i].rate; +	} + +	err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, +				 emc->debugfs.max_rate); +	if (err < 0) { +		dev_err(dev, "failed to set rate range [%lu-%lu] for %pC\n", +			emc->debugfs.min_rate, emc->debugfs.max_rate, +			emc->clk); +	} + +	emc->debugfs.root = debugfs_create_dir("emc", NULL); +	if (!emc->debugfs.root) { +		dev_err(emc->dev, "failed to create debugfs directory\n"); +		return; +	} + +	debugfs_create_file("available_rates", S_IRUGO, emc->debugfs.root, +			    emc, &tegra_emc_debug_available_rates_fops); +	debugfs_create_file("min_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_min_rate_fops); +	debugfs_create_file("max_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_max_rate_fops); +} +  static int tegra_emc_probe(struct platform_device *pdev)  {  	struct device_node *np; @@ -550,6 +722,9 @@ static int tegra_emc_probe(struct platform_device *pdev)  		goto unset_cb;  	} +	platform_set_drvdata(pdev, emc); +	tegra_emc_debugfs_init(emc); +  	return 0;  unset_cb: diff --git a/drivers/memory/tegra/tegra210.c b/drivers/memory/tegra/tegra210.c index b420268173fc..cc0482434c75 100644 --- a/drivers/memory/tegra/tegra210.c +++ b/drivers/memory/tegra/tegra210.c @@ -436,7 +436,7 @@ static const struct tegra_mc_client tegra210_mc_clients[] = {  			.reg = 0x37c,  			.shift = 0,  			.mask = 0xff, -			.def = 0x39, +			.def = 0x7a,  		},  	}, {  		.id = 0x4b, diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c index 0b6a5e451ea3..e3efd9529506 100644 --- a/drivers/memory/tegra/tegra30-emc.c +++ b/drivers/memory/tegra/tegra30-emc.c @@ -12,6 +12,7 @@  #include <linux/clk.h>  #include <linux/clk/tegra.h>  #include <linux/completion.h> +#include <linux/debugfs.h>  #include <linux/delay.h>  #include <linux/err.h>  #include <linux/interrupt.h> @@ -331,7 +332,9 @@ struct tegra_emc {  	struct clk *clk;  	void __iomem *regs;  	unsigned int irq; +	bool bad_state; +	struct emc_timing *new_timing;  	struct emc_timing *timings;  	unsigned int num_timings; @@ -345,10 +348,74 @@ struct tegra_emc {  	bool vref_cal_toggle : 1;  	bool zcal_long : 1;  	bool dll_on : 1; -	bool prepared : 1; -	bool bad_state : 1; + +	struct { +		struct dentry *root; +		unsigned long min_rate; +		unsigned long max_rate; +	} debugfs;  }; +static int emc_seq_update_timing(struct tegra_emc *emc) +{ +	u32 val; +	int err; + +	writel_relaxed(EMC_TIMING_UPDATE, emc->regs + EMC_TIMING_CONTROL); + +	err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_STATUS, val, +				!(val & EMC_STATUS_TIMING_UPDATE_STALLED), +				1, 200); +	if (err) { +		dev_err(emc->dev, "failed to update timing: %d\n", err); +		return err; +	} + +	return 0; +} + +static void emc_complete_clk_change(struct tegra_emc *emc) +{ +	struct emc_timing *timing = emc->new_timing; +	unsigned int dram_num; +	bool failed = false; +	int err; + +	/* re-enable auto-refresh */ +	dram_num = tegra_mc_get_emem_device_count(emc->mc); +	writel_relaxed(EMC_REFCTRL_ENABLE_ALL(dram_num), +		       emc->regs + EMC_REFCTRL); + +	/* restore auto-calibration */ +	if (emc->vref_cal_toggle) +		writel_relaxed(timing->emc_auto_cal_interval, +			       emc->regs + EMC_AUTO_CAL_INTERVAL); + +	/* restore dynamic self-refresh */ +	if (timing->emc_cfg_dyn_self_ref) { +		emc->emc_cfg |= EMC_CFG_DYN_SREF_ENABLE; +		writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG); +	} + +	/* set number of clocks to wait after each ZQ command */ +	if (emc->zcal_long) +		writel_relaxed(timing->emc_zcal_cnt_long, +			       emc->regs + EMC_ZCAL_WAIT_CNT); + +	/* wait for writes to settle */ +	udelay(2); + +	/* update restored timing */ +	err = emc_seq_update_timing(emc); +	if (err) +		failed = true; + +	/* restore early ACK */ +	mc_writel(emc->mc, emc->mc_override, MC_EMEM_ARB_OVERRIDE); + +	WRITE_ONCE(emc->bad_state, failed); +} +  static irqreturn_t tegra_emc_isr(int irq, void *data)  {  	struct tegra_emc *emc = data; @@ -359,10 +426,6 @@ static irqreturn_t tegra_emc_isr(int irq, void *data)  	if (!status)  		return IRQ_NONE; -	/* notify about EMC-CAR handshake completion */ -	if (status & EMC_CLKCHANGE_COMPLETE_INT) -		complete(&emc->clk_handshake_complete); -  	/* notify about HW problem */  	if (status & EMC_REFRESH_OVERFLOW_INT)  		dev_err_ratelimited(emc->dev, @@ -371,6 +434,18 @@ static irqreturn_t tegra_emc_isr(int irq, void *data)  	/* clear interrupts */  	writel_relaxed(status, emc->regs + EMC_INTSTATUS); +	/* notify about EMC-CAR handshake completion */ +	if (status & EMC_CLKCHANGE_COMPLETE_INT) { +		if (completion_done(&emc->clk_handshake_complete)) { +			dev_err_ratelimited(emc->dev, +					    "bogus handshake interrupt\n"); +			return IRQ_NONE; +		} + +		emc_complete_clk_change(emc); +		complete(&emc->clk_handshake_complete); +	} +  	return IRQ_HANDLED;  } @@ -438,24 +513,6 @@ static bool emc_dqs_preset(struct tegra_emc *emc, struct emc_timing *timing,  	return preset;  } -static int emc_seq_update_timing(struct tegra_emc *emc) -{ -	u32 val; -	int err; - -	writel_relaxed(EMC_TIMING_UPDATE, emc->regs + EMC_TIMING_CONTROL); - -	err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_STATUS, val, -				!(val & EMC_STATUS_TIMING_UPDATE_STALLED), -				1, 200); -	if (err) { -		dev_err(emc->dev, "failed to update timing: %d\n", err); -		return err; -	} - -	return 0; -} -  static int emc_prepare_mc_clk_cfg(struct tegra_emc *emc, unsigned long rate)  {  	struct tegra_mc *mc = emc->mc; @@ -582,8 +639,7 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)  				!(val & EMC_AUTO_CAL_STATUS_ACTIVE), 1, 300);  			if (err) {  				dev_err(emc->dev, -					"failed to disable auto-cal: %d\n", -					err); +					"auto-cal finish timeout: %d\n", err);  				return err;  			} @@ -621,9 +677,6 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)  		writel_relaxed(val, emc->regs + EMC_MRS_WAIT_CNT);  	} -	/* disable interrupt since read access is prohibited after stalling */ -	disable_irq(emc->irq); -  	/* this read also completes the writes */  	val = readl_relaxed(emc->regs + EMC_SEL_DPD_CTRL); @@ -739,20 +792,18 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)  				       emc->regs + EMC_ZQ_CAL);  	} -	/* re-enable auto-refresh */ -	writel_relaxed(EMC_REFCTRL_ENABLE_ALL(dram_num), -		       emc->regs + EMC_REFCTRL); -  	/* flow control marker 3 */  	writel_relaxed(0x1, emc->regs + EMC_UNSTALL_RW_AFTER_CLKCHANGE); -	reinit_completion(&emc->clk_handshake_complete); +	/* +	 * Read and discard an arbitrary MC register (Note: EMC registers +	 * can't be used) to ensure the register writes are completed. +	 */ +	mc_readl(emc->mc, MC_EMEM_ARB_OVERRIDE); -	/* interrupt can be re-enabled now */ -	enable_irq(emc->irq); +	reinit_completion(&emc->clk_handshake_complete); -	emc->bad_state = false; -	emc->prepared = true; +	emc->new_timing = timing;  	return 0;  } @@ -760,52 +811,25 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)  static int emc_complete_timing_change(struct tegra_emc *emc,  				      unsigned long rate)  { -	struct emc_timing *timing = emc_find_timing(emc, rate);  	unsigned long timeout; -	int ret;  	timeout = wait_for_completion_timeout(&emc->clk_handshake_complete,  					      msecs_to_jiffies(100));  	if (timeout == 0) {  		dev_err(emc->dev, "emc-car handshake failed\n"); -		emc->bad_state = true;  		return -EIO;  	} -	/* restore auto-calibration */ -	if (emc->vref_cal_toggle) -		writel_relaxed(timing->emc_auto_cal_interval, -			       emc->regs + EMC_AUTO_CAL_INTERVAL); - -	/* restore dynamic self-refresh */ -	if (timing->emc_cfg_dyn_self_ref) { -		emc->emc_cfg |= EMC_CFG_DYN_SREF_ENABLE; -		writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG); -	} - -	/* set number of clocks to wait after each ZQ command */ -	if (emc->zcal_long) -		writel_relaxed(timing->emc_zcal_cnt_long, -			       emc->regs + EMC_ZCAL_WAIT_CNT); - -	udelay(2); -	/* update restored timing */ -	ret = emc_seq_update_timing(emc); -	if (ret) -		emc->bad_state = true; - -	/* restore early ACK */ -	mc_writel(emc->mc, emc->mc_override, MC_EMEM_ARB_OVERRIDE); - -	emc->prepared = false; +	if (READ_ONCE(emc->bad_state)) +		return -EIO; -	return ret; +	return 0;  }  static int emc_unprepare_timing_change(struct tegra_emc *emc,  				       unsigned long rate)  { -	if (emc->prepared && !emc->bad_state) { +	if (!emc->bad_state) {  		/* shouldn't ever happen in practice */  		dev_err(emc->dev, "timing configuration can't be reverted\n");  		emc->bad_state = true; @@ -823,7 +847,13 @@ static int emc_clk_change_notify(struct notifier_block *nb,  	switch (msg) {  	case PRE_RATE_CHANGE: +		/* +		 * Disable interrupt since read accesses are prohibited after +		 * stalling. +		 */ +		disable_irq(emc->irq);  		err = emc_prepare_timing_change(emc, cnd->new_rate); +		enable_irq(emc->irq);  		break;  	case ABORT_RATE_CHANGE: @@ -1083,6 +1113,171 @@ static long emc_round_rate(unsigned long rate,  	return timing->rate;  } +/* + * debugfs interface + * + * The memory controller driver exposes some files in debugfs that can be used + * to control the EMC frequency. The top-level directory can be found here: + * + *   /sys/kernel/debug/emc + * + * It contains the following files: + * + *   - available_rates: This file contains a list of valid, space-separated + *     EMC frequencies. + * + *   - min_rate: Writing a value to this file sets the given frequency as the + *       floor of the permitted range. If this is higher than the currently + *       configured EMC frequency, this will cause the frequency to be + *       increased so that it stays within the valid range. + * + *   - max_rate: Similarily to the min_rate file, writing a value to this file + *       sets the given frequency as the ceiling of the permitted range. If + *       the value is lower than the currently configured EMC frequency, this + *       will cause the frequency to be decreased so that it stays within the + *       valid range. + */ + +static bool tegra_emc_validate_rate(struct tegra_emc *emc, unsigned long rate) +{ +	unsigned int i; + +	for (i = 0; i < emc->num_timings; i++) +		if (rate == emc->timings[i].rate) +			return true; + +	return false; +} + +static int tegra_emc_debug_available_rates_show(struct seq_file *s, void *data) +{ +	struct tegra_emc *emc = s->private; +	const char *prefix = ""; +	unsigned int i; + +	for (i = 0; i < emc->num_timings; i++) { +		seq_printf(s, "%s%lu", prefix, emc->timings[i].rate); +		prefix = " "; +	} + +	seq_puts(s, "\n"); + +	return 0; +} + +static int tegra_emc_debug_available_rates_open(struct inode *inode, +						struct file *file) +{ +	return single_open(file, tegra_emc_debug_available_rates_show, +			   inode->i_private); +} + +static const struct file_operations tegra_emc_debug_available_rates_fops = { +	.open = tegra_emc_debug_available_rates_open, +	.read = seq_read, +	.llseek = seq_lseek, +	.release = single_release, +}; + +static int tegra_emc_debug_min_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.min_rate; + +	return 0; +} + +static int tegra_emc_debug_min_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_min_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.min_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_min_rate_fops, +			tegra_emc_debug_min_rate_get, +			tegra_emc_debug_min_rate_set, "%llu\n"); + +static int tegra_emc_debug_max_rate_get(void *data, u64 *rate) +{ +	struct tegra_emc *emc = data; + +	*rate = emc->debugfs.max_rate; + +	return 0; +} + +static int tegra_emc_debug_max_rate_set(void *data, u64 rate) +{ +	struct tegra_emc *emc = data; +	int err; + +	if (!tegra_emc_validate_rate(emc, rate)) +		return -EINVAL; + +	err = clk_set_max_rate(emc->clk, rate); +	if (err < 0) +		return err; + +	emc->debugfs.max_rate = rate; + +	return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_max_rate_fops, +			tegra_emc_debug_max_rate_get, +			tegra_emc_debug_max_rate_set, "%llu\n"); + +static void tegra_emc_debugfs_init(struct tegra_emc *emc) +{ +	struct device *dev = emc->dev; +	unsigned int i; +	int err; + +	emc->debugfs.min_rate = ULONG_MAX; +	emc->debugfs.max_rate = 0; + +	for (i = 0; i < emc->num_timings; i++) { +		if (emc->timings[i].rate < emc->debugfs.min_rate) +			emc->debugfs.min_rate = emc->timings[i].rate; + +		if (emc->timings[i].rate > emc->debugfs.max_rate) +			emc->debugfs.max_rate = emc->timings[i].rate; +	} + +	err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, +				 emc->debugfs.max_rate); +	if (err < 0) { +		dev_err(dev, "failed to set rate range [%lu-%lu] for %pC\n", +			emc->debugfs.min_rate, emc->debugfs.max_rate, +			emc->clk); +	} + +	emc->debugfs.root = debugfs_create_dir("emc", NULL); +	if (!emc->debugfs.root) { +		dev_err(emc->dev, "failed to create debugfs directory\n"); +		return; +	} + +	debugfs_create_file("available_rates", S_IRUGO, emc->debugfs.root, +			    emc, &tegra_emc_debug_available_rates_fops); +	debugfs_create_file("min_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_min_rate_fops); +	debugfs_create_file("max_rate", S_IRUGO | S_IWUSR, emc->debugfs.root, +			    emc, &tegra_emc_debug_max_rate_fops); +} +  static int tegra_emc_probe(struct platform_device *pdev)  {  	struct platform_device *mc; @@ -1169,6 +1364,7 @@ static int tegra_emc_probe(struct platform_device *pdev)  	}  	platform_set_drvdata(pdev, emc); +	tegra_emc_debugfs_init(emc);  	return 0; @@ -1181,13 +1377,17 @@ unset_cb:  static int tegra_emc_suspend(struct device *dev)  {  	struct tegra_emc *emc = dev_get_drvdata(dev); +	int err; -	/* -	 * Suspending in a bad state will hang machine. The "prepared" var -	 * shall be always false here unless it's a kernel bug that caused -	 * suspending in a wrong order. -	 */ -	if (WARN_ON(emc->prepared) || emc->bad_state) +	/* take exclusive control over the clock's rate */ +	err = clk_rate_exclusive_get(emc->clk); +	if (err) { +		dev_err(emc->dev, "failed to acquire clk: %d\n", err); +		return err; +	} + +	/* suspending in a bad state will hang machine */ +	if (WARN(emc->bad_state, "hardware in a bad state\n"))  		return -EINVAL;  	emc->bad_state = true; @@ -1202,6 +1402,8 @@ static int tegra_emc_resume(struct device *dev)  	emc_setup_hw(emc);  	emc->bad_state = false; +	clk_rate_exclusive_put(emc->clk); +  	return 0;  } | 
