summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2012-11-14 22:16:18 -0800
committerRohan Somvanshi <rsomvanshi@nvidia.com>2012-11-21 09:16:04 -0800
commita61a220e2d18242e425037e9f1c91e49ebb07ed0 (patch)
treed4e01dfb41d7b42242c05b247a5d287025c1939b
parented17f852cb90d5b04c72abe949edf48104d57045 (diff)
ARM: tegra11: power: Add core EDP basic implementation
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>
-rw-r--r--arch/arm/mach-tegra/Kconfig11
-rw-r--r--arch/arm/mach-tegra/Makefile2
-rw-r--r--arch/arm/mach-tegra/edp.c3
-rw-r--r--arch/arm/mach-tegra/edp_core.c338
-rw-r--r--arch/arm/mach-tegra/include/mach/edp.h31
-rw-r--r--arch/arm/mach-tegra/tegra11_clocks.c3
-rw-r--r--arch/arm/mach-tegra/tegra11_edp.c203
7 files changed, 590 insertions, 1 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index ea1088c95eec..f9de65b1ca9b 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -124,6 +124,7 @@ config ARCH_TEGRA_11x_SOC
select TEGRA_ISOMGR_SYSFS
select TEGRA_ISOMGR_DEBUG
select TEGRA_ERRATA_1157520
+ #select TEGRA_CORE_EDP_LIMITS
help
Support for NVIDIA Tegra 11x family of SoCs, based upon the
ARM Cortex-A15MP CPU
@@ -549,7 +550,7 @@ config TEGRA_MC_PROFILE
basis, and report the log through sysfs.
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
@@ -854,4 +855,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
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 9391070c93ea..d1f7b479aee3 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -78,6 +78,8 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_dvfs.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra3_dvfs.o
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_2x_SOC) += tegra2_speedo.o
diff --git a/arch/arm/mach-tegra/edp.c b/arch/arm/mach-tegra/edp.c
index 8478e82f96b1..0128b4c13263 100644
--- a/arch/arm/mach-tegra/edp.c
+++ b/arch/arm/mach-tegra/edp.c
@@ -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
index 000000000000..ffbe56743a1a
--- /dev/null
+++ b/arch/arm/mach-tegra/edp_core.c
@@ -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
diff --git a/arch/arm/mach-tegra/include/mach/edp.h b/arch/arm/mach-tegra/include/mach/edp.h
index a2860f69c157..34c3a828d6d8 100644
--- a/arch/arm/mach-tegra/include/mach/edp.h
+++ b/arch/arm/mach-tegra/include/mach/edp.h
@@ -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 */
diff --git a/arch/arm/mach-tegra/tegra11_clocks.c b/arch/arm/mach-tegra/tegra11_clocks.c
index 12b1a3ecf0ef..21ed6b527ff2 100644
--- a/arch/arm/mach-tegra/tegra11_clocks.c
+++ b/arch/arm/mach-tegra/tegra11_clocks.c
@@ -6368,6 +6368,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),
@@ -6376,6 +6377,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),
@@ -6395,6 +6397,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
index 000000000000..6c225c8fce89
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra11_edp.c
@@ -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;
+}