summaryrefslogtreecommitdiff
path: root/arch/arm
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-02-05 01:11:13 -0800
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:42:20 -0800
commitefe7de861db97bea6a3964e7c7f4d0b13c07d1c9 (patch)
treee68181e8ba7702e47bd96a6b692c6198afa72a04 /arch/arm
parentc0f1802a8e2b91a5185c10bb0bf62a14154456a0 (diff)
ARM: tegra: Add auto-hotplug support for Tegra3
Initial implementation of Tegra3 quad core CPU management. Add closed control loop on top of cpufreq DFS. Target frequency range is bounded by Fmax(Vnominal) for low power cluster - currently set to 456MHz, and Fmax(Vminimum) for high power cluster - currently set to 356MHz. When CPU frequency is scaled below the target range, slave high power CPUs are gradually brought down and eventually CPU is switched to the low power cluster. When CPU frequency is scaled above the target range, CPU is switched to the high power cluster and slave high power CPUs are gradually brought up. The auto hotplug support is disabled on boot. It can be explicitly enabled via sysfs interface. Original-Change-Id: Ie0e5cf1f334d9c53932db05950cfcf5addd271d7 Reviewed-on: http://git-master/r/18500 Reviewed-by: Aleksandr Frid <afrid@nvidia.com> Tested-by: Aleksandr Frid <afrid@nvidia.com> Reviewed-by: Jonathan Mayo <jmayo@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com> Original-Change-Id: I86152069aa2bed73e0148a4bcab897811e1a5827 Rebase-Id: R9cf5f5f8868c659db526cb49ddf276a79d93ef1a
Diffstat (limited to 'arch/arm')
-rw-r--r--arch/arm/mach-tegra/Kconfig9
-rw-r--r--arch/arm/mach-tegra/Makefile3
-rw-r--r--arch/arm/mach-tegra/cpu-tegra.c12
-rw-r--r--arch/arm/mach-tegra/cpu-tegra3.c242
-rw-r--r--arch/arm/mach-tegra/pm.h13
-rw-r--r--arch/arm/mach-tegra/sysfs-cluster.c2
6 files changed, 280 insertions, 1 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 197e85ae2cee..d477116db830 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -224,6 +224,15 @@ config TEGRA_CLOCK_DEBUG_WRITE
depends on DEBUG_FS
default n
+config TEGRA_AUTO_HOTPLUG
+ bool "Enable automatic CPU hot-plugging"
+ depends on HOTPLUG_CPU && CPU_FREQ
+ default y
+ help
+ This option enables turning CPUs off/on and switching tegra
+ high/low power CPU clusters automatically, corresponding to
+ CPU frequency scaling.
+
config TEGRA_MC_PROFILE
tristate "Enable profiling memory controller utilization"
default n
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 6a28ed1ca57f..3d109fe25031 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -52,6 +52,9 @@ obj-$(CONFIG_SMP) += headsmp.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += headsmp-t3.o
obj-$(CONFIG_TEGRA_SYSTEM_DMA) += dma.o
obj-$(CONFIG_CPU_FREQ) += cpu-tegra.o
+ifeq ($(CONFIG_TEGRA_AUTO_HOTPLUG),y)
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += cpu-tegra3.o
+endif
obj-$(CONFIG_TEGRA_PCI) += pcie.o
obj-$(CONFIG_USB_SUPPORT) += usb_phy.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c
index 3da60ac97072..ea6b7aaffa90 100644
--- a/arch/arm/mach-tegra/cpu-tegra.c
+++ b/arch/arm/mach-tegra/cpu-tegra.c
@@ -36,6 +36,7 @@
#include <mach/clk.h>
#include "clock.h"
+#include "pm.h"
static struct cpufreq_frequency_table *freq_table;
@@ -283,6 +284,10 @@ static int tegra_target(struct cpufreq_policy *policy,
ret = tegra_update_cpu_speed(new_speed);
out:
mutex_unlock(&tegra_cpu_lock);
+
+ if (ret == 0)
+ tegra_auto_hotplug_governor(new_speed);
+
return ret;
}
@@ -373,6 +378,8 @@ static struct cpufreq_driver tegra_cpufreq_driver = {
static int __init tegra_cpufreq_init(void)
{
+ int ret = 0;
+
struct tegra_cpufreq_table_data *table_data =
tegra_cpufreq_table_get();
BUG_ON(!table_data);
@@ -392,6 +399,10 @@ static int __init tegra_cpufreq_init(void)
throttle_lowest_index = table_data->throttle_lowest_index;
throttle_highest_index = table_data->throttle_highest_index;
#endif
+ ret = tegra_auto_hotplug_init();
+ if (ret)
+ return ret;
+
freq_table = table_data->freq_table;
return cpufreq_register_driver(&tegra_cpufreq_driver);
}
@@ -401,6 +412,7 @@ static void __exit tegra_cpufreq_exit(void)
#ifdef CONFIG_TEGRA_THERMAL_THROTTLE
destroy_workqueue(workqueue);
#endif
+ tegra_auto_hotplug_exit();
cpufreq_unregister_driver(&tegra_cpufreq_driver);
}
diff --git a/arch/arm/mach-tegra/cpu-tegra3.c b/arch/arm/mach-tegra/cpu-tegra3.c
new file mode 100644
index 000000000000..6208e5fa9241
--- /dev/null
+++ b/arch/arm/mach-tegra/cpu-tegra3.c
@@ -0,0 +1,242 @@
+/*
+ * arch/arm/mach-tegra/cpu-tegra3.c
+ *
+ * CPU auto-hotplug for Tegra3 CPUs
+ *
+ * Copyright (c) 2011, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/cpu.h>
+
+#include "pm.h"
+
+#define INITIAL_STATE TEGRA_HP_DISABLED
+#define IDLE_HYSTERESIS 100000
+#define UP_DELAY_MS 1000
+#define DOWN_DELAY_MS 2000
+
+static DEFINE_MUTEX(tegra_hp_lock);
+
+static struct workqueue_struct *hotplug_wq;
+static struct delayed_work hotplug_work;
+
+static unsigned long up_delay;
+static unsigned long down_delay;
+module_param(up_delay, ulong, 0644);
+module_param(down_delay, ulong, 0644);
+
+static unsigned int idle_top_freq;
+static unsigned int idle_bottom_freq;
+module_param(idle_top_freq, uint, 0644);
+module_param(idle_bottom_freq, uint, 0644);
+
+static unsigned int lpcpu_max_freq;
+
+enum {
+ TEGRA_HP_DISABLED = 0,
+ TEGRA_HP_IDLE,
+ TEGRA_HP_DOWN,
+ TEGRA_HP_UP,
+};
+static int hp_state;
+
+static int hp_state_set(const char *arg, const struct kernel_param *kp)
+{
+ int ret = 0;
+ int old_state;
+
+ mutex_lock(&tegra_hp_lock);
+
+ old_state = hp_state;
+ ret = param_set_int(arg, kp);
+
+ if (ret == 0) {
+ switch (hp_state) {
+ case TEGRA_HP_DISABLED:
+ if (old_state != TEGRA_HP_DISABLED)
+ pr_info("Tegra auto-hotplug disabled\n");
+ break;
+ case TEGRA_HP_IDLE:
+ case TEGRA_HP_DOWN:
+ case TEGRA_HP_UP:
+ if (old_state == TEGRA_HP_DISABLED) {
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, up_delay);
+ pr_info("Tegra auto-hotplug enabled\n");
+ }
+ break;
+ default:
+ pr_warn("%s: unable to set tegra hotplug state %d\n",
+ __func__, hp_state);
+ hp_state = old_state;
+ }
+ }
+ mutex_unlock(&tegra_hp_lock);
+ return ret;
+}
+
+static int hp_state_get(char *buffer, const struct kernel_param *kp)
+{
+ return param_get_int(buffer, kp);
+}
+
+static struct kernel_param_ops tegra_hp_state_ops = {
+ .set = hp_state_set,
+ .get = hp_state_get,
+};
+module_param_cb(auto_hotplug, &tegra_hp_state_ops, &hp_state, 0644);
+
+
+static void tegra_auto_hotplug_work_func(struct work_struct *work)
+{
+ bool up = false;
+ unsigned int cpu = nr_cpu_ids;
+
+ mutex_lock(&tegra_hp_lock);
+
+ if (is_lp_cluster()) {
+ mutex_unlock(&tegra_hp_lock);
+ return;
+ }
+
+ switch (hp_state) {
+ case TEGRA_HP_DISABLED:
+ case TEGRA_HP_IDLE:
+ break;
+ case TEGRA_HP_DOWN:
+ cpu = cpumask_next(0, cpu_online_mask);
+ if (cpu < nr_cpu_ids) {
+ up = false;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, down_delay);
+ }
+ else
+ tegra_cluster_control(0, TEGRA_POWER_CLUSTER_LP |
+ TEGRA_POWER_CLUSTER_IMMEDIATE);
+ break;
+ case TEGRA_HP_UP:
+ cpu = cpumask_next_zero(0, cpu_online_mask);
+ if (cpu < nr_cpu_ids) {
+ up = true;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, up_delay);
+ }
+ break;
+ default:
+ pr_err("%s: invalid tegra hotplug state %d\n",
+ __func__, hp_state);
+ }
+ mutex_unlock(&tegra_hp_lock);
+
+ if (cpu < nr_cpu_ids) {
+ if (up)
+ cpu_up(cpu);
+ else
+ cpu_down(cpu);
+ }
+}
+
+void tegra_auto_hotplug_governor(unsigned int cpu_freq)
+{
+ mutex_lock(&tegra_hp_lock);
+
+ if (is_lp_cluster() && (cpu_freq > lpcpu_max_freq)) {
+ tegra_cluster_control(0, TEGRA_POWER_CLUSTER_G |
+ TEGRA_POWER_CLUSTER_IMMEDIATE);
+ }
+
+ switch (hp_state) {
+ case TEGRA_HP_DISABLED:
+ break;
+ case TEGRA_HP_IDLE:
+ if (cpu_freq > idle_top_freq) {
+ hp_state = TEGRA_HP_UP;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, up_delay);
+ }
+ else if (cpu_freq <= idle_bottom_freq) {
+ hp_state = TEGRA_HP_DOWN;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, down_delay);
+ }
+ break;
+ case TEGRA_HP_DOWN:
+ if (cpu_freq > idle_top_freq) {
+ hp_state = TEGRA_HP_UP;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, up_delay);
+ }
+ else if (cpu_freq > idle_bottom_freq) {
+ hp_state = TEGRA_HP_IDLE;
+ }
+ break;
+ case TEGRA_HP_UP:
+ if (cpu_freq <= idle_bottom_freq) {
+ hp_state = TEGRA_HP_DOWN;
+ queue_delayed_work(
+ hotplug_wq, &hotplug_work, down_delay);
+ }
+ else if (cpu_freq <= idle_top_freq) {
+ hp_state = TEGRA_HP_IDLE;
+ }
+ break;
+ default:
+ pr_err("%s: invalid tegra hotplug state %d\n",
+ __func__, hp_state);
+ BUG();
+ }
+ mutex_unlock(&tegra_hp_lock);
+}
+
+int tegra_auto_hotplug_init(void)
+{
+ /*
+ * Not bound to the issuer CPU (=> high-priority), has rescue worker
+ * task, single-threaded, frrezeable.
+ */
+ hotplug_wq = alloc_workqueue(
+ "cpu-tegra3", WQ_UNBOUND | WQ_RESCUER | WQ_FREEZEABLE, 1);
+ if (!hotplug_wq)
+ return -ENOMEM;
+ INIT_DELAYED_WORK(&hotplug_work, tegra_auto_hotplug_work_func);
+
+ lpcpu_max_freq = tegra_get_lpcpu_max_rate() / 1000;
+ idle_top_freq = lpcpu_max_freq;
+ idle_bottom_freq = idle_top_freq - IDLE_HYSTERESIS;
+
+ up_delay = msecs_to_jiffies(UP_DELAY_MS);
+ down_delay = msecs_to_jiffies(DOWN_DELAY_MS);
+
+ hp_state = INITIAL_STATE;
+ pr_info("Tegra auto-hotplug initialized: %s\n",
+ (hp_state == TEGRA_HP_DISABLED) ? "disabled" : "enabled");
+
+ return 0;
+}
+
+void tegra_auto_hotplug_exit(void)
+{
+ destroy_workqueue(hotplug_wq);
+}
diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h
index e333cc476408..f5293ea8a85b 100644
--- a/arch/arm/mach-tegra/pm.h
+++ b/arch/arm/mach-tegra/pm.h
@@ -61,6 +61,19 @@ void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat);
void tegra_idle_lp2(void);
+#if defined(CONFIG_TEGRA_AUTO_HOTPLUG) && defined(CONFIG_ARCH_TEGRA_3x_SOC)
+int tegra_auto_hotplug_init(void);
+void tegra_auto_hotplug_exit(void);
+void tegra_auto_hotplug_governor(unsigned int cpu_freq);
+#else
+static inline int tegra_auto_hotplug_init(void)
+{ return 0; }
+static inline void tegra_auto_hotplug_exit(void)
+{ }
+static inline void tegra_auto_hotplug_governor(unsigned int cpu_freq)
+{ }
+#endif
+
u64 tegra_rtc_read_ms(void);
/*
diff --git a/arch/arm/mach-tegra/sysfs-cluster.c b/arch/arm/mach-tegra/sysfs-cluster.c
index 46f157cab3ff..a16b09c3a236 100644
--- a/arch/arm/mach-tegra/sysfs-cluster.c
+++ b/arch/arm/mach-tegra/sysfs-cluster.c
@@ -142,7 +142,7 @@ static struct kobj_attribute cluster_powermode_attr =
#endif
#if DEBUG_CLUSTER_SWITCH
-unsigned int tegra_cluster_debug = 1;
+unsigned int tegra_cluster_debug = 0;
static struct kobj_attribute cluster_debug_attr =
__ATTR(debug, 0640, sysfscluster_show, sysfscluster_store);
#endif