summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlof Johansson <olof@lixom.net>2012-11-21 08:29:02 (GMT)
committerOlof Johansson <olof@lixom.net>2012-11-21 10:13:08 (GMT)
commitd5fe60d37913707e331ead1248a3bf995acec034 (patch)
treea8baa1cd671243a0647abf184d76b119a1c44675
parent5e505bb9e7ffcebf02e760425de55d2c52127cef (diff)
parent29a0e7beab8e05be53d5b9c050253a4dc417041e (diff)
downloadlinux-d5fe60d37913707e331ead1248a3bf995acec034.tar.xz
Merge tag 'tegra-for-3.8-cpuidle' of git://git.kernel.org/pub/scm/linux/kernel/git/swarren/linux-tegra into next/soc
From Stephen Warren: ARM: tegra: cpuidle enhancements A cpuidle state "LP2" is added, which power-gates the CPUs. Support for CPUs 1..n is essentially complete, although support for CPU0 could benefit from future use of coupled-cpuidle or similar techniques. A couple of very minor cleanups to cpuidle were included too. This pull request is based on tegra-for-3.8-soc. * tag 'tegra-for-3.8-cpuidle' of git://git.kernel.org/pub/scm/linux/kernel/git/swarren/linux-tegra: ARM: tegra: retain L2 content over CPU suspend/resume ARM: tegra30: cpuidle: add powered-down state for CPU0 ARM: tegra30: flowctrl: add cpu_suspend_exter/exit function ARM: tegra30: clocks: add CPU low-power function into tegra_cpu_car_ops ARM: tegra30: common: enable csite clock ARM: tegra30: cpuidle: add powered-down state for secondary CPUs ARM: tegra: cpuidle: add CPU resume function ARM: tegra: cpuidle: separate cpuidle driver for different chips ARM: tegra: rename the file of "sleep-tXX" to "sleep-tegraXX" ARM: tegra: cpuidle: replace LP3 with ARM_CPUIDLE_WFI_STATE
-rw-r--r--arch/arm/mach-tegra/Makefile11
-rw-r--r--arch/arm/mach-tegra/common.c7
-rw-r--r--arch/arm/mach-tegra/cpuidle-tegra20.c66
-rw-r--r--arch/arm/mach-tegra/cpuidle-tegra30.c188
-rw-r--r--arch/arm/mach-tegra/cpuidle.c83
-rw-r--r--arch/arm/mach-tegra/cpuidle.h32
-rw-r--r--arch/arm/mach-tegra/flowctrl.c47
-rw-r--r--arch/arm/mach-tegra/flowctrl.h8
-rw-r--r--arch/arm/mach-tegra/headsmp.S71
-rw-r--r--arch/arm/mach-tegra/pm.c216
-rw-r--r--arch/arm/mach-tegra/pm.h35
-rw-r--r--arch/arm/mach-tegra/reset.c6
-rw-r--r--arch/arm/mach-tegra/reset.h9
-rw-r--r--arch/arm/mach-tegra/sleep-tegra20.S (renamed from arch/arm/mach-tegra/sleep-t20.S)0
-rw-r--r--arch/arm/mach-tegra/sleep-tegra30.S (renamed from arch/arm/mach-tegra/sleep-t30.S)66
-rw-r--r--arch/arm/mach-tegra/sleep.S78
-rw-r--r--arch/arm/mach-tegra/sleep.h37
-rw-r--r--arch/arm/mach-tegra/tegra30_clocks.c108
-rw-r--r--arch/arm/mach-tegra/tegra_cpu_car.h37
19 files changed, 1032 insertions, 73 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index 6cc23cc..0979e8b 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -8,17 +8,24 @@ obj-y += pmc.o
obj-y += flowctrl.o
obj-y += powergate.o
obj-y += apbio.o
+obj-y += pm.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
obj-$(CONFIG_CPU_IDLE) += sleep.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_clocks.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_clocks_data.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_speedo.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_emc.o
-obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-t20.o
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += sleep-tegra20.o
+ifeq ($(CONFIG_CPU_IDLE),y)
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += cpuidle-tegra20.o
+endif
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_clocks.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_clocks_data.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30_speedo.o
-obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-t30.o
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += sleep-tegra30.o
+ifeq ($(CONFIG_CPU_IDLE),y)
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += cpuidle-tegra30.o
+endif
obj-$(CONFIG_SMP) += platsmp.o headsmp.o
obj-$(CONFIG_SMP) += reset.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
diff --git a/arch/arm/mach-tegra/common.c b/arch/arm/mach-tegra/common.c
index 3e03e5f..11a74db 100644
--- a/arch/arm/mach-tegra/common.c
+++ b/arch/arm/mach-tegra/common.c
@@ -36,6 +36,7 @@
#include "pmc.h"
#include "apbio.h"
#include "sleep.h"
+#include "pm.h"
/*
* Storage for debug-macro.S's state.
@@ -108,6 +109,7 @@ static __initdata struct tegra_clk_init_table tegra30_clk_init_table[] = {
{ "sclk", "pll_p_out4", 102000000, true },
{ "hclk", "sclk", 102000000, true },
{ "pclk", "hclk", 51000000, true },
+ { "csite", NULL, 0, true },
{ NULL, NULL, 0, 0},
};
#endif
@@ -116,6 +118,7 @@ static __initdata struct tegra_clk_init_table tegra30_clk_init_table[] = {
static void __init tegra_init_cache(void)
{
#ifdef CONFIG_CACHE_L2X0
+ int ret;
void __iomem *p = IO_ADDRESS(TEGRA_ARM_PERIF_BASE) + 0x3000;
u32 aux_ctrl, cache_type;
@@ -123,7 +126,9 @@ static void __init tegra_init_cache(void)
aux_ctrl = (cache_type & 0x700) << (17-8);
aux_ctrl |= 0x7C400001;
- l2x0_of_init(aux_ctrl, 0x8200c3fe);
+ ret = l2x0_of_init(aux_ctrl, 0x8200c3fe);
+ if (!ret)
+ l2x0_saved_regs_addr = virt_to_phys(&l2x0_saved_regs);
#endif
}
diff --git a/arch/arm/mach-tegra/cpuidle-tegra20.c b/arch/arm/mach-tegra/cpuidle-tegra20.c
new file mode 100644
index 0000000..d32e8b0
--- /dev/null
+++ b/arch/arm/mach-tegra/cpuidle-tegra20.c
@@ -0,0 +1,66 @@
+/*
+ * CPU idle driver for Tegra CPUs
+ *
+ * Copyright (c) 2010-2012, NVIDIA Corporation.
+ * Copyright (c) 2011 Google, Inc.
+ * Author: Colin Cross <ccross@android.com>
+ * Gary King <gking@nvidia.com>
+ *
+ * Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cpuidle.h>
+
+#include <asm/cpuidle.h>
+
+static struct cpuidle_driver tegra_idle_driver = {
+ .name = "tegra_idle",
+ .owner = THIS_MODULE,
+ .en_core_tk_irqen = 1,
+ .state_count = 1,
+ .states = {
+ [0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
+ },
+};
+
+static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
+
+int __init tegra20_cpuidle_init(void)
+{
+ int ret;
+ unsigned int cpu;
+ struct cpuidle_device *dev;
+ struct cpuidle_driver *drv = &tegra_idle_driver;
+
+ ret = cpuidle_register_driver(&tegra_idle_driver);
+ if (ret) {
+ pr_err("CPUidle driver registration failed\n");
+ return ret;
+ }
+
+ for_each_possible_cpu(cpu) {
+ dev = &per_cpu(tegra_idle_device, cpu);
+ dev->cpu = cpu;
+
+ dev->state_count = drv->state_count;
+ ret = cpuidle_register_device(dev);
+ if (ret) {
+ pr_err("CPU%u: CPUidle device registration failed\n",
+ cpu);
+ return ret;
+ }
+ }
+ return 0;
+}
diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c
new file mode 100644
index 0000000..5e8cbf5
--- /dev/null
+++ b/arch/arm/mach-tegra/cpuidle-tegra30.c
@@ -0,0 +1,188 @@
+/*
+ * CPU idle driver for Tegra CPUs
+ *
+ * Copyright (c) 2010-2012, NVIDIA Corporation.
+ * Copyright (c) 2011 Google, Inc.
+ * Author: Colin Cross <ccross@android.com>
+ * Gary King <gking@nvidia.com>
+ *
+ * Rework for 3.3 by Peter De Schrijver <pdeschrijver@nvidia.com>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cpuidle.h>
+#include <linux/cpu_pm.h>
+#include <linux/clockchips.h>
+
+#include <asm/cpuidle.h>
+#include <asm/proc-fns.h>
+#include <asm/suspend.h>
+#include <asm/smp_plat.h>
+
+#include "pm.h"
+#include "sleep.h"
+#include "tegra_cpu_car.h"
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra30_idle_lp2(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index);
+#endif
+
+static struct cpuidle_driver tegra_idle_driver = {
+ .name = "tegra_idle",
+ .owner = THIS_MODULE,
+ .en_core_tk_irqen = 1,
+#ifdef CONFIG_PM_SLEEP
+ .state_count = 2,
+#else
+ .state_count = 1,
+#endif
+ .states = {
+ [0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
+#ifdef CONFIG_PM_SLEEP
+ [1] = {
+ .enter = tegra30_idle_lp2,
+ .exit_latency = 2000,
+ .target_residency = 2200,
+ .power_usage = 0,
+ .flags = CPUIDLE_FLAG_TIME_VALID,
+ .name = "powered-down",
+ .desc = "CPU power gated",
+ },
+#endif
+ },
+};
+
+static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
+
+#ifdef CONFIG_PM_SLEEP
+static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ struct cpuidle_state *state = &drv->states[index];
+ u32 cpu_on_time = state->exit_latency;
+ u32 cpu_off_time = state->target_residency - state->exit_latency;
+
+ /* All CPUs entering LP2 is not working.
+ * Don't let CPU0 enter LP2 when any secondary CPU is online.
+ */
+ if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) {
+ cpu_do_idle();
+ return false;
+ }
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
+
+ tegra_idle_lp2_last(cpu_on_time, cpu_off_time);
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
+
+ return true;
+}
+
+#ifdef CONFIG_SMP
+static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
+
+ smp_wmb();
+
+ save_cpu_arch_register();
+
+ cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);
+
+ restore_cpu_arch_register();
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
+
+ return true;
+}
+#else
+static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ return true;
+}
+#endif
+
+static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv,
+ int index)
+{
+ u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu;
+ bool entered_lp2 = false;
+ bool last_cpu;
+
+ local_fiq_disable();
+
+ last_cpu = tegra_set_cpu_in_lp2(cpu);
+ cpu_pm_enter();
+
+ if (cpu == 0) {
+ if (last_cpu)
+ entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv,
+ index);
+ else
+ cpu_do_idle();
+ } else {
+ entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index);
+ }
+
+ cpu_pm_exit();
+ tegra_clear_cpu_in_lp2(cpu);
+
+ local_fiq_enable();
+
+ smp_rmb();
+
+ return (entered_lp2) ? index : 0;
+}
+#endif
+
+int __init tegra30_cpuidle_init(void)
+{
+ int ret;
+ unsigned int cpu;
+ struct cpuidle_device *dev;
+ struct cpuidle_driver *drv = &tegra_idle_driver;
+
+#ifdef CONFIG_PM_SLEEP
+ tegra_tear_down_cpu = tegra30_tear_down_cpu;
+#endif
+
+ ret = cpuidle_register_driver(&tegra_idle_driver);
+ if (ret) {
+ pr_err("CPUidle driver registration failed\n");
+ return ret;
+ }
+
+ for_each_possible_cpu(cpu) {
+ dev = &per_cpu(tegra_idle_device, cpu);
+ dev->cpu = cpu;
+
+ dev->state_count = drv->state_count;
+ ret = cpuidle_register_device(dev);
+ if (ret) {
+ pr_err("CPU%u: CPUidle device registration failed\n",
+ cpu);
+ return ret;
+ }
+ }
+ return 0;
+}
diff --git a/arch/arm/mach-tegra/cpuidle.c b/arch/arm/mach-tegra/cpuidle.c
index 9a6f051..d065139 100644
--- a/arch/arm/mach-tegra/cpuidle.c
+++ b/arch/arm/mach-tegra/cpuidle.c
@@ -23,83 +23,26 @@
#include <linux/kernel.h>
#include <linux/module.h>
-#include <linux/cpu.h>
-#include <linux/cpuidle.h>
-#include <linux/hrtimer.h>
-#include <asm/proc-fns.h>
-
-static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
- struct cpuidle_driver *drv, int index);
-
-struct cpuidle_driver tegra_idle_driver = {
- .name = "tegra_idle",
- .owner = THIS_MODULE,
- .state_count = 1,
- .states = {
- [0] = {
- .enter = tegra_idle_enter_lp3,
- .exit_latency = 10,
- .target_residency = 10,
- .power_usage = 600,
- .flags = CPUIDLE_FLAG_TIME_VALID,
- .name = "LP3",
- .desc = "CPU flow-controlled",
- },
- },
-};
-
-static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
-
-static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
- struct cpuidle_driver *drv, int index)
-{
- ktime_t enter, exit;
- s64 us;
-
- local_irq_disable();
- local_fiq_disable();
-
- enter = ktime_get();
-
- cpu_do_idle();
-
- exit = ktime_sub(ktime_get(), enter);
- us = ktime_to_us(exit);
-
- local_fiq_enable();
- local_irq_enable();
-
- dev->last_residency = us;
-
- return index;
-}
+#include "fuse.h"
+#include "cpuidle.h"
static int __init tegra_cpuidle_init(void)
{
int ret;
- unsigned int cpu;
- struct cpuidle_device *dev;
- struct cpuidle_driver *drv = &tegra_idle_driver;
- ret = cpuidle_register_driver(&tegra_idle_driver);
- if (ret) {
- pr_err("CPUidle driver registration failed\n");
- return ret;
+ switch (tegra_chip_id) {
+ case TEGRA20:
+ ret = tegra20_cpuidle_init();
+ break;
+ case TEGRA30:
+ ret = tegra30_cpuidle_init();
+ break;
+ default:
+ ret = -ENODEV;
+ break;
}
- for_each_possible_cpu(cpu) {
- dev = &per_cpu(tegra_idle_device, cpu);
- dev->cpu = cpu;
-
- dev->state_count = drv->state_count;
- ret = cpuidle_register_device(dev);
- if (ret) {
- pr_err("CPU%u: CPUidle device registration failed\n",
- cpu);
- return ret;
- }
- }
- return 0;
+ return ret;
}
device_initcall(tegra_cpuidle_init);
diff --git a/arch/arm/mach-tegra/cpuidle.h b/arch/arm/mach-tegra/cpuidle.h
new file mode 100644
index 0000000..496204d
--- /dev/null
+++ b/arch/arm/mach-tegra/cpuidle.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012, NVIDIA Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MACH_TEGRA_CPUIDLE_H
+#define __MACH_TEGRA_CPUIDLE_H
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+int tegra20_cpuidle_init(void);
+#else
+static inline int tegra20_cpuidle_init(void) { return -ENODEV; }
+#endif
+
+#ifdef CONFIG_ARCH_TEGRA_3x_SOC
+int tegra30_cpuidle_init(void);
+#else
+static inline int tegra30_cpuidle_init(void) { return -ENODEV; }
+#endif
+
+#endif
diff --git a/arch/arm/mach-tegra/flowctrl.c b/arch/arm/mach-tegra/flowctrl.c
index ffaa286..a2250dd 100644
--- a/arch/arm/mach-tegra/flowctrl.c
+++ b/arch/arm/mach-tegra/flowctrl.c
@@ -21,6 +21,7 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/io.h>
+#include <linux/cpumask.h>
#include "flowctrl.h"
#include "iomap.h"
@@ -50,6 +51,14 @@ static void flowctrl_update(u8 offset, u32 value)
readl_relaxed(addr);
}
+u32 flowctrl_read_cpu_csr(unsigned int cpuid)
+{
+ u8 offset = flowctrl_offset_cpu_csr[cpuid];
+ void __iomem *addr = IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + offset;
+
+ return readl(addr);
+}
+
void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value)
{
return flowctrl_update(flowctrl_offset_cpu_csr[cpuid], value);
@@ -59,3 +68,41 @@ void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value)
{
return flowctrl_update(flowctrl_offset_halt_cpu[cpuid], value);
}
+
+void flowctrl_cpu_suspend_enter(unsigned int cpuid)
+{
+ unsigned int reg;
+ int i;
+
+ reg = flowctrl_read_cpu_csr(cpuid);
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; /* clear wfe bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP; /* clear wfi bitmap */
+ reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr flag */
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event flag */
+ reg |= TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 << cpuid; /* pwr gating on wfi */
+ reg |= FLOW_CTRL_CSR_ENABLE; /* pwr gating */
+ flowctrl_write_cpu_csr(cpuid, reg);
+
+ for (i = 0; i < num_possible_cpus(); i++) {
+ if (i == cpuid)
+ continue;
+ reg = flowctrl_read_cpu_csr(i);
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG;
+ reg |= FLOW_CTRL_CSR_INTR_FLAG;
+ flowctrl_write_cpu_csr(i, reg);
+ }
+}
+
+void flowctrl_cpu_suspend_exit(unsigned int cpuid)
+{
+ unsigned int reg;
+
+ /* Disable powergating via flow controller for CPU0 */
+ reg = flowctrl_read_cpu_csr(cpuid);
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP; /* clear wfe bitmap */
+ reg &= ~TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP; /* clear wfi bitmap */
+ reg &= ~FLOW_CTRL_CSR_ENABLE; /* clear enable */
+ reg |= FLOW_CTRL_CSR_INTR_FLAG; /* clear intr */
+ reg |= FLOW_CTRL_CSR_EVENT_FLAG; /* clear event */
+ flowctrl_write_cpu_csr(cpuid, reg);
+}
diff --git a/arch/arm/mach-tegra/flowctrl.h b/arch/arm/mach-tegra/flowctrl.h
index 1942817..0798dec 100644
--- a/arch/arm/mach-tegra/flowctrl.h
+++ b/arch/arm/mach-tegra/flowctrl.h
@@ -34,9 +34,17 @@
#define FLOW_CTRL_HALT_CPU1_EVENTS 0x14
#define FLOW_CTRL_CPU1_CSR 0x18
+#define TEGRA30_FLOW_CTRL_CSR_WFI_CPU0 (1 << 8)
+#define TEGRA30_FLOW_CTRL_CSR_WFE_BITMAP (0xF << 4)
+#define TEGRA30_FLOW_CTRL_CSR_WFI_BITMAP (0xF << 8)
+
#ifndef __ASSEMBLY__
+u32 flowctrl_read_cpu_csr(unsigned int cpuid);
void flowctrl_write_cpu_csr(unsigned int cpuid, u32 value);
void flowctrl_write_cpu_halt(unsigned int cpuid, u32 value);
+
+void flowctrl_cpu_suspend_enter(unsigned int cpuid);
+void flowctrl_cpu_suspend_exit(unsigned int cpuid);
#endif
#endif
diff --git a/arch/arm/mach-tegra/headsmp.S b/arch/arm/mach-tegra/headsmp.S
index 93f0370..4a317fa 100644
--- a/arch/arm/mach-tegra/headsmp.S
+++ b/arch/arm/mach-tegra/headsmp.S
@@ -2,6 +2,8 @@
#include <linux/init.h>
#include <asm/cache.h>
+#include <asm/asm-offsets.h>
+#include <asm/hardware/cache-l2x0.h>
#include "flowctrl.h"
#include "iomap.h"
@@ -68,6 +70,64 @@ ENTRY(tegra_secondary_startup)
b secondary_startup
ENDPROC(tegra_secondary_startup)
+#ifdef CONFIG_PM_SLEEP
+/*
+ * tegra_resume
+ *
+ * CPU boot vector when restarting the a CPU following
+ * an LP2 transition. Also branched to by LP0 and LP1 resume after
+ * re-enabling sdram.
+ */
+ENTRY(tegra_resume)
+ bl v7_invalidate_l1
+ /* Enable coresight */
+ mov32 r0, 0xC5ACCE55
+ mcr p14, 0, r0, c7, c12, 6
+
+ cpu_id r0
+ cmp r0, #0 @ CPU0?
+ bne cpu_resume @ no
+
+#ifdef CONFIG_ARCH_TEGRA_3x_SOC
+ /* Are we on Tegra20? */
+ mov32 r6, TEGRA_APB_MISC_BASE
+ ldr r0, [r6, #APB_MISC_GP_HIDREV]
+ and r0, r0, #0xff00
+ cmp r0, #(0x20 << 8)
+ beq 1f @ Yes
+ /* Clear the flow controller flags for this CPU. */
+ mov32 r2, TEGRA_FLOW_CTRL_BASE + FLOW_CTRL_CPU0_CSR @ CPU0 CSR
+ ldr r1, [r2]
+ /* Clear event & intr flag */
+ orr r1, r1, \
+ #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG
+ movw r0, #0x0FFD @ enable, cluster_switch, immed, & bitmaps
+ bic r1, r1, r0
+ str r1, [r2]
+1:
+#endif
+
+#ifdef CONFIG_HAVE_ARM_SCU
+ /* enable SCU */
+ mov32 r0, TEGRA_ARM_PERIF_BASE
+ ldr r1, [r0]
+ orr r1, r1, #1
+ str r1, [r0]
+#endif
+
+ /* L2 cache resume & re-enable */
+ l2_cache_resume r0, r1, r2, l2x0_saved_regs_addr
+
+ b cpu_resume
+ENDPROC(tegra_resume)
+#endif
+
+#ifdef CONFIG_CACHE_L2X0
+ .globl l2x0_saved_regs_addr
+l2x0_saved_regs_addr:
+ .long 0
+#endif
+
.align L1_CACHE_SHIFT
ENTRY(__tegra_cpu_reset_handler_start)
@@ -121,6 +181,17 @@ ENTRY(__tegra_cpu_reset_handler)
1:
#endif
+ /* Waking up from LP2? */
+ ldr r9, [r12, #RESET_DATA(MASK_LP2)]
+ tst r9, r11 @ if in_lp2
+ beq __is_not_lp2
+ ldr lr, [r12, #RESET_DATA(STARTUP_LP2)]
+ cmp lr, #0
+ bleq __die @ no LP2 startup handler
+ bx lr
+
+__is_not_lp2:
+
#ifdef CONFIG_SMP
/*
* Can only be secondary boot (initial or hotplug) but CPU 0
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c
new file mode 100644
index 0000000..1b11707
--- /dev/null
+++ b/arch/arm/mach-tegra/pm.c
@@ -0,0 +1,216 @@
+/*
+ * CPU complex suspend & resume functions for Tegra SoCs
+ *
+ * Copyright (c) 2009-2012, NVIDIA Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#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)
+{
+ /* read diagnostic register */
+ asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc");
+ return;
+}
+
+void restore_cpu_arch_register(void)
+{
+ /* write diagnostic register */
+ asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc");
+ 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;
+
+ spin_lock(&tegra_lp2_lock);
+
+ BUG_ON(!(*cpu_in_lp2 & BIT(phy_cpu_id)));
+ *cpu_in_lp2 &= ~BIT(phy_cpu_id);
+
+ spin_unlock(&tegra_lp2_lock);
+}
+
+bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id)
+{
+ bool last_cpu = false;
+ cpumask_t *cpu_lp2_mask = tegra_cpu_lp2_mask;
+ u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;
+
+ spin_lock(&tegra_lp2_lock);
+
+ BUG_ON((*cpu_in_lp2 & BIT(phy_cpu_id)));
+ *cpu_in_lp2 |= BIT(phy_cpu_id);
+
+ if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask))
+ last_cpu = true;
+
+ 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();
+
+ cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);
+
+ restore_cpu_complex();
+ cpu_cluster_pm_exit();
+}
+#endif
diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h
new file mode 100644
index 0000000..787335c
--- /dev/null
+++ b/arch/arm/mach-tegra/pm.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ * Copyright (c) 2010-2012 NVIDIA Corporation. All rights reserved.
+ *
+ * Author:
+ * Colin Cross <ccross@google.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MACH_TEGRA_PM_H_
+#define _MACH_TEGRA_PM_H_
+
+extern unsigned long l2x0_saved_regs_addr;
+
+void save_cpu_arch_register(void);
+void restore_cpu_arch_register(void);
+
+void tegra_clear_cpu_in_lp2(int phy_cpu_id);
+bool tegra_set_cpu_in_lp2(int phy_cpu_id);
+
+void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time);
+extern void (*tegra_tear_down_cpu)(void);
+
+#endif /* _MACH_TEGRA_PM_H_ */
diff --git a/arch/arm/mach-tegra/reset.c b/arch/arm/mach-tegra/reset.c
index e05da7d..3fd89ec 100644
--- a/arch/arm/mach-tegra/reset.c
+++ b/arch/arm/mach-tegra/reset.c
@@ -25,6 +25,7 @@
#include "iomap.h"
#include "irammap.h"
#include "reset.h"
+#include "sleep.h"
#include "fuse.h"
#define TEGRA_IRAM_RESET_BASE (TEGRA_IRAM_BASE + \
@@ -79,5 +80,10 @@ void __init tegra_cpu_reset_handler_init(void)
virt_to_phys((void *)tegra_secondary_startup);
#endif
+#ifdef CONFIG_PM_SLEEP
+ __tegra_cpu_reset_handler_data[TEGRA_RESET_STARTUP_LP2] =
+ virt_to_phys((void *)tegra_resume);
+#endif
+
tegra_cpu_reset_handler_enable();
}
diff --git a/arch/arm/mach-tegra/reset.h b/arch/arm/mach-tegra/reset.h
index de88bf8..c90d8e9 100644
--- a/arch/arm/mach-tegra/reset.h
+++ b/arch/arm/mach-tegra/reset.h
@@ -29,6 +29,8 @@
#ifndef __ASSEMBLY__
+#include "irammap.h"
+
extern unsigned long __tegra_cpu_reset_handler_data[TEGRA_RESET_DATA_SIZE];
void __tegra_cpu_reset_handler_start(void);
@@ -36,6 +38,13 @@ void __tegra_cpu_reset_handler(void);
void __tegra_cpu_reset_handler_end(void);
void tegra_secondary_startup(void);
+#ifdef CONFIG_PM_SLEEP
+#define tegra_cpu_lp2_mask \
+ (IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + \
+ ((u32)&__tegra_cpu_reset_handler_data[TEGRA_RESET_MASK_LP2] - \
+ (u32)__tegra_cpu_reset_handler_start)))
+#endif
+
#define tegra_cpu_reset_handler_offset \
((u32)__tegra_cpu_reset_handler - \
(u32)__tegra_cpu_reset_handler_start)
diff --git a/arch/arm/mach-tegra/sleep-t20.S b/arch/arm/mach-tegra/sleep-tegra20.S
index 72ce709..72ce709 100644
--- a/arch/arm/mach-tegra/sleep-t20.S
+++ b/arch/arm/mach-tegra/sleep-tegra20.S
diff --git a/arch/arm/mach-tegra/sleep-t30.S b/arch/arm/mach-tegra/sleep-tegra30.S
index be7614b7..562a8e7 100644
--- a/arch/arm/mach-tegra/sleep-t30.S
+++ b/arch/arm/mach-tegra/sleep-tegra30.S
@@ -17,6 +17,7 @@
#include <linux/linkage.h>
#include <asm/assembler.h>
+#include <asm/asm-offsets.h>
#include "sleep.h"
#include "flowctrl.h"
@@ -80,6 +81,7 @@ delay_1:
ldr r3, [r1] @ read CSR
str r3, [r1] @ clear CSR
tst r0, #TEGRA30_POWER_HOTPLUG_SHUTDOWN
+ moveq r3, #FLOW_CTRL_WAIT_FOR_INTERRUPT @ For LP2
movne r3, #FLOW_CTRL_WAITEVENT @ For hotplug
str r3, [r2]
ldr r0, [r2]
@@ -103,3 +105,67 @@ wfe_war:
ENDPROC(tegra30_cpu_shutdown)
#endif
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * tegra30_sleep_cpu_secondary_finish(unsigned long v2p)
+ *
+ * Enters LP2 on secondary CPU by exiting coherency and powergating the CPU.
+ */
+ENTRY(tegra30_sleep_cpu_secondary_finish)
+ mov r7, lr
+
+ /* Flush and disable the L1 data cache */
+ bl tegra_disable_clean_inv_dcache
+
+ /* Powergate this CPU. */
+ mov r0, #0 @ power mode flags (!hotplug)
+ bl tegra30_cpu_shutdown
+ mov r0, #1 @ never return here
+ mov pc, r7
+ENDPROC(tegra30_sleep_cpu_secondary_finish)
+
+/*
+ * tegra30_tear_down_cpu
+ *
+ * Switches the CPU to enter sleep.
+ */
+ENTRY(tegra30_tear_down_cpu)
+ mov32 r6, TEGRA_FLOW_CTRL_BASE
+
+ b tegra30_enter_sleep
+ENDPROC(tegra30_tear_down_cpu)
+
+/*
+ * tegra30_enter_sleep
+ *
+ * uses flow controller to enter sleep state
+ * executes from IRAM with SDRAM in selfrefresh when target state is LP0 or LP1
+ * executes from SDRAM with target state is LP2
+ * r6 = TEGRA_FLOW_CTRL_BASE
+ */
+tegra30_enter_sleep:
+ cpu_id r1
+
+ cpu_to_csr_reg r2, r1
+ ldr r0, [r6, r2]
+ orr r0, r0, #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG
+ orr r0, r0, #FLOW_CTRL_CSR_ENABLE
+ str r0, [r6, r2]
+
+ mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT
+ orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ
+ cpu_to_halt_reg r2, r1
+ str r0, [r6, r2]
+ dsb
+ ldr r0, [r6, r2] /* memory barrier */
+
+halted:
+ isb
+ dsb
+ wfi /* CPU should be power gated here */
+
+ /* !!!FIXME!!! Implement halt failure handler */
+ b halted
+
+#endif
diff --git a/arch/arm/mach-tegra/sleep.S b/arch/arm/mach-tegra/sleep.S
index 08e9481..26afa7c 100644
--- a/arch/arm/mach-tegra/sleep.S
+++ b/arch/arm/mach-tegra/sleep.S
@@ -25,9 +25,87 @@
#include <linux/linkage.h>
#include <asm/assembler.h>
+#include <asm/cache.h>
+#include <asm/cp15.h>
+#include <asm/hardware/cache-l2x0.h>
#include "iomap.h"
#include "flowctrl.h"
#include "sleep.h"
+#ifdef CONFIG_PM_SLEEP
+/*
+ * tegra_disable_clean_inv_dcache
+ *
+ * disable, clean & invalidate the D-cache
+ *
+ * Corrupted registers: r1-r3, r6, r8, r9-r11
+ */
+ENTRY(tegra_disable_clean_inv_dcache)
+ stmfd sp!, {r0, r4-r5, r7, r9-r11, lr}
+ dmb @ ensure ordering
+
+ /* Disable the D-cache */
+ mrc p15, 0, r2, c1, c0, 0
+ bic r2, r2, #CR_C
+ mcr p15, 0, r2, c1, c0, 0
+ isb
+
+ /* Flush the D-cache */
+ bl v7_flush_dcache_louis
+
+ /* Trun off coherency */
+ exit_smp r4, r5
+
+ ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc}
+ENDPROC(tegra_disable_clean_inv_dcache)
+
+/*
+ * tegra_sleep_cpu_finish(unsigned long v2p)
+ *
+ * enters suspend in LP2 by turning off the mmu and jumping to
+ * tegra?_tear_down_cpu
+ */
+ENTRY(tegra_sleep_cpu_finish)
+ /* Flush and disable the L1 data cache */
+ bl tegra_disable_clean_inv_dcache
+
+ mov32 r6, tegra_tear_down_cpu
+ ldr r1, [r6]
+ add r1, r1, r0
+
+ mov32 r3, tegra_shut_off_mmu
+ add r3, r3, r0
+ mov r0, r1
+
+ mov pc, r3
+ENDPROC(tegra_sleep_cpu_finish)
+
+/*
+ * tegra_shut_off_mmu
+ *
+ * r0 = physical address to jump to with mmu off
+ *
+ * called with VA=PA mapping
+ * turns off MMU, icache, dcache and branch prediction
+ */
+ .align L1_CACHE_SHIFT
+ .pushsection .idmap.text, "ax"
+ENTRY(tegra_shut_off_mmu)
+ mrc p15, 0, r3, c1, c0, 0
+ movw r2, #CR_I | CR_Z | CR_C | CR_M
+ bic r3, r3, r2
+ dsb
+ mcr p15, 0, r3, c1, c0, 0
+ isb
+#ifdef CONFIG_CACHE_L2X0
+ /* Disable L2 cache */
+ mov32 r4, TEGRA_ARM_PERIF_BASE + 0x3000
+ mov r5, #0
+ str r5, [r4, #L2X0_CTRL]
+#endif
+ mov pc, r0
+ENDPROC(tegra_shut_off_mmu)
+ .popsection
+#endif
diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h
index 4889b28..9821ee7 100644
--- a/arch/arm/mach-tegra/sleep.h
+++ b/arch/arm/mach-tegra/sleep.h
@@ -71,7 +71,41 @@
str \tmp2, [\tmp1] @ invalidate SCU tags for CPU
dsb
.endm
+
+/* Macro to resume & re-enable L2 cache */
+#ifndef L2X0_CTRL_EN
+#define L2X0_CTRL_EN 1
+#endif
+
+#ifdef CONFIG_CACHE_L2X0
+.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
+ adr \tmp1, \phys_l2x0_saved_regs
+ ldr \tmp1, [\tmp1]
+ ldr \tmp2, [\tmp1, #L2X0_R_PHY_BASE]
+ ldr \tmp3, [\tmp2, #L2X0_CTRL]
+ tst \tmp3, #L2X0_CTRL_EN
+ bne exit_l2_resume
+ ldr \tmp3, [\tmp1, #L2X0_R_TAG_LATENCY]
+ str \tmp3, [\tmp2, #L2X0_TAG_LATENCY_CTRL]
+ ldr \tmp3, [\tmp1, #L2X0_R_DATA_LATENCY]
+ str \tmp3, [\tmp2, #L2X0_DATA_LATENCY_CTRL]
+ ldr \tmp3, [\tmp1, #L2X0_R_PREFETCH_CTRL]
+ str \tmp3, [\tmp2, #L2X0_PREFETCH_CTRL]
+ ldr \tmp3, [\tmp1, #L2X0_R_PWR_CTRL]
+ str \tmp3, [\tmp2, #L2X0_POWER_CTRL]
+ ldr \tmp3, [\tmp1, #L2X0_R_AUX_CTRL]
+ str \tmp3, [\tmp2, #L2X0_AUX_CTRL]
+ mov \tmp3, #L2X0_CTRL_EN
+ str \tmp3, [\tmp2, #L2X0_CTRL]
+exit_l2_resume:
+.endm
+#else /* CONFIG_CACHE_L2X0 */
+.macro l2_cache_resume, tmp1, tmp2, tmp3, phys_l2x0_saved_regs
+.endm
+#endif /* CONFIG_CACHE_L2X0 */
#else
+void tegra_resume(void);
+int tegra_sleep_cpu_finish(unsigned long);
#ifdef CONFIG_HOTPLUG_CPU
void tegra20_hotplug_init(void);
@@ -81,5 +115,8 @@ static inline void tegra20_hotplug_init(void) {}
static inline void tegra30_hotplug_init(void) {}
#endif
+int tegra30_sleep_cpu_secondary_finish(unsigned long);
+void tegra30_tear_down_cpu(void);
+
#endif
#endif
diff --git a/arch/arm/mach-tegra/tegra30_clocks.c b/arch/arm/mach-tegra/tegra30_clocks.c
index f5b453f..efc000e 100644
--- a/arch/arm/mach-tegra/tegra30_clocks.c
+++ b/arch/arm/mach-tegra/tegra30_clocks.c
@@ -31,6 +31,8 @@
#include <asm/clkdev.h>
+#include <mach/powergate.h>
+
#include "clock.h"
#include "fuse.h"
#include "iomap.h"
@@ -309,6 +311,31 @@
#define CPU_CLOCK(cpu) (0x1 << (8 + cpu))
#define CPU_RESET(cpu) (0x1111ul << (cpu))
+#define CLK_RESET_CCLK_BURST 0x20
+#define CLK_RESET_CCLK_DIVIDER 0x24
+#define CLK_RESET_PLLX_BASE 0xe0
+#define CLK_RESET_PLLX_MISC 0xe4
+
+#define CLK_RESET_SOURCE_CSITE 0x1d4
+
+#define CLK_RESET_CCLK_BURST_POLICY_SHIFT 28
+#define CLK_RESET_CCLK_RUN_POLICY_SHIFT 4
+#define CLK_RESET_CCLK_IDLE_POLICY_SHIFT 0
+#define CLK_RESET_CCLK_IDLE_POLICY 1
+#define CLK_RESET_CCLK_RUN_POLICY 2
+#define CLK_RESET_CCLK_BURST_POLICY_PLLX 8
+
+#ifdef CONFIG_PM_SLEEP
+static struct cpu_clk_suspend_context {
+ u32 pllx_misc;
+ u32 pllx_base;
+
+ u32 cpu_burst;
+ u32 clk_csite_src;
+ u32 cclk_divider;
+} tegra30_cpu_clk_sctx;
+#endif
+
/**
* Structure defining the fields for USB UTMI clocks Parameters.
*/
@@ -2386,12 +2413,93 @@ static void tegra30_disable_cpu_clock(u32 cpu)
reg_clk_base + TEGRA_CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
}
+#ifdef CONFIG_PM_SLEEP
+static bool tegra30_cpu_rail_off_ready(void)
+{
+ unsigned int cpu_rst_status;
+ int cpu_pwr_status;
+
+ cpu_rst_status = readl(reg_clk_base +
+ TEGRA30_CLK_RST_CONTROLLER_CPU_CMPLX_STATUS);
+ cpu_pwr_status = tegra_powergate_is_powered(TEGRA_POWERGATE_CPU1) ||
+ tegra_powergate_is_powered(TEGRA_POWERGATE_CPU2) ||
+ tegra_powergate_is_powered(TEGRA_POWERGATE_CPU3);
+
+ if (((cpu_rst_status & 0xE) != 0xE) || cpu_pwr_status)
+ return false;
+
+ return true;
+}
+
+static void tegra30_cpu_clock_suspend(void)
+{
+ /* switch coresite to clk_m, save off original source */
+ tegra30_cpu_clk_sctx.clk_csite_src =
+ readl(reg_clk_base + CLK_RESET_SOURCE_CSITE);
+ writel(3<<30, reg_clk_base + CLK_RESET_SOURCE_CSITE);
+
+ tegra30_cpu_clk_sctx.cpu_burst =
+ readl(reg_clk_base + CLK_RESET_CCLK_BURST);
+ tegra30_cpu_clk_sctx.pllx_base =
+ readl(reg_clk_base + CLK_RESET_PLLX_BASE);
+ tegra30_cpu_clk_sctx.pllx_misc =
+ readl(reg_clk_base + CLK_RESET_PLLX_MISC);
+ tegra30_cpu_clk_sctx.cclk_divider =
+ readl(reg_clk_base + CLK_RESET_CCLK_DIVIDER);
+}
+
+static void tegra30_cpu_clock_resume(void)
+{
+ unsigned int reg, policy;
+
+ /* Is CPU complex already running on PLLX? */
+ reg = readl(reg_clk_base + CLK_RESET_CCLK_BURST);
+ policy = (reg >> CLK_RESET_CCLK_BURST_POLICY_SHIFT) & 0xF;
+
+ if (policy == CLK_RESET_CCLK_IDLE_POLICY)
+ reg = (reg >> CLK_RESET_CCLK_IDLE_POLICY_SHIFT) & 0xF;
+ else if (policy == CLK_RESET_CCLK_RUN_POLICY)
+ reg = (reg >> CLK_RESET_CCLK_RUN_POLICY_SHIFT) & 0xF;
+ else
+ BUG();
+
+ if (reg != CLK_RESET_CCLK_BURST_POLICY_PLLX) {
+ /* restore PLLX settings if CPU is on different PLL */
+ writel(tegra30_cpu_clk_sctx.pllx_misc,
+ reg_clk_base + CLK_RESET_PLLX_MISC);
+ writel(tegra30_cpu_clk_sctx.pllx_base,
+ reg_clk_base + CLK_RESET_PLLX_BASE);
+
+ /* wait for PLL stabilization if PLLX was enabled */
+ if (tegra30_cpu_clk_sctx.pllx_base & (1 << 30))
+ udelay(300);
+ }
+
+ /*
+ * Restore original burst policy setting for calls resulting from CPU
+ * LP2 in idle or system suspend.
+ */
+ writel(tegra30_cpu_clk_sctx.cclk_divider,
+ reg_clk_base + CLK_RESET_CCLK_DIVIDER);
+ writel(tegra30_cpu_clk_sctx.cpu_burst,
+ reg_clk_base + CLK_RESET_CCLK_BURST);
+
+ writel(tegra30_cpu_clk_sctx.clk_csite_src,
+ reg_clk_base + CLK_RESET_SOURCE_CSITE);
+}
+#endif
+
static struct tegra_cpu_car_ops tegra30_cpu_car_ops = {
.wait_for_reset = tegra30_wait_cpu_in_reset,
.put_in_reset = tegra30_put_cpu_in_reset,
.out_of_reset = tegra30_cpu_out_of_reset,
.enable_clock = tegra30_enable_cpu_clock,
.disable_clock = tegra30_disable_cpu_clock,
+#ifdef CONFIG_PM_SLEEP
+ .rail_off_ready = tegra30_cpu_rail_off_ready,
+ .suspend = tegra30_cpu_clock_suspend,
+ .resume = tegra30_cpu_clock_resume,
+#endif
};
void __init tegra30_cpu_car_ops_init(void)
diff --git a/arch/arm/mach-tegra/tegra_cpu_car.h b/arch/arm/mach-tegra/tegra_cpu_car.h
index 30d063a..9764d31 100644
--- a/arch/arm/mach-tegra/tegra_cpu_car.h
+++ b/arch/arm/mach-tegra/tegra_cpu_car.h
@@ -30,6 +30,12 @@
* CPU clock un-gate
* disable_clock:
* CPU clock gate
+ * rail_off_ready:
+ * CPU is ready for rail off
+ * suspend:
+ * save the clock settings when CPU go into low-power state
+ * resume:
+ * restore the clock settings when CPU exit low-power state
*/
struct tegra_cpu_car_ops {
void (*wait_for_reset)(u32 cpu);
@@ -37,6 +43,11 @@ struct tegra_cpu_car_ops {
void (*out_of_reset)(u32 cpu);
void (*enable_clock)(u32 cpu);
void (*disable_clock)(u32 cpu);
+#ifdef CONFIG_PM_SLEEP
+ bool (*rail_off_ready)(void);
+ void (*suspend)(void);
+ void (*resume)(void);
+#endif
};
extern struct tegra_cpu_car_ops *tegra_cpu_car_ops;
@@ -81,6 +92,32 @@ static inline void tegra_disable_cpu_clock(u32 cpu)
tegra_cpu_car_ops->disable_clock(cpu);
}
+#ifdef CONFIG_PM_SLEEP
+static inline bool tegra_cpu_rail_off_ready(void)
+{
+ if (WARN_ON(!tegra_cpu_car_ops->rail_off_ready))
+ return false;
+
+ return tegra_cpu_car_ops->rail_off_ready();
+}
+
+static inline void tegra_cpu_clock_suspend(void)
+{
+ if (WARN_ON(!tegra_cpu_car_ops->suspend))
+ return;
+
+ tegra_cpu_car_ops->suspend();
+}
+
+static inline void tegra_cpu_clock_resume(void)
+{
+ if (WARN_ON(!tegra_cpu_car_ops->resume))
+ return;
+
+ tegra_cpu_car_ops->resume();
+}
+#endif
+
void tegra20_cpu_car_ops_init(void);
void tegra30_cpu_car_ops_init(void);