diff options
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/asm_macros.h | 10 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpuidle-t3.c | 397 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpuidle.h | 25 | ||||
-rw-r--r-- | arch/arm/mach-tegra/platsmp.c | 7 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm-t3.c | 3 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm.c | 19 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep-t3.S | 93 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep.S | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep.h | 10 |
10 files changed, 552 insertions, 15 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 191b7e320f9a..a63aac09c228 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -68,6 +68,7 @@ ifeq ($(CONFIG_CPU_IDLE),y) obj-y += cpuidle.o ifeq ($(CONFIG_PM_SLEEP),y) obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += cpuidle-t2.o +obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += cpuidle-t3.o endif endif obj-$(CONFIG_TEGRA_IOVMM) += iovmm.o diff --git a/arch/arm/mach-tegra/asm_macros.h b/arch/arm/mach-tegra/asm_macros.h index 24d1449efbae..61a7028a6cc0 100644 --- a/arch/arm/mach-tegra/asm_macros.h +++ b/arch/arm/mach-tegra/asm_macros.h @@ -19,6 +19,16 @@ #ifdef __ASSEMBLY__ +/* waits until the microsecond counter (base) is > rn */ +.macro wait_until, rn, base, tmp + add \rn, \rn, #1 +1002: ldr \tmp, [\base] + sub \tmp, \tmp, \rn + ands \tmp, \tmp, #0x80000000 + dmb + bne 1002b +.endm + /* returns the offset of the flow controller halt register for a cpu */ .macro cpu_to_halt_reg rd, rcpu cmp \rcpu, #0 diff --git a/arch/arm/mach-tegra/cpuidle-t3.c b/arch/arm/mach-tegra/cpuidle-t3.c new file mode 100644 index 000000000000..25f7d26d665c --- /dev/null +++ b/arch/arm/mach-tegra/cpuidle-t3.c @@ -0,0 +1,397 @@ +/* + * arch/arm/mach-tegra/cpuidle-t3.c + * + * CPU idle driver for Tegra3 CPUs + * + * Copyright (c) 2010-2011, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/kernel.h> +#include <linux/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/ratelimit.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/cpu_pm.h> +#include <asm/hardware/gic.h> +#include <asm/localtimer.h> + +#include <mach/iomap.h> +#include <mach/irqs.h> + +#include <trace/events/power.h> + +#include "clock.h" +#include "cpuidle.h" +#include "dvfs.h" +#include "fuse.h" +#include "gic.h" +#include "pm.h" +#include "reset.h" +#include "sleep.h" + +#define CLK_RST_CONTROLLER_CPU_CMPLX_STATUS \ + (IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x470) + +#ifdef CONFIG_SMP +static s64 tegra_cpu_wake_by_time[4] = { + LLONG_MAX, LLONG_MAX, LLONG_MAX, LLONG_MAX }; +#endif + +static struct clk *cpu_clk_for_dvfs; + +static struct { + unsigned int cpu_ready_count[5]; + unsigned int tear_down_count[5]; + unsigned long long cpu_wants_lp2_time[5]; + unsigned long long in_lp2_time; + 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; + +static inline unsigned int time_to_bin(unsigned int time) +{ + return fls(time); +} + +static inline void tegra_irq_unmask(int irq) +{ + struct irq_data *data = irq_get_irq_data(irq); + data->chip->irq_unmask(data); +} + +static inline unsigned int cpu_number(unsigned int n) +{ + return is_lp_cluster() ? 4 : n; +} + +void tegra3_cpu_idle_stats_lp2_ready(unsigned int cpu) +{ + idle_stats.cpu_ready_count[cpu_number(cpu)]++; +} + +void tegra3_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us) +{ + idle_stats.cpu_wants_lp2_time[cpu_number(cpu)] += us; +} + +bool tegra3_lp2_is_allowed(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + s64 request; + + if (!tegra_all_cpus_booted) + return false; + + /* On A01, LP2 on slave CPU's cause ranhdom CPU hangs. + * Refer to Bug 804085. + */ + if ((tegra_get_revision() == TEGRA_REVISION_A01) && + num_online_cpus() > 1) + return false; + + /* FIXME: All CPU's entering LP2 is not working. + * Don't let CPU0 enter LP2 when any secondary CPU is online. + */ + if ((dev->cpu == 0) && (num_online_cpus() > 1)) + return false; + + if (dev->cpu == 0) { + u32 reg = readl(CLK_RST_CONTROLLER_CPU_CMPLX_STATUS); + if ((reg & 0xE) != 0xE) + return false; + + if (tegra_dvfs_rail_updating(cpu_clk_for_dvfs)) + return false; + } + + request = ktime_to_us(tick_nohz_get_sleep_length()); + if (request < state->target_residency) { + /* Not enough time left to enter LP2 */ + return false; + } + + return true; +} + +static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev, + struct cpuidle_state *state, s64 request) +{ + ktime_t enter; + ktime_t exit; + bool sleep_completed = false; + int bin; + + /* LP2 entry time */ + enter = ktime_get(); + + if (request < state->target_residency) { + /* Not enough time left to enter LP2 */ + tegra_cpu_wfi(); + return; + } + +#ifdef CONFIG_SMP + if (!is_lp_cluster() && (num_online_cpus() > 1)) { + s64 wake_time; + unsigned int i; + + /* Disable the distributor -- this is the only way to + prevent the other CPUs from responding to interrupts + and potentially fiddling with the distributor + registers while we're fiddling with them. */ + tegra_gic_dist_disable(); + + /* Did an interrupt come in for another CPU before we + could disable the distributor? */ + if (!tegra3_lp2_is_allowed(dev, state)) { + /* Yes, re-enable the distributor and LP3. */ + tegra_gic_dist_enable(); + tegra_cpu_wfi(); + return; + } + + /* Save and disable the affinity setting for the other + CPUs and route all interrupts to CPU0. */ + tegra_gic_disable_affinity(); + + /* Re-enable the distributor. */ + tegra_gic_dist_enable(); + + /* LP2 initial targeted wake time */ + wake_time = ktime_to_us(enter) + request; + + /* CPU0 must wake up before any of the other CPUs. */ + smp_rmb(); + for (i = 1; i < CONFIG_NR_CPUS; i++) + wake_time = min_t(s64, wake_time, + tegra_cpu_wake_by_time[i]); + + /* LP2 actual targeted wake time */ + request = wake_time - ktime_to_us(enter); + BUG_ON(wake_time < 0LL); + } +#endif + + if (request > state->target_residency) { + s64 sleep_time = request - tegra_lp2_exit_latency; + + bin = time_to_bin((u32)request / 1000); + idle_stats.tear_down_count[cpu_number(dev->cpu)]++; + idle_stats.lp2_count++; + idle_stats.lp2_count_bin[bin]++; + + trace_power_start(POWER_CSTATE, 2, dev->cpu); + + if (tegra_idle_lp2_last(sleep_time, 0) == 0) + sleep_completed = true; + else { + int irq = tegra_gic_pending_interrupt(); + idle_stats.lp2_int_count[irq]++; + } + } + +#ifdef CONFIG_SMP + if (!is_lp_cluster() && (num_online_cpus() > 1)) { + + /* Disable the distributor. */ + tegra_gic_dist_disable(); + + /* Restore the other CPU's interrupt affinity. */ + tegra_gic_restore_affinity(); + + /* Re-enable the distributor. */ + tegra_gic_dist_enable(); + } +#endif + + 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); + } +} + +static void tegra3_idle_enter_lp2_cpu_n(struct cpuidle_device *dev, + struct cpuidle_state *state, s64 request) +{ +#ifdef CONFIG_SMP + s64 sleep_time = request - tegra_lp2_exit_latency; + + tegra_lp2_set_trigger(sleep_time); + + idle_stats.tear_down_count[cpu_number(dev->cpu)]++; + + trace_power_start(POWER_CSTATE, 2, dev->cpu); + + /* Save time this CPU must be awakened by. */ + tegra_cpu_wake_by_time[dev->cpu] = ktime_to_us(ktime_get()) + request; + smp_wmb(); + + flush_cache_all(); + barrier(); +/* !!!FIXME!!! __cortex_a9_save(0); */ + /* CPUn is powergated */ + + /* CPUn woke up */ + barrier(); + + tegra_cpu_wake_by_time[dev->cpu] = LLONG_MAX; + + if (sleep_time) + tegra_lp2_set_trigger(0); +#endif +} + +void tegra3_idle_lp2(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + s64 request = ktime_to_us(tick_nohz_get_sleep_length()); + bool last_cpu = tegra_set_cpu_in_lp2(dev->cpu); + + cpu_pm_enter(); + + if (last_cpu) + tegra3_idle_enter_lp2_cpu_0(dev, state, request); + else + tegra3_idle_enter_lp2_cpu_n(dev, state, request); + + cpu_pm_exit(); + tegra_clear_cpu_in_lp2(dev->cpu); +} + +int tegra_cpudile_init_soc(void) +{ + cpu_clk_for_dvfs = tegra_get_clock_by_name("cpu_g"); + return 0; +} + +#ifdef CONFIG_DEBUG_FS +int tegra3_lp2_debug_show(struct seq_file *s, void *data) +{ + int bin; + int i; + seq_printf(s, " cpu0 cpu1 cpu2 cpu3 cpulp\n"); + seq_printf(s, "-----------------------------------------------------------------------------\n"); + seq_printf(s, "cpu ready: %8u %8u %8u %8u %8u\n", + idle_stats.cpu_ready_count[0], + idle_stats.cpu_ready_count[1], + idle_stats.cpu_ready_count[2], + idle_stats.cpu_ready_count[3], + idle_stats.cpu_ready_count[4]); + seq_printf(s, "tear down: %8u %8u %8u %8u %8u\n", + idle_stats.tear_down_count[0], + idle_stats.tear_down_count[1], + idle_stats.tear_down_count[2], + idle_stats.tear_down_count[3], + idle_stats.tear_down_count[4]); + seq_printf(s, "lp2: %8u\n", idle_stats.lp2_count); + 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 %8llu %8llu %8llu ms\n", + div64_u64(idle_stats.cpu_wants_lp2_time[0], 1000), + div64_u64(idle_stats.cpu_wants_lp2_time[1], 1000), + div64_u64(idle_stats.cpu_wants_lp2_time[2], 1000), + div64_u64(idle_stats.cpu_wants_lp2_time[3], 1000), + div64_u64(idle_stats.cpu_wants_lp2_time[4], 1000)); + + seq_printf(s, "lp2 time: %8llu ms %7d%% %7d%% %7d%% %7d%% %7d%%\n", + div64_u64(idle_stats.in_lp2_time, 1000), + (int)(idle_stats.cpu_wants_lp2_time[0] ? + div64_u64(idle_stats.in_lp2_time * 100, + idle_stats.cpu_wants_lp2_time[0]) : 0), + (int)(idle_stats.cpu_wants_lp2_time[1] ? + div64_u64(idle_stats.in_lp2_time * 100, + idle_stats.cpu_wants_lp2_time[1]) : 0), + (int)(idle_stats.cpu_wants_lp2_time[2] ? + div64_u64(idle_stats.in_lp2_time * 100, + idle_stats.cpu_wants_lp2_time[2]) : 0), + (int)(idle_stats.cpu_wants_lp2_time[3] ? + div64_u64(idle_stats.in_lp2_time * 100, + idle_stats.cpu_wants_lp2_time[3]) : 0), + (int)(idle_stats.cpu_wants_lp2_time[4] ? + div64_u64(idle_stats.in_lp2_time * 100, + idle_stats.cpu_wants_lp2_time[4]) : 0)); + 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; +} +#endif diff --git a/arch/arm/mach-tegra/cpuidle.h b/arch/arm/mach-tegra/cpuidle.h index 5d0c3d695342..d5e1e3e0152e 100644 --- a/arch/arm/mach-tegra/cpuidle.h +++ b/arch/arm/mach-tegra/cpuidle.h @@ -35,12 +35,25 @@ bool tegra2_lp2_is_allowed(struct cpuidle_device *dev, int tegra2_lp2_debug_show(struct seq_file *s, void *data); #endif #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +void tegra3_idle_lp2(struct cpuidle_device *dev, struct cpuidle_state *state); +void tegra3_cpu_idle_stats_lp2_ready(unsigned int cpu); +void tegra3_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us); +bool tegra3_lp2_is_allowed(struct cpuidle_device *dev, + struct cpuidle_state *state); +#ifdef CONFIG_DEBUG_FS +int tegra3_lp2_debug_show(struct seq_file *s, void *data); +#endif +#endif static inline void tegra_cpu_idle_stats_lp2_ready(unsigned int cpu) { #ifdef CONFIG_ARCH_TEGRA_2x_SOC tegra2_cpu_idle_stats_lp2_ready(cpu); #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + tegra3_cpu_idle_stats_lp2_ready(cpu); +#endif } static inline void tegra_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us) @@ -48,6 +61,9 @@ static inline void tegra_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us) #ifdef CONFIG_ARCH_TEGRA_2x_SOC tegra2_cpu_idle_stats_lp2_time(cpu, us); #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + tegra3_cpu_idle_stats_lp2_time(cpu, us); +#endif } static inline void tegra_idle_lp2(struct cpuidle_device *dev, @@ -56,6 +72,9 @@ static inline void tegra_idle_lp2(struct cpuidle_device *dev, #ifdef CONFIG_ARCH_TEGRA_2x_SOC tegra2_idle_lp2(dev, state); #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + tegra3_idle_lp2(dev, state); +#endif } static inline bool tegra_lp2_is_allowed(struct cpuidle_device *dev, @@ -64,6 +83,9 @@ static inline bool tegra_lp2_is_allowed(struct cpuidle_device *dev, #ifdef CONFIG_ARCH_TEGRA_2x_SOC return tegra2_lp2_is_allowed(dev, state); #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + return tegra3_lp2_is_allowed(dev, state); +#endif } #ifdef CONFIG_DEBUG_FS @@ -72,6 +94,9 @@ static inline int tegra_lp2_debug_show(struct seq_file *s, void *data) #ifdef CONFIG_ARCH_TEGRA_2x_SOC return tegra2_lp2_debug_show(s, data); #endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + return tegra3_lp2_debug_show(s, data); +#endif } #endif diff --git a/arch/arm/mach-tegra/platsmp.c b/arch/arm/mach-tegra/platsmp.c index a7b81919ba90..6cd9c61b68e9 100644 --- a/arch/arm/mach-tegra/platsmp.c +++ b/arch/arm/mach-tegra/platsmp.c @@ -49,17 +49,12 @@ const struct cpumask *const tegra_cpu_init_mask = to_cpumask(tegra_cpu_init_bits #define CPU_CLOCK(cpu) (0x1<<(8+cpu)) #define CPU_RESET(cpu) (0x1111ul<<(cpu)) -#ifdef CONFIG_ARCH_TEGRA_2x_SOC -/* For Tegra2 use the software-written value of the reset register for status.*/ -#define CLK_RST_CONTROLLER_CPU_CMPLX_STATUS CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET -#else +#ifndef CONFIG_ARCH_TEGRA_2x_SOC #define CLK_RST_CONTROLLER_CLK_CPU_CMPLX_CLR \ (IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x34c) #define CAR_BOND_OUT_V \ (IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x390) #define CAR_BOND_OUT_V_CPU_G (1<<0) -#define CLK_RST_CONTROLLER_CPU_CMPLX_STATUS \ - (IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x470) #endif static void __iomem *scu_base = IO_ADDRESS(TEGRA_ARM_PERIF_BASE); diff --git a/arch/arm/mach-tegra/pm-t3.c b/arch/arm/mach-tegra/pm-t3.c index 5f869a124053..423b1e3d8cdb 100644 --- a/arch/arm/mach-tegra/pm-t3.c +++ b/arch/arm/mach-tegra/pm-t3.c @@ -31,6 +31,7 @@ #include <asm/hardware/gic.h> #include "clock.h" +#include "cpuidle.h" #include "gpio-names.h" #include "pm.h" #include "sleep.h" @@ -313,9 +314,11 @@ int tegra_cluster_control(unsigned int us, unsigned int flags) if (us) tegra_lp2_set_trigger(0); } else { + tegra_set_cpu_in_lp2(0); cpu_pm_enter(); tegra_idle_lp2_last(0, flags); cpu_pm_exit(); + tegra_clear_cpu_in_lp2(0); } local_irq_enable(); diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c index 3fe3ea1c289c..659db1d5d726 100644 --- a/arch/arm/mach-tegra/pm.c +++ b/arch/arm/mach-tegra/pm.c @@ -55,6 +55,7 @@ #include "board.h" #include "clock.h" #include "cpuidle.h" +#include "fuse.h" #include "gic.h" #include "pm.h" #include "pm-irq.h" @@ -433,16 +434,16 @@ bool tegra_set_cpu_in_lp2(int cpu) unsigned int tegra_idle_lp2_last(unsigned int sleep_time, unsigned int flags) { - u32 reg; + u32 mode; /* hardware + software power mode flags */ unsigned int remain; /* Only the last cpu down does the final suspend steps */ - reg = readl(pmc + PMC_CTRL); - reg |= TEGRA_POWER_CPU_PWRREQ_OE; - reg |= TEGRA_POWER_PWRREQ_OE; - reg |= flags; - reg &= ~TEGRA_POWER_EFFECT_LP0; - pmc_32kwritel(reg, PMC_CTRL); + mode = readl(pmc + PMC_CTRL); + mode |= TEGRA_POWER_CPU_PWRREQ_OE; + mode |= TEGRA_POWER_PWRREQ_OE; + mode &= ~TEGRA_POWER_EFFECT_LP0; + pmc_32kwritel(mode, PMC_CTRL); + mode |= flags; tegra_cluster_switch_time(flags, tegra_cluster_switch_time_id_start); @@ -454,7 +455,7 @@ unsigned int tegra_idle_lp2_last(unsigned int sleep_time, unsigned int flags) clk_get_rate_all_locked(tegra_pclk)); if (flags & TEGRA_POWER_CLUSTER_MASK) - tegra_cluster_switch_prolog(reg); + tegra_cluster_switch_prolog(mode); if (sleep_time) tegra_lp2_set_trigger(sleep_time); @@ -480,7 +481,7 @@ unsigned int tegra_idle_lp2_last(unsigned int sleep_time, unsigned int flags) tegra_lp2_set_trigger(0); if (flags & TEGRA_POWER_CLUSTER_MASK) - tegra_cluster_switch_epilog(reg); + tegra_cluster_switch_epilog(mode); tegra_cluster_switch_time(flags, tegra_cluster_switch_time_id_epilog); diff --git a/arch/arm/mach-tegra/sleep-t3.S b/arch/arm/mach-tegra/sleep-t3.S index 769ef6f1fd32..b234ab3a671f 100644 --- a/arch/arm/mach-tegra/sleep-t3.S +++ b/arch/arm/mach-tegra/sleep-t3.S @@ -69,7 +69,9 @@ ENTRY(tegra3_hotplug_shutdown) bl tegra3_cpu_reset mov pc, lr @ should never get here ENDPROC(tegra3_hotplug_shutdown) +#endif +#if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_PM_SLEEP) /* * tegra3_cpu_reset(unsigned long flags) * @@ -127,3 +129,94 @@ wfe_war: ENDPROC(tegra3_cpu_reset) #endif + +#ifdef CONFIG_PM_SLEEP +/* + * tegra3_sleep_cpu(unsigned long v2p) + * + * enters suspend in LP2 by turning off the mmu and jumping to + * tegra3_tear_down_cpu + */ +ENTRY(tegra3_sleep_cpu) + mov r3, lr @ set resume address to lr + bl tegra_cpu_save + + mov32 r1, tegra3_tear_down_cpu + add r1, r1, r0 + b tegra_turn_off_mmu +ENDPROC(tegra3_sleep_cpu) + +/* + * tegra3_tear_down_cpu + * + * Switches the CPU cluster to PLL-P and enters sleep. + */ +ENTRY(tegra3_tear_down_cpu) + bl tegra_cpu_pllp + b tegra3_enter_sleep +ENDPROC(tegra3_tear_down_cpu) + +/* START OF ROUTINES COPIED TO IRAM */ + .align L1_CACHE_SHIFT + .globl tegra3_iram_start +tegra3_iram_start: + +/* + * tegra3_lp1_reset + * + * reset vector for LP1 restore; copied into IRAM during suspend. + * brings the system back up to a safe starting point (SDRAM out of + * self-refresh, PLLC, PLLM and PLLP reenabled, CPU running on PLLP, + * system clock running on the same PLL that it suspended at), and + * jumps to tegra_lp2_startup to restore PLLX and virtual addressing. + * physical address of tegra_lp2_startup expected to be stored in + * PMC_SCRATCH41 + * + * NOTE: THIS *MUST* BE RELOCATED TO TEGRA_IRAM_CODE_AREA AND MUST BE FIRST. + */ + +/* !!!FIXME!!! Add LP1/LP1 code */ + +/* + * tegra3_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 + */ +tegra3_enter_sleep: + mov32 r7, TEGRA_TMRUS_BASE + ldr r1, [r7] + mov32 r4, TEGRA_PMC_BASE + str r1, [r4, #PMC_SCRATCH38] + dsb + mov32 r6, TEGRA_FLOW_CTRL_BASE + 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 + 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: + dsb + isb + wfi /* CPU should be power gated here */ + + /* !!!FIXME!!! Implement halt failure handler */ + b halted + + .ltorg +/* dummy symbol for end of IRAM */ + .align L1_CACHE_SHIFT + .globl tegra3_iram_end +tegra3_iram_end: + b . +#endif diff --git a/arch/arm/mach-tegra/sleep.S b/arch/arm/mach-tegra/sleep.S index 915a7b467f52..0c15989a4cd8 100644 --- a/arch/arm/mach-tegra/sleep.S +++ b/arch/arm/mach-tegra/sleep.S @@ -187,6 +187,8 @@ ENTRY(tegra_sleep_cpu) #ifdef CONFIG_ARCH_TEGRA_2x_SOC mov32 r1, tegra2_tear_down_cpu +#else + mov32 r1, tegra3_tear_down_cpu #endif add r1, r1, r0 b tegra_turn_off_mmu diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h index 5f418255de84..3caefe15f021 100644 --- a/arch/arm/mach-tegra/sleep.h +++ b/arch/arm/mach-tegra/sleep.h @@ -108,6 +108,9 @@ void tegra2_sleep_core(unsigned long v2p); void tegra2_hotplug_shutdown(void); void tegra2_sleep_wfi(unsigned long v2p); #else +extern void tegra3_iram_start; +extern void tegra3_iram_end; +void tegra3_sleep_core(unsigned long v2p); void tegra3_hotplug_shutdown(void); #endif @@ -115,6 +118,8 @@ static inline void *tegra_iram_start(void) { #ifdef CONFIG_ARCH_TEGRA_2x_SOC return &tegra2_iram_start; +#else + return &tegra3_iram_start; #endif } @@ -122,6 +127,8 @@ static inline void *tegra_iram_end(void) { #ifdef CONFIG_ARCH_TEGRA_2x_SOC return &tegra2_iram_end; +#else + return &tegra3_iram_end; #endif } @@ -129,6 +136,9 @@ static inline void tegra_sleep_core(unsigned long v2p) { #ifdef CONFIG_ARCH_TEGRA_2x_SOC tegra2_sleep_core(v2p); +#else + /* tegra3_sleep_core(v2p); !!!FIXME!!! not supported yet */ + BUG(); #endif } |