ARM: tegra: dvfs: Add interface to set fmax at vmin
Alex Frid [Sat, 21 Dec 2013 06:01:00 +0000 (22:01 -0800)]
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
Bug 1423429

Change-Id: I4f61ea6e3f8b6792ed058509339e16bff1947104
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/350016
(cherry picked from commit e6d6c5612f9dd22519297912365e0e54e31785ba)
Reviewed-on: http://git-master/r/410185
GVS: Gerrit_Virtual_Submit
Reviewed-by: Shreshtha Sahu <ssahu@nvidia.com>
Tested-by: Shreshtha Sahu <ssahu@nvidia.com>
Reviewed-by: Venkat Moganty <vmoganty@nvidia.com>

arch/arm/mach-tegra/clock.c
arch/arm/mach-tegra/dvfs.c
include/linux/clk/tegra.h

index c9941f2..4b21c8a 100644 (file)
@@ -1712,6 +1712,37 @@ static int use_alt_freq_set(void *data, u64 val)
 DEFINE_SIMPLE_ATTRIBUTE(use_alt_freq_fops,
                        use_alt_freq_get, use_alt_freq_set, "%llu\n");
 
+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;
@@ -1787,6 +1818,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:
index 47c38be..f0ab7df 100644 (file)
@@ -1004,12 +1004,74 @@ int tegra_dvfs_rail_get_override_floor(struct dvfs_rail *rail)
        }
        return -ENOENT;
 }
+
+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)
@@ -1022,6 +1084,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)
 {
index 59ea8e6..5e0eef1 100644 (file)
@@ -159,6 +159,7 @@ struct notifier_block;
 int tegra_dvfs_get_freqs(struct clk *c, unsigned long **freqs, int *num_freqs);
 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);
 int tegra_dvfs_rail_disable_by_name(const char *reg_id);
 int tegra_register_clk_rate_notifier(struct clk *c, struct notifier_block *nb);