All of lore.kernel.org
 help / color / mirror / Atom feed
From: Colin Cross <ccross@android.com>
To: linux-tegra@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org, olof@lixom.net,
	konkers@android.com, Colin Cross <ccross@android.com>,
	Gary King <gking@nvidia.com>,
	Russell King <linux@arm.linux.org.uk>,
	linux-kernel@vger.kernel.org
Subject: [PATCH v2 18/28] ARM: tegra: add CPU_IDLE driver
Date: Sun, 23 Jan 2011 18:01:23 -0800	[thread overview]
Message-ID: <1295834493-5019-19-git-send-email-ccross@android.com> (raw)
In-Reply-To: <1295834493-5019-1-git-send-email-ccross@android.com>

Supports clock-gated (LP3) SMP idle mode, and power-gated (LP2) idle.

Latency for LP2 idle state is calculated as a 2-sample weighted moving
average, to allow for future variations due to (e.g.) CPU frequency
scaling.

LP3 idle gates a single CPU core, but LP2 requires power gating both
CPU cores.  An interrupt is used to handshake between the two cores
to ensure both are ready to enter LP2, and another interrupt is used
to manage the process of entering LP2.

Signed-off-by: Gary King <gking@nvidia.com>
Signed-off-by: Colin Cross <ccross@android.com>
---
 arch/arm/mach-tegra/Makefile  |    1 +
 arch/arm/mach-tegra/cpuidle.c |  714 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 715 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-tegra/cpuidle.c

diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 0bf1441..75b1afa 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += headsmp-t2.o
 obj-$(CONFIG_TEGRA_SYSTEM_DMA)		+= dma.o
 obj-$(CONFIG_CPU_FREQ)                  += cpu-tegra.o
 obj-$(CONFIG_TEGRA_PCI)			+= pcie.o
+obj-$(CONFIG_CPU_IDLE)			+= cpuidle.o
 
 obj-${CONFIG_MACH_HARMONY}              += board-harmony.o
 obj-${CONFIG_MACH_HARMONY}              += board-harmony-pinmux.o
