diff options
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 9 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 3 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra.c | 12 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra3.c | 242 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm.h | 13 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sysfs-cluster.c | 2 |
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 |