Commit | Line | Data |
---|---|---|
d457ef35 JL |
1 | /* |
2 | * CPU complex suspend & resume functions for Tegra SoCs | |
3 | * | |
4 | * Copyright (c) 2009-2012, NVIDIA Corporation. All rights reserved. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | ||
19 | #include <linux/kernel.h> | |
20 | #include <linux/spinlock.h> | |
21 | #include <linux/io.h> | |
22 | #include <linux/cpumask.h> | |
d552920a JL |
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> | |
d457ef35 JL |
34 | |
35 | #include "iomap.h" | |
36 | #include "reset.h" | |
d552920a JL |
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 | |
d457ef35 JL |
46 | |
47 | #ifdef CONFIG_PM_SLEEP | |
48 | static unsigned int g_diag_reg; | |
49 | static DEFINE_SPINLOCK(tegra_lp2_lock); | |
d552920a JL |
50 | static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE); |
51 | static struct clk *tegra_pclk; | |
52 | void (*tegra_tear_down_cpu)(void); | |
d457ef35 JL |
53 | |
54 | void save_cpu_arch_register(void) | |
55 | { | |
56 | /* read diagnostic register */ | |
57 | asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc"); | |
58 | return; | |
59 | } | |
60 | ||
61 | void restore_cpu_arch_register(void) | |
62 | { | |
63 | /* write diagnostic register */ | |
64 | asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc"); | |
65 | return; | |
66 | } | |
67 | ||
d552920a JL |
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 | ||
d457ef35 JL |
151 | void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id) |
152 | { | |
153 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; | |
154 | ||
155 | spin_lock(&tegra_lp2_lock); | |
156 | ||
157 | BUG_ON(!(*cpu_in_lp2 & BIT(phy_cpu_id))); | |
158 | *cpu_in_lp2 &= ~BIT(phy_cpu_id); | |
159 | ||
160 | spin_unlock(&tegra_lp2_lock); | |
161 | } | |
162 | ||
163 | bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id) | |
164 | { | |
165 | bool last_cpu = false; | |
166 | cpumask_t *cpu_lp2_mask = tegra_cpu_lp2_mask; | |
167 | u32 *cpu_in_lp2 = tegra_cpu_lp2_mask; | |
168 | ||
169 | spin_lock(&tegra_lp2_lock); | |
170 | ||
171 | BUG_ON((*cpu_in_lp2 & BIT(phy_cpu_id))); | |
172 | *cpu_in_lp2 |= BIT(phy_cpu_id); | |
173 | ||
174 | if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask)) | |
175 | last_cpu = true; | |
176 | ||
177 | spin_unlock(&tegra_lp2_lock); | |
178 | return last_cpu; | |
179 | } | |
d552920a JL |
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(); | |
d552920a JL |
210 | |
211 | cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu); | |
212 | ||
d552920a JL |
213 | restore_cpu_complex(); |
214 | cpu_cluster_pm_exit(); | |
215 | } | |
d457ef35 | 216 | #endif |