ARM: tegra: dvfs: Add VDD_CORE override mechanism
Alex Frid [Tue, 12 Mar 2013 04:58:05 +0000 (21:58 -0700)]
Added mechanism to fix and lock (override) VDD_CORE rail voltage at
particular level. When override mode is entered, all scalable shared
buses and bus users (memory, graphics cbus clocks, system bus clocks)
are throttled to/below the rate safe at override voltage. Other clocks,
however, continue to run at rates set by the respective drivers. Hence
override voltage must be high enough to allow maximum rates of all core
clocks outside shared buses.

The lowest possible override level is determined by core dvfs tables,
and the supported override range is defined accordingly. Attempt to
set override voltage outside the range will fail, with the exception of
level 0 that is interpreted as request to exit override mode.

Override voltage cannot be changed if VDD_CORE is already locked: first
override mode has to be exited via zero level request, and then a new
override voltage can be set. No other override arbitration is provided.

Bug 1246712

Change-Id: I56e0dd250364c87b4036a07e6cdca750f556d008
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/209395
(cherry picked from commit 2ac5b251280f85313e891bec97c92d624144cbc0)
Reviewed-on: http://git-master/r/230086
Reviewed-by: Automatic_Commit_Validation_User
GVS: Gerrit_Virtual_Submit
Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>

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

index 02a180a..c266590 100644 (file)
@@ -132,6 +132,7 @@ config ARCH_TEGRA_11x_SOC
        select TEGRA_LP2_CPU_TIMER if !TEGRA_RAIL_OFF_MULTIPLE_CPUS
        select TEGRA_MC_PTSA if !TEGRA_FPGA_PLATFORM
        select TEGRA_THERMAL_THROTTLE_EXACT_FREQ
+       select TEGRA_VDD_CORE_OVERRIDE if TEGRA_EMC_SCALING_ENABLE
        select USB_ARCH_HAS_EHCI if USB_SUPPORT
        select USB_EHCI_TEGRA if USB_SUPPORT
        select USB_ULPI if USB_SUPPORT
@@ -783,4 +784,13 @@ config TEGRA_USE_NCT
        bool "Enable NCT partition access"
        help
          When enabled, we can read non-volatile items from NCT partition.
+
+config TEGRA_VDD_CORE_OVERRIDE
+       bool "Enable core rail override support"
+       depends on TEGRA_SILICON_PLATFORM
+       default n
+       help
+         When enabled, core rail can be fixed and locked at specified voltage
+         within override range, and core modules clocks are capped at rates
+         safe at override level.
 endif
index d9b865b..f450826 100644 (file)
@@ -85,6 +85,10 @@ static void dvfs_validate_cdevs(struct dvfs_rail *rail)
 
        if (rail->therm_mv_floors && !rail->vmin_cdev)
                WARN(1, "%s: missing vmin cooling device\n", rail->reg_id);
+
+       /* Limit override range to maximum floor */
+       if (rail->therm_mv_floors)
+               rail->min_override_millivolts = rail->therm_mv_floors[0];
 }
 
 int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n)
@@ -313,7 +317,10 @@ static inline int dvfs_rail_apply_limits(struct dvfs_rail *rail, int millivolts)
                        min_mv = rail->therm_mv_floors[i];
        }
 
-       millivolts += rail->offs_millivolts;
+       if (rail->override_millivolts)
+               millivolts = rail->override_millivolts;
+       else
+               millivolts += rail->offs_millivolts;
        if (millivolts > rail->max_millivolts)
                millivolts = rail->max_millivolts;
        else if (millivolts < min_mv)
@@ -553,6 +560,77 @@ int tegra_dvfs_set_rate(struct clk *c, unsigned long rate)
 }
 EXPORT_SYMBOL(tegra_dvfs_set_rate);
 
