diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 6cc23cc83509..0979e8bba78a 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 3e03e5f15c14..11a74db51e5d 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 000000000000..d32e8b0dbd4f --- /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 + * Gary King + * + * Rework for 3.3 by Peter De Schrijver + * + * 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 +#include +#include + +#include + +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 000000000000..5e8cbf5b799f --- /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 + * Gary King + * + * Rework for 3.3 by Peter De Schrijver + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 9a6f051b382e..d0651397aec7 100644 --- a/arch/arm/mach-tegra/cpuidle.c +++ b/arch/arm/mach-tegra/cpuidle.c @@ -23,83 +23,26 @@ #include #include -#include -#include -#include -#include - -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 000000000000..496204d34e55 --- /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 . + */ + +#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 ffaa286a71e1..a2250ddae797 100644 --- a/arch/arm/mach-tegra/flowctrl.c +++ b/arch/arm/mach-tegra/flowctrl.c @@ -21,6 +21,7 @@ #include #include #include +#include #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 19428173855e..0798dec1832d 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 93f0370cc95b..4a317fae6860 100644 --- a/arch/arm/mach-tegra/headsmp.S +++ b/arch/arm/mach-tegra/headsmp.S @@ -2,6 +2,8 @@ #include #include +#include +#include #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 000000000000..1b11707eaca0 --- /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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 000000000000..787335cc964c --- /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 + * + * 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 . + */ + +#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 e05da7d10c3b..3fd89ecd158e 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 de88bf851dd3..c90d8e9c4ad2 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 similarity index 100% rename from arch/arm/mach-tegra/sleep-t20.S rename to 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 similarity index 63% rename from arch/arm/mach-tegra/sleep-t30.S rename to arch/arm/mach-tegra/sleep-tegra30.S index be7614b7c5cb..562a8e7e413d 100644 --- a/arch/arm/mach-tegra/sleep-t30.S +++ b/arch/arm/mach-tegra/sleep-tegra30.S @@ -17,6 +17,7 @@ #include #include +#include #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 08e9481c049e..26afa7cbed11 100644 --- a/arch/arm/mach-tegra/sleep.S +++ b/arch/arm/mach-tegra/sleep.S @@ -25,9 +25,87 @@ #include #include +#include +#include +#include #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 4889b281c5f9..9821ee725420 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 f5b453f4bf4d..efc000e32e1c 100644 --- a/arch/arm/mach-tegra/tegra30_clocks.c +++ b/arch/arm/mach-tegra/tegra30_clocks.c @@ -31,6 +31,8 @@ #include +#include + #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 30d063ad2bef..9764d31032b7 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);