summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2013-12-20 22:01:00 -0800
committerHarry Hong <hhong@nvidia.com>2014-01-27 22:14:32 -0800
commitfb4dad5a785476fe8f9178b0bcb58b1f0324c87f (patch)
treeaa1d08a9c0442d4d9b88073e41ef7b9957c0a480
parent9e21b20941335f71f6c7c762cea876ee94c6d9fa (diff)
ARM: tegra: dvfs: Add interface to set fmax at vmin
Added interface to specify clock fmax/vmin limits at run-time. Calling this interface updates frequencies in the respective DVFS table to be consistent with the new limits (voltage ladder is preserved): - for voltages below new vmin, the respective frequencies are shifted below new fmax to the levels already present in the table; if the 1st table entry has frequency above new fmax, all entries below vmin are filled in with 1kHz (min rate used in DVFS tables). - for voltages above new vmin, the respective frequencies are set at or above new fmax (not necessarily present in DVFS table before) - if new vmin is already in the table the respective frequency is set to new fmax (not necessarily present in DVFS table before) Since, such update may result in changing voltage requirement up at the same clock frequency, the interface can be called only for clocks that are allowed to override core voltage (SDMMC on tegra platforms), and only if core voltage is already overridden to the level higher than new vmin. Bug 1423423 Reviewed-on: http://git-master/r/348351 (cherry picked from commit 1dc582f4488beeaea7e859496034a25300b1cb0b) Signed-off-by: Alex Frid <afrid@nvidia.com> Change-Id: I4f61ea6e3f8b6792ed058509339e16bff1947104 Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com> Reviewed-on: http://git-master/r/356521 Reviewed-by: Harry Hong <hhong@nvidia.com> Tested-by: Harry Hong <hhong@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/clock.c40
-rw-r--r--arch/arm/mach-tegra/dvfs.c72
-rw-r--r--arch/arm/mach-tegra/include/mach/clk.h1
3 files changed, 112 insertions, 1 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c
index af76e7171af1..12f9e8b33fc2 100644
--- a/arch/arm/mach-tegra/clock.c
+++ b/arch/arm/mach-tegra/clock.c
@@ -5,7 +5,7 @@
* Author:
* Colin Cross <ccross@google.com>
*
- * Copyright (c) 2019-2013, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2010-2013, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -1576,6 +1576,37 @@ static const struct file_operations possible_rates_fops = {
.release = single_release,
};
+static ssize_t fmax_at_vmin_write(struct file *file,
+ const char __user *userbuf, size_t count, loff_t *ppos)
+{
+ struct clk *c = file->f_path.dentry->d_inode->i_private;
+ unsigned long f_max;
+ int v_min;
+ char buf[32];
+
+ if (sizeof(buf) <= count)
+ return -EINVAL;
+
+ if (copy_from_user(buf, userbuf, count))
+ return -EFAULT;
+
+ /* terminate buffer and trim - white spaces may be appended
+ * at the end when invoked from shell command line */
+ buf[count] = '\0';
+ strim(buf);
+
+ if (sscanf(buf, "%lu_at_%d", &f_max, &v_min) != 2)
+ return -EINVAL;
+
+ tegra_dvfs_set_fmax_at_vmin(c, f_max, v_min);
+
+ return count;
+}
+
+static const struct file_operations fmax_at_vmin_fops = {
+ .write = fmax_at_vmin_write,
+};
+
static int clk_debugfs_register_one(struct clk *c)
{
struct dentry *d;
@@ -1636,6 +1667,13 @@ static int clk_debugfs_register_one(struct clk *c)
goto err_out;
}
+ if (c->dvfs && c->dvfs->can_override) {
+ d = debugfs_create_file("fmax_at_vmin", S_IWUSR, c->dent,
+ c, &fmax_at_vmin_fops);
+ if (!d)
+ goto err_out;
+ }
+
return 0;
err_out:
diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c
index 02d0dc9956cd..bd49a32e1a48 100644
--- a/arch/arm/mach-tegra/dvfs.c
+++ b/arch/arm/mach-tegra/dvfs.c
@@ -675,12 +675,74 @@ out:
mutex_unlock(&rail_override_lock);
return ret;
}
+
+static int dvfs_set_fmax_at_vmin(struct clk *c, unsigned long f_max, int v_min)
+{
+ int i, ret = 0;
+ struct dvfs *d = c->dvfs;
+ unsigned long f_min = 1000; /* 1kHz min rate in DVFS tables */
+
+ mutex_lock(&rail_override_lock);
+ mutex_lock(&dvfs_lock);
+
+ if (v_min > d->dvfs_rail->override_millivolts) {
+ pr_err("%s: new %s vmin %dmV is above override voltage %dmV\n",
+ __func__, c->name, v_min,
+ d->dvfs_rail->override_millivolts);
+ ret = -EPERM;
+ goto out;
+ }
+
+ if (v_min >= d->max_millivolts) {
+ pr_err("%s: new %s vmin %dmV is at/above max voltage %dmV\n",
+ __func__, c->name, v_min, d->max_millivolts);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * dvfs table update:
+ * - for voltages below new v_min the respective frequencies are shifted
+ * below new f_max to the levels already present in the table; if the
+ * 1st table entry has frequency above new fmax, all entries below v_min
+ * are filled in with 1kHz (min rate used in DVFS tables).
+ * - for voltages above new v_min, the respective frequencies are
+ * increased to at least new f_max
+ * - if new v_min is already in the table set the respective frequency
+ * to new f_max
+ */
+ for (i = 0; i < d->num_freqs; i++) {
+ int mv = d->millivolts[i];
+ unsigned long f = d->freqs[i];
+
+ if (mv < v_min) {
+ if (d->freqs[i] >= f_max)
+ d->freqs[i] = i ? d->freqs[i-1] : f_min;
+ } else if (mv > v_min) {
+ d->freqs[i] = max(f, f_max);
+ } else {
+ d->freqs[i] = f_max;
+ }
+ ret = __tegra_dvfs_set_rate(d, d->cur_rate);
+ }
+out:
+ mutex_unlock(&dvfs_lock);
+ mutex_unlock(&rail_override_lock);
+
+ return ret;
+}
#else
static int dvfs_override_core_voltage(int override_mv)
{
pr_err("%s: vdd core override is not supported\n", __func__);
return -ENOSYS;
}
+
+static int dvfs_set_fmax_at_vmin(struct clk *c, unsigned long f_max, int v_min)
+{
+ pr_err("%s: vdd core override is not supported\n", __func__);
+ return -ENOSYS;
+}
#endif
int tegra_dvfs_override_core_voltage(struct clk *c, int override_mv)
@@ -693,6 +755,16 @@ int tegra_dvfs_override_core_voltage(struct clk *c, int override_mv)
}
EXPORT_SYMBOL(tegra_dvfs_override_core_voltage);
+int tegra_dvfs_set_fmax_at_vmin(struct clk *c, unsigned long f_max, int v_min)
+{
+ if (!c->dvfs || !c->dvfs->can_override) {
+ pr_err("%s: %s cannot set fmax_at_vmin)\n", __func__, c->name);
+ return -EPERM;
+ }
+ return dvfs_set_fmax_at_vmin(c, f_max, v_min);
+}
+EXPORT_SYMBOL(tegra_dvfs_set_fmax_at_vmin);
+
/* May only be called during clock init, does not take any locks on clock c. */
int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d)
{
diff --git a/arch/arm/mach-tegra/include/mach/clk.h b/arch/arm/mach-tegra/include/mach/clk.h
index 89ac22d57269..18f6cf76938f 100644
--- a/arch/arm/mach-tegra/include/mach/clk.h
+++ b/arch/arm/mach-tegra/include/mach/clk.h
@@ -41,6 +41,7 @@ void tegra_periph_reset_assert(struct clk *c);
int tegra_dvfs_set_rate(struct clk *c, unsigned long rate);
int tegra_dvfs_override_core_voltage(struct clk *c, int override_mv);
+int tegra_dvfs_set_fmax_at_vmin(struct clk *c, unsigned long f_max, int v_min);
unsigned long clk_get_rate_all_locked(struct clk *c);
#ifdef CONFIG_ARCH_TEGRA_2x_SOC