diff --git a/arch/arm/mach-tegra/cpuidle.c b/arch/arm/mach-tegra/cpuidle.c
new file mode 100644
index 0000000..232e767
--- /dev/null
+++ b/arch/arm/mach-tegra/cpuidle.c
@@ -0,0 +1,714 @@
+/*
+ * arch/arm/mach-tegra/cpuidle.c
+ *
+ * CPU idle driver for Tegra CPUs
+ *
+ * Copyright (c) 2010, 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/cpu.h>
+#include <linux/cpuidle.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/suspend.h>
+#include <linux/tick.h>
+
+#include <asm/cacheflush.h>
+#include <asm/hardware/gic.h>
+#include <asm/localtimer.h>
+
+#include <mach/iomap.h>
+#include <mach/irqs.h>
+#include <mach/legacy_irq.h>
+#include <mach/suspend.h>
+
+#include "power.h"
+
+#define TEGRA_CPUIDLE_BOTH_IDLE		INT_QUAD_RES_24
+#define TEGRA_CPUIDLE_TEAR_DOWN		INT_QUAD_RES_25
+
+#define EVP_CPU_RESET_VECTOR \
+	(IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0x100)
+#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x340)
+#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x344)
+#define CLK_RST_CONTROLLER_CLK_CPU_CMPLX \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x4c)
+
+static bool lp2_in_idle __read_mostly = true;
+static bool lp2_disabled_by_suspend;
+module_param(lp2_in_idle, bool, 0644);
+
+static s64 tegra_cpu1_idle_time = LLONG_MAX;
+static int tegra_lp2_exit_latency;
+static int tegra_lp2_power_off_time;
+
+static struct {
+	unsigned int cpu_ready_count[2];
+	unsigned long long cpu_wants_lp2_time[2];
+	unsigned long long in_lp2_time;
+	unsigned int both_idle_count;
+	unsigned int tear_down_count;
+	unsigned int lp2_count;
+	unsigned int lp2_completed_count;
+	unsigned int lp2_count_bin[32];
+	unsigned int lp2_completed_count_bin[32];
+	unsigned int lp2_int_count[NR_IRQS];
+	unsigned int last_lp2_int_count[NR_IRQS];
+} idle_stats;
+
+struct cpuidle_driver tegra_idle = {
+	.name = "tegra_idle",
+	.owner = THIS_MODULE,
+};
+
+static DEFINE_PER_CPU(struct cpuidle_device *, idle_devices);
+
+#define FLOW_CTRL_WAITEVENT   (2<<29)
+#define FLOW_CTRL_JTAG_RESUME (1<<28)
+#define FLOW_CTRL_HALT_CPUx_EVENTS(cpu) ((cpu) ? ((cpu - 1) * 0x8 + 0x14) : 0x0)
+
+#define PMC_SCRATCH_38 0x134
+#define PMC_SCRATCH_39 0x138
+
+#define CLK_RESET_CLK_MASK_ARM 0x44
+
+static inline unsigned int time_to_bin(unsigned int time)
+{
+	return fls(time);
+}
+
+static inline void tegra_unmask_irq(int irq)
+{
+	struct irq_chip *chip = get_irq_chip(irq);
+	struct irq_data *data = irq_get_irq_data(irq);
+	chip->irq_unmask(data);
+}
+
+static inline void tegra_mask_irq(int irq)
+{
+	struct irq_chip *chip = get_irq_chip(irq);
+	struct irq_data *data = irq_get_irq_data(irq);
+	chip->irq_mask(data);
+}
+
+static inline int tegra_pending_interrupt(void)
+{
+	void __iomem *gic_cpu = IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x100);
+	u32 reg = readl(gic_cpu + 0x18);
+	reg &= 0x3FF;
+
+	return reg;
+}
+
+static inline void tegra_flow_wfi(struct cpuidle_device *dev)
+{
+	void __iomem *flow_ctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
+	u32 reg = FLOW_CTRL_WAITEVENT | FLOW_CTRL_JTAG_RESUME;
+
+	flow_ctrl = flow_ctrl + FLOW_CTRL_HALT_CPUx_EVENTS(dev->cpu);
+
+	dsb();
+	__raw_writel(reg, flow_ctrl);
+	reg = __raw_readl(flow_ctrl);
+	__asm__ volatile ("wfi");
+	__raw_writel(0, flow_ctrl);
+	reg = __raw_readl(flow_ctrl);
+}
+
+#ifdef CONFIG_SMP
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+	int wake_int;
+
+	tegra_unmask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	tegra_flow_wfi(dev);
+
+	wake_int = tegra_pending_interrupt();
+
+	tegra_mask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	return wake_int == TEGRA_CPUIDLE_BOTH_IDLE &&
+		tegra_pending_interrupt() == 1023;
+}
+
+static inline bool tegra_wait_for_tear_down(struct cpuidle_device *dev)
+{
+	int wake_int;
+	irq_set_affinity(TEGRA_CPUIDLE_TEAR_DOWN, cpumask_of(1));
+	tegra_unmask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	tegra_flow_wfi(dev);
+
+	wake_int = tegra_pending_interrupt();
+
+	tegra_mask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	return wake_int == TEGRA_CPUIDLE_TEAR_DOWN &&
+		tegra_pending_interrupt() == 1023;
+}
+
+static inline bool tegra_cpu_in_reset(int cpu)
+{
+	return !!(readl(CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET) & (1 << cpu));
+}
+
+static int tegra_tear_down_cpu1(void)
+{
+	u32 reg;
+
+	/* Signal to CPU1 to tear down */
+	tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	/* At this point, CPU0 can no longer abort LP2, but CP1 can */
+	/* TODO: any way not to poll here? Use the LP2 timer to wfi? */
+	/* takes ~80 us */
+	while (!tegra_cpu_in_reset(1) &&
+		tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+		cpu_relax();
+
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	/* If CPU1 aborted LP2, restart the process */
+	if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+		return -EAGAIN;
+
+	/* CPU1 is ready for LP2, clock gate it */
+	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+	writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+	return 0;
+}
+
+static void tegra_wake_cpu1(void)
+{
+	unsigned long boot_vector;
+	unsigned long old_boot_vector;
+	unsigned long timeout;
+	u32 reg;
+
+	boot_vector = virt_to_phys(tegra_hotplug_startup);
+	old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
+	writel(boot_vector, EVP_CPU_RESET_VECTOR);
+
+	/* enable cpu clock on cpu */
+	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+	writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+	reg = 0x1111 << 1;
+	writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
+
+	/* unhalt the cpu */
+	writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+	while (time_before(jiffies, timeout)) {
+		if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
+			break;
+		udelay(10);
+	}
+
+	/* put the old boot vector back */
+	writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
+
+	/* CPU1 is now started */
+}
+#else
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+	return true;
+}
+
+static inline int tegra_tear_down_cpu1(void)
+{
+	return 0;
+}
+
+static inline void tegra_wake_cpu1(void)
+{
+}
+#endif
+
+static void tegra_idle_enter_lp2_cpu0(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	s64 request;
+	ktime_t enter;
+	ktime_t exit;
+	bool sleep_completed = false;
+	int bin;
+
+restart:
+	if (!tegra_wait_for_both_idle(dev))
+		return;
+
+	idle_stats.both_idle_count++;
+
+	if (need_resched())
+		return;
+
+	/* CPU1 woke CPU0 because both are idle */
+
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	if (request < state->target_residency) {
+		/* Not enough time left to enter LP2 */
+		tegra_flow_wfi(dev);
+		return;
+	}
+
+	idle_stats.tear_down_count++;
+
+	if (tegra_tear_down_cpu1())
+		goto restart;
+
+	/* Enter LP2 */
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	smp_rmb();
+	request = min_t(s64, request, tegra_cpu1_idle_time);
+
+	enter = ktime_get();
+	if (request > state->target_residency) {
+		s64 sleep_time = request - tegra_lp2_exit_latency;
+
+		bin = time_to_bin((u32)request / 1000);
+		idle_stats.lp2_count++;
+		idle_stats.lp2_count_bin[bin]++;
+
+		if (tegra_suspend_lp2(sleep_time) == 0)
+			sleep_completed = true;
+		else
+			idle_stats.lp2_int_count[tegra_pending_interrupt()]++;
+	}
+
+	/* Bring CPU1 out of LP2 */
+	/* TODO: polls for CPU1 to boot, wfi would be better */
+	/* takes ~80 us */
+
+	/* set the reset vector to point to the secondary_startup routine */
+	smp_wmb();
+
+	tegra_wake_cpu1();
+
+	/*
+	 * TODO: is it worth going back to wfi if no interrupt is pending
+	 * and the requested sleep time has not passed?
+	 */
+
+	exit = ktime_get();
+	if (sleep_completed) {
+		/*
+		 * Stayed in LP2 for the full time until the next tick,
+		 * adjust the exit latency based on measurement
+		 */
+		int offset = ktime_to_us(ktime_sub(exit, enter)) - request;
+		int latency = tegra_lp2_exit_latency + offset / 16;
+		latency = clamp(latency, 0, 10000);
+		tegra_lp2_exit_latency = latency;
+		smp_wmb();
+
+		idle_stats.lp2_completed_count++;
+		idle_stats.lp2_completed_count_bin[bin]++;
+		idle_stats.in_lp2_time += ktime_to_us(ktime_sub(exit, enter));
+
+		pr_debug("%lld %lld %d %d\n", request,
+			ktime_to_us(ktime_sub(exit, enter)),
+			offset, bin);
+	}
+}
+
+#ifdef CONFIG_SMP
+static void tegra_idle_enter_lp2_cpu1(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	u32 twd_ctrl;
+	u32 twd_load;
+	s64 request;
+
+	tegra_legacy_force_irq_set(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	if (!tegra_wait_for_tear_down(dev))
+		goto out;
+
+	if (need_resched())
+		goto out;
+
+	/*
+	 * CPU1 woke CPU0 because both were idle
+	 * CPU0 responded by waking CPU1 to tell it to disable itself
+	 */
+
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	if (request < tegra_lp2_exit_latency) {
+		/*
+		 * Not enough time left to enter LP2
+		 * Signal to CPU0 that CPU1 rejects LP2, and stay in
+		 */
+		tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+		tegra_flow_wfi(dev);
+		goto out;
+	}
+
+	tegra_cpu1_idle_time = request;
+	smp_wmb();
+
+	/* Prepare CPU1 for LP2 by putting it in reset */
+
+	gic_cpu_exit(0);
+	barrier();
+	twd_ctrl = readl(twd_base + 0x8);
+	twd_load = readl(twd_base + 0);
+
+	flush_cache_all();
+	barrier();
+	__cortex_a9_save(0);
+	/* CPU1 is in reset, waiting for CPU0 to boot it, possibly after LP2 */
+
+
+	/* CPU0 booted CPU1 out of reset */
+	barrier();
+	cpu_init();
+	writel(twd_ctrl, twd_base + 0x8);
+	writel(twd_load, twd_base + 0);
+	gic_secondary_init(0);
+	tegra_unmask_irq(IRQ_LOCALTIMER);
+
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	writel(smp_processor_id(), EVP_CPU_RESET_VECTOR);
+
+	/*
+	 * TODO: is it worth going back to wfi if no interrupt is pending
+	 * and the requested sleep time has not passed?
+	 */
+
+	return;
+
+out:
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+}
+#endif
+
+static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	ktime_t enter, exit;
+	s64 us;
+
+	local_irq_disable();
+	local_fiq_disable();
+
+	enter = ktime_get();
+	if (!need_resched())
+		tegra_flow_wfi(dev);
+	exit = ktime_sub(ktime_get(), enter);
+	us = ktime_to_us(exit);
+
+	local_fiq_enable();
+	local_irq_enable();
+	return (int)us;
+}
+
+static int tegra_idle_enter_lp2(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	ktime_t enter, exit;
+	s64 us;
+
+	if (!lp2_in_idle || lp2_disabled_by_suspend)
+		return tegra_idle_enter_lp3(dev, state);
+
+	local_irq_disable();
+	local_fiq_disable();
+	enter = ktime_get();
+
+	idle_stats.cpu_ready_count[dev->cpu]++;
+
+#ifdef CONFIG_SMP
+	if (dev->cpu == 0)
+		tegra_idle_enter_lp2_cpu0(dev, state);
+	else
+		tegra_idle_enter_lp2_cpu1(dev, state);
+#else
+	tegra_idle_enter_lp2_cpu0(dev, state);
+#endif
+
+	exit = ktime_sub(ktime_get(), enter);
+	us = ktime_to_us(exit);
+
+	local_fiq_enable();
+	local_irq_enable();
+
+	/* cpu clockevents may have been reset by powerdown */
+	hrtimer_peek_ahead_timers();
+
+	smp_rmb();
+	state->exit_latency = tegra_lp2_exit_latency;
+	state->target_residency = tegra_lp2_exit_latency +
+		tegra_lp2_power_off_time;
+
+	idle_stats.cpu_wants_lp2_time[dev->cpu] += us;
+
+	return (int)us;
+}
+
+static int tegra_idle_enter(unsigned int cpu)
+{
+	struct cpuidle_device *dev;
+	struct cpuidle_state *state;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->state_count = 0;
+	dev->cpu = cpu;
+
+	tegra_lp2_power_off_time = tegra_cpu_power_off_time();
+
+	state = &dev->states[0];
+	snprintf(state->name, CPUIDLE_NAME_LEN, "LP3");
+	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU flow-controlled");
+	state->exit_latency = 10;
+	state->target_residency = 10;
+	state->power_usage = 600;
+	state->flags = CPUIDLE_FLAG_TIME_VALID;
+	state->enter = tegra_idle_enter_lp3;
+	dev->safe_state = state;
+	dev->state_count++;
+
+	state = &dev->states[1];
+	snprintf(state->name, CPUIDLE_NAME_LEN, "LP2");
+	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU power-gate");
+	state->exit_latency = tegra_cpu_power_good_time();
+
+	state->target_residency = tegra_cpu_power_off_time() +
+		tegra_cpu_power_good_time();
+	state->power_usage = 0;
+	state->flags = CPUIDLE_FLAG_TIME_VALID;
+	state->enter = tegra_idle_enter_lp2;
+
+	dev->power_specified = 1;
+	dev->safe_state = state;
+	dev->state_count++;
+
+	if (cpuidle_register_device(dev)) {
+		pr_err("CPU%u: failed to register idle device\n", cpu);
+		kfree(dev);
+		return -EIO;
+	}
+	per_cpu(idle_devices, cpu) = dev;
+	return 0;
+}
+
+/* The IRQs that are used for communication between the cpus to agree on the
+ * cpuidle state should never get handled
+ */
+static irqreturn_t tegra_cpuidle_irq(int irq, void *dev)
+{
+	pr_err("%s: unexpected interrupt %d on cpu %d\n", __func__, irq,
+		smp_processor_id());
+	BUG();
+}
+
+static int tegra_cpuidle_pm_notify(struct notifier_block *nb,
+	unsigned long event, void *dummy)
+{
+	if (event == PM_SUSPEND_PREPARE)
+		lp2_disabled_by_suspend = true;
+	else if (event == PM_POST_SUSPEND)
+		lp2_disabled_by_suspend = false;
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block tegra_cpuidle_pm_notifier = {
+	.notifier_call = tegra_cpuidle_pm_notify,
+};
+
+static int __init tegra_cpuidle_init(void)
+{
+	unsigned int cpu;
+	void __iomem *mask_arm;
+	unsigned int reg;
+	int ret;
+
+	irq_set_affinity(TEGRA_CPUIDLE_BOTH_IDLE, cpumask_of(0));
+	irq_set_affinity(TEGRA_CPUIDLE_TEAR_DOWN, cpumask_of(1));
+
+	ret = request_irq(TEGRA_CPUIDLE_BOTH_IDLE, tegra_cpuidle_irq,
+		IRQF_NOAUTOEN, "tegra_cpuidle_both_idle", NULL);
+	if (ret) {
+		pr_err("%s: Failed to request cpuidle irq\n", __func__);
+		return ret;
+	}
+
+	ret = request_irq(TEGRA_CPUIDLE_TEAR_DOWN, tegra_cpuidle_irq,
+		IRQF_NOAUTOEN, "tegra_cpuidle_tear_down_cpu1", NULL);
+	if (ret) {
+		pr_err("%s: Failed to request cpuidle irq\n", __func__);
+		return ret;
+	}
+
+
+	disable_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+	disable_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+	tegra_mask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+	tegra_mask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	mask_arm = IO_ADDRESS(TEGRA_CLK_RESET_BASE) + CLK_RESET_CLK_MASK_ARM;
+
+	reg = readl(mask_arm);
+	writel(reg | (1<<31), mask_arm);
+
+	ret = cpuidle_register_driver(&tegra_idle);
+
+	if (ret)
+		return ret;
+
+	for_each_possible_cpu(cpu) {
+		if (tegra_idle_enter(cpu))
+			pr_err("CPU%u: error initializing idle loop\n", cpu);
+	}
+
+	tegra_lp2_exit_latency = tegra_cpu_power_good_time();
+
+	register_pm_notifier(&tegra_cpuidle_pm_notifier);
+
+	return 0;
+}
+
+static void __exit tegra_cpuidle_exit(void)
+{
+	cpuidle_unregister_driver(&tegra_idle);
+}
+
+module_init(tegra_cpuidle_init);
+module_exit(tegra_cpuidle_exit);
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_lp2_debug_show(struct seq_file *s, void *data)
+{
+	int bin;
+	int i;
+	seq_printf(s, "                                    cpu0     cpu1\n");
+	seq_printf(s, "-------------------------------------------------\n");
+	seq_printf(s, "cpu ready:                      %8u %8u\n",
+		idle_stats.cpu_ready_count[0],
+		idle_stats.cpu_ready_count[1]);
+	seq_printf(s, "both idle:      %8u        %7u%% %7u%%\n",
+		idle_stats.both_idle_count,
+		idle_stats.both_idle_count * 100 /
+			(idle_stats.cpu_ready_count[0] ?: 1),
+		idle_stats.both_idle_count * 100 /
+			(idle_stats.cpu_ready_count[1] ?: 1));
+	seq_printf(s, "tear down:      %8u %7u%%\n", idle_stats.tear_down_count,
+		idle_stats.tear_down_count * 100 /
+			(idle_stats.both_idle_count ?: 1));
+	seq_printf(s, "lp2:            %8u %7u%%\n", idle_stats.lp2_count,
+		idle_stats.lp2_count * 100 /
+			(idle_stats.both_idle_count ?: 1));
+	seq_printf(s, "lp2 completed:  %8u %7u%%\n",
+		idle_stats.lp2_completed_count,
+		idle_stats.lp2_completed_count * 100 /
+			(idle_stats.lp2_count ?: 1));
+
+	seq_printf(s, "\n");
+	seq_printf(s, "cpu ready time:                 %8llu %8llu ms\n",
+		div64_u64(idle_stats.cpu_wants_lp2_time[0], 1000),
+		div64_u64(idle_stats.cpu_wants_lp2_time[1], 1000));
+	seq_printf(s, "lp2 time:       %8llu ms     %7d%% %7d%%\n",
+		div64_u64(idle_stats.in_lp2_time, 1000),
+		(int)div64_u64(idle_stats.in_lp2_time * 100,
+			idle_stats.cpu_wants_lp2_time[0] ?: 1),
+		(int)div64_u64(idle_stats.in_lp2_time * 100,
+			idle_stats.cpu_wants_lp2_time[1] ?: 1));
+
+	seq_printf(s, "\n");
+	seq_printf(s, "%19s %8s %8s %8s\n", "", "lp2", "comp", "%");
+	seq_printf(s, "-------------------------------------------------\n");
+	for (bin = 0; bin < 32; bin++) {
+		if (idle_stats.lp2_count_bin[bin] == 0)
+			continue;
+		seq_printf(s, "%6u - %6u ms: %8u %8u %7u%%\n",
+			1 << (bin - 1), 1 << bin,
+			idle_stats.lp2_count_bin[bin],
+			idle_stats.lp2_completed_count_bin[bin],
+			idle_stats.lp2_completed_count_bin[bin] * 100 /
+				idle_stats.lp2_count_bin[bin]);
+	}
+
+	seq_printf(s, "\n");
+	seq_printf(s, "%3s %20s %6s %10s\n",
+		"int", "name", "count", "last count");
+	seq_printf(s, "--------------------------------------------\n");
+	for (i = 0; i < NR_IRQS; i++) {
+		if (idle_stats.lp2_int_count[i] == 0)
+			continue;
+		seq_printf(s, "%3d %20s %6d %10d\n",
+			i, irq_to_desc(i)->action ?
+				irq_to_desc(i)->action->name ?: "???" : "???",
+			idle_stats.lp2_int_count[i],
+			idle_stats.lp2_int_count[i] -
+				idle_stats.last_lp2_int_count[i]);
+		idle_stats.last_lp2_int_count[i] = idle_stats.lp2_int_count[i];
+	};
+	return 0;
+}
+
+static int tegra_lp2_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, tegra_lp2_debug_show, inode->i_private);
+}
+
+static const struct file_operations tegra_lp2_debug_ops = {
+	.open		= tegra_lp2_debug_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int __init tegra_cpuidle_debug_init(void)
+{
+	struct dentry *dir;
+	struct dentry *d;
+
+	dir = debugfs_create_dir("cpuidle", NULL);
+	if (!dir)
+		return -ENOMEM;
+
+	d = debugfs_create_file("lp2", S_IRUGO, dir, NULL,
+		&tegra_lp2_debug_ops);
+	if (!d)
+		return -ENOMEM;
+
+	return 0;
+}
+#endif
+
+late_initcall(tegra_cpuidle_debug_init);
-- 
1.7.3.1


WARNING: multiple messages have this Message-ID (diff)
From: ccross@android.com (Colin Cross)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2 18/28] ARM: tegra: add CPU_IDLE driver
Date: Sun, 23 Jan 2011 18:01:23 -0800	[thread overview]
Message-ID: <1295834493-5019-19-git-send-email-ccross@android.com> (raw)
In-Reply-To: <1295834493-5019-1-git-send-email-ccross@android.com>

