diff options
-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); |