diff options
-rw-r--r-- | arch/arm/mach-tegra/tegra3_dvfs.c | 156 |
1 files changed, 119 insertions, 37 deletions
diff --git a/arch/arm/mach-tegra/tegra3_dvfs.c b/arch/arm/mach-tegra/tegra3_dvfs.c index bfab1961bd30..34c308d29672 100644 --- a/arch/arm/mach-tegra/tegra3_dvfs.c +++ b/arch/arm/mach-tegra/tegra3_dvfs.c @@ -513,11 +513,19 @@ void __init tegra_soc_init_dvfs(void) } /* - * sysfs interface to cap tegra dvsf domains frequencies + * sysfs and dvfs interfaces to cap tegra core domains frequencies */ +static DEFINE_MUTEX(core_cap_lock); + +struct core_cap { + int refcnt; + int level; +}; +static struct core_cap tegra3_core_cap; +static struct core_cap kdvfs_core_cap; +static struct core_cap user_core_cap; + 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 { @@ -530,32 +538,95 @@ static struct { { .cap_name = "cap.emc" }, }; + +static void core_cap_level_set(int level) +{ + int i, j; + + for (j = 0; j < ARRAY_SIZE(core_millivolts); j++) { + int v = core_millivolts[j]; + if ((v == 0) || (level < v)) + break; + } + j = (j == 0) ? 0 : j - 1; + level = core_millivolts[j]; + + if (level < tegra3_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 > tegra3_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]); + } + tegra3_core_cap.level = level; +} + +static void core_cap_update(void) +{ + int new_level = tegra3_dvfs_rail_vdd_core.max_millivolts; + + if (kdvfs_core_cap.refcnt) + new_level = min(new_level, kdvfs_core_cap.level); + if (user_core_cap.refcnt) + new_level = min(new_level, user_core_cap.level); + + if (tegra3_core_cap.level != new_level) + core_cap_level_set(new_level); +} + +static void core_cap_enable(bool enable) +{ + int i; + + if (enable) { + tegra3_core_cap.refcnt++; + if (tegra3_core_cap.refcnt == 1) + 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 (tegra3_core_cap.refcnt) { + tegra3_core_cap.refcnt--; + if (tegra3_core_cap.refcnt == 0) + 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); + } + core_cap_update(); +} + 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); + return sprintf(buf, "%d (%d)\n", tegra3_core_cap.refcnt ? 1 : 0, + user_core_cap.refcnt ? 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; + int state; if (sscanf(buf, "%d", &state) != 1) return -1; + mutex_lock(&core_cap_lock); + 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); + user_core_cap.refcnt++; + if (user_core_cap.refcnt == 1) + core_cap_enable(true); + } else if (user_core_cap.refcnt) { + user_core_cap.refcnt--; + if (user_core_cap.refcnt == 0) + core_cap_enable(false); } + + mutex_unlock(&core_cap_lock); return count; } @@ -563,37 +634,22 @@ static ssize_t core_cap_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", core_cap_level); + return sprintf(buf, "%d (%d)\n", tegra3_core_cap.level, + user_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; + int 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; + mutex_lock(&core_cap_lock); + user_core_cap.level = level; + core_cap_update(); + mutex_unlock(&core_cap_lock); return count; } @@ -608,6 +664,30 @@ const struct attribute *cap_attributes[] = { NULL, }; +void tegra_dvfs_core_cap_enable(bool enable) +{ + mutex_lock(&core_cap_lock); + + if (enable) { + kdvfs_core_cap.refcnt++; + if (kdvfs_core_cap.refcnt == 1) + core_cap_enable(true); + } else if (kdvfs_core_cap.refcnt) { + kdvfs_core_cap.refcnt--; + if (kdvfs_core_cap.refcnt == 0) + core_cap_enable(false); + } + mutex_unlock(&core_cap_lock); +} + +void tegra_dvfs_core_cap_level_set(int level) +{ + mutex_lock(&core_cap_lock); + kdvfs_core_cap.level = level; + core_cap_update(); + mutex_unlock(&core_cap_lock); +} + static int __init init_core_cap_one(struct clk *c, unsigned long *freqs) { int i, v, next_v; @@ -656,6 +736,9 @@ static int __init tegra_dvfs_init_core_cap(void) int i; struct clk *c = NULL; + tegra3_core_cap.level = kdvfs_core_cap.level = user_core_cap.level = + tegra3_dvfs_rail_vdd_core.max_millivolts; + 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 || @@ -666,7 +749,6 @@ static int __init tegra_dvfs_init_core_cap(void) } 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) { |