Commit 6b9acd93 authored by Thierry Reding's avatar Thierry Reding

memory: tegra: Refashion EMC debugfs interface on Tegra124

The current debugfs interface is only partially useful. While it allows
listing supported frequencies and testing individual clock rates, it is
limited in that it can't be used to restrict the range of frequencies
that the driver is allowed to set. This is something we may want to use
to test adaptive scaling once that's implemented.
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent e42617b8
...@@ -467,12 +467,20 @@ struct tegra_emc { ...@@ -467,12 +467,20 @@ struct tegra_emc {
void __iomem *regs; void __iomem *regs;
struct clk *clk;
enum emc_dram_type dram_type; enum emc_dram_type dram_type;
unsigned int dram_num; unsigned int dram_num;
struct emc_timing last_timing; struct emc_timing last_timing;
struct emc_timing *timings; struct emc_timing *timings;
unsigned int num_timings; unsigned int num_timings;
struct {
struct dentry *root;
unsigned long min_rate;
unsigned long max_rate;
} debugfs;
}; };
/* Timing change sequence functions */ /* Timing change sequence functions */
...@@ -998,38 +1006,51 @@ tegra_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code) ...@@ -998,38 +1006,51 @@ tegra_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code)
return NULL; 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; unsigned int i;
*rate = clk_get_rate(c);
return 0;
}
static int emc_debug_rate_set(void *data, u64 rate) for (i = 0; i < emc->num_timings; i++)
{ if (rate == emc->timings[i].rate)
struct clk *c = data; return true;
return clk_set_rate(c, rate); return false;
} }
DEFINE_SIMPLE_ATTRIBUTE(emc_debug_rate_fops, emc_debug_rate_get, static int tegra_emc_debug_available_rates_show(struct seq_file *s,
emc_debug_rate_set, "%lld\n"); void *data)
static int emc_debug_supported_rates_show(struct seq_file *s, void *data)
{ {
struct tegra_emc *emc = s->private; struct tegra_emc *emc = s->private;
const char *prefix = ""; const char *prefix = "";
unsigned int i; unsigned int i;
for (i = 0; i < emc->num_timings; i++) { for (i = 0; i < emc->num_timings; i++) {
struct emc_timing *timing = &emc->timings[i]; seq_printf(s, "%s%lu", prefix, emc->timings[i].rate);
seq_printf(s, "%s%lu", prefix, timing->rate);
prefix = " "; prefix = " ";
} }
...@@ -1038,46 +1059,126 @@ static int emc_debug_supported_rates_show(struct seq_file *s, void *data) ...@@ -1038,46 +1059,126 @@ static int emc_debug_supported_rates_show(struct seq_file *s, void *data)
return 0; return 0;
} }
static int emc_debug_supported_rates_open(struct inode *inode, static int tegra_emc_debug_available_rates_open(struct inode *inode,
struct file *file) 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); inode->i_private);
} }
static const struct file_operations emc_debug_supported_rates_fops = { static const struct file_operations tegra_emc_debug_available_rates_fops = {
.open = emc_debug_supported_rates_open, .open = tegra_emc_debug_available_rates_open,
.read = seq_read, .read = seq_read,
.llseek = seq_lseek, .llseek = seq_lseek,
.release = single_release, .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) static void emc_debugfs_init(struct device *dev, struct tegra_emc *emc)
{ {
struct dentry *root, *file; unsigned int i;
struct clk *clk; int err;
root = debugfs_create_dir("emc", NULL); emc->clk = devm_clk_get(dev, "emc");
if (!root) { if (IS_ERR(emc->clk)) {
dev_err(dev, "failed to create debugfs directory\n"); if (PTR_ERR(emc->clk) != -ENODEV) {
dev_err(dev, "failed to get EMC clock: %ld\n",
PTR_ERR(emc->clk));
return; return;
} }
}
clk = clk_get_sys("tegra-clk-debug", "emc"); emc->debugfs.min_rate = ULONG_MAX;
if (IS_ERR(clk)) { emc->debugfs.max_rate = 0;
dev_err(dev, "failed to get debug clock: %ld\n", PTR_ERR(clk));
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; return;
} }
file = debugfs_create_file("rate", S_IRUGO | S_IWUSR, root, clk, emc->debugfs.root = debugfs_create_dir("emc", NULL);
&emc_debug_rate_fops); if (!emc->debugfs.root) {
if (!file) dev_err(dev, "failed to create debugfs directory\n");
dev_err(dev, "failed to create debugfs entry\n"); return;
}
file = debugfs_create_file("supported_rates", S_IRUGO, root, emc, debugfs_create_file("available_rates", S_IRUGO, emc->debugfs.root, emc,
&emc_debug_supported_rates_fops); &tegra_emc_debug_available_rates_fops);
if (!file) debugfs_create_file("min_rate", S_IRUGO | S_IWUSR, emc->debugfs.root,
dev_err(dev, "failed to create debugfs entry\n"); 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) static int tegra_emc_probe(struct platform_device *pdev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment