ARM: tegra11: dvfs: Add rudimentary CPU dvfs table
Alex Frid [Thu, 22 Mar 2012 04:18:21 +0000 (21:18 -0700)]
Add rudimentary CPU dvfs table for Tegra11; connect G-CPU clock to
this table. Keep auto-dvfs flag disabled, to prevent any attempt to
actually change voltage.

Change-Id: I5abf2d79014c615f9b78376d1f89b724d303c2d3
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/91863
Reviewed-by: Scott Williams <scwilliams@nvidia.com>

Rebase-Id: R6bc93a18e1030826dc701485fe1999d5f7b5fb4c

arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/dvfs.c
arch/arm/mach-tegra/dvfs.h
arch/arm/mach-tegra/tegra11_dvfs.c [new file with mode: 0644]

index 9992516..2c3946b 100644 (file)
@@ -58,6 +58,7 @@ obj-$(CONFIG_TEGRA_FIQ_DEBUGGER)        += tegra_fiq_debugger.o
 obj-$(CONFIG_TEGRA_ARB_SEMAPHORE)       += arb_sema.o
 ifeq ($(CONFIG_TEGRA_SILICON_PLATFORM),y)
 obj-y                                   += dvfs.o
+obj-$(CONFIG_ARCH_TEGRA_11x_SOC)        += tegra11_dvfs.o
 obj-y                                   += latency_allowance.o
 obj-$(CONFIG_TEGRA_EDP_LIMITS)          += edp.o
 endif
index df55f89..536bd6b 100644 (file)
@@ -47,22 +47,43 @@ static LIST_HEAD(dvfs_rail_list);
 static DEFINE_MUTEX(dvfs_lock);
 static DEFINE_MUTEX(rail_disable_lock);
 
-static int dvfs_rail_update(struct dvfs_rail *rail);
-
-void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n)
+/* 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)
 {
        int i;
-       struct dvfs_relationship *rel;
 
-       mutex_lock(&dvfs_lock);
+       if (c->dvfs) {
+               pr_err("Error when enabling dvfs on %s for clock %s:\n",
+                       d->dvfs_rail->reg_id, c->name);
+               pr_err("DVFS already enabled for %s\n",
+                       c->dvfs->dvfs_rail->reg_id);
+               return -EINVAL;
+       }
 
-       for (i = 0; i < n; i++) {
-               rel = &rels[i];
-               list_add_tail(&rel->from_node, &rel->to->relationships_from);
-               list_add_tail(&rel->to_node, &rel->from->relationships_to);
+       for (i = 0; i < MAX_DVFS_FREQS; i++) {
+               if (d->millivolts[i] == 0)
+                       break;
+
+               d->freqs[i] *= d->freqs_mult;
+
+               /* If final frequencies are 0, pad with previous frequency */
+               if (d->freqs[i] == 0 && i > 1)
+                       d->freqs[i] = d->freqs[i - 1];
+       }
+       d->num_freqs = i;
+
+       if (d->auto_dvfs) {
+               c->auto_dvfs = true;
+               clk_set_cansleep(c);
        }
 
+       c->dvfs = d;
+
+       mutex_lock(&dvfs_lock);
+       list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs);
        mutex_unlock(&dvfs_lock);
+
+       return 0;
 }
 
 int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n)
@@ -88,6 +109,26 @@ int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n)
        return 0;
 };
 
+#ifdef CONFIG_TEGRA_SILICON_PLATFORM
+
+static int dvfs_rail_update(struct dvfs_rail *rail);
+
+void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n)
+{
+       int i;
+       struct dvfs_relationship *rel;
+
+       mutex_lock(&dvfs_lock);
+
+       for (i = 0; i < n; i++) {
+               rel = &rels[i];
+               list_add_tail(&rel->from_node, &rel->to->relationships_from);
+               list_add_tail(&rel->to_node, &rel->from->relationships_to);
+       }
+
+       mutex_unlock(&dvfs_lock);
+}
+
 static int dvfs_solve_relationship(struct dvfs_relationship *rel)
 {
        return rel->solve(rel->from, rel->to);
@@ -424,45 +465,6 @@ int tegra_dvfs_set_rate(struct clk *c, unsigned long rate)
 }
 EXPORT_SYMBOL(tegra_dvfs_set_rate);
 