+#ifdef CONFIG_TEGRA_VDD_CORE_OVERRIDE
+static DEFINE_MUTEX(rail_override_lock);
+
+int tegra_dvfs_override_core_voltage(int override_mv)
+{
+       int ret, floor, ceiling;
+       struct dvfs_rail *rail = tegra_core_rail;
+
+       if (!rail)
+               return -ENOENT;
+
+       floor = rail->min_override_millivolts;
+       ceiling = rail->nominal_millivolts;
+       if (override_mv && ((override_mv < floor) || (override_mv > ceiling))) {
+               pr_err("%s: override level %d outside the range [%d...%d]\n",
+                      __func__, override_mv, floor, ceiling);
+               return -EINVAL;
+       }
+
+       mutex_lock(&rail_override_lock);
+
+       if (override_mv == rail->override_millivolts) {
+               ret = 0;
+               goto out;
+       }
+
+       if (override_mv) {
+               ret = tegra_dvfs_core_cap_level_apply(override_mv);
+               if (ret) {
+                       pr_err("%s: failed to set cap for override level %d\n",
+                              __func__, override_mv);
+                       goto out;
+               }
+       }
+
+       mutex_lock(&dvfs_lock);
+       if (rail->disabled || rail->suspended) {
+               pr_err("%s: cannot scale %s rail\n", __func__,
+                      rail->disabled ? "disabled" : "suspended");
+               ret = -EPERM;
+               if (!override_mv) {
+                       mutex_unlock(&dvfs_lock);
+                       goto out;
+               }
+       } else {
+               rail->override_millivolts = override_mv;
+               ret = dvfs_rail_update(rail);
+               if (ret) {
+                       pr_err("%s: failed to set override level %d\n",
+                              __func__, override_mv);
+                       rail->override_millivolts = 0;
+                       dvfs_rail_update(rail);
+               }
+       }
+       mutex_unlock(&dvfs_lock);
+
+       if (!override_mv || ret)
+               tegra_dvfs_core_cap_level_apply(0);
+out:
+       mutex_unlock(&rail_override_lock);
+       return ret;
+}
+#else
+int tegra_dvfs_override_core_voltage(int override_mv)
+{
+       pr_err("%s: vdd core override is not supported\n", __func__);
+       return -ENOSYS;
+}
+#endif
+EXPORT_SYMBOL(tegra_dvfs_override_core_voltage);
+
 /* 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)
 {
@@ -585,6 +663,18 @@ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d)
 
        c->dvfs = d;
 
+       /*
+        * Minimum core override level is determined as maximum voltage required
+        * for clocks outside shared buses (shared bus rates can be capped to
+        * safe levels when override limit is set)
+        */
+       if (i && c->ops && !c->ops->shared_bus_update &&
+           !(c->flags & PERIPH_ON_CBUS)) {
+               int mv = tegra_dvfs_predict_millivolts(c, d->freqs[i-1]);
+               if (d->dvfs_rail->min_override_millivolts < mv)
+                       d->dvfs_rail->min_override_millivolts = mv;
+       }
+
        mutex_lock(&dvfs_lock);
        list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs);
        mutex_unlock(&dvfs_lock);
@@ -1092,6 +1182,13 @@ static int dvfs_tree_show(struct seq_file *s, void *data)
                }
                seq_printf(s, "   thermal    %-7d mV\n", thermal_mv_floor);
 
+               if (rail == tegra_core_rail) {
+                       seq_printf(s, "   override   %-7d mV [%-4d...%-4d]\n",
+                                  rail->override_millivolts,
+                                  rail->min_override_millivolts,
+                                  rail->nominal_millivolts);
+               }
+
                list_sort(NULL, &rail->dvfs, dvfs_tree_sort_cmp);
 
                list_for_each_entry(d, &rail->dvfs, reg_node) {
@@ -1187,6 +1284,22 @@ static int core_offs_set(void *data, u64 val)
 }
 DEFINE_SIMPLE_ATTRIBUTE(core_offs_fops, core_offs_get, core_offs_set, "%lld\n");
 
+static int core_override_get(void *data, u64 *val)
+{
+       if (tegra_core_rail) {
+               *val = (u64)tegra_core_rail->override_millivolts;
+               return 0;
+       }
+       *val = 0;
+       return -ENOENT;
+}
+static int core_override_set(void *data, u64 val)
+{
+       return tegra_dvfs_override_core_voltage((int)val);
+}
+DEFINE_SIMPLE_ATTRIBUTE(core_override_fops,
+                       core_override_get, core_override_set, "%llu\n");
+
 int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
 {
        struct dentry *d;
@@ -1211,6 +1324,11 @@ int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
        if (!d)
                return -ENOMEM;
 
+       d = debugfs_create_file("vdd_core_override", S_IRUGO | S_IWUSR,
+               clk_debugfs_root, NULL, &core_override_fops);
+       if (!d)
+               return -ENOMEM;
+
        return 0;
 }
 
index 04fb765..0030cf4 100644 (file)
@@ -63,6 +63,8 @@ struct dvfs_rail {
        int max_millivolts;
        int reg_max_millivolts;
        int nominal_millivolts;
+       int override_millivolts;
+       int min_override_millivolts;
        const int *therm_mv_floors;
        int therm_mv_floors_num;
        const int *therm_mv_caps;
@@ -316,4 +318,11 @@ static inline int tegra_dvfs_rail_get_thermal_floor(struct dvfs_rail *rail)
        return 0;
 }
 
+static inline int tegra_dvfs_rail_get_override_floor(struct dvfs_rail *rail)
+{
+       if (rail)
+               return rail->min_override_millivolts;
+       return -ENOENT;
+}
+
 #endif
index 35db3a7..595c4e3 100644 (file)
@@ -151,6 +151,7 @@ struct dvfs;
 struct notifier_block;
 
 int tegra_dvfs_set_rate(struct clk *c, unsigned long rate);
+int tegra_dvfs_override_core_voltage(int override_mv);
 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);