diff options
| -rw-r--r-- | arch/arm/mach-tegra/cpuidle-tegra20.c | 90 | ||||
| -rw-r--r-- | arch/arm/mach-tegra/pm.c | 3 | ||||
| -rw-r--r-- | arch/arm/mach-tegra/sleep-tegra20.S | 147 | ||||
| -rw-r--r-- | arch/arm/mach-tegra/sleep.h | 23 |
4 files changed, 259 insertions, 4 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-tegra20.c b/arch/arm/mach-tegra/cpuidle-tegra20.c index d32e8b0dbd4f..50f984d28faf 100644 --- a/arch/arm/mach-tegra/cpuidle-tegra20.c +++ b/arch/arm/mach-tegra/cpuidle-tegra20.c | |||
| @@ -22,21 +22,99 @@ | |||
| 22 | #include <linux/kernel.h> | 22 | #include <linux/kernel.h> |
| 23 | #include <linux/module.h> | 23 | #include <linux/module.h> |
| 24 | #include <linux/cpuidle.h> | 24 | #include <linux/cpuidle.h> |
| 25 | #include <linux/cpu_pm.h> | ||
| 26 | #include <linux/clockchips.h> | ||
| 25 | 27 | ||
| 26 | #include <asm/cpuidle.h> | 28 | #include <asm/cpuidle.h> |
| 29 | #include <asm/proc-fns.h> | ||
| 30 | #include <asm/suspend.h> | ||
| 31 | #include <asm/smp_plat.h> | ||
| 32 | |||
| 33 | #include "pm.h" | ||
| 34 | #include "sleep.h" | ||
| 35 | |||
| 36 | #ifdef CONFIG_PM_SLEEP | ||
| 37 | static int tegra20_idle_lp2(struct cpuidle_device *dev, | ||
| 38 | struct cpuidle_driver *drv, | ||
| 39 | int index); | ||
| 40 | #endif | ||
| 41 | |||
| 42 | static struct cpuidle_state tegra_idle_states[] = { | ||
| 43 | [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), | ||
| 44 | #ifdef CONFIG_PM_SLEEP | ||
| 45 | [1] = { | ||
| 46 | .enter = tegra20_idle_lp2, | ||
| 47 | .exit_latency = 5000, | ||
| 48 | .target_residency = 10000, | ||
| 49 | .power_usage = 0, | ||
| 50 | .flags = CPUIDLE_FLAG_TIME_VALID, | ||
| 51 | .name = "powered-down", | ||
| 52 | .desc = "CPU power gated", | ||
| 53 | }, | ||
| 54 | #endif | ||
| 55 | }; | ||
| 27 | 56 | ||
| 28 | static struct cpuidle_driver tegra_idle_driver = { | 57 | static struct cpuidle_driver tegra_idle_driver = { |
| 29 | .name = "tegra_idle", | 58 | .name = "tegra_idle", |
| 30 | .owner = THIS_MODULE, | 59 | .owner = THIS_MODULE, |
| 31 | .en_core_tk_irqen = 1, | 60 | .en_core_tk_irqen = 1, |
| 32 | .state_count = 1, | ||
| 33 | .states = { | ||
| 34 | [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), | ||
| 35 | }, | ||
| 36 | }; | 61 | }; |
| 37 | 62 | ||
| 38 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); | 63 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); |
| 39 | 64 | ||
| 65 | #ifdef CONFIG_PM_SLEEP | ||
| 66 | #ifdef CONFIG_SMP | ||
| 67 | static bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev, | ||
| 68 | struct cpuidle_driver *drv, | ||
| 69 | int index) | ||
| 70 | { | ||
| 71 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
| 72 | |||
| 73 | cpu_suspend(0, tegra20_sleep_cpu_secondary_finish); | ||
| 74 | |||
| 75 | tegra20_cpu_clear_resettable(); | ||
| 76 | |||
| 77 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
| 78 | |||
| 79 | return true; | ||
| 80 | } | ||
| 81 | #else | ||
| 82 | static inline bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev, | ||
| 83 | struct cpuidle_driver *drv, | ||
| 84 | int index) | ||
| 85 | { | ||
| 86 | return true; | ||
| 87 | } | ||
| 88 | #endif | ||
| 89 | |||
| 90 | static int tegra20_idle_lp2(struct cpuidle_device *dev, | ||
| 91 | struct cpuidle_driver *drv, | ||
| 92 | int index) | ||
| 93 | { | ||
| 94 | u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu; | ||
| 95 | bool entered_lp2 = false; | ||
| 96 | |||
| 97 | local_fiq_disable(); | ||
| 98 | |||
| 99 | tegra_set_cpu_in_lp2(cpu); | ||
| 100 | cpu_pm_enter(); | ||
| 101 | |||
| 102 | if (cpu == 0) | ||
| 103 | cpu_do_idle(); | ||
| 104 | else | ||
| 105 | entered_lp2 = tegra20_idle_enter_lp2_cpu_1(dev, drv, index); | ||
| 106 | |||
| 107 | cpu_pm_exit(); | ||
| 108 | tegra_clear_cpu_in_lp2(cpu); | ||
| 109 | |||
| 110 | local_fiq_enable(); | ||
| 111 | |||
| 112 | smp_rmb(); | ||
| 113 | |||
| 114 | return entered_lp2 ? index : 0; | ||
| 115 | } | ||
| 116 | #endif | ||
| 117 | |||
| 40 | int __init tegra20_cpuidle_init(void) | 118 | int __init tegra20_cpuidle_init(void) |
| 41 | { | 119 | { |
| 42 | int ret; | 120 | int ret; |
| @@ -44,6 +122,10 @@ int __init tegra20_cpuidle_init(void) | |||
| 44 | struct cpuidle_device *dev; | 122 | struct cpuidle_device *dev; |
| 45 | struct cpuidle_driver *drv = &tegra_idle_driver; | 123 | struct cpuidle_driver *drv = &tegra_idle_driver; |
| 46 | 124 | ||
| 125 | drv->state_count = ARRAY_SIZE(tegra_idle_states); | ||
| 126 | memcpy(drv->states, tegra_idle_states, | ||
| 127 | drv->state_count * sizeof(drv->states[0])); | ||
| 128 | |||
| 47 | ret = cpuidle_register_driver(&tegra_idle_driver); | 129 | ret = cpuidle_register_driver(&tegra_idle_driver); |
| 48 | if (ret) { | 130 | if (ret) { |
| 49 | pr_err("CPUidle driver registration failed\n"); | 131 | pr_err("CPUidle driver registration failed\n"); |
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c index abfe9b93cc0c..523604de666f 100644 --- a/arch/arm/mach-tegra/pm.c +++ b/arch/arm/mach-tegra/pm.c | |||
| @@ -36,6 +36,7 @@ | |||
| 36 | #include "iomap.h" | 36 | #include "iomap.h" |
| 37 | #include "reset.h" | 37 | #include "reset.h" |
| 38 | #include "flowctrl.h" | 38 | #include "flowctrl.h" |
| 39 | #include "fuse.h" | ||
| 39 | #include "sleep.h" | 40 | #include "sleep.h" |
| 40 | 41 | ||
| 41 | #define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ | 42 | #define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ |
| @@ -173,6 +174,8 @@ bool tegra_set_cpu_in_lp2(int phy_cpu_id) | |||
| 173 | 174 | ||
| 174 | if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask)) | 175 | if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask)) |
| 175 | last_cpu = true; | 176 | last_cpu = true; |
| 177 | else if (tegra_chip_id == TEGRA20 && phy_cpu_id == 1) | ||
| 178 | tegra20_cpu_set_resettable_soon(); | ||
| 176 | 179 | ||
| 177 | spin_unlock(&tegra_lp2_lock); | 180 | spin_unlock(&tegra_lp2_lock); |
| 178 | return last_cpu; | 181 | return last_cpu; |
diff --git a/arch/arm/mach-tegra/sleep-tegra20.S b/arch/arm/mach-tegra/sleep-tegra20.S index ad2ca07d0578..1074364e77ee 100644 --- a/arch/arm/mach-tegra/sleep-tegra20.S +++ b/arch/arm/mach-tegra/sleep-tegra20.S | |||
| @@ -21,6 +21,8 @@ | |||
| 21 | #include <linux/linkage.h> | 21 | #include <linux/linkage.h> |
| 22 | 22 | ||
| 23 | #include <asm/assembler.h> | 23 | #include <asm/assembler.h> |
| 24 | #include <asm/proc-fns.h> | ||
| 25 | #include <asm/cp15.h> | ||
| 24 | 26 | ||
| 25 | #include "sleep.h" | 27 | #include "sleep.h" |
| 26 | #include "flowctrl.h" | 28 | #include "flowctrl.h" |
| @@ -75,3 +77,148 @@ ENTRY(tegra20_cpu_shutdown) | |||
| 75 | mov pc, lr | 77 | mov pc, lr |
| 76 | ENDPROC(tegra20_cpu_shutdown) | 78 | ENDPROC(tegra20_cpu_shutdown) |
| 77 | #endif | 79 | #endif |
| 80 | |||
| 81 | #ifdef CONFIG_PM_SLEEP | ||
| 82 | /* | ||
| 83 | * tegra_pen_lock | ||
| 84 | * | ||
| 85 | * spinlock implementation with no atomic test-and-set and no coherence | ||
| 86 | * using Peterson's algorithm on strongly-ordered registers | ||
| 87 | * used to synchronize a cpu waking up from wfi with entering lp2 on idle | ||
| 88 | * | ||
| 89 | * The reference link of Peterson's algorithm: | ||
| 90 | * http://en.wikipedia.org/wiki/Peterson's_algorithm | ||
| 91 | * | ||
| 92 | * SCRATCH37 = r1 = !turn (inverted from Peterson's algorithm) | ||
| 93 | * on cpu 0: | ||
| 94 | * r2 = flag[0] (in SCRATCH38) | ||
| 95 | * r3 = flag[1] (in SCRATCH39) | ||
| 96 | * on cpu1: | ||
| 97 | * r2 = flag[1] (in SCRATCH39) | ||
| 98 | * r3 = flag[0] (in SCRATCH38) | ||
| 99 | * | ||
| 100 | * must be called with MMU on | ||
| 101 | * corrupts r0-r3, r12 | ||
| 102 | */ | ||
| 103 | ENTRY(tegra_pen_lock) | ||
| 104 | mov32 r3, TEGRA_PMC_VIRT | ||
| 105 | cpu_id r0 | ||
| 106 | add r1, r3, #PMC_SCRATCH37 | ||
| 107 | cmp r0, #0 | ||
| 108 | addeq r2, r3, #PMC_SCRATCH38 | ||
| 109 | addeq r3, r3, #PMC_SCRATCH39 | ||
| 110 | addne r2, r3, #PMC_SCRATCH39 | ||
| 111 | addne r3, r3, #PMC_SCRATCH38 | ||
| 112 | |||
| 113 | mov r12, #1 | ||
| 114 | str r12, [r2] @ flag[cpu] = 1 | ||
| 115 | dsb | ||
| 116 | str r12, [r1] @ !turn = cpu | ||
| 117 | 1: dsb | ||
| 118 | ldr r12, [r3] | ||
| 119 | cmp r12, #1 @ flag[!cpu] == 1? | ||
| 120 | ldreq r12, [r1] | ||
| 121 | cmpeq r12, r0 @ !turn == cpu? | ||
| 122 | beq 1b @ while !turn == cpu && flag[!cpu] == 1 | ||
| 123 | |||
| 124 | mov pc, lr @ locked | ||
| 125 | ENDPROC(tegra_pen_lock) | ||
| 126 | |||
| 127 | ENTRY(tegra_pen_unlock) | ||
| 128 | dsb | ||
| 129 | mov32 r3, TEGRA_PMC_VIRT | ||
| 130 | cpu_id r0 | ||
| 131 | cmp r0, #0 | ||
| 132 | addeq r2, r3, #PMC_SCRATCH38 | ||
| 133 | addne r2, r3, #PMC_SCRATCH39 | ||
| 134 | mov r12, #0 | ||
| 135 | str r12, [r2] | ||
| 136 | mov pc, lr | ||
| 137 | ENDPROC(tegra_pen_unlock) | ||
| 138 | |||
| 139 | /* | ||
| 140 | * tegra20_cpu_clear_resettable(void) | ||
| 141 | * | ||
| 142 | * Called to clear the "resettable soon" flag in PMC_SCRATCH41 when | ||
| 143 | * it is expected that the secondary CPU will be idle soon. | ||
| 144 | */ | ||
| 145 | ENTRY(tegra20_cpu_clear_resettable) | ||
| 146 | mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41 | ||
| 147 | mov r12, #CPU_NOT_RESETTABLE | ||
| 148 | str r12, [r1] | ||
| 149 | mov pc, lr | ||
| 150 | ENDPROC(tegra20_cpu_clear_resettable) | ||
| 151 | |||
| 152 | /* | ||
| 153 | * tegra20_cpu_set_resettable_soon(void) | ||
| 154 | * | ||
| 155 | * Called to set the "resettable soon" flag in PMC_SCRATCH41 when | ||
| 156 | * it is expected that the secondary CPU will be idle soon. | ||
| 157 | */ | ||
| 158 | ENTRY(tegra20_cpu_set_resettable_soon) | ||
| 159 | mov32 r1, TEGRA_PMC_VIRT + PMC_SCRATCH41 | ||
| 160 | mov r12, #CPU_RESETTABLE_SOON | ||
| 161 | str r12, [r1] | ||
| 162 | mov pc, lr | ||
| 163 | ENDPROC(tegra20_cpu_set_resettable_soon) | ||
| 164 | |||
| 165 | /* | ||
| 166 | * tegra20_sleep_cpu_secondary_finish(unsigned long v2p) | ||
| 167 | * | ||
| 168 | * Enters WFI on secondary CPU by exiting coherency. | ||
| 169 | */ | ||
| 170 | ENTRY(tegra20_sleep_cpu_secondary_finish) | ||
| 171 | stmfd sp!, {r4-r11, lr} | ||
| 172 | |||
| 173 | mrc p15, 0, r11, c1, c0, 1 @ save actlr before exiting coherency | ||
| 174 | |||
| 175 | /* Flush and disable the L1 data cache */ | ||
| 176 | bl tegra_disable_clean_inv_dcache | ||
| 177 | |||
| 178 | mov32 r0, TEGRA_PMC_VIRT + PMC_SCRATCH41 | ||
| 179 | mov r3, #CPU_RESETTABLE | ||
| 180 | str r3, [r0] | ||
| 181 | |||
| 182 | bl cpu_do_idle | ||
| 183 | |||
| 184 | /* | ||
| 185 | * cpu may be reset while in wfi, which will return through | ||
| 186 | * tegra_resume to cpu_resume | ||
| 187 | * or interrupt may wake wfi, which will return here | ||
| 188 | * cpu state is unchanged - MMU is on, cache is on, coherency | ||
| 189 | * is off, and the data cache is off | ||
| 190 | * | ||
| 191 | * r11 contains the original actlr | ||
| 192 | */ | ||
| 193 | |||
| 194 | bl tegra_pen_lock | ||
| 195 | |||
| 196 | mov32 r3, TEGRA_PMC_VIRT | ||
| 197 | add r0, r3, #PMC_SCRATCH41 | ||
| 198 | mov r3, #CPU_NOT_RESETTABLE | ||
| 199 | str r3, [r0] | ||
| 200 | |||
| 201 | bl tegra_pen_unlock | ||
| 202 | |||
| 203 | /* Re-enable the data cache */ | ||
| 204 | mrc p15, 0, r10, c1, c0, 0 | ||
| 205 | orr r10, r10, #CR_C | ||
| 206 | mcr p15, 0, r10, c1, c0, 0 | ||
| 207 | isb | ||
| 208 | |||
| 209 | mcr p15, 0, r11, c1, c0, 1 @ reenable coherency | ||
| 210 | |||
| 211 | /* Invalidate the TLBs & BTAC */ | ||
| 212 | mov r1, #0 | ||
| 213 | mcr p15, 0, r1, c8, c3, 0 @ invalidate shared TLBs | ||
| 214 | mcr p15, 0, r1, c7, c1, 6 @ invalidate shared BTAC | ||
| 215 | dsb | ||
| 216 | isb | ||
| 217 | |||
| 218 | /* the cpu was running with coherency disabled, | ||
| 219 | * caches may be out of date */ | ||
| 220 | bl v7_flush_kern_cache_louis | ||
| 221 | |||
| 222 | ldmfd sp!, {r4 - r11, pc} | ||
| 223 | ENDPROC(tegra20_sleep_cpu_secondary_finish) | ||
| 224 | #endif | ||
diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h index 56505c381ea8..e39a56beb3a0 100644 --- a/arch/arm/mach-tegra/sleep.h +++ b/arch/arm/mach-tegra/sleep.h | |||
| @@ -25,6 +25,19 @@ | |||
| 25 | + IO_PPSB_VIRT) | 25 | + IO_PPSB_VIRT) |
| 26 | #define TEGRA_CLK_RESET_VIRT (TEGRA_CLK_RESET_BASE - IO_PPSB_PHYS \ | 26 | #define TEGRA_CLK_RESET_VIRT (TEGRA_CLK_RESET_BASE - IO_PPSB_PHYS \ |
| 27 | + IO_PPSB_VIRT) | 27 | + IO_PPSB_VIRT) |
| 28 | #define TEGRA_PMC_VIRT (TEGRA_PMC_BASE - IO_APB_PHYS + IO_APB_VIRT) | ||
| 29 | |||
| 30 | /* PMC_SCRATCH37-39 and 41 are used for tegra_pen_lock and idle */ | ||
| 31 | #define PMC_SCRATCH37 0x130 | ||
| 32 | #define PMC_SCRATCH38 0x134 | ||
| 33 | #define PMC_SCRATCH39 0x138 | ||
| 34 | #define PMC_SCRATCH41 0x140 | ||
| 35 | |||
| 36 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
| 37 | #define CPU_RESETTABLE 2 | ||
| 38 | #define CPU_RESETTABLE_SOON 1 | ||
| 39 | #define CPU_NOT_RESETTABLE 0 | ||
| 40 | #endif | ||
| 28 | 41 | ||
| 29 | #ifdef __ASSEMBLY__ | 42 | #ifdef __ASSEMBLY__ |
| 30 | /* returns the offset of the flow controller halt register for a cpu */ | 43 | /* returns the offset of the flow controller halt register for a cpu */ |
| @@ -104,6 +117,8 @@ exit_l2_resume: | |||
| 104 | .endm | 117 | .endm |
| 105 | #endif /* CONFIG_CACHE_L2X0 */ | 118 | #endif /* CONFIG_CACHE_L2X0 */ |
| 106 | #else | 119 | #else |
| 120 | void tegra_pen_lock(void); | ||
| 121 | void tegra_pen_unlock(void); | ||
| 107 | void tegra_resume(void); | 122 | void tegra_resume(void); |
| 108 | int tegra_sleep_cpu_finish(unsigned long); | 123 | int tegra_sleep_cpu_finish(unsigned long); |
| 109 | void tegra_disable_clean_inv_dcache(void); | 124 | void tegra_disable_clean_inv_dcache(void); |
| @@ -116,6 +131,14 @@ static inline void tegra20_hotplug_init(void) {} | |||
| 116 | static inline void tegra30_hotplug_init(void) {} | 131 | static inline void tegra30_hotplug_init(void) {} |
| 117 | #endif | 132 | #endif |
| 118 | 133 | ||
| 134 | void tegra20_cpu_clear_resettable(void); | ||
| 135 | #ifdef CONFIG_ARCH_TEGRA_2x_SOC | ||
| 136 | void tegra20_cpu_set_resettable_soon(void); | ||
| 137 | #else | ||
| 138 | static inline void tegra20_cpu_set_resettable_soon(void) {} | ||
| 139 | #endif | ||
| 140 | |||
| 141 | int tegra20_sleep_cpu_secondary_finish(unsigned long); | ||
| 119 | int tegra30_sleep_cpu_secondary_finish(unsigned long); | 142 | int tegra30_sleep_cpu_secondary_finish(unsigned long); |
| 120 | void tegra30_tear_down_cpu(void); | 143 | void tegra30_tear_down_cpu(void); |
| 121 | 144 | ||
