ARM: tegra11: power: Add core EDP basic implementation
Alex Frid [Thu, 15 Nov 2012 06:16:18 +0000 (22:16 -0800)]
Added mechanism to limit maximum GPU and memory frequency in order
to keep core rail current within power supply capabilities. The
actual limits yet to be characterized, and they will depend on

(a) Chip SKU
(b) Regulator current limit
(c) Slow (LP) CPU state (On/Off)
(d) Temperature range (trip-points TBD)
(e) User profile (balanced, favor GPU, favor EMC)
(f) Core module state (reserved)

Dependencies (a) and (b) are resolved statically when core EDP
is initialized for the particular chip. Core EDP limits will be
changed dynamically when run-time conditions (c), (d), (e), and
(f) are changed.

This commit implements only initialization of the core EDP limits
table and debugfs access to the table. Dynamic control is not
implemented. EDP table data is just a template.

Core EDP configuration option is unselected by default.

Bug 1165638

Change-Id: Ia1187f4e5d59d2668a5058e47fea7ae668018413
Signed-off-by: Alex Frid <afrid@nvidia.com>
Reviewed-on: http://git-master/r/164832
Reviewed-by: Rohan Somvanshi <rsomvanshi@nvidia.com>
Tested-by: Rohan Somvanshi <rsomvanshi@nvidia.com>

arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/edp.c
arch/arm/mach-tegra/edp_core.c [new file with mode: 0644]
arch/arm/mach-tegra/include/mach/edp.h
arch/arm/mach-tegra/tegra11_clocks.c
arch/arm/mach-tegra/tegra11_edp.c [new file with mode: 0644]

index 30991a0..8d299a2 100644 (file)
@@ -110,6 +110,7 @@ config ARCH_TEGRA_11x_SOC
        select SOC_BUS
        select TEGRA_DUAL_CBUS
        select TEGRA_DYNAMIC_CBUS
+       #select TEGRA_CORE_EDP_LIMITS
        select TEGRA_ERRATA_1157520
        select TEGRA_ISOMGR
        select TEGRA_ISOMGR_SYSFS
@@ -311,7 +312,7 @@ config TEGRA_ERRATA_1157520
          early ack scoreboard for T11x.
 
 config TEGRA_EDP_LIMITS
-       bool "Enforce electrical design limits"
+       bool "Enforce electrical design limits on CPU rail"
        depends on TEGRA_SILICON_PLATFORM
        depends on CPU_FREQ
        depends on THERMAL
@@ -579,4 +580,12 @@ config TEGRA_ARBITRATION_EMEM_INTR
          Enable this to allow the kernel to track arbitration conflicts
          in the memory controller.
 
+config TEGRA_CORE_EDP_LIMITS
+       bool "Enforce electrical design limits on core rail"
+       depends on TEGRA_SILICON_PLATFORM
+       depends on THERMAL
+       default n
+       help
+         Limit maximum GPU and memory frequency to keep core rail current
+         within power supply capabilities.
 endif
index eb1ff9b..46df9e5 100644 (file)
@@ -73,6 +73,8 @@ obj-$(CONFIG_ARCH_TEGRA_11x_SOC)        += tegra11_dvfs.o
 ifeq ($(CONFIG_TEGRA_SILICON_PLATFORM),y)
 obj-$(CONFIG_TEGRA_LATENCY_ALLOWANCE)   += latency_allowance.o
 obj-$(CONFIG_TEGRA_EDP_LIMITS)          += edp.o
+obj-$(CONFIG_TEGRA_CORE_EDP_LIMITS)     += edp_core.o
+obj-$(CONFIG_ARCH_TEGRA_11x_SOC)        += tegra11_edp.o
 endif
 ifeq ($(CONFIG_TEGRA_SILICON_PLATFORM),y)
 obj-$(CONFIG_ARCH_TEGRA_11x_SOC)        += tegra11_speedo.o
index 8478e82..0128b4c 100644 (file)
@@ -908,6 +908,9 @@ static int __init tegra_edp_debugfs_init(void)
        if (!d_edp_reg_override)
                goto edp_reg_override_err;
 
+       if (tegra_core_edp_debugfs_init(edp_dir))
+               goto edp_reg_override_err;
+
        return 0;
 
 edp_reg_override_err:
