summaryrefslogtreecommitdiff
path: root/arch/arm
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-07-05 20:24:36 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:47:41 -0800
commitfdd7ffe8e9e5f8fa5f3eb28414096fbddaef8099 (patch)
tree2ca8ddc15c1caacdb8c3874caf89e55d6bcd0899 /arch/arm
parent671dd66e90b6ea33bddc680278933d84cf94019b (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 Original-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> Rebase-Id: Rfab75e46ceaf2f1ad29ec91d946b50f1fd8a58d7
Diffstat (limited to 'arch/arm')
-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 5844a66e7916..22947e243bbd 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"
@@ -170,9 +173,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),
@@ -491,3 +494,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);