aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoseph Lo <josephl@nvidia.com>2012-10-31 05:41:21 -0400
committerStephen Warren <swarren@nvidia.com>2012-11-15 17:09:22 -0500
commitd552920a02759cdc45d8507868de10ac2f5b9a18 (patch)
tree2c3c5f805e1657f64088631e63c145cc43739608
parent01459c69dd48badeb7833c3293e43f7b8ae75e31 (diff)
ARM: tegra30: cpuidle: add powered-down state for CPU0
This is a power gating idle mode. It support power gating vdd_cpu rail after all cpu cores in "powered-down" status. For Tegra30, the CPU0 can enter this state only when all secondary CPU is offline. We need to take care and make sure whole secondary CPUs were offline and checking the CPU power gate status. After that, the CPU0 can go into "powered-down" state safely. Then shut off the CPU rail. Be aware of that, you may see the legacy power state "LP2" in the code which is exactly the same meaning of "CPU power down". Base on the work by: Scott Williams <scwilliams@nvidia.com> Signed-off-by: Joseph Lo <josephl@nvidia.com> Signed-off-by: Stephen Warren <swarren@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/cpuidle-tegra30.c44
-rw-r--r--arch/arm/mach-tegra/pm.c144
-rw-r--r--arch/arm/mach-tegra/pm.h3
-rw-r--r--arch/arm/mach-tegra/sleep-tegra30.S44
-rw-r--r--arch/arm/mach-tegra/sleep.S42
-rw-r--r--arch/arm/mach-tegra/sleep.h2
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
37static int tegra30_idle_lp2(struct cpuidle_device *dev, 38static int tegra30_idle_lp2(struct cpuidle_device *dev,
@@ -67,6 +68,31 @@ static struct cpuidle_driver tegra_idle_driver = {
67static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); 68static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);
68 69
69#ifdef CONFIG_PM_SLEEP 70#ifdef CONFIG_PM_SLEEP
71static 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
71static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev, 97static 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
28static unsigned int g_diag_reg; 48static unsigned int g_diag_reg;
29static DEFINE_SPINLOCK(tegra_lp2_lock); 49static DEFINE_SPINLOCK(tegra_lp2_lock);
50static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
51static struct clk *tegra_pclk;
52void (*tegra_tear_down_cpu)(void);
30 53
31void save_cpu_arch_register(void) 54void 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
68static 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 */
107static 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 */
133static 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
45void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id) 151void __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
181static 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
197void 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);
27void tegra_clear_cpu_in_lp2(int phy_cpu_id); 27void tegra_clear_cpu_in_lp2(int phy_cpu_id);
28bool tegra_set_cpu_in_lp2(int phy_cpu_id); 28bool tegra_set_cpu_in_lp2(int phy_cpu_id);
29 29
30void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time);
31extern 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
126ENDPROC(tegra30_sleep_cpu_secondary_finish) 126ENDPROC(tegra30_sleep_cpu_secondary_finish)
127
128/*
129 * tegra30_tear_down_cpu
130 *
131 * Switches the CPU to enter sleep.
132 */
133ENTRY(tegra30_tear_down_cpu)
134 mov32 r6, TEGRA_FLOW_CTRL_BASE
135
136 b tegra30_enter_sleep
137ENDPROC(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 */
147tegra30_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
163halted:
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}
60ENDPROC(tegra_disable_clean_inv_dcache) 61ENDPROC(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 */
69ENTRY(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
82ENDPROC(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"
94ENTRY(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
102ENDPROC(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
75void tegra_resume(void); 75void tegra_resume(void);
76int tegra_sleep_cpu_finish(unsigned long);
76 77
77#ifdef CONFIG_HOTPLUG_CPU 78#ifdef CONFIG_HOTPLUG_CPU
78void tegra20_hotplug_init(void); 79void tegra20_hotplug_init(void);
@@ -83,6 +84,7 @@ static inline void tegra30_hotplug_init(void) {}
83#endif 84#endif
84 85
85int tegra30_sleep_cpu_secondary_finish(unsigned long); 86int tegra30_sleep_cpu_secondary_finish(unsigned long);
87void tegra30_tear_down_cpu(void);
86 88
87#endif 89#endif
88#endif 90#endif