Supports clock-gated (LP3) SMP idle mode, and power-gated (LP2) idle.

Latency for LP2 idle state is calculated as a 2-sample weighted moving
average, to allow for future variations due to (e.g.) CPU frequency
scaling.

LP3 idle gates a single CPU core, but LP2 requires power gating both
CPU cores.  An interrupt is used to handshake between the two cores
to ensure both are ready to enter LP2, and another interrupt is used
to manage the process of entering LP2.

Signed-off-by: Gary King <gking@nvidia.com>
Signed-off-by: Colin Cross <ccross@android.com>
---
 arch/arm/mach-tegra/Makefile  |    1 +
 arch/arm/mach-tegra/cpuidle.c |  714 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 715 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-tegra/cpuidle.c

diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 0bf1441..75b1afa 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC)         += headsmp-t2.o
 obj-$(CONFIG_TEGRA_SYSTEM_DMA)		+= dma.o
 obj-$(CONFIG_CPU_FREQ)                  += cpu-tegra.o
 obj-$(CONFIG_TEGRA_PCI)			+= pcie.o
+obj-$(CONFIG_CPU_IDLE)			+= cpuidle.o
 
 obj-${CONFIG_MACH_HARMONY}              += board-harmony.o
 obj-${CONFIG_MACH_HARMONY}              += board-harmony-pinmux.o