-/* 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)
-{
-       int i;
-
-       if (c->dvfs) {
-               pr_err("Error when enabling dvfs on %s for clock %s:\n",
-                       d->dvfs_rail->reg_id, c->name);
-               pr_err("DVFS already enabled for %s\n",
-                       c->dvfs->dvfs_rail->reg_id);
-               return -EINVAL;
-       }
-
-       for (i = 0; i < MAX_DVFS_FREQS; i++) {
-               if (d->millivolts[i] == 0)
-                       break;
-
-               d->freqs[i] *= d->freqs_mult;
-
-               /* If final frequencies are 0, pad with previous frequency */
-               if (d->freqs[i] == 0 && i > 1)
-                       d->freqs[i] = d->freqs[i - 1];
-       }
-       d->num_freqs = i;
-
-       if (d->auto_dvfs) {
-               c->auto_dvfs = true;
-               clk_set_cansleep(c);
-       }
-
-       c->dvfs = d;
-
-       mutex_lock(&dvfs_lock);
-       list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs);
-       mutex_unlock(&dvfs_lock);
-
-       return 0;
-}
-
 static bool tegra_dvfs_all_rails_suspended(void)
 {
        struct dvfs_rail *rail;
@@ -832,3 +834,4 @@ int __init dvfs_debugfs_init(struct dentry *clk_debugfs_root)
 }
 
 #endif
+#endif
index 5d25901..96318c9 100644 (file)
@@ -100,11 +100,12 @@ struct dvfs {
 
 extern struct dvfs_rail *tegra_cpu_rail;
 
-#ifdef CONFIG_TEGRA_SILICON_PLATFORM
+int tegra_dvfs_init_rails(struct dvfs_rail *dvfs_rails[], int n);
 int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d);
+
+#ifdef CONFIG_TEGRA_SILICON_PLATFORM
 int dvfs_debugfs_init(struct dentry *clk_debugfs_root);
 int tegra_dvfs_late_init(void);
-int tegra_dvfs_init_rails(struct dvfs_rail *dvfs_rails[], int n);
 void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n);
 void tegra_dvfs_rail_enable(struct dvfs_rail *rail);
 void tegra_dvfs_rail_disable(struct dvfs_rail *rail);
@@ -126,8 +127,6 @@ static inline int dvfs_debugfs_init(struct dentry *clk_debugfs_root)
 { return 0; }
 static inline int tegra_dvfs_late_init(void)
 { return 0; }
-static inline int tegra_dvfs_init_rails(struct dvfs_rail *dvfs_rails[], int n)
-{ return 0; }
 static inline void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n)
 {}
 static inline void tegra_dvfs_rail_enable(struct dvfs_rail *rail)