diff --git a/arch/arm/mach-tegra/edp_core.c b/arch/arm/mach-tegra/edp_core.c
new file mode 100644 (file)
index 0000000..ffbe567
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ * arch/arm/mach-tegra/edp_core.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 <mach/edp.h>
+
+#include "clock.h"
+#include "fuse.h"
+
+static DEFINE_MUTEX(core_edp_lock);
+
+static struct tegra_core_edp_limits core_edp_limits;
+static const struct tegra_core_edp_limits *limits;
+
+static bool core_edp_scpu_state;
+static int core_edp_profile;
+static int core_edp_modules_state;
+static int core_edp_thermal_idx;
+
+static const char *profile_names[CORE_EDP_PROFILES_NUM] = {
+       [CORE_EDP_PROFILE_BALANCED]  = "profile_balanced",
+       [CORE_EDP_PROFILE_FAVOR_GPU] = "profile_favor_gpu",
+       [CORE_EDP_PROFILE_FAVOR_EMC] = "profile_favor_emc",
+};
+
+static unsigned long *get_cap_rates(bool scpu_state, int profile,
+                                   int m_state, int t_idx)
+{
+       unsigned long *cap_rates = scpu_state ?
+               limits->cap_rates_scpu_on : limits->cap_rates_scpu_off;
+
+       cap_rates += ((profile * limits->core_modules_states + m_state) *
+                     limits->temperature_ranges + t_idx) *
+                     limits->cap_clocks_num;
+
+       return cap_rates;
+}
+
+static unsigned long *get_current_cap_rates(void)
+{
+       return get_cap_rates(core_edp_scpu_state, core_edp_profile,
+                            core_edp_modules_state, core_edp_thermal_idx);
+}
+
+static int set_cap_rates(unsigned long *new_rates)
+{
+       int i, ret;
+
+       for (i = 0; i < limits->cap_clocks_num; i++) {
+               struct clk *c = limits->cap_clocks[i];
+               ret = clk_set_rate(c, new_rates[i]);
+               if (ret) {
+                       pr_err("%s: Failed to set %s rate %lu\n",
+                              __func__, c->name, new_rates[i]);
+                       return ret;
+               }
+       }
+       return 0;
+}
+
+#if 0
+static int update_cap_rates(unsigned long *new_rates, unsigned long *old_rates)
+{
+       int i, ret;
+
+       /* 1st lower caps */
+       for (i = 0; i < limits->cap_clocks_num; i++) {
+               if (new_rates[i] < old_rates[i]) {
+                       struct clk *c = limits->cap_clocks[i];
+                       ret = clk_set_rate(c, new_rates[i]);
+                       if (ret) {
+                               pr_err("%s: Failed to set %s rate %lu\n",
+                                      __func__, c->name, new_rates[i]);
+                               return ret;
+                       }
+
+               }
+       }
+
+       /* then increase caps */
+       for (i = 0; i < limits->cap_clocks_num; i++) {
+               if (new_rates[i] > old_rates[i]) {
+                       struct clk *c = limits->cap_clocks[i];
+                       ret = clk_set_rate(c, new_rates[i]);
+                       if (ret) {
+                               pr_err("%s: Failed to set %s rate %lu\n",
+                                      __func__, c->name, new_rates[i]);
+                               return ret;
+                       }
+
+               }
+       }
+       return 0;
+}
+#endif
+
+/* FIXME: resume sync ? */
+
+static int __init start_core_edp(void)
+{
+       int ret;
+
+       /*
+        * Default state:
+        * always boot G-cluster (no cpu on core rail),
+        * non-throttled EMC profile
+        * all core modules that affect EDP are On
+        * unknown temperature - assume maximum (WC)
+        */
+       core_edp_scpu_state = false;
+       core_edp_profile = CORE_EDP_PROFILE_FAVOR_EMC;
+       core_edp_modules_state = 0;
+       core_edp_thermal_idx = limits->temperature_ranges - 1;
+
+       ret = set_cap_rates(get_current_cap_rates());
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+void __init tegra_init_core_edp_limits(unsigned int regulator_mA)
+{
+       int i;
+       unsigned long *cap_rates;
+
+       switch (tegra_chip_id) {
+       case TEGRA11X:
+               if (tegra11x_select_core_edp_table(
+                       regulator_mA, &core_edp_limits))
+                       return;
+               break;
+       default:
+               pr_err("%s: core edp is not supported on chip ID %d\n",
+                      __func__, tegra_chip_id);
+               return;
+       }
+
+       limits = &core_edp_limits;
+
+       if (start_core_edp()) {
+               limits = NULL;
+               return;
+       }
+
+       cap_rates = get_current_cap_rates();
+       pr_info("Core EDP limits are initialized at:\n");
+       for (i = 0; i < limits->cap_clocks_num; i++)
+               pr_info("    %10s: %lu\n",
+                       limits->cap_clocks[i]->name, cap_rates[i]);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static int edp_table_show(struct seq_file *s, void *data)
+{
+       int i, j, k, l;
+       unsigned long *cap_rates;
+
+       seq_printf(s, "VDD_CORE EDP TABLE (cap rates in kHz)\n");
+
+       seq_printf(s, "%10s", " Temp.");
+       for (l = 0; l < limits->cap_clocks_num; l++)
+               seq_printf(s, "%10s", limits->cap_clocks[l]->name);
+       seq_printf(s, "\n");
+       for (l = 0; l < 10+10*limits->cap_clocks_num; l++)
+               seq_printf(s, "-");
+       seq_printf(s, "\n");
+
+       seq_printf(s, "SCPU ON\n");
+       for (i = 0; i < CORE_EDP_PROFILES_NUM; i++) {
+               seq_printf(s, "%-19s%d\n", profile_names[i], i);
+               for (j = 0; j < limits->core_modules_states; j++) {
+                       seq_printf(s, "%-19s%d\n", "modules_state", j);
+                       for (k = 0; k < limits->temperature_ranges; k++) {
+                               seq_printf(s, "%8dC:", limits->temperatures[k]);
+                               cap_rates = get_cap_rates(true, i, j, k);
+                               for (l = 0; l < limits->cap_clocks_num; l++)
+                                       seq_printf(s, "%10lu",
+                                                  cap_rates[l]/1000);
+                               seq_printf(s, "\n");
+                       }
+               }
+       }
+
+       seq_printf(s, "SCPU OFF\n");
+       for (i = 0; i < CORE_EDP_PROFILES_NUM; i++) {
+               seq_printf(s, "%-19s%d\n", profile_names[i], i);
+               for (j = 0; j < limits->core_modules_states; j++) {
+                       seq_printf(s, "%-19s%d\n", "modules_state", j);
+                       for (k = 0; k < limits->temperature_ranges; k++) {
+                               seq_printf(s, "%8dC:", limits->temperatures[k]);
+                               cap_rates = get_cap_rates(false, i, j, k);
+                               for (l = 0; l < limits->cap_clocks_num; l++)
+                                       seq_printf(s, "%10lu",
+                                                  cap_rates[l]/1000);
+                               seq_printf(s, "\n");
+                       }
+               }
+       }
+
+       return 0;
+}
+static int edp_table_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, edp_table_show, inode->i_private);
+}
+static const struct file_operations edp_table_fops = {
+       .open           = edp_table_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int profile_show(struct seq_file *s, void *data)
+{
+       seq_printf(s, "%s\n", profile_names[core_edp_profile]);
+       return 0;
+}
+static int profile_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, profile_show, inode->i_private);
+}
+static const struct file_operations profile_fops = {
+       .open           = profile_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int range_show(struct seq_file *s, void *data)
+{
+       seq_printf(s, "%d\n", limits->temperatures[core_edp_thermal_idx]);
+       return 0;
+}
+static int range_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, range_show, inode->i_private);
+}
+static const struct file_operations range_fops = {
+       .open           = range_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int rates_show(struct seq_file *s, void *data)
+{
+       int i;
+       unsigned long *cap_rates;
+
+       mutex_lock(&core_edp_lock);
+       cap_rates = get_current_cap_rates();
+       mutex_unlock(&core_edp_lock);
+
+       for (i = 0; i < limits->cap_clocks_num; i++)
+               seq_printf(s, "%-10srate (kHz): %lu\n",
+                          limits->cap_clocks[i]->name, cap_rates[i] / 1000);
+       return 0;
+}
+static int rates_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, rates_show, inode->i_private);
+}
+static const struct file_operations rates_fops = {
+       .open           = rates_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+int __init tegra_core_edp_debugfs_init(struct dentry *edp_dir)
+{
+       struct dentry *dir, *d;
+
+       if (!limits)
+               return 0;
+
+       dir = debugfs_create_dir("vdd_core", edp_dir);
+       if (!dir)
+               return -ENOMEM;
+
+       d = debugfs_create_file("edp", S_IRUGO, dir, NULL, &edp_table_fops);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_bool("scpu_state", S_IRUGO, dir,
+                               (u32 *)&core_edp_scpu_state);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_file("profile", S_IRUGO, dir, NULL, &profile_fops);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_u32("modules_state", S_IRUGO, dir,
+                              (u32 *)&core_edp_modules_state);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_file("therm_range", S_IRUGO, dir, NULL, &range_fops);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_file("rates", S_IRUGO, dir, NULL, &rates_fops);
+       if (!d)
+               goto err_out;
+
+       return 0;
+
+err_out:
+       debugfs_remove_recursive(dir);
+       return -ENOMEM;
+
+
+}
+
+#endif
index a2860f6..34c3a82 100644 (file)
@@ -55,6 +55,25 @@ struct tegra_edp_freq_voltage_table {
        int voltage_mV;
 };
 
+enum tegra_core_edp_profiles {
+       CORE_EDP_PROFILE_BALANCED = 0,
+       CORE_EDP_PROFILE_FAVOR_GPU,
+       CORE_EDP_PROFILE_FAVOR_EMC,
+
+       CORE_EDP_PROFILES_NUM,
+};
+
+struct tegra_core_edp_limits {
+       int sku;
+       struct clk **cap_clocks;
+       int cap_clocks_num;
+       int *temperatures;
+       int temperature_ranges;
+       int core_modules_states;
+       unsigned long *cap_rates_scpu_on;
+       unsigned long *cap_rates_scpu_off;
+};
+
 #ifdef CONFIG_TEGRA_EDP_LIMITS
 struct thermal_cooling_device *edp_cooling_device_create(void *v);
 void tegra_init_cpu_edp_limits(unsigned int regulator_mA);
@@ -99,4 +118,16 @@ void __init tegra_battery_edp_init(unsigned int cap);
 static inline void tegra_battery_edp_init(unsigned int cap) {}
 #endif
 
+#ifdef CONFIG_TEGRA_CORE_EDP_LIMITS
+void tegra_init_core_edp_limits(unsigned int regulator_mA);
+int tegra_core_edp_debugfs_init(struct dentry *edp_dir);
+#else
+static inline void tegra_init_core_edp_limits(unsigned int regulator_mA)
+{}
+static inline int tegra_core_edp_debugfs_init(struct dentry *edp_dir)
+{ return 0; }
+#endif
+int tegra11x_select_core_edp_table(unsigned int regulator_mA,
+                                  struct tegra_core_edp_limits *limits);
+
 #endif /* __MACH_EDP_H */
index c69d512..f7f76df 100644 (file)
@@ -6371,6 +6371,7 @@ struct clk tegra_list_clks[] = {
        SHARED_CLK("iso.emc",   "iso",                  "emc",  &tegra_clk_emc, NULL, 0, SHARED_BW),
        SHARED_CLK("floor.emc", "floor.emc",            NULL,   &tegra_clk_emc, NULL, 0, 0),
        SHARED_CLK("override.emc", "override.emc",      NULL,   &tegra_clk_emc, NULL, 0, SHARED_OVERRIDE),
+       SHARED_CLK("edp.emc",   "edp.emc",              NULL,   &tegra_clk_emc, NULL, 0, SHARED_CEILING),
 
 #ifdef CONFIG_TEGRA_DUAL_CBUS
        DUAL_CBUS_CLK("3d.cbus",        "tegra_gr3d",           "gr3d", &tegra_clk_c2bus, "3d",  0, 0),
@@ -6379,6 +6380,7 @@ struct clk tegra_list_clks[] = {
        SHARED_CLK("cap.c2bus",         "cap.c2bus",            NULL,   &tegra_clk_c2bus, NULL,  0, SHARED_CEILING),
        SHARED_CLK("floor.c2bus",       "floor.c2bus",          NULL,   &tegra_clk_c2bus, NULL,  0, 0),
        SHARED_CLK("override.c2bus",    "override.c2bus",       NULL,   &tegra_clk_c2bus, NULL,  0, SHARED_OVERRIDE),
+       SHARED_CLK("edp.c2bus",         "edp.c2bus",            NULL,   &tegra_clk_c2bus, NULL,  0, SHARED_CEILING),
 
        DUAL_CBUS_CLK("msenc.cbus",     "tegra_msenc",          "msenc",  &tegra_clk_c3bus, "msenc", 0, 0),
        DUAL_CBUS_CLK("tsec.cbus",      "tegra_tsec",           "tsec",   &tegra_clk_c3bus, "tsec", 0, 0),
@@ -6398,6 +6400,7 @@ struct clk tegra_list_clks[] = {
        SHARED_CLK("cap.cbus",  "cap.cbus",             NULL,   &tegra_clk_cbus, NULL,  0, SHARED_CEILING),
        SHARED_CLK("floor.cbus", "floor.cbus",          NULL,   &tegra_clk_cbus, NULL,  0, 0),
        SHARED_CLK("override.cbus", "override.cbus",    NULL,   &tegra_clk_cbus, NULL,  0, SHARED_OVERRIDE),
+       SHARED_CLK("edp.cbus",  "edp.cbus",             NULL,   &tegra_clk_cbus, NULL,  0, SHARED_CEILING),
 #endif
 };
 
diff --git a/arch/arm/mach-tegra/tegra11_edp.c b/arch/arm/mach-tegra/tegra11_edp.c
new file mode 100644 (file)
index 0000000..6c225c8
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * arch/arm/mach-tegra/tegra11_edp.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 <mach/edp.h>
+
+#include "clock.h"
+#include "fuse.h"
+
+#define CORE_MODULES_STATES 1
+#define TEMPERATURE_RANGES 3
+#define CAP_CLKS_NUM 2
+#define        TOTAL_CAPS (CORE_EDP_PROFILES_NUM * CORE_MODULES_STATES *\
+                       TEMPERATURE_RANGES * CAP_CLKS_NUM)
+
+struct core_edp_entry {
+       int sku;
+       unsigned int cap_mA;
+       int mult;
+       unsigned long cap_scpu_on[CORE_EDP_PROFILES_NUM][
+               CORE_MODULES_STATES][TEMPERATURE_RANGES][CAP_CLKS_NUM];
+       unsigned long cap_scpu_off[CORE_EDP_PROFILES_NUM][
+               CORE_MODULES_STATES][TEMPERATURE_RANGES][CAP_CLKS_NUM];
+};
+
+static int temperatures[] = { 50, 60, 120 };
+
+#ifdef CONFIG_TEGRA_DUAL_CBUS
+static char *cap_clks_names[] = { "edp.c2bus", "edp.emc" };
+#else
+static char *cap_clks_names[] = { "edp.cbus", "edp.emc" };
+#endif
+static struct clk *cap_clks[CAP_CLKS_NUM];
+
+static struct core_edp_entry core_edp_table[] = {
+       {
+               .sku            = 0,            /* SKU = 0 */
+               .cap_mA         = 4000,         /* 4A cap */
+               .mult           = 1000000,      /* MHZ */
+               .cap_scpu_on    = {
+                       /* balanced profile */
+                       {       /* core modules power state 0 (all ON) */
+                               {{ 520, 800 },
+                                { 456, 550 },
+                                { 370, 350 },
+                               },
+                       },
+                       /* favor gpu */
+                       {       /* core modules power state 0 (all ON) */
+                               {{ 520, 800 },
+                                { 520, 300 },
+                                { 520, 150 },
+                               },
+                       },
+                       /* favor emc */
+                       {       /* core modules power state 0 (all ON) */
+                               {{ 520, 800 },
+                                { 372, 800 },
+                                { 100, 800 },
+                               }
+                       },
+               },
+               .cap_scpu_off   = {
+                       /* balanced profile */
+                       {       /* core modules power state 0 (all ON) */
+                               {{ 520, 800 },
+                                { 456, 600 },
+                                { 450, 400 },
+                               },
+                       },
+                       /* favor gpu */
+                       {       /* core modules power state 0 (all ON) */
+                               {{ 520, 800 },
+                                { 520, 400 },
+                                { 520, 200 },
+                               },
+                       },
+                       /* favor emc */
+                       {       /* core modules power state 0 (all ON)  */
+                               {{ 520, 800 },
+                                { 450, 800 },
+                                { 380, 800 },
+                               },
+                       },
+               },
+       },
+};
+
+static struct core_edp_entry *find_edp_entry(int sku, unsigned int regulator_mA)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(core_edp_table); i++) {
+               struct core_edp_entry *entry = &core_edp_table[i];
+               if ((entry->sku == sku) && (entry->cap_mA == regulator_mA))
+                       return entry;
+       }
+       return NULL;
+}
+
+static unsigned long clip_cap_rate(struct clk *cap_clk, unsigned long rate)
+{
+       unsigned long floor, ceiling;
+       struct clk *p = clk_get_parent(cap_clk);
+
+       if (!p || !p->ops || !p->ops->shared_bus_update) {
+               WARN(1, "%s: edp cap clk %s is not a shared bus user\n",
+                       __func__, cap_clk->name);
+               return rate;
+       }
+
+       /*
+        * Clip cap rate to shared bus possible rates (going up via shared
+        * bus * ladder since bus clocks always rounds up with resolution of
+        * at least 2kHz)
+        */
+       ceiling = clk_round_rate(p, clk_get_min_rate(p));
+       do {
+               floor = ceiling;
+               ceiling = clk_round_rate(p, floor + 2000);
+               if (IS_ERR_VALUE(ceiling)) {
+                       pr_err("%s: failed to clip %lu to %s possible rates\n",
+                              __func__, rate, p->name);
+                       return rate;
+               }
+       } while ((floor < ceiling) && (ceiling <= rate));
+
+       if (floor > rate)
+               WARN(1, "%s: %s cap rate %lu is below %s floor %lu\n",
+                       __func__, cap_clk->name, rate, p->name, floor);
+       return floor;
+}
+
+int __init tegra11x_select_core_edp_table(unsigned int regulator_mA,
+                                         struct tegra_core_edp_limits *limits)
+{
+       int i;
+       int sku = tegra_sku_id;
+       unsigned long *cap_rates;
+       struct core_edp_entry *edp_entry;
+
+       BUG_ON(ARRAY_SIZE(temperatures) != TEMPERATURE_RANGES);
+       BUG_ON(ARRAY_SIZE(cap_clks_names) != CAP_CLKS_NUM);
+       for (i = 0; i < CAP_CLKS_NUM; i++) {
+               struct clk *c = tegra_get_clock_by_name(cap_clks_names[i]);
+               if (!c) {
+                       pr_err("%s: failed to find edp cap clock %s\n",
+                              __func__, cap_clks_names[i]);
+                       return -ENODEV;
+               }
+               cap_clks[i] = c;
+       }
+
+       edp_entry = find_edp_entry(sku, regulator_mA);
+       if (!edp_entry) {
+               pr_err("%s: failed to find edp entry for sku %d cap mA %d\n",
+                      __func__, sku, regulator_mA);
+               return -ENODATA;
+       }
+
+       limits->sku = sku;
+       limits->cap_clocks = cap_clks;
+       limits->cap_clocks_num = CAP_CLKS_NUM;
+       limits->temperatures = temperatures;
+       limits->temperature_ranges = TEMPERATURE_RANGES;
+       limits->core_modules_states = CORE_MODULES_STATES;
+
+       cap_rates = &edp_entry->cap_scpu_on[0][0][0][0];
+       limits->cap_rates_scpu_on = cap_rates;
+       for (i = 0; i < TOTAL_CAPS; i++, cap_rates++) {
+               unsigned long rate = *cap_rates * edp_entry->mult;
+               *cap_rates = clip_cap_rate(cap_clks[i % CAP_CLKS_NUM], rate);
+       }
+
+       cap_rates = &edp_entry->cap_scpu_off[0][0][0][0];
+       limits->cap_rates_scpu_off = cap_rates;
+       for (i = 0; i < TOTAL_CAPS; i++, cap_rates++) {
+               unsigned long rate = *cap_rates * edp_entry->mult;
+               *cap_rates = clip_cap_rate(cap_clks[i % CAP_CLKS_NUM], rate);
+       }
+
+       return 0;
+}