diff --git a/arch/arm/mach-tegra/cpuidle.c b/arch/arm/mach-tegra/cpuidle.c
new file mode 100644
index 0000000..232e767
--- /dev/null
+++ b/arch/arm/mach-tegra/cpuidle.c
@@ -0,0 +1,714 @@
+/*
+ * arch/arm/mach-tegra/cpuidle.c
+ *
+ * CPU idle driver for Tegra CPUs
+ *
+ * Copyright (c) 2010, 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/cpu.h>
+#include <linux/cpuidle.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/smp.h>
+#include <linux/suspend.h>
+#include <linux/tick.h>
+
+#include <asm/cacheflush.h>
+#include <asm/hardware/gic.h>
+#include <asm/localtimer.h>
+
+#include <mach/iomap.h>
+#include <mach/irqs.h>
+#include <mach/legacy_irq.h>
+#include <mach/suspend.h>
+
+#include "power.h"
+
+#define TEGRA_CPUIDLE_BOTH_IDLE		INT_QUAD_RES_24
+#define TEGRA_CPUIDLE_TEAR_DOWN		INT_QUAD_RES_25
+
+#define EVP_CPU_RESET_VECTOR \
+	(IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE) + 0x100)
+#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x340)
+#define CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x344)
+#define CLK_RST_CONTROLLER_CLK_CPU_CMPLX \
+	(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x4c)
+
+static bool lp2_in_idle __read_mostly = true;
+static bool lp2_disabled_by_suspend;
+module_param(lp2_in_idle, bool, 0644);
+
+static s64 tegra_cpu1_idle_time = LLONG_MAX;
+static int tegra_lp2_exit_latency;
+static int tegra_lp2_power_off_time;
+
+static struct {
+	unsigned int cpu_ready_count[2];
+	unsigned long long cpu_wants_lp2_time[2];
+	unsigned long long in_lp2_time;
+	unsigned int both_idle_count;
+	unsigned int tear_down_count;
+	unsigned int lp2_count;
+	unsigned int lp2_completed_count;
+	unsigned int lp2_count_bin[32];
+	unsigned int lp2_completed_count_bin[32];
+	unsigned int lp2_int_count[NR_IRQS];
+	unsigned int last_lp2_int_count[NR_IRQS];
+} idle_stats;
+
+struct cpuidle_driver tegra_idle = {
+	.name = "tegra_idle",
+	.owner = THIS_MODULE,
+};
+
+static DEFINE_PER_CPU(struct cpuidle_device *, idle_devices);
+
+#define FLOW_CTRL_WAITEVENT   (2<<29)
+#define FLOW_CTRL_JTAG_RESUME (1<<28)
+#define FLOW_CTRL_HALT_CPUx_EVENTS(cpu) ((cpu) ? ((cpu - 1) * 0x8 + 0x14) : 0x0)
+
+#define PMC_SCRATCH_38 0x134
+#define PMC_SCRATCH_39 0x138
+
+#define CLK_RESET_CLK_MASK_ARM 0x44
+
+static inline unsigned int time_to_bin(unsigned int time)
+{
+	return fls(time);
+}
+
+static inline void tegra_unmask_irq(int irq)
+{
+	struct irq_chip *chip = get_irq_chip(irq);
+	struct irq_data *data = irq_get_irq_data(irq);
+	chip->irq_unmask(data);
+}
+
+static inline void tegra_mask_irq(int irq)
+{
+	struct irq_chip *chip = get_irq_chip(irq);
+	struct irq_data *data = irq_get_irq_data(irq);
+	chip->irq_mask(data);
+}
+
+static inline int tegra_pending_interrupt(void)
+{
+	void __iomem *gic_cpu = IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x100);
+	u32 reg = readl(gic_cpu + 0x18);
+	reg &= 0x3FF;
+
+	return reg;
+}
+
+static inline void tegra_flow_wfi(struct cpuidle_device *dev)
+{
+	void __iomem *flow_ctrl = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE);
+	u32 reg = FLOW_CTRL_WAITEVENT | FLOW_CTRL_JTAG_RESUME;
+
+	flow_ctrl = flow_ctrl + FLOW_CTRL_HALT_CPUx_EVENTS(dev->cpu);
+
+	dsb();
+	__raw_writel(reg, flow_ctrl);
+	reg = __raw_readl(flow_ctrl);
+	__asm__ volatile ("wfi");
+	__raw_writel(0, flow_ctrl);
+	reg = __raw_readl(flow_ctrl);
+}
+
+#ifdef CONFIG_SMP
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+	int wake_int;
+
+	tegra_unmask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	tegra_flow_wfi(dev);
+
+	wake_int = tegra_pending_interrupt();
+
+	tegra_mask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	return wake_int == TEGRA_CPUIDLE_BOTH_IDLE &&
+		tegra_pending_interrupt() == 1023;
+}
+
+static inline bool tegra_wait_for_tear_down(struct cpuidle_device *dev)
+{
+	int wake_int;
+	irq_set_affinity(TEGRA_CPUIDLE_TEAR_DOWN, cpumask_of(1));
+	tegra_unmask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	tegra_flow_wfi(dev);
+
+	wake_int = tegra_pending_interrupt();
+
+	tegra_mask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	return wake_int == TEGRA_CPUIDLE_TEAR_DOWN &&
+		tegra_pending_interrupt() == 1023;
+}
+
+static inline bool tegra_cpu_in_reset(int cpu)
+{
+	return !!(readl(CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET) & (1 << cpu));
+}
+
+static int tegra_tear_down_cpu1(void)
+{
+	u32 reg;
+
+	/* Signal to CPU1 to tear down */
+	tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	/* At this point, CPU0 can no longer abort LP2, but CP1 can */
+	/* TODO: any way not to poll here? Use the LP2 timer to wfi? */
+	/* takes ~80 us */
+	while (!tegra_cpu_in_reset(1) &&
+		tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+		cpu_relax();
+
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	/* If CPU1 aborted LP2, restart the process */
+	if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+		return -EAGAIN;
+
+	/* CPU1 is ready for LP2, clock gate it */
+	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+	writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+	return 0;
+}
+
+static void tegra_wake_cpu1(void)
+{
+	unsigned long boot_vector;
+	unsigned long old_boot_vector;
+	unsigned long timeout;
+	u32 reg;
+
+	boot_vector = virt_to_phys(tegra_hotplug_startup);
+	old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
+	writel(boot_vector, EVP_CPU_RESET_VECTOR);
+
+	/* enable cpu clock on cpu */
+	reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+	writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+	reg = 0x1111 << 1;
+	writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
+
+	/* unhalt the cpu */
+	writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+	while (time_before(jiffies, timeout)) {
+		if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
+			break;
+		udelay(10);
+	}
+
+	/* put the old boot vector back */
+	writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
+
+	/* CPU1 is now started */
+}
+#else
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+	return true;
+}
+
+static inline int tegra_tear_down_cpu1(void)
+{
+	return 0;
+}
+
+static inline void tegra_wake_cpu1(void)
+{
+}
+#endif
+
+static void tegra_idle_enter_lp2_cpu0(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	s64 request;
+	ktime_t enter;
+	ktime_t exit;
+	bool sleep_completed = false;
+	int bin;
+
+restart:
+	if (!tegra_wait_for_both_idle(dev))
+		return;
+
+	idle_stats.both_idle_count++;
+
+	if (need_resched())
+		return;
+
+	/* CPU1 woke CPU0 because both are idle */
+
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	if (request < state->target_residency) {
+		/* Not enough time left to enter LP2 */
+		tegra_flow_wfi(dev);
+		return;
+	}
+
+	idle_stats.tear_down_count++;
+
+	if (tegra_tear_down_cpu1())
+		goto restart;
+
+	/* Enter LP2 */
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	smp_rmb();
+	request = min_t(s64, request, tegra_cpu1_idle_time);
+
+	enter = ktime_get();
+	if (request > state->target_residency) {
+		s64 sleep_time = request - tegra_lp2_exit_latency;
+
+		bin = time_to_bin((u32)request / 1000);
+		idle_stats.lp2_count++;
+		idle_stats.lp2_count_bin[bin]++;
+
+		if (tegra_suspend_lp2(sleep_time) == 0)
+			sleep_completed = true;
+		else
+			idle_stats.lp2_int_count[tegra_pending_interrupt()]++;
+	}
+
+	/* Bring CPU1 out of LP2 */
+	/* TODO: polls for CPU1 to boot, wfi would be better */
+	/* takes ~80 us */
+
+	/* set the reset vector to point to the secondary_startup routine */
+	smp_wmb();
+
+	tegra_wake_cpu1();
+
+	/*
+	 * TODO: is it worth going back to wfi if no interrupt is pending
+	 * and the requested sleep time has not passed?
+	 */
+
+	exit = ktime_get();
+	if (sleep_completed) {
+		/*
+		 * Stayed in LP2 for the full time until the next tick,
+		 * adjust the exit latency based on measurement
+		 */
+		int offset = ktime_to_us(ktime_sub(exit, enter)) - request;
+		int latency = tegra_lp2_exit_latency + offset / 16;
+		latency = clamp(latency, 0, 10000);
+		tegra_lp2_exit_latency = latency;
+		smp_wmb();
+
+		idle_stats.lp2_completed_count++;
+		idle_stats.lp2_completed_count_bin[bin]++;
+		idle_stats.in_lp2_time += ktime_to_us(ktime_sub(exit, enter));
+
+		pr_debug("%lld %lld %d %d\n", request,
+			ktime_to_us(ktime_sub(exit, enter)),
+			offset, bin);
+	}
+}
+
+#ifdef CONFIG_SMP
+static void tegra_idle_enter_lp2_cpu1(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	u32 twd_ctrl;
+	u32 twd_load;
+	s64 request;
+
+	tegra_legacy_force_irq_set(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	if (!tegra_wait_for_tear_down(dev))
+		goto out;
+
+	if (need_resched())
+		goto out;
+
+	/*
+	 * CPU1 woke CPU0 because both were idle
+	 * CPU0 responded by waking CPU1 to tell it to disable itself
+	 */
+
+	request = ktime_to_us(tick_nohz_get_sleep_length());
+	if (request < tegra_lp2_exit_latency) {
+		/*
+		 * Not enough time left to enter LP2
+		 * Signal to CPU0 that CPU1 rejects LP2, and stay in
+		 */
+		tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+		tegra_flow_wfi(dev);
+		goto out;
+	}
+
+	tegra_cpu1_idle_time = request;
+	smp_wmb();
+
+	/* Prepare CPU1 for LP2 by putting it in reset */
+
+	gic_cpu_exit(0);
+	barrier();
+	twd_ctrl = readl(twd_base + 0x8);
+	twd_load = readl(twd_base + 0);
+
+	flush_cache_all();
+	barrier();
+	__cortex_a9_save(0);
+	/* CPU1 is in reset, waiting for CPU0 to boot it, possibly after LP2 */
+
+
+	/* CPU0 booted CPU1 out of reset */
+	barrier();
+	cpu_init();
+	writel(twd_ctrl, twd_base + 0x8);
+	writel(twd_load, twd_base + 0);
+	gic_secondary_init(0);
+	tegra_unmask_irq(IRQ_LOCALTIMER);
+
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+
+	writel(smp_processor_id(), EVP_CPU_RESET_VECTOR);
+
+	/*
+	 * TODO: is it worth going back to wfi if no interrupt is pending
+	 * and the requested sleep time has not passed?
+	 */
+
+	return;
+
+out:
+	tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
+}
+#endif
+
+static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	ktime_t enter, exit;
+	s64 us;
+
+	local_irq_disable();
+	local_fiq_disable();
+
+	enter = ktime_get();
+	if (!need_resched())
+		tegra_flow_wfi(dev);
+	exit = ktime_sub(ktime_get(), enter);
+	us = ktime_to_us(exit);
+
+	local_fiq_enable();
+	local_irq_enable();
+	return (int)us;
+}
+
+static int tegra_idle_enter_lp2(struct cpuidle_device *dev,
+	struct cpuidle_state *state)
+{
+	ktime_t enter, exit;
+	s64 us;
+
+	if (!lp2_in_idle || lp2_disabled_by_suspend)
+		return tegra_idle_enter_lp3(dev, state);
+
+	local_irq_disable();
+	local_fiq_disable();
+	enter = ktime_get();
+
+	idle_stats.cpu_ready_count[dev->cpu]++;
+
+#ifdef CONFIG_SMP
+	if (dev->cpu == 0)
+		tegra_idle_enter_lp2_cpu0(dev, state);
+	else
+		tegra_idle_enter_lp2_cpu1(dev, state);
+#else
+	tegra_idle_enter_lp2_cpu0(dev, state);
+#endif
+
+	exit = ktime_sub(ktime_get(), enter);
+	us = ktime_to_us(exit);
+
+	local_fiq_enable();
+	local_irq_enable();
+
+	/* cpu clockevents may have been reset by powerdown */
+	hrtimer_peek_ahead_timers();
+
+	smp_rmb();
+	state->exit_latency = tegra_lp2_exit_latency;
+	state->target_residency = tegra_lp2_exit_latency +
+		tegra_lp2_power_off_time;
+
+	idle_stats.cpu_wants_lp2_time[dev->cpu] += us;
+
+	return (int)us;
+}
+
+static int tegra_idle_enter(unsigned int cpu)
+{
+	struct cpuidle_device *dev;
+	struct cpuidle_state *state;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->state_count = 0;
+	dev->cpu = cpu;
+
+	tegra_lp2_power_off_time = tegra_cpu_power_off_time();
+
+	state = &dev->states[0];
+	snprintf(state->name, CPUIDLE_NAME_LEN, "LP3");
+	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU flow-controlled");
+	state->exit_latency = 10;
+	state->target_residency = 10;
+	state->power_usage = 600;
+	state->flags = CPUIDLE_FLAG_TIME_VALID;
+	state->enter = tegra_idle_enter_lp3;
+	dev->safe_state = state;
+	dev->state_count++;
+
+	state = &dev->states[1];
+	snprintf(state->name, CPUIDLE_NAME_LEN, "LP2");
+	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU power-gate");
+	state->exit_latency = tegra_cpu_power_good_time();
+
+	state->target_residency = tegra_cpu_power_off_time() +
+		tegra_cpu_power_good_time();
+	state->power_usage = 0;
+	state->flags = CPUIDLE_FLAG_TIME_VALID;
+	state->enter = tegra_idle_enter_lp2;
+
+	dev->power_specified = 1;
+	dev->safe_state = state;
+	dev->state_count++;
+
+	if (cpuidle_register_device(dev)) {
+		pr_err("CPU%u: failed to register idle device\n", cpu);
+		kfree(dev);
+		return -EIO;
+	}
+	per_cpu(idle_devices, cpu) = dev;
+	return 0;
+}
+
+/* The IRQs that are used for communication between the cpus to agree on the
+ * cpuidle state should never get handled
+ */
+static irqreturn_t tegra_cpuidle_irq(int irq, void *dev)
+{
+	pr_err("%s: unexpected interrupt %d on cpu %d\n", __func__, irq,
+		smp_processor_id());
+	BUG();
+}
+
+static int tegra_cpuidle_pm_notify(struct notifier_block *nb,
+	unsigned long event, void *dummy)
+{
+	if (event == PM_SUSPEND_PREPARE)
+		lp2_disabled_by_suspend = true;
+	else if (event == PM_POST_SUSPEND)
+		lp2_disabled_by_suspend = false;
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block tegra_cpuidle_pm_notifier = {
+	.notifier_call = tegra_cpuidle_pm_notify,
+};
+
+static int __init tegra_cpuidle_init(void)
+{
+	unsigned int cpu;
+	void __iomem *mask_arm;
+	unsigned int reg;
+	int ret;
+
+	irq_set_affinity(TEGRA_CPUIDLE_BOTH_IDLE, cpumask_of(0));
+	irq_set_affinity(TEGRA_CPUIDLE_TEAR_DOWN, cpumask_of(1));
+
+	ret = request_irq(TEGRA_CPUIDLE_BOTH_IDLE, tegra_cpuidle_irq,
+		IRQF_NOAUTOEN, "tegra_cpuidle_both_idle", NULL);
+	if (ret) {
+		pr_err("%s: Failed to request cpuidle irq\n", __func__);
+		return ret;
+	}
+
+	ret = request_irq(TEGRA_CPUIDLE_TEAR_DOWN, tegra_cpuidle_irq,
+		IRQF_NOAUTOEN, "tegra_cpuidle_tear_down_cpu1", NULL);
+	if (ret) {
+		pr_err("%s: Failed to request cpuidle irq\n", __func__);
+		return ret;
+	}
+
+
+	disable_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+	disable_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+	tegra_mask_irq(TEGRA_CPUIDLE_BOTH_IDLE);
+	tegra_mask_irq(TEGRA_CPUIDLE_TEAR_DOWN);
+
+	mask_arm = IO_ADDRESS(TEGRA_CLK_RESET_BASE) + CLK_RESET_CLK_MASK_ARM;
+
+	reg = readl(mask_arm);
+	writel(reg | (1<<31), mask_arm);
+
+	ret = cpuidle_register_driver(&tegra_idle);
+
+	if (ret)
+		return ret;
+
+	for_each_possible_cpu(cpu) {
+		if (tegra_idle_enter(cpu))
+			pr_err("CPU%u: error initializing idle loop\n", cpu);
+	}
+
+	tegra_lp2_exit_latency = tegra_cpu_power_good_time();
+
+	register_pm_notifier(&tegra_cpuidle_pm_notifier);
+
+	return 0;
+}
+
+static void __exit tegra_cpuidle_exit(void)
+{
+	cpuidle_unregister_driver(&tegra_idle);
+}
+
+module_init(tegra_cpuidle_init);
+module_exit(tegra_cpuidle_exit);
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_lp2_debug_show(struct seq_file *s, void *data)
+{
+	int bin;
+	int i;
+	seq_printf(s, "                                    cpu0     cpu1\n");
+	seq_printf(s, "-------------------------------------------------\n");
+	seq_printf(s, "cpu ready:                      %8u %8u\n",
+		idle_stats.cpu_ready_count[0],
+		idle_stats.cpu_ready_count[1]);
+	seq_printf(s, "both idle:      %8u        %7u%% %7u%%\n",
+		idle_stats.both_idle_count,
+		idle_stats.both_idle_count * 100 /
+			(idle_stats.cpu_ready_count[0] ?: 1),
+		idle_stats.both_idle_count * 100 /
+			(idle_stats.cpu_ready_count[1] ?: 1));
+	seq_printf(s, "tear down:      %8u %7u%%\n", idle_stats.tear_down_count,
+		idle_stats.tear_down_count * 100 /
+			(idle_stats.both_idle_count ?: 1));
+	seq_printf(s, "lp2:            %8u %7u%%\n", idle_stats.lp2_count,
+		idle_stats.lp2_count * 100 /
+			(idle_stats.both_idle_count ?: 1));
+	seq_printf(s, "lp2 completed:  %8u %7u%%\n",
+		idle_stats.lp2_completed_count,
+		idle_stats.lp2_completed_count * 100 /
+			(idle_stats.lp2_count ?: 1));
+
+	seq_printf(s, "\n");
+	seq_printf(s, "cpu ready time:                 %8llu %8llu ms\n",
+		div64_u64(idle_stats.cpu_wants_lp2_time[0], 1000),
+		div64_u64(idle_stats.cpu_wants_lp2_time[1], 1000));
+	seq_printf(s, "lp2 time:       %8llu ms     %7d%% %7d%%\n",
+		div64_u64(idle_stats.in_lp2_time, 1000),
+		(int)div64_u64(idle_stats.in_lp2_time * 100,
+			idle_stats.cpu_wants_lp2_time[0] ?: 1),
+		(int)div64_u64(idle_stats.in_lp2_time * 100,
+			idle_stats.cpu_wants_lp2_time[1] ?: 1));
+
+	seq_printf(s, "\n");
+	seq_printf(s, "%19s %8s %8s %8s\n", "", "lp2", "comp", "%");
+	seq_printf(s, "-------------------------------------------------\n");
+	for (bin = 0; bin < 32; bin++) {
+		if (idle_stats.lp2_count_bin[bin] == 0)
+			continue;
+		seq_printf(s, "%6u - %6u ms: %8u %8u %7u%%\n",
+			1 << (bin - 1), 1 << bin,
+			idle_stats.lp2_count_bin[bin],
+			idle_stats.lp2_completed_count_bin[bin],
+			idle_stats.lp2_completed_count_bin[bin] * 100 /
+				idle_stats.lp2_count_bin[bin]);
+	}
+
+	seq_printf(s, "\n");
+	seq_printf(s, "%3s %20s %6s %10s\n",
+		"int", "name", "count", "last count");
+	seq_printf(s, "--------------------------------------------\n");
+	for (i = 0; i < NR_IRQS; i++) {
+		if (idle_stats.lp2_int_count[i] == 0)
+			continue;
+		seq_printf(s, "%3d %20s %6d %10d\n",
+			i, irq_to_desc(i)->action ?
+				irq_to_desc(i)->action->name ?: "???" : "???",
+			idle_stats.lp2_int_count[i],
+			idle_stats.lp2_int_count[i] -
+				idle_stats.last_lp2_int_count[i]);
+		idle_stats.last_lp2_int_count[i] = idle_stats.lp2_int_count[i];
+	};
+	return 0;
+}
+
+static int tegra_lp2_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, tegra_lp2_debug_show, inode->i_private);
+}
+
+static const struct file_operations tegra_lp2_debug_ops = {
+	.open		= tegra_lp2_debug_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int __init tegra_cpuidle_debug_init(void)
+{
+	struct dentry *dir;
+	struct dentry *d;
+
+	dir = debugfs_create_dir("cpuidle", NULL);
+	if (!dir)
+		return -ENOMEM;
+
+	d = debugfs_create_file("lp2", S_IRUGO, dir, NULL,
+		&tegra_lp2_debug_ops);
+	if (!d)
+		return -ENOMEM;
+
+	return 0;
+}
+#endif
+
+late_initcall(tegra_cpuidle_debug_init);
-- 
1.7.3.1

  parent reply	other threads:[~2011-01-24  2:06 UTC|newest]

Thread overview: 137+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-01-24  2:01 [PATCH v2 00/28] Updates for Tegra support in 2.6.39 Colin Cross
2011-01-24  2:01 ` [PATCH v2 01/28] ARM: tegra: irq: Rename gic pointers to avoid conflicts Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-26  7:10   ` Colin Cross
2011-01-26  7:10     ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 02/28] ARM: gic: Add functions to save and restore gic state Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-02-01 13:03   ` Russell King - ARM Linux
2011-02-01 13:03     ` Russell King - ARM Linux
2011-01-24  2:01 ` [PATCH v2 03/28] ARM: gic: Export irq chip functions Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  6:39   ` Santosh Shilimkar
2011-01-24  6:39     ` Santosh Shilimkar
2011-01-24  2:01 ` [PATCH v2 04/28] ARM: mm: cache-l2x0: Add support for re-enabling l2x0 Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-25 15:14   ` Catalin Marinas
2011-01-25 15:14     ` Catalin Marinas
2011-01-25 15:41     ` Russell King - ARM Linux
2011-01-25 15:41       ` Russell King - ARM Linux
2011-01-25 18:14       ` Catalin Marinas
2011-01-25 18:14         ` Catalin Marinas
2011-01-25 18:32         ` Santosh Shilimkar
2011-01-25 18:32           ` Santosh Shilimkar
2011-01-25 18:39           ` Will Deacon
     [not found]           ` <-8932138696981683633@unknownmsgid>
