summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-07-05 20:24:36 -0700
committerVarun Colbert <vcolbert@nvidia.com>2011-07-15 16:02:08 -0700
commit06a93ea42ddc74f35441e2a2fd2f0c08a0039348 (patch)
treeaead44c7e7800b89f0d04d6e39f71cf95e40b7d9
parent6eeccbf7aee3ceae0d711df5df53f20f1195b1c7 (diff)
ARM: tegra: power: Add Tegra3 sysfs core cap interface
Added Tegra3 sysfs throttling interface for VDD_CORE domains: /sys/kernel/tegra_cap/core_cap_level - common cap level for all core shared buses: emc (memory), sbus (system clock), and cbus (graphics clocks). Cap level is specified in millivolts, and maximum rate limits from the respective dvfs tables are applied to all bus clocks. Note that cap level affects only bus frequencies. Core voltage is not necessarily set at the cap level, since CPU and/or fixed peripheral clocks outside the buses may require higher voltages. /sys/kernel/tegra_cap/core_cap_state - provides enable/disable control of cap level throttling effect. Updated system clock dvfs table (new data better matching cap levels). Bug 837005 Change-Id: I77b1d1c95ba623dcfb3f8290ec686e181558b84a Reviewed-on: http://git-master/r/40778 Tested-by: Aleksandr Frid <afrid@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Reviewed-by: Narendra Damahe <ndamahe@nvidia.com> Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/tegra3_dvfs.c181
1 files changed, 178 insertions, 3 deletions
diff --git a/arch/arm/mach-tegra/tegra3_dvfs.c b/arch/arm/mach-tegra/tegra3_dvfs.c
index f7b569b449cc..39f0b176108b 100644
--- a/arch/arm/mach-tegra/tegra3_dvfs.c
+++ b/arch/arm/mach-tegra/tegra3_dvfs.c
@@ -18,6 +18,9 @@
#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"
@@ -167,9 +170,9 @@ static struct dvfs core_dvfs_table[] = {
CORE_DVFS("emc", 1, 1, KHZ, 408000, 408000, 408000, 408000, 667000, 667000, 667000),
CORE_DVFS("emc", 2, 1, KHZ, 408000, 408000, 408000, 408000, 667000, 667000, 800000),
- CORE_DVFS("sbus", 0, 1, KHZ, 136000, 163000, 191000, 216000, 216000, 216000, 216000),
- CORE_DVFS("sbus", 1, 1, KHZ, 136000, 163000, 191000, 216000, 245000, 245000, 245000),
- CORE_DVFS("sbus", 2, 1, KHZ, 140000, 169000, 203000, 228000, 254000, 279000, 300000),
+ CORE_DVFS("sbus", 0, 1, KHZ, 136000, 164000, 191000, 216000, 216000, 216000, 216000),
+ CORE_DVFS("sbus", 1, 1, KHZ, 136000, 164000, 191000, 216000, 245000, 245000, 245000),
+ CORE_DVFS("sbus", 2, 1, KHZ, 140000, 169000, 204000, 228000, 254000, 279000, 300000),
CORE_DVFS("vde", 0, 1, KHZ, 228000, 275000, 332000, 380000, 416000, 416000, 416000),
CORE_DVFS("mpe", 0, 1, KHZ, 234000, 285000, 332000, 380000, 416000, 416000, 416000),
@@ -487,3 +490,175 @@ void __init tegra_soc_init_dvfs(void)
tegra3_dvfs_rail_vdd_core.nominal_millivolts,
tegra_dvfs_core_disabled ? "disabled" : "enabled");
}
+
+/*
+ * sysfs interface to cap tegra dvsf domains frequencies
+ */
+static struct kobject *cap_kobj;
+static int core_cap_count;
+static int core_cap_level;
+
+/* Arranged in order required for enabling/lowering the cap */
+static struct {
+ const char *cap_name;
+ struct clk *cap_clk;
+ unsigned long freqs[MAX_DVFS_FREQS];
+} core_cap_table[] = {
+ { .cap_name = "cap.cbus" },
+ { .cap_name = "cap.sclk" },
+ { .cap_name = "cap.emc" },
+};
+
+static ssize_t
+core_cap_state_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", core_cap_count ? 1 : 0);
+}
+static ssize_t
+core_cap_state_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int i, state;
+
+ if (sscanf(buf, "%d", &state) != 1)
+ return -1;
+
+ if (state) {
+ core_cap_count++;
+ for (i = 0; i < ARRAY_SIZE(core_cap_table); i++)
+ if (core_cap_table[i].cap_clk)
+ clk_enable(core_cap_table[i].cap_clk);
+ } else if (core_cap_count) {
+ core_cap_count--;
+ for (i = ARRAY_SIZE(core_cap_table) - 1; i >= 0; i--)
+ if (core_cap_table[i].cap_clk)
+ clk_disable(core_cap_table[i].cap_clk);
+ }
+ return count;
+}
+
+static ssize_t
+core_cap_level_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", core_cap_level);
+}
+static ssize_t
+core_cap_level_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int i, j, level;
+
+ if (sscanf(buf, "%d", &level) != 1)
+ return -1;
+
+ for (j = 0; j < ARRAY_SIZE(core_millivolts); j++) {
+ int v = core_millivolts[j];
+ if ((v == 0) || (level < v))
+ break;
+ }
+ j = (j == 0) ? : j - 1;
+ level = core_millivolts[j];
+
+ if (level < core_cap_level) {
+ for (i = 0; i < ARRAY_SIZE(core_cap_table); i++)
+ if (core_cap_table[i].cap_clk)
+ clk_set_rate(core_cap_table[i].cap_clk,
+ core_cap_table[i].freqs[j]);
+ } else if (level > core_cap_level) {
+ for (i = ARRAY_SIZE(core_cap_table) - 1; i >= 0; i--)
+ if (core_cap_table[i].cap_clk)
+ clk_set_rate(core_cap_table[i].cap_clk,
+ core_cap_table[i].freqs[j]);
+ }
+ core_cap_level = level;
+ return count;
+}
+
+static struct kobj_attribute cap_state_attribute =
+ __ATTR(core_cap_state, 0666, core_cap_state_show, core_cap_state_store);
+static struct kobj_attribute cap_level_attribute =
+ __ATTR(core_cap_level, 0666, core_cap_level_show, core_cap_level_store);
+
+const struct attribute *cap_attributes[] = {
+ &cap_state_attribute.attr,
+ &cap_level_attribute.attr,
+ NULL,
+};
+
+static int __init init_core_cap_one(struct clk *c, unsigned long *freqs)
+{
+ int i, v, next_v;
+ unsigned long rate, next_rate = 0;
+
+ for (i = 0; i < ARRAY_SIZE(core_millivolts); i++) {
+ v = core_millivolts[i];
+ if (v == 0)
+ break;
+
+ for (;;) {
+ rate = next_rate;
+ next_rate = clk_round_rate(c, rate + 1000);
+ if (IS_ERR_VALUE(next_rate)) {
+ pr_debug("tegra3_dvfs: failed to round %s"
+ " rate %lu", c->name, rate);
+ return -EINVAL;
+ }
+ if (rate == next_rate)
+ break;
+
+ next_v = tegra_dvfs_predict_millivolts(
+ c->parent, next_rate);
+ if (IS_ERR_VALUE(next_rate)) {
+ pr_debug("tegra3_dvfs: failed to predict %s mV"
+ " for rate %lu", c->name, next_rate);
+ return -EINVAL;
+ }
+ if (next_v > v)
+ break;
+ }
+
+ if (rate == 0) {
+ rate = next_rate;
+ pr_warn("tegra3_dvfs: minimum %s cap %lu requires"
+ " %d mV", c->name, rate, next_v);
+ }
+ freqs[i] = rate;
+ next_rate = rate;
+ }
+ return 0;
+}
+
+static int __init tegra_dvfs_init_core_cap(void)
+{
+ int i;
+ struct clk *c = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(core_cap_table); i++) {
+ c = tegra_get_clock_by_name(core_cap_table[i].cap_name);
+ if (!c || !c->parent ||
+ init_core_cap_one(c, core_cap_table[i].freqs)) {
+ pr_err("tegra3_dvfs: failed to initialize %s frequency"
+ " table", core_cap_table[i].cap_name);
+ continue;
+ }
+ core_cap_table[i].cap_clk = c;
+ }
+ core_cap_level = tegra3_dvfs_rail_vdd_core.max_millivolts;
+
+ cap_kobj = kobject_create_and_add("tegra_cap", kernel_kobj);
+ if (!cap_kobj) {
+ pr_err("tegra3_dvfs: failed to create sysfs cap object");
+ return 0;
+ }
+
+ if (sysfs_create_files(cap_kobj, cap_attributes)) {
+ pr_err("tegra3_dvfs: failed to create sysfs cap interface");
+ return 0;
+ }
+ pr_info("tegra dvfs: tegra sysfs cap interface is initialized\n");
+
+ return 0;
+}
+late_initcall(tegra_dvfs_init_core_cap);