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 | ||
18 | #include <linux/kernel.h> | |
0337c3e0 | 19 | #include <linux/clk.h> |
d3b8bdd5 SW |
20 | #include <linux/io.h> |
21 | #include <linux/of.h> | |
291fde31 | 22 | #include <linux/of_address.h> |
d3b8bdd5 | 23 | |
c8c2e606 JL |
24 | #include "fuse.h" |
25 | #include "pm.h" | |
4b51ccbc | 26 | #include "pmc.h" |
c8c2e606 JL |
27 | #include "sleep.h" |
28 | ||
29 | #define TEGRA_POWER_EFFECT_LP0 (1 << 14) /* LP0 when CPU pwr gated */ | |
30 | #define TEGRA_POWER_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */ | |
31 | #define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */ | |
4b51ccbc | 32 | |
c141753f JL |
33 | #define PMC_CTRL 0x0 |
34 | #define PMC_CTRL_INTR_LOW (1 << 17) | |
35 | #define PMC_PWRGATE_TOGGLE 0x30 | |
36 | #define PMC_PWRGATE_TOGGLE_START (1 << 8) | |
37 | #define PMC_REMOVE_CLAMPING 0x34 | |
38 | #define PMC_PWRGATE_STATUS 0x38 | |
39 | ||
0337c3e0 JL |
40 | #define PMC_CPUPWRGOOD_TIMER 0xc8 |
41 | #define PMC_CPUPWROFF_TIMER 0xcc | |
42 | ||
c141753f JL |
43 | #define TEGRA_POWERGATE_PCIE 3 |
44 | #define TEGRA_POWERGATE_VDEC 4 | |
45 | #define TEGRA_POWERGATE_CPU1 9 | |
46 | #define TEGRA_POWERGATE_CPU2 10 | |
47 | #define TEGRA_POWERGATE_CPU3 11 | |
48 | ||
49 | static u8 tegra_cpu_domains[] = { | |
50 | 0xFF, /* not available for CPU0 */ | |
51 | TEGRA_POWERGATE_CPU1, | |
52 | TEGRA_POWERGATE_CPU2, | |
53 | TEGRA_POWERGATE_CPU3, | |
54 | }; | |
55 | static DEFINE_SPINLOCK(tegra_powergate_lock); | |
d3b8bdd5 | 56 | |
291fde31 JL |
57 | static void __iomem *tegra_pmc_base; |
58 | static bool tegra_pmc_invert_interrupt; | |
0337c3e0 | 59 | static struct clk *tegra_pclk; |
291fde31 | 60 | |
4b51ccbc JL |
61 | struct pmc_pm_data { |
62 | u32 cpu_good_time; /* CPU power good time in uS */ | |
63 | u32 cpu_off_time; /* CPU power off time in uS */ | |
64 | u32 core_osc_time; /* Core power good osc time in uS */ | |
65 | u32 core_pmu_time; /* Core power good pmu time in uS */ | |
66 | u32 core_off_time; /* Core power off time in uS */ | |
67 | bool corereq_high; /* Core power request active-high */ | |
68 | bool sysclkreq_high; /* System clock request active-high */ | |
69 | bool combined_req; /* Combined pwr req for CPU & Core */ | |
70 | bool cpu_pwr_good_en; /* CPU power good signal is enabled */ | |
71 | u32 lp0_vec_phy_addr; /* The phy addr of LP0 warm boot code */ | |
72 | u32 lp0_vec_size; /* The size of LP0 warm boot code */ | |
73 | enum tegra_suspend_mode suspend_mode; | |
74 | }; | |
75 | static struct pmc_pm_data pmc_pm_data; | |
76 | ||
d3b8bdd5 SW |
77 | static inline u32 tegra_pmc_readl(u32 reg) |
78 | { | |
291fde31 | 79 | return readl(tegra_pmc_base + reg); |
d3b8bdd5 SW |
80 | } |
81 | ||
82 | static inline void tegra_pmc_writel(u32 val, u32 reg) | |
83 | { | |
291fde31 | 84 | writel(val, tegra_pmc_base + reg); |
d3b8bdd5 SW |
85 | } |
86 | ||
c141753f JL |
87 | static int tegra_pmc_get_cpu_powerdomain_id(int cpuid) |
88 | { | |
89 | if (cpuid <= 0 || cpuid >= num_possible_cpus()) | |
90 | return -EINVAL; | |
91 | return tegra_cpu_domains[cpuid]; | |
92 | } | |
93 | ||
94 | static bool tegra_pmc_powergate_is_powered(int id) | |
95 | { | |
96 | return (tegra_pmc_readl(PMC_PWRGATE_STATUS) >> id) & 1; | |
97 | } | |
98 | ||
99 | static int tegra_pmc_powergate_set(int id, bool new_state) | |
100 | { | |
101 | bool old_state; | |
102 | unsigned long flags; | |
103 | ||
104 | spin_lock_irqsave(&tegra_powergate_lock, flags); | |
105 | ||
106 | old_state = tegra_pmc_powergate_is_powered(id); | |
107 | WARN_ON(old_state == new_state); | |
108 | ||
109 | tegra_pmc_writel(PMC_PWRGATE_TOGGLE_START | id, PMC_PWRGATE_TOGGLE); | |
110 | ||
111 | spin_unlock_irqrestore(&tegra_powergate_lock, flags); | |
112 | ||
113 | return 0; | |
114 | } | |
115 | ||
116 | static int tegra_pmc_powergate_remove_clamping(int id) | |
117 | { | |
118 | u32 mask; | |
119 | ||
120 | /* | |
121 | * Tegra has a bug where PCIE and VDE clamping masks are | |
122 | * swapped relatively to the partition ids. | |
123 | */ | |
124 | if (id == TEGRA_POWERGATE_VDEC) | |
125 | mask = (1 << TEGRA_POWERGATE_PCIE); | |
126 | else if (id == TEGRA_POWERGATE_PCIE) | |
127 | mask = (1 << TEGRA_POWERGATE_VDEC); | |
128 | else | |
129 | mask = (1 << id); | |
130 | ||
131 | tegra_pmc_writel(mask, PMC_REMOVE_CLAMPING); | |
132 | ||
133 | return 0; | |
134 | } | |
135 | ||
136 | bool tegra_pmc_cpu_is_powered(int cpuid) | |
137 | { | |
138 | int id; | |
139 | ||
140 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
141 | if (id < 0) | |
142 | return false; | |
143 | return tegra_pmc_powergate_is_powered(id); | |
144 | } | |
145 | ||
146 | int tegra_pmc_cpu_power_on(int cpuid) | |
147 | { | |
148 | int id; | |
149 | ||
150 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
151 | if (id < 0) | |
152 | return id; | |
153 | return tegra_pmc_powergate_set(id, true); | |
154 | } | |
155 | ||
156 | int tegra_pmc_cpu_remove_clamping(int cpuid) | |
157 | { | |
158 | int id; | |
159 | ||
160 | id = tegra_pmc_get_cpu_powerdomain_id(cpuid); | |
161 | if (id < 0) | |
162 | return id; | |
163 | return tegra_pmc_powergate_remove_clamping(id); | |
164 | } | |
165 | ||
0337c3e0 | 166 | #ifdef CONFIG_PM_SLEEP |
c8c2e606 | 167 | static void set_power_timers(u32 us_on, u32 us_off, unsigned long rate) |
0337c3e0 JL |
168 | { |
169 | unsigned long long ticks; | |
170 | unsigned long long pclk; | |
0337c3e0 JL |
171 | static unsigned long tegra_last_pclk; |
172 | ||
0337c3e0 JL |
173 | if (WARN_ON_ONCE(rate <= 0)) |
174 | pclk = 100000000; | |
175 | else | |
176 | pclk = rate; | |
177 | ||
178 | if ((rate != tegra_last_pclk)) { | |
179 | ticks = (us_on * pclk) + 999999ull; | |
180 | do_div(ticks, 1000000); | |
181 | tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWRGOOD_TIMER); | |
182 | ||
183 | ticks = (us_off * pclk) + 999999ull; | |
184 | do_div(ticks, 1000000); | |
185 | tegra_pmc_writel((unsigned long)ticks, PMC_CPUPWROFF_TIMER); | |
186 | wmb(); | |
187 | } | |
188 | tegra_last_pclk = pclk; | |
189 | } | |
c8c2e606 JL |
190 | |
191 | enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void) | |
192 | { | |
193 | return pmc_pm_data.suspend_mode; | |
194 | } | |
195 | ||
196 | void tegra_pmc_pm_set(enum tegra_suspend_mode mode) | |
197 | { | |
198 | u32 reg; | |
199 | unsigned long rate = 0; | |
200 | ||
201 | reg = tegra_pmc_readl(PMC_CTRL); | |
202 | reg |= TEGRA_POWER_CPU_PWRREQ_OE; | |
203 | reg &= ~TEGRA_POWER_EFFECT_LP0; | |
204 | ||
205 | switch (mode) { | |
206 | case TEGRA_SUSPEND_LP2: | |
207 | rate = clk_get_rate(tegra_pclk); | |
208 | break; | |
209 | default: | |
210 | break; | |
211 | } | |
212 | ||
213 | set_power_timers(pmc_pm_data.cpu_good_time, pmc_pm_data.cpu_off_time, | |
214 | rate); | |
215 | ||
216 | tegra_pmc_writel(reg, PMC_CTRL); | |
217 | } | |
218 | ||
219 | void tegra_pmc_suspend_init(void) | |
220 | { | |
221 | u32 reg; | |
222 | ||
223 | /* Always enable CPU power request */ | |
224 | reg = tegra_pmc_readl(PMC_CTRL); | |
225 | reg |= TEGRA_POWER_CPU_PWRREQ_OE; | |
226 | tegra_pmc_writel(reg, PMC_CTRL); | |
227 | } | |
0337c3e0 JL |
228 | #endif |
229 | ||
d3b8bdd5 | 230 | static const struct of_device_id matches[] __initconst = { |
88c4aba9 JL |
231 | { .compatible = "nvidia,tegra114-pmc" }, |
232 | { .compatible = "nvidia,tegra30-pmc" }, | |
d3b8bdd5 SW |
233 | { .compatible = "nvidia,tegra20-pmc" }, |
234 | { } | |
235 | }; | |
d3b8bdd5 | 236 | |
1d54e089 | 237 | static void __init tegra_pmc_parse_dt(void) |
d3b8bdd5 | 238 | { |
291fde31 | 239 | struct device_node *np; |
4b51ccbc JL |
240 | u32 prop; |
241 | enum tegra_suspend_mode suspend_mode; | |
242 | u32 core_good_time[2] = {0, 0}; | |
243 | u32 lp0_vec[2] = {0, 0}; | |
291fde31 JL |
244 | |
245 | np = of_find_matching_node(NULL, matches); | |
246 | BUG_ON(!np); | |
d3b8bdd5 | 247 | |
291fde31 | 248 | tegra_pmc_base = of_iomap(np, 0); |
d3b8bdd5 | 249 | |
291fde31 JL |
250 | tegra_pmc_invert_interrupt = of_property_read_bool(np, |
251 | "nvidia,invert-interrupt"); | |
0337c3e0 JL |
252 | tegra_pclk = of_clk_get_by_name(np, "pclk"); |
253 | WARN_ON(IS_ERR(tegra_pclk)); | |
4b51ccbc JL |
254 | |
255 | /* Grabbing the power management configurations */ | |
256 | if (of_property_read_u32(np, "nvidia,suspend-mode", &prop)) { | |
257 | suspend_mode = TEGRA_SUSPEND_NONE; | |
258 | } else { | |
259 | switch (prop) { | |
260 | case 0: | |
261 | suspend_mode = TEGRA_SUSPEND_LP0; | |
262 | break; | |
263 | case 1: | |
264 | suspend_mode = TEGRA_SUSPEND_LP1; | |
265 | break; | |
266 | case 2: | |
267 | suspend_mode = TEGRA_SUSPEND_LP2; | |
268 | break; | |
269 | default: | |
270 | suspend_mode = TEGRA_SUSPEND_NONE; | |
271 | break; | |
272 | } | |
273 | } | |
c8c2e606 | 274 | suspend_mode = tegra_pm_validate_suspend_mode(suspend_mode); |
4b51ccbc JL |
275 | |
276 | if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &prop)) | |
277 | suspend_mode = TEGRA_SUSPEND_NONE; | |
278 | pmc_pm_data.cpu_good_time = prop; | |
279 | ||
280 | if (of_property_read_u32(np, "nvidia,cpu-pwr-off-time", &prop)) | |
281 | suspend_mode = TEGRA_SUSPEND_NONE; | |
282 | pmc_pm_data.cpu_off_time = prop; | |
283 | ||
284 | if (of_property_read_u32_array(np, "nvidia,core-pwr-good-time", | |
285 | core_good_time, ARRAY_SIZE(core_good_time))) | |
286 | suspend_mode = TEGRA_SUSPEND_NONE; | |
287 | pmc_pm_data.core_osc_time = core_good_time[0]; | |
288 | pmc_pm_data.core_pmu_time = core_good_time[1]; | |
289 | ||
290 | if (of_property_read_u32(np, "nvidia,core-pwr-off-time", | |
291 | &prop)) | |
292 | suspend_mode = TEGRA_SUSPEND_NONE; | |
293 | pmc_pm_data.core_off_time = prop; | |
294 | ||
295 | pmc_pm_data.corereq_high = of_property_read_bool(np, | |
296 | "nvidia,core-power-req-active-high"); | |
297 | ||
298 | pmc_pm_data.sysclkreq_high = of_property_read_bool(np, | |
299 | "nvidia,sys-clock-req-active-high"); | |
300 | ||
301 | pmc_pm_data.combined_req = of_property_read_bool(np, | |
302 | "nvidia,combined-power-req"); | |
303 | ||
304 | pmc_pm_data.cpu_pwr_good_en = of_property_read_bool(np, | |
305 | "nvidia,cpu-pwr-good-en"); | |
306 | ||
307 | if (of_property_read_u32_array(np, "nvidia,lp0-vec", lp0_vec, | |
308 | ARRAY_SIZE(lp0_vec))) | |
309 | if (suspend_mode == TEGRA_SUSPEND_LP0) | |
310 | suspend_mode = TEGRA_SUSPEND_LP1; | |
311 | ||
312 | pmc_pm_data.lp0_vec_phy_addr = lp0_vec[0]; | |
313 | pmc_pm_data.lp0_vec_size = lp0_vec[1]; | |
314 | ||
315 | pmc_pm_data.suspend_mode = suspend_mode; | |
291fde31 JL |
316 | } |
317 | ||
318 | void __init tegra_pmc_init(void) | |
319 | { | |
320 | u32 val; | |
d3b8bdd5 | 321 | |
291fde31 | 322 | tegra_pmc_parse_dt(); |
d3b8bdd5 SW |
323 | |
324 | val = tegra_pmc_readl(PMC_CTRL); | |
291fde31 | 325 | if (tegra_pmc_invert_interrupt) |
d3b8bdd5 SW |
326 | val |= PMC_CTRL_INTR_LOW; |
327 | else | |
328 | val &= ~PMC_CTRL_INTR_LOW; | |
329 | tegra_pmc_writel(val, PMC_CTRL); | |
330 | } |