diff options
author | Alex Frid <afrid@nvidia.com> | 2011-07-05 20:24:36 -0700 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-07-15 16:02:08 -0700 |
commit | 06a93ea42ddc74f35441e2a2fd2f0c08a0039348 (patch) | |
tree | aead44c7e7800b89f0d04d6e39f71cf95e40b7d9 /arch/arm/mach-tegra/tegra3_dvfs.c | |
parent | 6eeccbf7aee3ceae0d711df5df53f20f1195b1c7 (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>
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_dvfs.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra3_dvfs.c | 181 |
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); |