diff options
Diffstat (limited to 'arch/arm/mach-tegra/cpuidle-tegra20.c')
-rw-r--r-- | arch/arm/mach-tegra/cpuidle-tegra20.c | 197 |
1 files changed, 193 insertions, 4 deletions
diff --git a/arch/arm/mach-tegra/cpuidle-tegra20.c b/arch/arm/mach-tegra/cpuidle-tegra20.c index d32e8b0dbd4f..825ced4f7a40 100644 --- a/arch/arm/mach-tegra/cpuidle-tegra20.c +++ b/arch/arm/mach-tegra/cpuidle-tegra20.c | |||
@@ -22,21 +22,199 @@ | |||
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> | ||
27 | #include <linux/clk/tegra.h> | ||
25 | 28 | ||
26 | #include <asm/cpuidle.h> | 29 | #include <asm/cpuidle.h> |
30 | #include <asm/proc-fns.h> | ||
31 | #include <asm/suspend.h> | ||
32 | #include <asm/smp_plat.h> | ||
33 | |||
34 | #include "pm.h" | ||
35 | #include "sleep.h" | ||
36 | #include "iomap.h" | ||
37 | #include "irq.h" | ||
38 | #include "flowctrl.h" | ||
39 | |||
40 | #ifdef CONFIG_PM_SLEEP | ||
41 | static bool abort_flag; | ||
42 | static atomic_t abort_barrier; | ||
43 | static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev, | ||
44 | struct cpuidle_driver *drv, | ||
45 | int index); | ||
46 | #endif | ||
47 | |||
48 | static struct cpuidle_state tegra_idle_states[] = { | ||
49 | [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), | ||
50 | #ifdef CONFIG_PM_SLEEP | ||
51 | [1] = { | ||
52 | .enter = tegra20_idle_lp2_coupled, | ||
53 | .exit_latency = 5000, | ||
54 | .target_residency = 10000, | ||
55 | .power_usage = 0, | ||
56 | .flags = CPUIDLE_FLAG_TIME_VALID | | ||
57 | CPUIDLE_FLAG_COUPLED, | ||
58 | .name = "powered-down", | ||
59 | .desc = "CPU power gated", | ||
60 | }, | ||
61 | #endif | ||
62 | }; | ||
27 | 63 | ||
28 | static struct cpuidle_driver tegra_idle_driver = { | 64 | static struct cpuidle_driver tegra_idle_driver = { |
29 | .name = "tegra_idle", | 65 | .name = "tegra_idle", |
30 | .owner = THIS_MODULE, | 66 | .owner = THIS_MODULE, |
31 | .en_core_tk_irqen = 1, | 67 | .en_core_tk_irqen = 1, |
32 | .state_count = 1, | ||
33 | .states = { | ||
34 | [0] = ARM_CPUIDLE_WFI_STATE_PWR(600), | ||
35 | }, | ||
36 | }; | 68 | }; |
37 | 69 | ||
38 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); | 70 | static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device); |
39 | 71 | ||
72 | #ifdef CONFIG_PM_SLEEP | ||
73 | #ifdef CONFIG_SMP | ||
74 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); | ||
75 | |||
76 | static int tegra20_reset_sleeping_cpu_1(void) | ||
77 | { | ||
78 | int ret = 0; | ||
79 | |||
80 | tegra_pen_lock(); | ||
81 | |||
82 | if (readl(pmc + PMC_SCRATCH41) == CPU_RESETTABLE) | ||
83 | tegra20_cpu_shutdown(1); | ||
84 | else | ||
85 | ret = -EINVAL; | ||
86 | |||
87 | tegra_pen_unlock(); | ||
88 | |||
89 | return ret; | ||
90 | } | ||
91 | |||
92 | static void tegra20_wake_cpu1_from_reset(void) | ||
93 | { | ||
94 | tegra_pen_lock(); | ||
95 | |||
96 | tegra20_cpu_clear_resettable(); | ||
97 | |||
98 | /* enable cpu clock on cpu */ | ||
99 | tegra_enable_cpu_clock(1); | ||
100 | |||
101 | /* take the CPU out of reset */ | ||
102 | tegra_cpu_out_of_reset(1); | ||
103 | |||
104 | /* unhalt the cpu */ | ||
105 | flowctrl_write_cpu_halt(1, 0); | ||
106 | |||
107 | tegra_pen_unlock(); | ||
108 | } | ||
109 | |||
110 | static int tegra20_reset_cpu_1(void) | ||
111 | { | ||
112 | if (!cpu_online(1) || !tegra20_reset_sleeping_cpu_1()) | ||
113 | return 0; | ||
114 | |||
115 | tegra20_wake_cpu1_from_reset(); | ||
116 | return -EBUSY; | ||
117 | } | ||
118 | #else | ||
119 | static inline void tegra20_wake_cpu1_from_reset(void) | ||
120 | { | ||
121 | } | ||
122 | |||
123 | static inline int tegra20_reset_cpu_1(void) | ||
124 | { | ||
125 | return 0; | ||
126 | } | ||
127 | #endif | ||
128 | |||
129 | static bool tegra20_cpu_cluster_power_down(struct cpuidle_device *dev, | ||
130 | struct cpuidle_driver *drv, | ||
131 | int index) | ||
132 | { | ||
133 | struct cpuidle_state *state = &drv->states[index]; | ||
134 | u32 cpu_on_time = state->exit_latency; | ||
135 | u32 cpu_off_time = state->target_residency - state->exit_latency; | ||
136 | |||
137 | while (tegra20_cpu_is_resettable_soon()) | ||
138 | cpu_relax(); | ||
139 | |||
140 | if (tegra20_reset_cpu_1() || !tegra_cpu_rail_off_ready()) | ||
141 | return false; | ||
142 | |||
143 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
144 | |||
145 | tegra_idle_lp2_last(cpu_on_time, cpu_off_time); | ||
146 | |||
147 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
148 | |||
149 | if (cpu_online(1)) | ||
150 | tegra20_wake_cpu1_from_reset(); | ||
151 | |||
152 | return true; | ||
153 | } | ||
154 | |||
155 | #ifdef CONFIG_SMP | ||
156 | static bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev, | ||
157 | struct cpuidle_driver *drv, | ||
158 | int index) | ||
159 | { | ||
160 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu); | ||
161 | |||
162 | cpu_suspend(0, tegra20_sleep_cpu_secondary_finish); | ||
163 | |||
164 | tegra20_cpu_clear_resettable(); | ||
165 | |||
166 | clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu); | ||
167 | |||
168 | return true; | ||
169 | } | ||
170 | #else | ||
171 | static inline bool tegra20_idle_enter_lp2_cpu_1(struct cpuidle_device *dev, | ||
172 | struct cpuidle_driver *drv, | ||
173 | int index) | ||
174 | { | ||
175 | return true; | ||
176 | } | ||
177 | #endif | ||
178 | |||
179 | static int tegra20_idle_lp2_coupled(struct cpuidle_device *dev, | ||
180 | struct cpuidle_driver *drv, | ||
181 | int index) | ||
182 | { | ||
183 | u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu; | ||
184 | bool entered_lp2 = false; | ||
185 | |||
186 | if (tegra_pending_sgi()) | ||
187 | ACCESS_ONCE(abort_flag) = true; | ||
188 | |||
189 | cpuidle_coupled_parallel_barrier(dev, &abort_barrier); | ||
190 | |||
191 | if (abort_flag) { | ||
192 | cpuidle_coupled_parallel_barrier(dev, &abort_barrier); | ||
193 | abort_flag = false; /* clean flag for next coming */ | ||
194 | return -EINTR; | ||
195 | } | ||
196 | |||
197 | local_fiq_disable(); | ||
198 | |||
199 | tegra_set_cpu_in_lp2(cpu); | ||
200 | cpu_pm_enter(); | ||
201 | |||
202 | if (cpu == 0) | ||
203 | entered_lp2 = tegra20_cpu_cluster_power_down(dev, drv, index); | ||
204 | else | ||
205 | entered_lp2 = tegra20_idle_enter_lp2_cpu_1(dev, drv, index); | ||
206 | |||
207 | cpu_pm_exit(); | ||
208 | tegra_clear_cpu_in_lp2(cpu); | ||
209 | |||
210 | local_fiq_enable(); | ||
211 | |||
212 | smp_rmb(); | ||
213 | |||
214 | return entered_lp2 ? index : 0; | ||
215 | } | ||
216 | #endif | ||
217 | |||
40 | int __init tegra20_cpuidle_init(void) | 218 | int __init tegra20_cpuidle_init(void) |
41 | { | 219 | { |
42 | int ret; | 220 | int ret; |
@@ -44,6 +222,14 @@ int __init tegra20_cpuidle_init(void) | |||
44 | struct cpuidle_device *dev; | 222 | struct cpuidle_device *dev; |
45 | struct cpuidle_driver *drv = &tegra_idle_driver; | 223 | struct cpuidle_driver *drv = &tegra_idle_driver; |
46 | 224 | ||
225 | #ifdef CONFIG_PM_SLEEP | ||
226 | tegra_tear_down_cpu = tegra20_tear_down_cpu; | ||
227 | #endif | ||
228 | |||
229 | drv->state_count = ARRAY_SIZE(tegra_idle_states); | ||
230 | memcpy(drv->states, tegra_idle_states, | ||
231 | drv->state_count * sizeof(drv->states[0])); | ||
232 | |||
47 | ret = cpuidle_register_driver(&tegra_idle_driver); | 233 | ret = cpuidle_register_driver(&tegra_idle_driver); |
48 | if (ret) { | 234 | if (ret) { |
49 | pr_err("CPUidle driver registration failed\n"); | 235 | pr_err("CPUidle driver registration failed\n"); |
@@ -53,6 +239,9 @@ int __init tegra20_cpuidle_init(void) | |||
53 | for_each_possible_cpu(cpu) { | 239 | for_each_possible_cpu(cpu) { |
54 | dev = &per_cpu(tegra_idle_device, cpu); | 240 | dev = &per_cpu(tegra_idle_device, cpu); |
55 | dev->cpu = cpu; | 241 | dev->cpu = cpu; |
242 | #ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED | ||
243 | dev->coupled_cpus = *cpu_possible_mask; | ||
244 | #endif | ||
56 | 245 | ||
57 | dev->state_count = drv->state_count; | 246 | dev->state_count = drv->state_count; |
58 | ret = cpuidle_register_device(dev); | 247 | ret = cpuidle_register_device(dev); |