summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra11_edp.c
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 /arch/arm/mach-tegra/tegra11_edp.c
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>
Diffstat (limited to 'arch/arm/mach-tegra/tegra11_edp.c')
-rw-r--r--arch/arm/mach-tegra/tegra11_edp.c203
1 files changed, 203 insertions, 0 deletions
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;
+}