diff options
-rw-r--r-- | arch/arm/mach-tegra/cpuidle-tegra30.c | 44 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm.c | 144 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm.h | 3 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep-tegra30.S | 44 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep.S | 42 | ||||
-rw-r--r-- | arch/arm/mach-tegra/sleep.h | 2 |
6 files changed, 275 insertions, 4 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-tegra30.c b/arch/arm/mach-tegra/cpuidle-tegra30.c index cc48d7fa3358..5e8cbf5b799f 100644 --- a/arch/arm/mach-tegra/cpuidle-tegra30.c +++ b/arch/arm/mach-tegra/cpuidle-tegra30.c | |||
@@ -32,6 +32,7 @@ | |||
32 | 32 | ||
33 | #include "pm.h" | 33 | #include "pm.h" |
34 | #include "sleep.h" | 34 | #include "sleep.h" |
35 | #include "tegra_cpu_car.h" | ||
35 | 36 | ||
36 | #ifdef CONFIG_PM_SLEEP | 37 | #ifdef CONFIG_PM_SLEEP |
37 | static int tegra30_idle_lp2(struct cpuidle_device *dev, | 38 | static int tegra30_idle_lp2(struct cpuidle_device *dev, |
@@ -67,6 +68,31 @@ static struct cpuidle_driver tegra_idle_driver = { | |||
67 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); | 68 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); |
68 | 69 | ||
69 | #ifdef CONFIG_PM_SLEEP | 70 | #ifdef CONFIG_PM_SLEEP |
71 | static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev, | ||
72 | struct cpuidle_driver *drv, | ||
73 | int index) | ||
74 | { | ||
75 | struct cpuidle_state *state = &drv->states[index]; | ||
76 | u32 cpu_on_time = state->exit_latency; | ||
77 | u32 cpu_off_time = state->target_residency - state->exit_latency; | ||
78 | |||
79 | /* All CPUs entering LP2 is not working. | ||
80 | * Don't let CPU0 enter LP2 when any secondary CPU is online. | ||
81 | */ | ||
82 | if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) { | ||
83 | cpu_do_idle(); | ||
84 | return false; | ||
85 | } | ||
86 | |||
87 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
88 | |||
89 | tegra_idle_lp2_last(cpu_on_time, cpu_off_time); | ||
90 | |||
91 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
92 | |||
93 | return true; | ||
94 | } | ||
95 | |||
70 | #ifdef CONFIG_SMP | 96 | #ifdef CONFIG_SMP |
71 | static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, | 97 | static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, |
72 | struct cpuidle_driver *drv, | 98 | struct cpuidle_driver *drv, |
@@ -101,16 +127,22 @@ static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev, | |||
101 | { | 127 | { |
102 | u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu; | 128 | u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu; |
103 | bool entered_lp2 = false; | 129 | bool entered_lp2 = false; |
130 | bool last_cpu; | ||
104 | 131 | ||
105 | local_fiq_disable(); | 132 | local_fiq_disable(); |
106 | 133 | ||
107 | tegra_set_cpu_in_lp2(cpu); | 134 | last_cpu = tegra_set_cpu_in_lp2(cpu); |
108 | cpu_pm_enter(); | 135 | cpu_pm_enter(); |
109 | 136 | ||
110 | if (cpu == 0) | 137 | if (cpu == 0) { |
111 | cpu_do_idle(); | 138 | if (last_cpu) |
112 | else | 139 | entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv, |
140 | index); | ||
141 | else | ||
142 | cpu_do_idle(); | ||
143 | } else { | ||
113 | entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index); | 144 | entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index); |
145 | } | ||
114 | 146 | ||
115 | cpu_pm_exit(); | 147 | cpu_pm_exit(); |
116 | tegra_clear_cpu_in_lp2(cpu); | 148 | tegra_clear_cpu_in_lp2(cpu); |
@@ -130,6 +162,10 @@ int __init tegra30_cpuidle_init(void) | |||
130 | struct cpuidle_device *dev; | 162 | struct cpuidle_device *dev; |
131 | struct cpuidle_driver *drv = &tegra_idle_driver; | 163 | struct cpuidle_driver *drv = &tegra_idle_driver; |
132 | 164 | ||
165 | #ifdef CONFIG_PM_SLEEP | ||
166 | tegra_tear_down_cpu = tegra30_tear_down_cpu; | ||
167 | #endif | ||
168 | |||
133 | ret = cpuidle_register_driver(&tegra_idle_driver); | 169 | ret = cpuidle_register_driver(&tegra_idle_driver); |
134 | if (ret) { | 170 | if (ret) { |
135 | pr_err("CPUidle driver registration failed\n"); | 171 | pr_err("CPUidle driver registration failed\n"); |
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c index f88595abc471..1460c3db8245 100644 --- a/arch/arm/mach-tegra/pm.c +++ b/arch/arm/mach-tegra/pm.c | |||
@@ -20,13 +20,36 @@ | |||
20 | #include <linux/spinlock.h> | 20 | #include <linux/spinlock.h> |
21 | #include <linux/io.h> | 21 | #include <linux/io.h> |
22 | #include <linux/cpumask.h> | 22 | #include <linux/cpumask.h> |
23 | #include <linux/delay.h> | ||
24 | #include <linux/cpu_pm.h> | ||
25 | #include <linux/clk.h> | ||
26 | #include <linux/err.h> | ||
27 | |||
28 | #include <asm/smp_plat.h> | ||
29 | #include <asm/cacheflush.h> | ||
30 | #include <asm/suspend.h> | ||
31 | #include <asm/idmap.h> | ||
32 | #include <asm/proc-fns.h> | ||
33 | #include <asm/tlbflush.h> | ||
23 | 34 | ||
24 | #include "iomap.h" | 35 | #include "iomap.h" |
25 | #include "reset.h" | 36 | #include "reset.h" |
37 | #include "flowctrl.h" | ||
38 | #include "sleep.h" | ||
39 | #include "tegra_cpu_car.h" | ||
40 | |||
41 | #define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ | ||
42 | |||
43 | #define PMC_CTRL 0x0 | ||
44 | #define PMC_CPUPWRGOOD_TIMER 0xc8 | ||
45 | #define PMC_CPUPWROFF_TIMER 0xcc | ||
26 | 46 | ||
27 | #ifdef CONFIG_PM_SLEEP | 47 | #ifdef CONFIG_PM_SLEEP |
28 | static unsigned int g_diag_reg; | 48 | static unsigned int g_diag_reg; |
29 | static DEFINE_SPINLOCK(tegra_lp2_lock); | 49 | static DEFINE_SPINLOCK(tegra_lp2_lock); |
50 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); | ||
51 | static struct clk *tegra_pclk; | ||
52 | void (*tegra_tear_down_cpu)(void); | ||
30 | 53 | ||
31 | void save_cpu_arch_register(void) | 54 | void save_cpu_arch_register(void) |
32 | { | 55 | { |
@@ -42,6 +65,89 @@ void restore_cpu_arch_register(void) | |||
42 | return; | 65 | return; |
43 | } | 66 | } |
44 | 67 | ||
68 | static void set_power_timers(unsigned long us_on, unsigned long us_off) | ||
69 | { | ||
70 | unsigned long long ticks; | ||
71 | unsigned long long pclk; | ||
72 | unsigned long rate; | ||
73 | static unsigned long tegra_last_pclk; | ||
74 | |||
75 | if (tegra_pclk == NULL) { | ||
76 | tegra_pclk = clk_get_sys(NULL, "pclk"); | ||
77 | WARN_ON(IS_ERR(tegra_pclk)); | ||
78 | } | ||
79 | |||
80 | rate = clk_get_rate(tegra_pclk); | ||
81 | |||
82 | if (WARN_ON_ONCE(rate <= 0)) | ||
83 | pclk = 100000000; | ||
84 | else | ||
85 | pclk = rate; | ||
86 | |||
87 | if ((rate != tegra_last_pclk)) { | ||
88 | ticks = (us_on * pclk) + 999999ull; | ||
89 | do_div(ticks, 1000000); | ||
90 | writel((unsigned long)ticks, pmc + PMC_CPUPWRGOOD_TIMER); | ||
91 | |||
92 | ticks = (us_off * pclk) + 999999ull; | ||
93 | do_div(ticks, 1000000); | ||
94 | writel((unsigned long)ticks, pmc + PMC_CPUPWROFF_TIMER); | ||
95 | wmb(); | ||
96 | } | ||
97 | tegra_last_pclk = pclk; | ||
98 | } | ||
99 | |||
100 | /* | ||
101 | * restore_cpu_complex | ||
102 | * | ||
103 | * restores cpu clock setting, clears flow controller | ||
104 | * | ||
105 | * Always called on CPU 0. | ||
106 | */ | ||
107 | static void restore_cpu_complex(void) | ||
108 | { | ||
109 | int cpu = smp_processor_id(); | ||
110 | |||
111 | BUG_ON(cpu != 0); | ||
112 | |||
113 | #ifdef CONFIG_SMP | ||
114 | cpu = cpu_logical_map(cpu); | ||
115 | #endif | ||
116 | |||
117 | /* Restore the CPU clock settings */ | ||
118 | tegra_cpu_clock_resume(); | ||
119 | |||
120 | flowctrl_cpu_suspend_exit(cpu); | ||
121 | |||
122 | restore_cpu_arch_register(); | ||
123 | } | ||
124 | |||
125 | /* | ||
126 | * suspend_cpu_complex | ||
127 | * | ||
128 | * saves pll state for use by restart_plls, prepares flow controller for | ||
129 | * transition to suspend state | ||
130 | * | ||
131 | * Must always be called on cpu 0. | ||
132 | */ | ||
133 | static void suspend_cpu_complex(void) | ||
134 | { | ||
135 | int cpu = smp_processor_id(); | ||
136 | |||
137 | BUG_ON(cpu != 0); | ||
138 | |||
139 | #ifdef CONFIG_SMP | ||
140 | cpu = cpu_logical_map(cpu); | ||
141 | #endif | ||
142 | |||
143 | /* Save the CPU clock settings */ | ||
144 | tegra_cpu_clock_suspend(); | ||
145 | |||
146 | flowctrl_cpu_suspend_enter(cpu); | ||
147 | |||
148 | save_cpu_arch_register(); | ||
149 | } | ||
150 | |||
45 | void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id) | 151 | void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id) |
46 | { | 152 | { |
47 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; | 153 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; |
@@ -71,4 +177,42 @@ bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id) | |||
71 | spin_unlock(&tegra_lp2_lock); | 177 | spin_unlock(&tegra_lp2_lock); |
72 | return last_cpu; | 178 | return last_cpu; |
73 | } | 179 | } |
180 | |||
181 | static int tegra_sleep_cpu(unsigned long v2p) | ||
182 | { | ||
183 | /* Switch to the identity mapping. */ | ||
184 | cpu_switch_mm(idmap_pgd, &init_mm); | ||
185 | |||
186 | /* Flush the TLB. */ | ||
187 | local_flush_tlb_all(); | ||
188 | |||
189 | tegra_sleep_cpu_finish(v2p); | ||
190 | |||
191 | /* should never here */ | ||
192 | BUG(); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time) | ||
198 | { | ||
199 | u32 mode; | ||
200 | |||
201 | /* Only the last cpu down does the final suspend steps */ | ||
202 | mode = readl(pmc + PMC_CTRL); | ||
203 | mode |= TEGRA_POWER_CPU_PWRREQ_OE; | ||
204 | writel(mode, pmc + PMC_CTRL); | ||
205 | |||
206 | set_power_timers(cpu_on_time, cpu_off_time); | ||
207 | |||
208 | cpu_cluster_pm_enter(); | ||
209 | suspend_cpu_complex(); | ||
210 | outer_disable(); | ||
211 | |||
212 | cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu); | ||
213 | |||
214 | outer_resume(); | ||
215 | restore_cpu_complex(); | ||
216 | cpu_cluster_pm_exit(); | ||
217 | } | ||
74 | #endif | 218 | #endif |
diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h index bcfc45faad78..512345c9eec3 100644 --- a/arch/arm/mach-tegra/pm.h +++ b/arch/arm/mach-tegra/pm.h | |||
@@ -27,4 +27,7 @@ void restore_cpu_arch_register(void); | |||
27 | void tegra_clear_cpu_in_lp2(int phy_cpu_id); | 27 | void tegra_clear_cpu_in_lp2(int phy_cpu_id); |
28 | bool tegra_set_cpu_in_lp2(int phy_cpu_id); | 28 | bool tegra_set_cpu_in_lp2(int phy_cpu_id); |
29 | 29 | ||
30 | void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time); | ||
31 | extern void (*tegra_tear_down_cpu)(void); | ||
32 | |||
30 | #endif /* _MACH_TEGRA_PM_H_ */ | 33 | #endif /* _MACH_TEGRA_PM_H_ */ |
diff --git a/arch/arm/mach-tegra/sleep-tegra30.S b/arch/arm/mach-tegra/sleep-tegra30.S index 59984d718481..562a8e7e413d 100644 --- a/arch/arm/mach-tegra/sleep-tegra30.S +++ b/arch/arm/mach-tegra/sleep-tegra30.S | |||
@@ -124,4 +124,48 @@ ENTRY(tegra30_sleep_cpu_secondary_finish) | |||
124 | mov r0, #1 @ never return here | 124 | mov r0, #1 @ never return here |
125 | mov pc, r7 | 125 | mov pc, r7 |
126 | ENDPROC(tegra30_sleep_cpu_secondary_finish) | 126 | ENDPROC(tegra30_sleep_cpu_secondary_finish) |
127 | |||
128 | /* | ||
129 | * tegra30_tear_down_cpu | ||
130 | * | ||
131 | * Switches the CPU to enter sleep. | ||
132 | */ | ||
133 | ENTRY(tegra30_tear_down_cpu) | ||
134 | mov32 r6, TEGRA_FLOW_CTRL_BASE | ||
135 | |||
136 | b tegra30_enter_sleep | ||
137 | ENDPROC(tegra30_tear_down_cpu) | ||
138 | |||
139 | /* | ||
140 | * tegra30_enter_sleep | ||
141 | * | ||
142 | * uses flow controller to enter sleep state | ||
143 | * executes from IRAM with SDRAM in selfrefresh when target state is LP0 or LP1 | ||
144 | * executes from SDRAM with target state is LP2 | ||
145 | * r6 = TEGRA_FLOW_CTRL_BASE | ||
146 | */ | ||
147 | tegra30_enter_sleep: | ||
148 | cpu_id r1 | ||
149 | |||
150 | cpu_to_csr_reg r2, r1 | ||
151 | ldr r0, [r6, r2] | ||
152 | orr r0, r0, #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG | ||
153 | orr r0, r0, #FLOW_CTRL_CSR_ENABLE | ||
154 | str r0, [r6, r2] | ||
155 | |||
156 | mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT | ||
157 | orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ | ||
158 | cpu_to_halt_reg r2, r1 | ||
159 | str r0, [r6, r2] | ||
160 | dsb | ||
161 | ldr r0, [r6, r2] /* memory barrier */ | ||
162 | |||
163 | halted: | ||
164 | isb | ||
165 | dsb | ||
166 | wfi /* CPU should be power gated here */ | ||
167 | |||
168 | /* !!!FIXME!!! Implement halt failure handler */ | ||
169 | b halted | ||
170 | |||
127 | #endif | 171 | #endif |
diff --git a/arch/arm/mach-tegra/sleep.S b/arch/arm/mach-tegra/sleep.S index 91548a77dd95..88f4de986a52 100644 --- a/arch/arm/mach-tegra/sleep.S +++ b/arch/arm/mach-tegra/sleep.S | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/linkage.h> | 25 | #include <linux/linkage.h> |
26 | 26 | ||
27 | #include <asm/assembler.h> | 27 | #include <asm/assembler.h> |
28 | #include <asm/cache.h> | ||
28 | #include <asm/cp15.h> | 29 | #include <asm/cp15.h> |
29 | 30 | ||
30 | #include "iomap.h" | 31 | #include "iomap.h" |
@@ -59,4 +60,45 @@ ENTRY(tegra_disable_clean_inv_dcache) | |||
59 | ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc} | 60 | ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc} |
60 | ENDPROC(tegra_disable_clean_inv_dcache) | 61 | ENDPROC(tegra_disable_clean_inv_dcache) |
61 | 62 | ||
63 | /* | ||
64 | * tegra_sleep_cpu_finish(unsigned long v2p) | ||
65 | * | ||
66 | * enters suspend in LP2 by turning off the mmu and jumping to | ||
67 | * tegra?_tear_down_cpu | ||
68 | */ | ||
69 | ENTRY(tegra_sleep_cpu_finish) | ||
70 | /* Flush and disable the L1 data cache */ | ||
71 | bl tegra_disable_clean_inv_dcache | ||
72 | |||
73 | mov32 r6, tegra_tear_down_cpu | ||
74 | ldr r1, [r6] | ||
75 | add r1, r1, r0 | ||
76 | |||
77 | mov32 r3, tegra_shut_off_mmu | ||
78 | add r3, r3, r0 | ||
79 | mov r0, r1 | ||
80 | |||
81 | mov pc, r3 | ||
82 | ENDPROC(tegra_sleep_cpu_finish) | ||
83 | |||
84 | /* | ||
85 | * tegra_shut_off_mmu | ||
86 | * | ||
87 | * r0 = physical address to jump to with mmu off | ||
88 | * | ||
89 | * called with VA=PA mapping | ||
90 | * turns off MMU, icache, dcache and branch prediction | ||
91 | */ | ||
92 | .align L1_CACHE_SHIFT | ||
93 | .pushsection .idmap.text, "ax" | ||
94 | ENTRY(tegra_shut_off_mmu) | ||
95 | mrc p15, 0, r3, c1, c0, 0 | ||
96 | movw r2, #CR_I | CR_Z | CR_C | CR_M | ||
97 | bic r3, r3, r2 | ||
98 | dsb | ||
99 | mcr p15, 0, r3, c1, c0, 0 | ||
100 | isb | ||
101 | mov pc, r0 | ||
102 | ENDPROC(tegra_shut_off_mmu) | ||
103 | .popsection | ||
62 | #endif | 104 | #endif |
diff --git a/arch/arm/mach-tegra/sleep.h b/arch/arm/mach-tegra/sleep.h index bacf549bd54d..6e1b9490c1cf 100644 --- a/arch/arm/mach-tegra/sleep.h +++ b/arch/arm/mach-tegra/sleep.h | |||
@@ -73,6 +73,7 @@ | |||
73 | .endm | 73 | .endm |
74 | #else | 74 | #else |
75 | void tegra_resume(void); | 75 | void tegra_resume(void); |
76 | int tegra_sleep_cpu_finish(unsigned long); | ||
76 | 77 | ||
77 | #ifdef CONFIG_HOTPLUG_CPU | 78 | #ifdef CONFIG_HOTPLUG_CPU |
78 | void tegra20_hotplug_init(void); | 79 | void tegra20_hotplug_init(void); |
@@ -83,6 +84,7 @@ static inline void tegra30_hotplug_init(void) {} | |||
83 | #endif | 84 | #endif |
84 | 85 | ||
85 | int tegra30_sleep_cpu_secondary_finish(unsigned long); | 86 | int tegra30_sleep_cpu_secondary_finish(unsigned long); |
87 | void tegra30_tear_down_cpu(void); | ||
86 | 88 | ||
87 | #endif | 89 | #endif |
88 | #endif | 90 | #endif |