Commit | Line | Data |
---|---|---|
d3b8bdd5 | 1 | /* |
291fde31 | 2 | * Copyright (C) 2012,2013 NVIDIA CORPORATION. All rights reserved. |
d3b8bdd5 SW |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms and conditions of the GNU General Public License, | |
6 | * version 2, as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
11 | * more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | * | |
16 | */ | |
17 | ||
0337c3e0 | 18 | #include <linux/clk.h> |
d3b8bdd5 | 19 | #include <linux/io.h> |
a0524acc | 20 | #include <linux/kernel.h> |
d3b8bdd5 | 21 | #include <linux/of.h> |
291fde31 | 22 | #include <linux/of_address.h> |
306a7f91 | 23 | |
304664ea | 24 | #include <soc/tegra/fuse.h> |
306a7f91 | 25 | #include <soc/tegra/powergate.h> |
d3b8bdd5 | 26 | |
3f1be81e | 27 | #include "flowctrl.h" |
c8c2e606 | 28 | #include "pm.h" |
4b51ccbc | 29 | #include "pmc.h" |
c8c2e606 JL |
30 | #include "sleep.h" |
31 | ||
444f9a80 JL |
32 | #define TEGRA_POWER_SYSCLK_POLARITY (1 << 10) /* sys clk polarity */ |
33 | #define TEGRA_POWER_SYSCLK_OE (1 << 11) /* system clock enable */ | |
c8c2e606 JL |
34 | #define TEGRA_POWER_EFFECT_LP0 (1 << 14) /* LP0 when CPU pwr gated */ |
35 | #define TEGRA_POWER_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */ | |
36 | #define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ | |
4b51ccbc | 37 | |
c141753f JL |
38 | #define PMC_CTRL 0x0 |
39 | #define PMC_CTRL_INTR_LOW (1 << 17) | |
40 | #define PMC_PWRGATE_TOGGLE 0x30 | |
41 | #define PMC_PWRGATE_TOGGLE_START (1 << 8) | |
42 | #define PMC_REMOVE_CLAMPING 0x34 | |
43 | #define PMC_PWRGATE_STATUS 0x38 | |
44 | ||
498bb3da TR |
45 | #define PMC_SCRATCH0 0x50 |
46 | #define PMC_SCRATCH0_MODE_RECOVERY (1 << 31) | |
47 | #define PMC_SCRATCH0_MODE_BOOTLOADER (1 << 30) | |
48 | #define PMC_SCRATCH0_MODE_RCM (1 << 1) | |
49 | #define PMC_SCRATCH0_MODE_MASK (PMC_SCRATCH0_MODE_RECOVERY | \ | |
50 | PMC_SCRATCH0_MODE_BOOTLOADER | \ | |
51 | PMC_SCRATCH0_MODE_RCM) | |
52 | ||
0337c3e0 JL |
53 | #define PMC_CPUPWRGOOD_TIMER 0xc8 |
54 | #define PMC_CPUPWROFF_TIMER 0xcc | |
55 | ||
c141753f JL |
56 | static u8 tegra_cpu_domains[] = { |
57 | 0xFF, /* not available for CPU0 */ | |
58 | TEGRA_POWERGATE_CPU1, | |
59 | TEGRA_POWERGATE_CPU2, | |
60 | TEGRA_POWERGATE_CPU3, | |
61 | }; | |
62 | static DEFINE_SPINLOCK(tegra_powergate_lock); | |
d3b8bdd5 | 63 | |
291fde31 JL |
64 | static void __iomem *tegra_pmc_base; |
65 | static bool tegra_pmc_invert_interrupt; | |
0337c3e0 | 66 | static struct clk *tegra_pclk; |
291fde31 | 67 | |
4b51ccbc JL |
68 | struct pmc_pm_data { |
69 | u32 cpu_good_time; /* CPU power good time in uS */ | |
70 | u32 cpu_off_time; /* CPU power off time in uS */ | |
71 | u32 core_osc_time; /* Core power good osc time in uS */ | |
72 | u32 core_pmu_time; /* Core power good pmu time in uS */ | |
73 | u32 core_off_time; /* Core power off time in uS */ | |
74 | bool corereq_high; /* Core power request active-high */ | |
75 | bool sysclkreq_high; /* System clock request active-high */ | |
76 | bool combined_req; /* Combined pwr req for CPU & Core */ | |
77 | bool cpu_pwr_good_en; /* CPU power good signal is enabled */ | |
78 | u32 lp0_vec_phy_addr; /* The phy addr of LP0 warm boot code */ | |
79 | u32 lp0_vec_size; /* The size of LP0 warm boot code */ | |
80 | enum tegra_suspend_mode suspend_mode; | |
81 | }; | |
82 | static struct pmc_pm_data pmc_pm_data; | |
83 | ||
d3b8bdd5 SW |
84 | static inline u32 tegra_pmc_readl(u32 reg) |
85 | { | |
291fde31 | 86 | return readl(tegra_pmc_base + reg); |
d3b8bdd5 SW |
87 | } |
88 | ||
89 | static inline void tegra_pmc_writel(u32 val, u32 reg) | |
90 | { | |
291fde31 | 91 | writel(val, tegra_pmc_base + reg); |
d3b8bdd5 SW |
92 | } |
93 | ||
c141753f JL |
94 | static int tegra_pmc_get_cpu_powerdomain_id(int cpuid) |
95 | { | |
96 | if (cpuid <= 0 || cpuid >= num_possible_cpus()) | |
97 | return -EINVAL; | |
98 | return tegra_cpu_domains[cpuid]; | |
99 | } | |
100 | ||
101 | static bool tegra_pmc_powergate_is_powered(int id) | |
102 | { | |
103 | return (tegra_pmc_readl(PMC_PWRGATE_STATUS) >> id) & 1; | |
104 | } | |
105 | ||
106 | static int tegra_pmc_powergate_set(int id, bool new_state) | |
107 | { | |
108 | bool old_state; | |
109 | unsigned long flags; | |
110 | ||
111 | spin_lock_irqsave(&tegra_powergate_lock, flags); | |
112 | ||
113 | old_state = tegra_pmc_powergate_is_powered(id); | |
114 | WARN_ON(old_state == new_state); | |
115 | ||
116 | tegra_pmc_writel(PMC_PWRGATE_TOGGLE_START | id, PMC_PWRGATE_TOGGLE); | |
117 | ||
118 | spin_unlock_irqrestore(&tegra_powergate_lock, flags); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | static int tegra_pmc_powergate_remove_clamping(int id) | |
124 | { | |
125 | u32 mask; | |
126 | ||
127 | /* | |
128 | * Tegra has a bug where PCIE and VDE clamping masks are | |
129 | * swapped relatively to the partition ids. | |
130 | */ | |
131 | if (id == TEGRA_POWERGATE_VDEC) | |
132 | mask = (1 << TEGRA_POWERGATE_PCIE); | |
133 | else if (id == TEGRA_POWERGATE_PCIE) | |
134 | mask = (1 << TEGRA_POWERGATE_VDEC); | |
135 | else | |
136 | mask = (1 << id); | |
137 | ||
138 | tegra_pmc_writel(mask, PMC_REMOVE_CLAMPING); | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | bool tegra_pmc_cpu_is_powered(int cpuid) | |
144 | { | |
145 | int id; | |
146 | ||
147 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
148 | if (id < 0) | |
149 | return false; | |
150 | return tegra_pmc_powergate_is_powered(id); | |
151 | } | |
152 | ||
153 | int tegra_pmc_cpu_power_on(int cpuid) | |
154 | { | |
155 | int id; | |
156 | ||
157 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
158 | if (id < 0) | |
159 | return id; | |
160 | return tegra_pmc_powergate_set(id, true); | |
161 | } | |
162 | ||
163 | int tegra_pmc_cpu_remove_clamping(int cpuid) | |
164 | { | |
165 | int id; | |
166 | ||
167 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
168 | if (id < 0) | |
169 | return id; | |
170 | return tegra_pmc_powergate_remove_clamping(id); | |
171 | } | |
172 | ||
51100bdc SW |
173 | void tegra_pmc_restart(enum reboot_mode mode, const char *cmd) |
174 | { | |
175 | u32 val; | |
176 | ||
498bb3da TR |
177 | val = tegra_pmc_readl(PMC_SCRATCH0); |
178 | val &= ~PMC_SCRATCH0_MODE_MASK; | |
179 | ||
180 | if (cmd) { | |
181 | if (strcmp(cmd, "recovery") == 0) | |
182 | val |= PMC_SCRATCH0_MODE_RECOVERY; | |
183 | ||
184 | if (strcmp(cmd, "bootloader") == 0) | |
185 | val |= PMC_SCRATCH0_MODE_BOOTLOADER; | |
186 | ||
187 | if (strcmp(cmd, "forced-recovery") == 0) | |
188 | val |= PMC_SCRATCH0_MODE_RCM; | |
189 | } | |
190 | ||
191 | tegra_pmc_writel(val, PMC_SCRATCH0); | |
192 | ||
51100bdc SW |
193 | val = tegra_pmc_readl(0); |
194 | val |= 0x10; | |
195 | tegra_pmc_writel(val, 0); | |
196 | } | |
197 | ||
0337c3e0 | 198 | #ifdef CONFIG_PM_SLEEP |
c8c2e606 | 199 | static void set_power_timers(u32 us_on, u32 us_off, unsigned long rate) |
0337c3e0 JL |
200 | { |
201 | unsigned long long ticks; | |
202 | unsigned long long pclk; | |
0337c3e0 JL |
203 | static unsigned long tegra_last_pclk; |
204 | ||
0337c3e0 JL |
205 | if (WARN_ON_ONCE(rate <= 0)) |
206 | pclk = 100000000; | |
207 | else | |
208 | pclk = rate; | |
209 | ||
210 | if ((rate != tegra_last_pclk)) { | |
211 | ticks = (us_on * pclk) + 999999ull; | |
212 | do_div(ticks, 1000000); | |
213 | tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWRGOOD_TIMER); | |
214 | ||
215 | ticks = (us_off * pclk) + 999999ull; | |
216 | do_div(ticks, 1000000); | |
217 | tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWROFF_TIMER); | |
218 | wmb(); | |
219 | } | |
220 | tegra_last_pclk = pclk; | |
221 | } | |
c8c2e606 JL |
222 | |
223 | enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) | |
224 | { | |
225 | return pmc_pm_data.suspend_mode; | |
226 | } | |
227 | ||
95872f42 JL |
228 | void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) |
229 | { | |
230 | if (mode < TEGRA_SUSPEND_NONE || mode >= TEGRA_MAX_SUSPEND_MODE) | |
231 | return; | |
232 | ||
233 | pmc_pm_data.suspend_mode = mode; | |
234 | } | |
235 | ||
236 | void tegra_pmc_suspend(void) | |
237 | { | |
238 | tegra_pmc_writel(virt_to_phys(tegra_resume), PMC_SCRATCH41); | |
239 | } | |
240 | ||
241 | void tegra_pmc_resume(void) | |
242 | { | |
243 | tegra_pmc_writel(0x0, PMC_SCRATCH41); | |
244 | } | |
245 | ||
c8c2e606 JL |
246 | void tegra_pmc_pm_set(enum tegra_suspend_mode mode) |
247 | { | |
3f1be81e | 248 | u32 reg, csr_reg; |
c8c2e606 JL |
249 | unsigned long rate = 0; |
250 | ||
251 | reg = tegra_pmc_readl(PMC_CTRL); | |
252 | reg |= TEGRA_POWER_CPU_PWRREQ_OE; | |
253 | reg &= ~TEGRA_POWER_EFFECT_LP0; | |
254 | ||
304664ea | 255 | switch (tegra_get_chip_id()) { |
3f1be81e JL |
256 | case TEGRA20: |
257 | case TEGRA30: | |
258 | break; | |
259 | default: | |
260 | /* Turn off CRAIL */ | |
261 | csr_reg = flowctrl_read_cpu_csr(0); | |
262 | csr_reg &= ~FLOW_CTRL_CSR_ENABLE_EXT_MASK; | |
263 | csr_reg |= FLOW_CTRL_CSR_ENABLE_EXT_CRAIL; | |
264 | flowctrl_write_cpu_csr(0, csr_reg); | |
265 | break; | |
266 | } | |
267 | ||
c8c2e606 | 268 | switch (mode) { |
95872f42 JL |
269 | case TEGRA_SUSPEND_LP1: |
270 | rate = 32768; | |
271 | break; | |
c8c2e606 JL |
272 | case TEGRA_SUSPEND_LP2: |
273 | rate = clk_get_rate(tegra_pclk); | |
274 | break; | |
275 | default: | |
276 | break; | |
277 | } | |
278 | ||
279 | set_power_timers(pmc_pm_data.cpu_good_time, pmc_pm_data.cpu_off_time, | |
280 | rate); | |
281 | ||
282 | tegra_pmc_writel(reg, PMC_CTRL); | |
283 | } | |
284 | ||
285 | void tegra_pmc_suspend_init(void) | |
286 | { | |
287 | u32 reg; | |
288 | ||
289 | /* Always enable CPU power request */ | |
290 | reg = tegra_pmc_readl(PMC_CTRL); | |
291 | reg |= TEGRA_POWER_CPU_PWRREQ_OE; | |
292 | tegra_pmc_writel(reg, PMC_CTRL); | |
444f9a80 JL |
293 | |
294 | reg = tegra_pmc_readl(PMC_CTRL); | |
295 | ||
296 | if (!pmc_pm_data.sysclkreq_high) | |
297 | reg |= TEGRA_POWER_SYSCLK_POLARITY; | |
298 | else | |
299 | reg &= ~TEGRA_POWER_SYSCLK_POLARITY; | |
300 | ||
301 | /* configure the output polarity while the request is tristated */ | |
302 | tegra_pmc_writel(reg, PMC_CTRL); | |
303 | ||
304 | /* now enable the request */ | |
305 | reg |= TEGRA_POWER_SYSCLK_OE; | |
306 | tegra_pmc_writel(reg, PMC_CTRL); | |
c8c2e606 | 307 | } |
0337c3e0 JL |
308 | #endif |
309 | ||
d3b8bdd5 | 310 | static const struct of_device_id matches[] __initconst = { |
6ca91f9d | 311 | { .compatible = "nvidia,tegra124-pmc" }, |
88c4aba9 JL |
312 | { .compatible = "nvidia,tegra114-pmc" }, |
313 | { .compatible = "nvidia,tegra30-pmc" }, | |
d3b8bdd5 SW |
314 | { .compatible = "nvidia,tegra20-pmc" }, |
315 | { } | |
316 | }; | |
d3b8bdd5 | 317 | |
d2207071 | 318 | void __init tegra_pmc_init_irq(void) |
d3b8bdd5 | 319 | { |
291fde31 | 320 | struct device_node *np; |
d2207071 | 321 | u32 val; |
291fde31 JL |
322 | |
323 | np = of_find_matching_node(NULL, matches); | |
324 | BUG_ON(!np); | |
d3b8bdd5 | 325 | |
291fde31 | 326 | tegra_pmc_base = of_iomap(np, 0); |
d3b8bdd5 | 327 | |
291fde31 JL |
328 | tegra_pmc_invert_interrupt = of_property_read_bool(np, |
329 | "nvidia,invert-interrupt"); | |
d2207071 SW |
330 | |
331 | val = tegra_pmc_readl(PMC_CTRL); | |
332 | if (tegra_pmc_invert_interrupt) | |
333 | val |= PMC_CTRL_INTR_LOW; | |
334 | else | |
335 | val &= ~PMC_CTRL_INTR_LOW; | |
336 | tegra_pmc_writel(val, PMC_CTRL); | |
337 | } | |
338 | ||
339 | void __init tegra_pmc_init(void) | |
340 | { | |
341 | struct device_node *np; | |
342 | u32 prop; | |
343 | enum tegra_suspend_mode suspend_mode; | |
344 | u32 core_good_time[2] = {0, 0}; | |
345 | u32 lp0_vec[2] = {0, 0}; | |
346 | ||
347 | np = of_find_matching_node(NULL, matches); | |
348 | BUG_ON(!np); | |
349 | ||
0337c3e0 JL |
350 | tegra_pclk = of_clk_get_by_name(np, "pclk"); |
351 | WARN_ON(IS_ERR(tegra_pclk)); | |
4b51ccbc JL |
352 | |
353 | /* Grabbing the power management configurations */ | |
354 | if (of_property_read_u32(np, "nvidia,suspend-mode", &prop)) { | |
355 | suspend_mode = TEGRA_SUSPEND_NONE; | |
356 | } else { | |
357 | switch (prop) { | |
358 | case 0: | |
359 | suspend_mode = TEGRA_SUSPEND_LP0; | |
360 | break; | |
361 | case 1: | |
362 | suspend_mode = TEGRA_SUSPEND_LP1; | |
363 | break; | |
364 | case 2: | |
365 | suspend_mode = TEGRA_SUSPEND_LP2; | |
366 | break; | |
367 | default: | |
368 | suspend_mode = TEGRA_SUSPEND_NONE; | |
369 | break; | |
370 | } | |
371 | } | |
c8c2e606 | 372 | suspend_mode = tegra_pm_validate_suspend_mode(suspend_mode); |
4b51ccbc JL |
373 | |
374 | if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &prop)) | |
375 | suspend_mode = TEGRA_SUSPEND_NONE; | |
376 | pmc_pm_data.cpu_good_time = prop; | |
377 | ||
378 | if (of_property_read_u32(np, "nvidia,cpu-pwr-off-time", &prop)) | |
379 | suspend_mode = TEGRA_SUSPEND_NONE; | |
380 | pmc_pm_data.cpu_off_time = prop; | |
381 | ||
382 | if (of_property_read_u32_array(np, "nvidia,core-pwr-good-time", | |
383 | core_good_time, ARRAY_SIZE(core_good_time))) | |
384 | suspend_mode = TEGRA_SUSPEND_NONE; | |
385 | pmc_pm_data.core_osc_time = core_good_time[0]; | |
386 | pmc_pm_data.core_pmu_time = core_good_time[1]; | |
387 | ||
388 | if (of_property_read_u32(np, "nvidia,core-pwr-off-time", | |
389 | &prop)) | |
390 | suspend_mode = TEGRA_SUSPEND_NONE; | |
391 | pmc_pm_data.core_off_time = prop; | |
392 | ||
393 | pmc_pm_data.corereq_high = of_property_read_bool(np, | |
394 | "nvidia,core-power-req-active-high"); | |
395 | ||
396 | pmc_pm_data.sysclkreq_high = of_property_read_bool(np, | |
397 | "nvidia,sys-clock-req-active-high"); | |
398 | ||
399 | pmc_pm_data.combined_req = of_property_read_bool(np, | |
400 | "nvidia,combined-power-req"); | |
401 | ||
402 | pmc_pm_data.cpu_pwr_good_en = of_property_read_bool(np, | |
403 | "nvidia,cpu-pwr-good-en"); | |
404 | ||
405 | if (of_property_read_u32_array(np, "nvidia,lp0-vec", lp0_vec, | |
406 | ARRAY_SIZE(lp0_vec))) | |
407 | if (suspend_mode == TEGRA_SUSPEND_LP0) | |
408 | suspend_mode = TEGRA_SUSPEND_LP1; | |
409 | ||
410 | pmc_pm_data.lp0_vec_phy_addr = lp0_vec[0]; | |
411 | pmc_pm_data.lp0_vec_size = lp0_vec[1]; | |
412 | ||
413 | pmc_pm_data.suspend_mode = suspend_mode; | |
291fde31 | 414 | } |