]>
Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
6f88e9bc KH |
2 | /* |
3 | * pm.c - Common OMAP2+ power management-related code | |
4 | * | |
5 | * Copyright (C) 2010 Texas Instruments, Inc. | |
6 | * Copyright (C) 2010 Nokia Corporation | |
6f88e9bc KH |
7 | */ |
8 | ||
9 | #include <linux/kernel.h> | |
10 | #include <linux/init.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/err.h> | |
e4db1c74 | 13 | #include <linux/pm_opp.h> |
dc28094b | 14 | #include <linux/export.h> |
1416408d | 15 | #include <linux/suspend.h> |
44773ba1 | 16 | #include <linux/clk.h> |
24d7b40a | 17 | #include <linux/cpu.h> |
6f88e9bc | 18 | |
335aece5 G |
19 | #include <asm/system_misc.h> |
20 | ||
25c7d49e | 21 | #include "omap_device.h" |
4e65331c | 22 | #include "common.h" |
6f88e9bc | 23 | |
e4c060db | 24 | #include "soc.h" |
1416408d | 25 | #include "prcm-common.h" |
e1d6f472 | 26 | #include "voltage.h" |
72e06d08 | 27 | #include "powerdomain.h" |
1540f214 | 28 | #include "clockdomain.h" |
0c0a5d61 | 29 | #include "pm.h" |
eb6a2c75 | 30 | |
2e4b62dc | 31 | #ifdef CONFIG_SUSPEND |
1416408d PW |
32 | /* |
33 | * omap_pm_suspend: points to a function that does the SoC-specific | |
34 | * suspend work | |
35 | */ | |
2e4b62dc DG |
36 | static int (*omap_pm_suspend)(void); |
37 | #endif | |
1416408d | 38 | |
74d29168 | 39 | #ifdef CONFIG_PM |
908b75e8 TK |
40 | /** |
41 | * struct omap2_oscillator - Describe the board main oscillator latencies | |
42 | * @startup_time: oscillator startup latency | |
43 | * @shutdown_time: oscillator shutdown latency | |
44 | */ | |
45 | struct omap2_oscillator { | |
46 | u32 startup_time; | |
47 | u32 shutdown_time; | |
48 | }; | |
49 | ||
50 | static struct omap2_oscillator oscillator = { | |
51 | .startup_time = ULONG_MAX, | |
52 | .shutdown_time = ULONG_MAX, | |
53 | }; | |
54 | ||
55 | void omap_pm_setup_oscillator(u32 tstart, u32 tshut) | |
56 | { | |
57 | oscillator.startup_time = tstart; | |
58 | oscillator.shutdown_time = tshut; | |
59 | } | |
60 | ||
61 | void omap_pm_get_oscillator(u32 *tstart, u32 *tshut) | |
62 | { | |
63 | if (!tstart || !tshut) | |
64 | return; | |
65 | ||
66 | *tstart = oscillator.startup_time; | |
67 | *tshut = oscillator.shutdown_time; | |
68 | } | |
74d29168 | 69 | #endif |
908b75e8 | 70 | |
33d3842d | 71 | int omap_pm_clkdms_setup(struct clockdomain *clkdm, void *unused) |
92206fd2 | 72 | { |
1d9a5425 | 73 | clkdm_allow_idle(clkdm); |
92206fd2 PW |
74 | return 0; |
75 | } | |
76 | ||
1482d8be | 77 | /* |
1e2d2df3 | 78 | * This API is to be called during init to set the various voltage |
1482d8be TG |
79 | * domains to the voltage as per the opp table. Typically we boot up |
80 | * at the nominal voltage. So this function finds out the rate of | |
81 | * the clock associated with the voltage domain, finds out the correct | |
1e2d2df3 | 82 | * opp entry and sets the voltage domain to the voltage specified |
1482d8be TG |
83 | * in the opp entry |
84 | */ | |
85 | static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, | |
0f7aa005 | 86 | const char *oh_name) |
1482d8be TG |
87 | { |
88 | struct voltagedomain *voltdm; | |
89 | struct clk *clk; | |
47d43ba7 | 90 | struct dev_pm_opp *opp; |
1482d8be | 91 | unsigned long freq, bootup_volt; |
0f7aa005 | 92 | struct device *dev; |
1482d8be | 93 | |
0f7aa005 | 94 | if (!vdd_name || !clk_name || !oh_name) { |
e9a5190a | 95 | pr_err("%s: invalid parameters\n", __func__); |
1482d8be TG |
96 | goto exit; |
97 | } | |
98 | ||
24d7b40a KH |
99 | if (!strncmp(oh_name, "mpu", 3)) |
100 | /* | |
101 | * All current OMAPs share voltage rail and clock | |
102 | * source, so CPU0 is used to represent the MPU-SS. | |
103 | */ | |
104 | dev = get_cpu_device(0); | |
105 | else | |
106 | dev = omap_device_get_by_hwmod_name(oh_name); | |
107 | ||
0f7aa005 BC |
108 | if (IS_ERR(dev)) { |
109 | pr_err("%s: Unable to get dev pointer for hwmod %s\n", | |
110 | __func__, oh_name); | |
111 | goto exit; | |
112 | } | |
113 | ||
81a60482 | 114 | voltdm = voltdm_lookup(vdd_name); |
93b44bea | 115 | if (!voltdm) { |
e9a5190a | 116 | pr_err("%s: unable to get vdd pointer for vdd_%s\n", |
1482d8be TG |
117 | __func__, vdd_name); |
118 | goto exit; | |
119 | } | |
120 | ||
121 | clk = clk_get(NULL, clk_name); | |
122 | if (IS_ERR(clk)) { | |
e9a5190a | 123 | pr_err("%s: unable to get clk %s\n", __func__, clk_name); |
1482d8be TG |
124 | goto exit; |
125 | } | |
126 | ||
5dcc3b97 | 127 | freq = clk_get_rate(clk); |
1482d8be TG |
128 | clk_put(clk); |
129 | ||
5d4879cd | 130 | opp = dev_pm_opp_find_freq_ceil(dev, &freq); |
1482d8be | 131 | if (IS_ERR(opp)) { |
e9a5190a | 132 | pr_err("%s: unable to find boot up OPP for vdd_%s\n", |
1482d8be TG |
133 | __func__, vdd_name); |
134 | goto exit; | |
135 | } | |
136 | ||
5d4879cd | 137 | bootup_volt = dev_pm_opp_get_voltage(opp); |
8a31d9d9 VK |
138 | dev_pm_opp_put(opp); |
139 | ||
1482d8be | 140 | if (!bootup_volt) { |
7852ec05 PW |
141 | pr_err("%s: unable to find voltage corresponding to the bootup OPP for vdd_%s\n", |
142 | __func__, vdd_name); | |
1482d8be TG |
143 | goto exit; |
144 | } | |
145 | ||
5e5651be | 146 | voltdm_scale(voltdm, bootup_volt); |
1482d8be TG |
147 | return 0; |
148 | ||
149 | exit: | |
e9a5190a | 150 | pr_err("%s: unable to set vdd_%s\n", __func__, vdd_name); |
1482d8be TG |
151 | return -EINVAL; |
152 | } | |
153 | ||
1416408d PW |
154 | #ifdef CONFIG_SUSPEND |
155 | static int omap_pm_enter(suspend_state_t suspend_state) | |
156 | { | |
157 | int ret = 0; | |
158 | ||
159 | if (!omap_pm_suspend) | |
160 | return -ENOENT; /* XXX doublecheck */ | |
161 | ||
162 | switch (suspend_state) { | |
1416408d PW |
163 | case PM_SUSPEND_MEM: |
164 | ret = omap_pm_suspend(); | |
165 | break; | |
166 | default: | |
167 | ret = -EINVAL; | |
168 | } | |
169 | ||
170 | return ret; | |
171 | } | |
172 | ||
173 | static int omap_pm_begin(suspend_state_t state) | |
174 | { | |
f7b861b7 | 175 | cpu_idle_poll_ctrl(true); |
cb6675d6 | 176 | if (soc_is_omap34xx()) |
1416408d PW |
177 | omap_prcm_irq_prepare(); |
178 | return 0; | |
179 | } | |
180 | ||
181 | static void omap_pm_end(void) | |
182 | { | |
f7b861b7 | 183 | cpu_idle_poll_ctrl(false); |
1416408d PW |
184 | } |
185 | ||
d3be6d2a | 186 | static void omap_pm_wake(void) |
1416408d | 187 | { |
cb6675d6 | 188 | if (soc_is_omap34xx()) |
1416408d PW |
189 | omap_prcm_irq_complete(); |
190 | } | |
191 | ||
192 | static const struct platform_suspend_ops omap_pm_ops = { | |
193 | .begin = omap_pm_begin, | |
194 | .end = omap_pm_end, | |
195 | .enter = omap_pm_enter, | |
d3be6d2a | 196 | .wake = omap_pm_wake, |
1416408d PW |
197 | .valid = suspend_valid_only_mem, |
198 | }; | |
199 | ||
2e4b62dc DG |
200 | /** |
201 | * omap_common_suspend_init - Set common suspend routines for OMAP SoCs | |
202 | * @pm_suspend: function pointer to SoC specific suspend function | |
203 | */ | |
204 | void omap_common_suspend_init(void *pm_suspend) | |
205 | { | |
206 | omap_pm_suspend = pm_suspend; | |
207 | suspend_set_ops(&omap_pm_ops); | |
208 | } | |
1416408d PW |
209 | #endif /* CONFIG_SUSPEND */ |
210 | ||
1482d8be TG |
211 | static void __init omap3_init_voltages(void) |
212 | { | |
cb6675d6 | 213 | if (!soc_is_omap34xx()) |
1482d8be TG |
214 | return; |
215 | ||
0f7aa005 BC |
216 | omap2_set_init_voltage("mpu_iva", "dpll1_ck", "mpu"); |
217 | omap2_set_init_voltage("core", "l3_ick", "l3_main"); | |
1482d8be TG |
218 | } |
219 | ||
1376ee1d TG |
220 | static void __init omap4_init_voltages(void) |
221 | { | |
cb6675d6 | 222 | if (!soc_is_omap44xx()) |
1376ee1d TG |
223 | return; |
224 | ||
0f7aa005 BC |
225 | omap2_set_init_voltage("mpu", "dpll_mpu_ck", "mpu"); |
226 | omap2_set_init_voltage("core", "l3_div_ck", "l3_main_1"); | |
227 | omap2_set_init_voltage("iva", "dpll_iva_m5x2_ck", "iva"); | |
1376ee1d TG |
228 | } |
229 | ||
02b83dcb TL |
230 | int __maybe_unused omap_pm_nop_init(void) |
231 | { | |
232 | return 0; | |
233 | } | |
234 | ||
235 | int (*omap_pm_soc_init)(void); | |
236 | ||
bbd707ac | 237 | int __init omap2_common_pm_late_init(void) |
2f34ce81 | 238 | { |
02b83dcb TL |
239 | int error; |
240 | ||
241 | if (!omap_pm_soc_init) | |
242 | return 0; | |
243 | ||
2ee5f528 | 244 | /* Init the voltage layer */ |
cb6675d6 TL |
245 | omap3_twl_init(); |
246 | omap4_twl_init(); | |
2ee5f528 | 247 | omap_voltage_late_init(); |
1482d8be | 248 | |
2ee5f528 TL |
249 | /* Initialize the voltages */ |
250 | omap3_init_voltages(); | |
251 | omap4_init_voltages(); | |
49ded525 | 252 | |
2ee5f528 TL |
253 | /* Smartreflex device init */ |
254 | omap_devinit_smartreflex(); | |
2f34ce81 | 255 | |
02b83dcb TL |
256 | error = omap_pm_soc_init(); |
257 | if (error) | |
258 | pr_warn("%s: pm soc init failed: %i\n", __func__, error); | |
259 | ||
260 | omap2_clk_enable_autoidle_all(); | |
261 | ||
2f34ce81 TG |
262 | return 0; |
263 | } | |
02b83dcb | 264 | omap_late_initcall(omap2_common_pm_late_init); |