summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/pm.c
diff options
context:
space:
mode:
authorJoseph Lo <josephl@nvidia.com>2012-10-31 17:41:21 +0800
committerStephen Warren <swarren@nvidia.com>2012-11-15 15:09:22 -0700
commitd552920a02759cdc45d8507868de10ac2f5b9a18 (patch)
tree2c3c5f805e1657f64088631e63c145cc43739608 /arch/arm/mach-tegra/pm.c
parent01459c69dd48badeb7833c3293e43f7b8ae75e31 (diff)
downloadlinux-d552920a02759cdc45d8507868de10ac2f5b9a18.tar.gz
linux-d552920a02759cdc45d8507868de10ac2f5b9a18.tar.bz2
linux-d552920a02759cdc45d8507868de10ac2f5b9a18.zip
ARM: tegra30: cpuidle: add powered-down state for CPU0
This is a power gating idle mode. It support power gating vdd_cpu rail after all cpu cores in "powered-down" status. For Tegra30, the CPU0 can enter this state only when all secondary CPU is offline. We need to take care and make sure whole secondary CPUs were offline and checking the CPU power gate status. After that, the CPU0 can go into "powered-down" state safely. Then shut off the CPU rail. Be aware of that, you may see the legacy power state "LP2" in the code which is exactly the same meaning of "CPU power down". Base on the work by: Scott Williams <scwilliams@nvidia.com> Signed-off-by: Joseph Lo <josephl@nvidia.com> Signed-off-by: Stephen Warren <swarren@nvidia.com>
Diffstat (limited to 'arch/arm/mach-tegra/pm.c')
-rw-r--r--arch/arm/mach-tegra/pm.c144
1 files changed, 144 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
index f88595abc471..1460c3db8245 100644
--- a/arch/arm/mach-tegra/pm.c
+++ b/arch/arm/mach-tegra/pm.c
@@ -20,13 +20,36 @@
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/cpu_pm.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <asm/smp_plat.h>
+#include <asm/cacheflush.h>
+#include <asm/suspend.h>
+#include <asm/idmap.h>
+#include <asm/proc-fns.h>
+#include <asm/tlbflush.h>
#include "iomap.h"
#include "reset.h"
+#include "flowctrl.h"
+#include "sleep.h"
+#include "tegra_cpu_car.h"
+
+#define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */
+
+#define PMC_CTRL 0x0
+#define PMC_CPUPWRGOOD_TIMER 0xc8
+#define PMC_CPUPWROFF_TIMER 0xcc
#ifdef CONFIG_PM_SLEEP
static unsigned int g_diag_reg;
static DEFINE_SPINLOCK(tegra_lp2_lock);
+static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
+static struct clk *tegra_pclk;
+void (*tegra_tear_down_cpu)(void);
void save_cpu_arch_register(void)
{
@@ -42,6 +65,89 @@ void restore_cpu_arch_register(void)
return;
}
+static void set_power_timers(unsigned long us_on, unsigned long us_off)
+{
+ unsigned long long ticks;
+ unsigned long long pclk;
+ unsigned long rate;
+ static unsigned long tegra_last_pclk;
+
+ if (tegra_pclk == NULL) {
+ tegra_pclk = clk_get_sys(NULL, "pclk");
+ WARN_ON(IS_ERR(tegra_pclk));
+ }
+
+ rate = clk_get_rate(tegra_pclk);
+
+ if (WARN_ON_ONCE(rate <= 0))
+ pclk = 100000000;
+ else
+ pclk = rate;
+
+ if ((rate != tegra_last_pclk)) {
+ ticks = (us_on * pclk) + 999999ull;
+ do_div(ticks, 1000000);
+ writel((unsigned long)ticks, pmc + PMC_CPUPWRGOOD_TIMER);
+
+ ticks = (us_off * pclk) + 999999ull;
+ do_div(ticks, 1000000);
+ writel((unsigned long)ticks, pmc + PMC_CPUPWROFF_TIMER);
+ wmb();
+ }
+ tegra_last_pclk = pclk;
+}
+
+/*
+ * restore_cpu_complex
+ *
+ * restores cpu clock setting, clears flow controller
+ *
+ * Always called on CPU 0.
+ */
+static void restore_cpu_complex(void)
+{
+ int cpu = smp_processor_id();
+
+ BUG_ON(cpu != 0);
+
+#ifdef CONFIG_SMP
+ cpu = cpu_logical_map(cpu);
+#endif
+
+ /* Restore the CPU clock settings */
+ tegra_cpu_clock_resume();
+
+ flowctrl_cpu_suspend_exit(cpu);
+
+ restore_cpu_arch_register();
+}
+
+/*
+ * suspend_cpu_complex
+ *
+ * saves pll state for use by restart_plls, prepares flow controller for
+ * transition to suspend state
+ *
+ * Must always be called on cpu 0.
+ */
+static void suspend_cpu_complex(void)
+{
+ int cpu = smp_processor_id();
+
+ BUG_ON(cpu != 0);
+
+#ifdef CONFIG_SMP
+ cpu = cpu_logical_map(cpu);
+#endif
+
+ /* Save the CPU clock settings */
+ tegra_cpu_clock_suspend();
+
+ flowctrl_cpu_suspend_enter(cpu);
+
+ save_cpu_arch_register();
+}
+
void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id)
{
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;
@@ -71,4 +177,42 @@ bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id)
spin_unlock(&tegra_lp2_lock);
return last_cpu;
}
+
+static int tegra_sleep_cpu(unsigned long v2p)
+{
+ /* Switch to the identity mapping. */
+ cpu_switch_mm(idmap_pgd, &init_mm);
+
+ /* Flush the TLB. */
+ local_flush_tlb_all();
+
+ tegra_sleep_cpu_finish(v2p);
+
+ /* should never here */
+ BUG();
+
+ return 0;
+}
+
+void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time)
+{
+ u32 mode;
+
+ /* Only the last cpu down does the final suspend steps */
+ mode = readl(pmc + PMC_CTRL);
+ mode |= TEGRA_POWER_CPU_PWRREQ_OE;
+ writel(mode, pmc + PMC_CTRL);
+
+ set_power_timers(cpu_on_time, cpu_off_time);
+
+ cpu_cluster_pm_enter();
+ suspend_cpu_complex();
+ outer_disable();
+
+ cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);
+
+ outer_resume();
+ restore_cpu_complex();
+ cpu_cluster_pm_exit();
+}
#endif