summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-04-06 20:43:55 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:42:30 -0800
commit44d215b19e2a90005cbf7c03586f30b712883a5e (patch)
treea3311d9d22f8e458faff25d86d0b5e70a5adf49a /arch/arm/mach-tegra
parent6ea707c882369365877deb6c8f2af744a174bc2e (diff)
ARM: tegra: power: Add CPU EDP support
CPU electrical design point (EDP) limits specify maximum CPU frequency depending on number of CPU cores on-line, and chip temperature. This commit added initial edp governor to cpufreq driver. Governor is aware of CPU departure/arrival, but temperature dependency is yet to be added. Therefore CPU EDP support is left disabled for now. Original-Change-Id: Ia875aa6904df7ec25ac98863d59a173703034241 Reviewed-on: http://git-master/r/26982 Tested-by: Aleksandr Frid <afrid@nvidia.com> Reviewed-by: Diwakar Tundlam <dtundlam@nvidia.com> Tested-by: Diwakar Tundlam <dtundlam@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Original-Change-Id: Iae2e9d47c2d3fd4cb32104adbad4f4b26c46064c Rebase-Id: Rde24788e86558e1c21b18a1857a8b52220ba8e2a
Diffstat (limited to 'arch/arm/mach-tegra')
-rw-r--r--arch/arm/mach-tegra/Kconfig9
-rw-r--r--arch/arm/mach-tegra/cpu-tegra.c122
-rw-r--r--arch/arm/mach-tegra/pm.h12
3 files changed, 143 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 72be8786a165..4ff216ea7e25 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -242,4 +242,13 @@ config TEGRA_MC_PROFILE
sampling of the memory controller usage on a client-by-client
basis, and report the log through sysfs.
+config TEGRA_EDP_LIMITS
+ bool "Enforce electrical design limits"
+ depends on CPU_FREQ
+ default n
+ help
+ Limit maximum CPU frequency based on temperature and number
+ of on-line CPUs to keep CPU rail current within power supply
+ capabilities.
+
endif
diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c
index ac86d84a7710..dae7042c350f 100644
--- a/arch/arm/mach-tegra/cpu-tegra.c
+++ b/arch/arm/mach-tegra/cpu-tegra.c
@@ -32,6 +32,7 @@
#include <linux/io.h>
#include <linux/suspend.h>
#include <linux/debugfs.h>
+#include <linux/cpu.h>
#include <asm/system.h>
@@ -40,6 +41,8 @@
#include "clock.h"
#include "pm.h"
+/* tegra throttling and edp governors require frequencies in the table
+ to be in ascending order */
static struct cpufreq_frequency_table *freq_table;
@@ -187,6 +190,121 @@ void tegra_throttling_enable(bool enable)
}
#endif /* CONFIG_TEGRA_THERMAL_THROTTLE */
+#ifdef CONFIG_TEGRA_EDP_LIMITS
+
+static const struct tegra_edp_limits *cpu_edp_limits;
+static int cpu_edp_limits_size;
+static int edp_thermal_index;
+static cpumask_t edp_cpumask;
+static unsigned int edp_limit;
+
+static void edp_update_limit(void)
+{
+ int i;
+ unsigned int limit;
+
+ if (!cpu_edp_limits)
+ return;
+
+ limit = cpu_edp_limits[edp_thermal_index].freq_limits[
+ cpumask_weight(&edp_cpumask) - 1];
+
+ for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) {
+ if (freq_table[i].frequency > limit) {
+ break;
+ }
+ }
+ BUG_ON(i == 0); /* min freq above the limit or table empty */
+ edp_limit = freq_table[i-1].frequency;
+}
+
+static unsigned int edp_governor_speed(unsigned int requested_speed)
+{
+ if ((!cpu_edp_limits) || (requested_speed <= edp_limit))
+ return requested_speed;
+ else
+ return edp_limit;
+}
+
+static int tegra_cpu_edp_notify(
+ struct notifier_block *nb, unsigned long event, void *hcpu)
+{
+ int ret = 0;
+ unsigned int cpu_speed, new_speed;
+ int cpu = (long)hcpu;
+
+ switch (event) {
+ case CPU_UP_PREPARE:
+ mutex_lock(&tegra_cpu_lock);
+ cpu_set(cpu, edp_cpumask);
+ edp_update_limit();
+
+ cpu_speed = tegra_getspeed(0);
+ new_speed = edp_governor_speed(cpu_speed);
+ if (cpu_speed != new_speed) {
+ ret = tegra_update_cpu_speed(new_speed);
+ if (ret) {
+ cpu_clear(cpu, edp_cpumask);
+ edp_update_limit();
+ }
+ printk(KERN_DEBUG "tegra CPU:%sforce EDP limit %u kHz"
+ "\n", ret ? " failed to " : " ", new_speed);
+ }
+ mutex_unlock(&tegra_cpu_lock);
+ break;
+ case CPU_DEAD:
+ mutex_lock(&tegra_cpu_lock);
+ cpu_clear(cpu, edp_cpumask);
+ edp_update_limit();
+ mutex_unlock(&tegra_cpu_lock);
+ break;
+ }
+ return notifier_from_errno(ret);
+}
+
+static struct notifier_block tegra_cpu_edp_notifier = {
+ .notifier_call = tegra_cpu_edp_notify,
+};
+
+static void tegra_cpu_edp_init(bool resume)
+{
+ if (!cpu_edp_limits) {
+ if (!resume)
+ pr_info("tegra CPU: no EDP table is provided\n");
+ return;
+ }
+
+ edp_thermal_index = 0;
+ edp_cpumask = *cpu_online_mask;
+ edp_update_limit();
+
+ if (!resume)
+ register_hotcpu_notifier(&tegra_cpu_edp_notifier);
+
+ pr_info("tegra CPU: set EDP limit %u MHz\n", edp_limit / 1000);
+}
+
+static void tegra_cpu_edp_exit(void)
+{
+ if (!cpu_edp_limits)
+ return;
+
+ unregister_hotcpu_notifier(&tegra_cpu_edp_notifier);
+}
+
+void tegra_init_cpu_edp_limits(const struct tegra_edp_limits *limits, int size)
+{
+ cpu_edp_limits = limits;
+ cpu_edp_limits_size = cpu_edp_limits_size;
+}
+
+#else /* CONFIG_TEGRA_EDP_LIMITS */
+
+#define edp_governor_speed(requested_speed) (requested_speed)
+#define tegra_cpu_edp_init(resume)
+#define tegra_cpu_edp_exit()
+#endif /* CONFIG_TEGRA_EDP_LIMITS */
+
int tegra_verify_speed(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, freq_table);
@@ -278,6 +396,7 @@ static int tegra_target(struct cpufreq_policy *policy,
target_cpu_speed[policy->cpu] = freq;
new_speed = throttle_governor_speed(tegra_cpu_highest_speed());
+ new_speed = edp_governor_speed(new_speed);
ret = tegra_update_cpu_speed(new_speed);
if (ret == 0)
tegra_auto_hotplug_governor(new_speed);
@@ -299,6 +418,7 @@ static int tegra_pm_notify(struct notifier_block *nb, unsigned long event,
tegra_update_cpu_speed(freq_table[0].frequency);
} else if (event == PM_POST_SUSPEND) {
is_suspended = false;
+ tegra_cpu_edp_init(true);
}
mutex_unlock(&tegra_cpu_lock);
@@ -400,6 +520,7 @@ static int __init tegra_cpufreq_init(void)
return ret;
freq_table = table_data->freq_table;
+ tegra_cpu_edp_init(false);
return cpufreq_register_driver(&tegra_cpufreq_driver);
}
@@ -408,6 +529,7 @@ static void __exit tegra_cpufreq_exit(void)
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
destroy_workqueue(workqueue);
#endif
+ tegra_cpu_edp_exit();
tegra_auto_hotplug_exit();
cpufreq_unregister_driver(&tegra_cpufreq_driver);
}
diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h
index 116885ad78db..b19209292a04 100644
--- a/arch/arm/mach-tegra/pm.h
+++ b/arch/arm/mach-tegra/pm.h
@@ -78,6 +78,18 @@ void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat);
void tegra_idle_lp2(void);
+struct tegra_edp_limits {
+ int temperature;
+ unsigned int freq_limits[CONFIG_NR_CPUS];
+};
+#ifdef CONFIG_TEGRA_EDP_LIMITS
+void tegra_init_cpu_edp_limits(const struct tegra_edp_limits *limits, int size);
+#else
+static inline void tegra_init_cpu_edp_limits(
+ const struct tegra_edp_limits *limits, int size)
+{ }
+#endif
+
#if defined(CONFIG_TEGRA_AUTO_HOTPLUG) && !defined(CONFIG_ARCH_TEGRA_2x_SOC)
int tegra_auto_hotplug_init(struct mutex *cpu_lock);
void tegra_auto_hotplug_exit(void);