summaryrefslogtreecommitdiff
path: root/drivers/memory/tegra/tegra124-emc.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-02-09 01:04:19 +0300
committerLinus Torvalds <torvalds@linux-foundation.org>2020-02-09 01:04:19 +0300
commiteab3540562fb44f830e09492374fcc69a283ce47 (patch)
treec8b4ce22b246e8c284787c7c8487f61585c493f2 /drivers/memory/tegra/tegra124-emc.c
parent1afa9c3b7c9bdcb562e2afe9f58cc99d0b071cdc (diff)
parent88b4750151a2739761bb1af7fedeae1ff5d9aed9 (diff)
downloadlinux-eab3540562fb44f830e09492374fcc69a283ce47.tar.xz
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
Pull ARM SoC-related driver updates from Olof Johansson: "Various driver updates for platforms: - Nvidia: Fuse support for Tegra194, continued memory controller pieces for Tegra30 - NXP/FSL: Refactorings of QuickEngine drivers to support ARM/ARM64/PPC - NXP/FSL: i.MX8MP SoC driver pieces - TI Keystone: ring accelerator driver - Qualcomm: SCM driver cleanup/refactoring + support for new SoCs. - Xilinx ZynqMP: feature checking interface for firmware. Mailbox communication for power management - Overall support patch set for cpuidle on more complex hierarchies (PSCI-based) and misc cleanups, refactorings of Marvell, TI, other platforms" * tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (166 commits) drivers: soc: xilinx: Use mailbox IPI callback dt-bindings: power: reset: xilinx: Add bindings for ipi mailbox drivers: soc: ti: knav_qmss_queue: Pass lockdep expression to RCU lists MAINTAINERS: Add brcmstb PCIe controller entry soc/tegra: fuse: Unmap registers once they are not needed anymore soc/tegra: fuse: Correct straps' address for older Tegra124 device trees soc/tegra: fuse: Warn if straps are not ready soc/tegra: fuse: Cache values of straps and Chip ID registers memory: tegra30-emc: Correct error message for timed out auto calibration memory: tegra30-emc: Firm up hardware programming sequence memory: tegra30-emc: Firm up suspend/resume sequence soc/tegra: regulators: Do nothing if voltage is unchanged memory: tegra: Correct reset value of xusb_hostr soc/tegra: fuse: Add APB DMA dependency for Tegra20 bus: tegra-aconnect: Remove PM_CLK dependency dt-bindings: mediatek: add MT6765 power dt-bindings soc: mediatek: cmdq: delete not used define memory: tegra: Add support for the Tegra194 memory controller memory: tegra: Only include support for enabled SoCs memory: tegra: Support DVFS on Tegra186 and later ...
Diffstat (limited to 'drivers/memory/tegra/tegra124-emc.c')
-rw-r--r--drivers/memory/tegra/tegra124-emc.c185
1 files changed, 143 insertions, 42 deletions
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)