diff options
Diffstat (limited to 'arch/arm/mach-tegra/pm.c')
-rw-r--r-- | arch/arm/mach-tegra/pm.c | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/pm.c b/arch/arm/mach-tegra/pm.c new file mode 100644 index 000000000000..1b11707eaca0 --- /dev/null +++ b/arch/arm/mach-tegra/pm.c | |||
@@ -0,0 +1,216 @@ | |||
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> | ||
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> | ||
34 | |||
35 | #include "iomap.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 | ||
46 | |||
47 | #ifdef CONFIG_PM_SLEEP | ||
48 | static unsigned int g_diag_reg; | ||
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); | ||
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 | |||
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 | |||
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 | } | ||
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 | |||
211 | cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu); | ||
212 | |||
213 | restore_cpu_complex(); | ||
214 | cpu_cluster_pm_exit(); | ||
215 | } | ||
216 | #endif | ||