diff --git a/arch/arm/mach-tegra/tegra11_dvfs.c b/arch/arm/mach-tegra/tegra11_dvfs.c
new file mode 100644 (file)
index 0000000..0c1b2fe
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * arch/arm/mach-tegra/tegra11_dvfs.c
+ *
+ * Copyright (C) 2012 NVIDIA Corporation.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kobject.h>
+#include <linux/err.h>
+
+#include "clock.h"
+#include "dvfs.h"
+#include "fuse.h"
+#include "board.h"
+
+static bool tegra_dvfs_cpu_disabled;
+static bool tegra_dvfs_core_disabled;
+static struct dvfs *cpu_dvfs;
+
+static const int cpu_millivolts[MAX_DVFS_FREQS] = {
+       800, 825, 850, 900, 912, 975, 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, 1237, 1250};
+
+static const int core_millivolts[MAX_DVFS_FREQS] = {
+       950, 1000, 1050, 1100, 1150, 1200, 1250, 1300, 1350};
+
+#define KHZ 1000
+#define MHZ 1000000
+
+/* FIXME: need tegra11 step */
+#define VDD_SAFE_STEP                  100
+
+static struct dvfs_rail tegra11_dvfs_rail_vdd_cpu = {
+       .reg_id = "vdd_cpu",
+       .max_millivolts = 1250,
+       .min_millivolts = 800,
+       .step = VDD_SAFE_STEP,
+       .jmp_to_zero = true,
+};
+
+static struct dvfs_rail tegra11_dvfs_rail_vdd_core = {
+       .reg_id = "vdd_core",
+       .max_millivolts = 1350,
+       .min_millivolts = 950,
+       .step = VDD_SAFE_STEP,
+};
+
+static struct dvfs_rail *tegra11_dvfs_rails[] = {
+       &tegra11_dvfs_rail_vdd_cpu,
+       &tegra11_dvfs_rail_vdd_core,
+};
+
+#ifdef CONFIG_TEGRA_SILICON_PLATFORM
+#define CPU_AUTO true
+#else
+#define CPU_AUTO false
+#endif
+
+#define CPU_DVFS(_clk_name, _speedo_id, _process_id, _mult, _freqs...) \
+       {                                                               \
+               .clk_name       = _clk_name,                            \
+               .speedo_id      = _speedo_id,                           \
+               .process_id     = _process_id,                          \
+               .freqs          = {_freqs},                             \
+               .freqs_mult     = _mult,                                \
+               .millivolts     = cpu_millivolts,                       \
+               .auto_dvfs      = CPU_AUTO,                             \
+               .dvfs_rail      = &tegra11_dvfs_rail_vdd_cpu,           \
+       }
+
+static struct dvfs cpu_dvfs_table[] = {
+       /* Cpu voltages (mV):         800, 825, 850, 900,  912,  975, 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, 1237 */
+       CPU_DVFS("cpu_g",  0, 0, MHZ,   1, 460, 550, 680,  680,  820,  970, 1040, 1080, 1150, 1200, 1240, 1280, 1320, 1360, 1500),
+
+       /*
+        * "Safe entry" to be used when no match for chip speedo, process
+        *  corner is found (just to boot at low rate); must be the last one
+        */
+       CPU_DVFS("cpu_g", -1, -1, MHZ,  1,   1, 216, 216, 300),
+};
+
+#define CORE_DVFS(_clk_name, _speedo_id, _auto, _mult, _freqs...)      \
+       {                                                       \
+               .clk_name       = _clk_name,                    \
+               .speedo_id      = _speedo_id,                   \
+               .process_id     = -1,                           \
+               .freqs          = {_freqs},                     \
+               .freqs_mult     = _mult,                        \
+               .millivolts     = core_millivolts,              \
+               .auto_dvfs      = _auto,                        \
+               .dvfs_rail      = &tegra11_dvfs_rail_vdd_core,  \
+       }
+
+static struct dvfs core_dvfs_table[] = {
+       /* Core voltages (mV):              950,   1000,   1050,   1100,   1150,    1200,    1250,    1300,    1350 */
+};
+
+
+int tegra_dvfs_disable_core_set(const char *arg, const struct kernel_param *kp)
+{
+       int ret;
+
+       ret = param_set_bool(arg, kp);
+       if (ret)
+               return ret;
+
+       if (tegra_dvfs_core_disabled)
+               tegra_dvfs_rail_disable(&tegra11_dvfs_rail_vdd_core);
+       else
+               tegra_dvfs_rail_enable(&tegra11_dvfs_rail_vdd_core);
+
+       return 0;
+}
+
+int tegra_dvfs_disable_cpu_set(const char *arg, const struct kernel_param *kp)
+{
+       int ret;
+
+       ret = param_set_bool(arg, kp);
+       if (ret)
+               return ret;
+
+       if (tegra_dvfs_cpu_disabled)
+               tegra_dvfs_rail_disable(&tegra11_dvfs_rail_vdd_cpu);
+       else
+               tegra_dvfs_rail_enable(&tegra11_dvfs_rail_vdd_cpu);
+
+       return 0;
+}
+
+int tegra_dvfs_disable_get(char *buffer, const struct kernel_param *kp)
+{
+       return param_get_bool(buffer, kp);
+}
+
+static struct kernel_param_ops tegra_dvfs_disable_core_ops = {
+       .set = tegra_dvfs_disable_core_set,
+       .get = tegra_dvfs_disable_get,
+};
+
+static struct kernel_param_ops tegra_dvfs_disable_cpu_ops = {
+       .set = tegra_dvfs_disable_cpu_set,
+       .get = tegra_dvfs_disable_get,
+};
+
+module_param_cb(disable_core, &tegra_dvfs_disable_core_ops,
+       &tegra_dvfs_core_disabled, 0644);
+module_param_cb(disable_cpu, &tegra_dvfs_disable_cpu_ops,
+       &tegra_dvfs_cpu_disabled, 0644);
+
+static void __init init_dvfs_one(struct dvfs *d, int nominal_mv_index)
+{
+       int ret;
+       struct clk *c = tegra_get_clock_by_name(d->clk_name);
+
+       if (!c) {
+               pr_debug("tegra11_dvfs: no clock found for %s\n",
+                       d->clk_name);
+               return;
+       }
+
+       /*
+        * Update max rate for auto-dvfs clocks, except EMC.
+        * EMC is a special case, since EMC dvfs is board dependent: max rate
+        * and EMC scaling frequencies are determined by tegra BCT (flashed
+        * together with the image) and board specific EMC DFS table; we will
+        * check the scaling ladder against nominal core voltage when the table
+        * is loaded (and if on particular board the table is not loaded, EMC
+        * scaling is disabled).
+        */
+       if (!(c->flags & PERIPH_EMC_ENB) && d->auto_dvfs) {
+               BUG_ON(!d->freqs[nominal_mv_index]);
+               tegra_init_max_rate(
+                       c, d->freqs[nominal_mv_index] * d->freqs_mult);
+       }
+       d->max_millivolts = d->dvfs_rail->nominal_millivolts;
+
+       ret = tegra_enable_dvfs_on_clk(c, d);
+       if (ret)
+               pr_err("tegra11_dvfs: failed to enable dvfs on %s\n", c->name);
+}
+
+static bool __init match_dvfs_one(struct dvfs *d, int speedo_id, int process_id)
+{
+       if ((d->process_id != -1 && d->process_id != process_id) ||
+               (d->speedo_id != -1 && d->speedo_id != speedo_id)) {
+               pr_debug("tegra11_dvfs: rejected %s speedo %d,"
+                       " process %d\n", d->clk_name, d->speedo_id,
+                       d->process_id);
+               return false;
+       }
+       return true;
+}
+
+static int __init get_cpu_nominal_mv_index(
+       int speedo_id, int process_id, struct dvfs **cpu_dvfs)
+{
+       int i, j, mv;
+       struct dvfs *d;
+       struct clk *c;
+
+       /*
+        * Find maximum cpu voltage that satisfies cpu_to_core dependency for
+        * nominal core voltage ("solve from cpu to core at nominal"). Clip
+        * result to the nominal cpu level for the chips with this speedo_id.
+        */
+       mv = tegra11_dvfs_rail_vdd_core.nominal_millivolts;
+       for (i = 0; i < MAX_DVFS_FREQS; i++) {
+               if (cpu_millivolts[i] == 0)
+                       break;
+       }
+       BUG_ON(i == 0);
+       mv = cpu_millivolts[i - 1];
+       BUG_ON(mv < tegra11_dvfs_rail_vdd_cpu.min_millivolts);
+       mv = min(mv, tegra_cpu_speedo_mv());
+
+       /*
+        * Find matching cpu dvfs entry, and use it to determine index to the
+        * final nominal voltage, that satisfies the following requirements:
+        * - allows CPU to run at minimum of the maximum rates specified in
+        *   the dvfs entry and clock tree
+        * - does not violate cpu_to_core dependency as determined above
+        */
+       for (i = 0, j = 0; j <  ARRAY_SIZE(cpu_dvfs_table); j++) {
+               d = &cpu_dvfs_table[j];
+               if (match_dvfs_one(d, speedo_id, process_id)) {
+                       c = tegra_get_clock_by_name(d->clk_name);
+                       BUG_ON(!c);
+
+                       for (; i < MAX_DVFS_FREQS; i++) {
+                               if ((d->freqs[i] == 0) ||
+                                   (cpu_millivolts[i] == 0) ||
+                                   (mv < cpu_millivolts[i]))
+                                       break;
+
+                               if (c->max_rate <= d->freqs[i]*d->freqs_mult) {
+                                       i++;
+                                       break;
+                               }
+                       }
+                       break;
+               }
+       }
+
+       BUG_ON(i == 0);
+       if (j == (ARRAY_SIZE(cpu_dvfs_table) - 1))
+               pr_err("tegra11_dvfs: WARNING!!!\n"
+                      "tegra11_dvfs: no cpu dvfs table found for chip speedo_id"
+                      " %d and process_id %d: set CPU rate limit at %lu\n"
+                      "tegra11_dvfs: WARNING!!!\n",
+                      speedo_id, process_id, d->freqs[i-1] * d->freqs_mult);
+
+       *cpu_dvfs = d;
+       return i - 1;
+}
+
+static int __init get_core_nominal_mv_index(int speedo_id)
+{
+       int i;
+       int mv = tegra_core_speedo_mv();
+       int core_edp_limit = get_core_edp();
+
+       /*
+        * Start with nominal level for the chips with this speedo_id. Then,
+        * make sure core nominal voltage is below edp limit for the board
+        * (if edp limit is set).
+        */
+       if (core_edp_limit)
+               mv = min(mv, core_edp_limit);
+
+       /* Round nominal level down to the nearest core scaling step */
+       for (i = 0; i < MAX_DVFS_FREQS; i++) {
+               if ((core_millivolts[i] == 0) || (mv < core_millivolts[i]))
+                       break;
+       }
+
+       if (i == 0) {
+               pr_err("tegra11_dvfs: unable to adjust core dvfs table to"
+                      " nominal voltage %d\n", mv);
+               return -ENOSYS;
+       }
+       return i - 1;
+}
+
+void __init tegra_soc_init_dvfs(void)
+{
+       int cpu_speedo_id = tegra_cpu_speedo_id();
+       int soc_speedo_id = tegra_soc_speedo_id();
+       int cpu_process_id = tegra_cpu_process_id();
+       int core_process_id = tegra_core_process_id();
+
+       int i;
+       int core_nominal_mv_index;
+       int cpu_nominal_mv_index;
+
+#ifndef CONFIG_TEGRA_CORE_DVFS
+       tegra_dvfs_core_disabled = true;
+#endif
+#ifndef CONFIG_TEGRA_CPU_DVFS
+       tegra_dvfs_cpu_disabled = true;
+#endif
+
+       /*
+        * Find nominal voltages for core (1st) and cpu rails before rail
+        * init. Nominal voltage index in the scaling ladder will also be
+        * used to determine max dvfs frequency for the respective domains.
+        */
+       core_nominal_mv_index = get_core_nominal_mv_index(soc_speedo_id);
+       if (core_nominal_mv_index < 0) {
+               tegra11_dvfs_rail_vdd_core.disabled = true;
+               tegra_dvfs_core_disabled = true;
+               core_nominal_mv_index = 0;
+       }
+       tegra11_dvfs_rail_vdd_core.nominal_millivolts =
+               core_millivolts[core_nominal_mv_index];
+
+       cpu_nominal_mv_index = get_cpu_nominal_mv_index(
+               cpu_speedo_id, cpu_process_id, &cpu_dvfs);
+       BUG_ON((cpu_nominal_mv_index < 0) || (!cpu_dvfs));
+       tegra11_dvfs_rail_vdd_cpu.nominal_millivolts =
+               cpu_millivolts[cpu_nominal_mv_index];
+
+       /* Init rail structures and dependencies */
+       tegra_dvfs_init_rails(tegra11_dvfs_rails,
+               ARRAY_SIZE(tegra11_dvfs_rails));
+
+       /* Search core dvfs table for speedo/process matching entries and
+          initialize dvfs-ed clocks */
+       for (i = 0; i <  ARRAY_SIZE(core_dvfs_table); i++) {
+               struct dvfs *d = &core_dvfs_table[i];
+               if (!match_dvfs_one(d, soc_speedo_id, core_process_id))
+                       continue;
+               init_dvfs_one(d, core_nominal_mv_index);
+       }
+
+       /* Initialize matching cpu dvfs entry already found when nominal
+          voltage was determined */
+       init_dvfs_one(cpu_dvfs, cpu_nominal_mv_index);
+
+       /* Finally disable dvfs on rails if necessary */
+       if (tegra_dvfs_core_disabled)
+               tegra_dvfs_rail_disable(&tegra11_dvfs_rail_vdd_core);
+       if (tegra_dvfs_cpu_disabled)
+               tegra_dvfs_rail_disable(&tegra11_dvfs_rail_vdd_cpu);
+
+       pr_info("tegra dvfs: VDD_CPU nominal %dmV, scaling %s\n",
+               tegra11_dvfs_rail_vdd_cpu.nominal_millivolts,
+               tegra_dvfs_cpu_disabled ? "disabled" : "enabled");
+       pr_info("tegra dvfs: VDD_CORE nominal %dmV, scaling %s\n",
+               tegra11_dvfs_rail_vdd_core.nominal_millivolts,
+               tegra_dvfs_core_disabled ? "disabled" : "enabled");
+}
+
+int tegra_dvfs_rail_disable_prepare(struct dvfs_rail *rail)
+{
+       return 0;
+}
+
+int tegra_dvfs_rail_post_enable(struct dvfs_rail *rail)
+{
+       return 0;
+}
+