summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/cpu-tegra3.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/cpu-tegra3.c')
-rw-r--r--arch/arm/mach-tegra/cpu-tegra3.c242
1 files changed, 242 insertions, 0 deletions
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);
+}