summaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
Diffstat (limited to 'arch')
-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