summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/tegra3_dvfs.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-08-09 15:13:40 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:49:37 -0800
commit2f8be47bd7c24f246b3573ee43dfcede03df1fe7 (patch)
treea1828c07c45496dba40ffbb3ee20674e4e5f64e2 /arch/arm/mach-tegra/tegra3_dvfs.c
parent7c811169909f22407448b8da4f3fbb017a803b85 (diff)
ARM: tegra: dvfs: Add Tegra3 dvfs core cap interface
Added Tegra3 kernel dvfs throttling interface for VDD_CORE domains. Requests from this interface are combined with sysfs capping requests and the most aggressive (minimal) cap level is set. (cherry picked from commit da2fa3965fb89ec7faeb49c7b9009bdd949f8fb7) (cherry picked from commit 2b8b7d8d6feba48dde56c7fa3e92f57a9b26e75a) Change-Id: Iadcb8cac04f0793fc2fdc8d8203d3bc973004a1d Reviewed-on: http://git-master/r/61023 Tested-by: Aleksandr Frid <afrid@nvidia.com> Reviewed-by: Yu-Huan Hsu <yhsu@nvidia.com> Rebase-Id: Rc4cfbf50089133bd111c1a156d7a77586c6181c2
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_dvfs.c')
-rw-r--r--arch/arm/mach-tegra/tegra3_dvfs.c156
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) {