2011-02-04 23:32             ` Colin Cross
2011-02-04 23:32               ` Colin Cross
2011-02-04 23:32               ` Colin Cross
2011-02-04 23:43               ` Russell King - ARM Linux
2011-02-04 23:43                 ` Russell King - ARM Linux
     [not found]                 ` <20110204234331.GF8732-l+eeeJia6m9vn6HldHNs0ANdhmdF6hFW@public.gmane.org>
2011-02-05  1:44                   ` Colin Cross
2011-02-05  1:44                     ` Colin Cross
2011-02-05  1:44                     ` Colin Cross
     [not found]                     ` <AANLkTi=fHnivHXHnYrQvdP6JWbEA3t1X3DuBxj5gN3H0-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-02-05  7:51                       ` Santosh Shilimkar
2011-02-05  7:51                         ` Santosh Shilimkar
2011-02-05  7:51                         ` Santosh Shilimkar
     [not found]                         ` <1bebe4b5c8590059b70a146d5486fa6a-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-02-05  9:47                           ` Russell King - ARM Linux
2011-02-05  9:47                             ` Russell King - ARM Linux
2011-02-05  9:47                             ` Russell King - ARM Linux
2011-02-05 10:41                             ` [PATCH v2 04/28] ARM: mm: cache-l2x0: Add support forre-enabling l2x0 Santosh Shilimkar
2011-02-05 10:41                               ` Santosh Shilimkar
2011-02-05 16:36                               ` Colin Cross
2011-02-05 16:36                                 ` Colin Cross
     [not found]                                 ` <AANLkTik_r4k_5o+F47vRbGPcWLwfgHgWqhym49XfhBZ9-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-02-07  6:13                                   ` Santosh Shilimkar
2011-02-07  6:13                                     ` Santosh Shilimkar
2011-02-07  6:13                                     ` Santosh Shilimkar
2011-01-24  2:01 ` [PATCH v2 05/28] ARM: tegra: Centralize macros to define debug uart base Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 06/28] ARM: tegra: Add api to control internal powergating Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 07/28] ARM: tegra: irqs: Update irq list Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 08/28] ARM: tegra: Add prototypes for subsystem suspend functions Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 09/28] ARM: tegra: clock: Suspend fixes, and add new clocks Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 10/28] ARM: tegra: pinmux: Add missing drive pingroups and fix suspend Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 11/28] ARM: tegra: timer: Add idle and suspend support to timers Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 12/28] ARM: tegra: irq: Add support for suspend wake sources Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 13/28] ARM: tegra: Add suspend and hotplug support Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  7:31   ` Colin Cross
2011-01-24  7:31     ` Colin Cross
2011-01-24  9:07   ` Russell King - ARM Linux
2011-01-24  9:07     ` Russell King - ARM Linux
2011-01-24  9:26     ` Colin Cross
2011-01-24  9:26       ` Colin Cross
2011-01-24 10:15       ` Russell King - ARM Linux
2011-01-24 10:15         ` Russell King - ARM Linux
2011-01-24  2:01 ` [PATCH v2 14/28] ARM: tegra: irq: Add set_wake and set_type support for suspend Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 15/28] ARM: tegra: irq: Add debugfs file to show wake irqs Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-27 19:45   ` Stephen Boyd
2011-01-27 19:45     ` Stephen Boyd
2011-01-24  2:01 ` [PATCH v2 16/28] ARM: tegra: irq: Implement retrigger Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 17/28] ARM: tegra: gpio: Add support for waking from suspend Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` Colin Cross [this message]
2011-01-24  2:01   ` [PATCH v2 18/28] ARM: tegra: add CPU_IDLE driver Colin Cross
2011-01-27 20:38   ` Stephen Boyd
2011-01-27 20:38     ` Stephen Boyd
2011-01-27 21:51     ` Colin Cross
2011-01-27 21:51       ` Colin Cross
2011-01-27 22:07       ` Colin Cross
2011-01-27 22:07         ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 19/28] ARM: tegra: iomap: Add missing devices, fix use of SZ_8, SZ_64 Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 20/28] ARM: tegra: cpufreq: Disable cpufreq during suspend Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24 14:41   ` Mark Brown
2011-01-24 14:41     ` Mark Brown
2011-01-24 18:50     ` Colin Cross
2011-01-24 18:50       ` Colin Cross
2011-01-24 19:35       ` Mark Brown
2011-01-24 19:35         ` Mark Brown
2011-01-24 19:52         ` Colin Cross
2011-01-24 19:52           ` Colin Cross
2011-01-24 20:26           ` Mark Brown
2011-01-24 20:26             ` Mark Brown
2011-01-24 20:52             ` Colin Cross
2011-01-24 20:52               ` Colin Cross
2011-01-24 21:08               ` Mark Brown
2011-01-24 21:08                 ` Mark Brown
2011-01-24 21:24                 ` Colin Cross
2011-01-24 21:24                   ` Colin Cross
2011-01-25  4:26         ` Kyungmin Park
2011-01-25  4:26           ` Kyungmin Park
2011-01-24  2:01 ` [PATCH v2 21/28] ARM: tegra: Allow overriding arch_reset Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 22/28] ARM: tegra: dma: Fix critical data corruption bugs Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 23/28] ARM: tegra: add tegra_defconfig Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 24/28] ARM: tegra: Use writel_relaxed in tegra_init_cache Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 25/28] ARM: tegra: suspend: Save protected aperture across LP0 Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 26/28] ARM: tegra: suspend: protect suspend functions with CONFIG_PM Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 27/28] ARM: tegra: enable emc clock updates after lp0 Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24  2:01 ` [PATCH v2 28/28] ARM: tegra: clock: Add forward reference to struct clk Colin Cross
2011-01-24  2:01   ` Colin Cross
2011-01-24 10:26   ` Russell King - ARM Linux
2011-01-24 10:26     ` Russell King - ARM Linux
2011-01-25  1:23     ` Colin Cross
2011-01-25  1:23       ` Colin Cross
2011-01-26  3:19   ` Grant Likely
2011-01-26  3:19     ` Grant Likely
2011-01-26  3:21     ` Colin Cross
2011-01-26  3:21       ` Colin Cross
2011-01-26  4:16       ` Grant Likely
2011-01-26  4:16         ` Grant Likely

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1295834493-5019-19-git-send-email-ccross@android.com \
    --to=ccross@android.com \
    --cc=gking@nvidia.com \
    --cc=konkers@android.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-tegra@vger.kernel.org \
    --cc=linux@arm.linux.org.uk \
    --cc=olof@lixom.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.