1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
2 | // |
3 | // This file is provided under a dual BSD/GPLv2 license. When using or |
4 | // redistributing this file, you may do so under either license. |
5 | // |
6 | // Copyright(c) 2018 Intel Corporation. All rights reserved. |
7 | // |
8 | // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> |
9 | // |
10 | |
11 | #include "ops.h" |
12 | #include "sof-priv.h" |
13 | #include "sof-audio.h" |
14 | |
15 | /* |
16 | * Helper function to determine the target DSP state during |
17 | * system suspend. This function only cares about the device |
18 | * D-states. Platform-specific substates, if any, should be |
19 | * handled by the platform-specific parts. |
20 | */ |
21 | static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) |
22 | { |
23 | u32 target_dsp_state; |
24 | |
25 | switch (sdev->system_suspend_target) { |
26 | case SOF_SUSPEND_S5: |
27 | case SOF_SUSPEND_S4: |
28 | /* DSP should be in D3 if the system is suspending to S3+ */ |
29 | case SOF_SUSPEND_S3: |
30 | /* DSP should be in D3 if the system is suspending to S3 */ |
31 | target_dsp_state = SOF_DSP_PM_D3; |
32 | break; |
33 | case SOF_SUSPEND_S0IX: |
34 | /* |
35 | * Currently, the only criterion for retaining the DSP in D0 |
36 | * is that there are streams that ignored the suspend trigger. |
37 | * Additional criteria such Soundwire clock-stop mode and |
38 | * device suspend latency considerations will be added later. |
39 | */ |
40 | if (snd_sof_stream_suspend_ignored(sdev)) |
41 | target_dsp_state = SOF_DSP_PM_D0; |
42 | else |
43 | target_dsp_state = SOF_DSP_PM_D3; |
44 | break; |
45 | default: |
46 | /* This case would be during runtime suspend */ |
47 | target_dsp_state = SOF_DSP_PM_D3; |
48 | break; |
49 | } |
50 | |
51 | return target_dsp_state; |
52 | } |
53 | |
54 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) |
55 | static void sof_cache_debugfs(struct snd_sof_dev *sdev) |
56 | { |
57 | struct snd_sof_dfsentry *dfse; |
58 | |
59 | list_for_each_entry(dfse, &sdev->dfsentry_list, list) { |
60 | |
61 | /* nothing to do if debugfs buffer is not IO mem */ |
62 | if (dfse->type == SOF_DFSENTRY_TYPE_BUF) |
63 | continue; |
64 | |
65 | /* cache memory that is only accessible in D0 */ |
66 | if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) |
67 | memcpy_fromio(dfse->cache_buf, dfse->io_mem, |
68 | dfse->size); |
69 | } |
70 | } |
71 | #endif |
72 | |
73 | static int sof_resume(struct device *dev, bool runtime_resume) |
74 | { |
75 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
76 | const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); |
77 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); |
78 | u32 old_state = sdev->dsp_power_state.state; |
79 | int ret; |
80 | |
81 | /* do nothing if dsp resume callbacks are not set */ |
82 | if (!runtime_resume && !sof_ops(sdev)->resume) |
83 | return 0; |
84 | |
85 | if (runtime_resume && !sof_ops(sdev)->runtime_resume) |
86 | return 0; |
87 | |
88 | /* DSP was never successfully started, nothing to resume */ |
89 | if (sdev->first_boot) |
90 | return 0; |
91 | |
92 | /* |
93 | * if the runtime_resume flag is set, call the runtime_resume routine |
94 | * or else call the system resume routine |
95 | */ |
96 | if (runtime_resume) |
97 | ret = snd_sof_dsp_runtime_resume(sdev); |
98 | else |
99 | ret = snd_sof_dsp_resume(sdev); |
100 | if (ret < 0) { |
101 | dev_err(sdev->dev, |
102 | "error: failed to power up DSP after resume\n" ); |
103 | return ret; |
104 | } |
105 | |
106 | if (sdev->dspless_mode_selected) { |
107 | sof_set_fw_state(sdev, new_state: SOF_DSPLESS_MODE); |
108 | return 0; |
109 | } |
110 | |
111 | /* |
112 | * Nothing further to be done for platforms that support the low power |
113 | * D0 substate. Resume trace and return when resuming from |
114 | * low-power D0 substate |
115 | */ |
116 | if (!runtime_resume && sof_ops(sdev)->set_power_state && |
117 | old_state == SOF_DSP_PM_D0) { |
118 | ret = sof_fw_trace_resume(sdev); |
119 | if (ret < 0) |
120 | /* non fatal */ |
121 | dev_warn(sdev->dev, |
122 | "failed to enable trace after resume %d\n" , ret); |
123 | return 0; |
124 | } |
125 | |
126 | sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_PREPARE); |
127 | |
128 | /* load the firmware */ |
129 | ret = snd_sof_load_firmware(sdev); |
130 | if (ret < 0) { |
131 | dev_err(sdev->dev, |
132 | "error: failed to load DSP firmware after resume %d\n" , |
133 | ret); |
134 | sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_FAILED); |
135 | return ret; |
136 | } |
137 | |
138 | sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_IN_PROGRESS); |
139 | |
140 | /* |
141 | * Boot the firmware. The FW boot status will be modified |
142 | * in snd_sof_run_firmware() depending on the outcome. |
143 | */ |
144 | ret = snd_sof_run_firmware(sdev); |
145 | if (ret < 0) { |
146 | dev_err(sdev->dev, |
147 | "error: failed to boot DSP firmware after resume %d\n" , |
148 | ret); |
149 | sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_FAILED); |
150 | return ret; |
151 | } |
152 | |
153 | /* resume DMA trace */ |
154 | ret = sof_fw_trace_resume(sdev); |
155 | if (ret < 0) { |
156 | /* non fatal */ |
157 | dev_warn(sdev->dev, |
158 | "warning: failed to init trace after resume %d\n" , |
159 | ret); |
160 | } |
161 | |
162 | /* restore pipelines */ |
163 | if (tplg_ops && tplg_ops->set_up_all_pipelines) { |
164 | ret = tplg_ops->set_up_all_pipelines(sdev, false); |
165 | if (ret < 0) { |
166 | dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n" , ret); |
167 | goto setup_fail; |
168 | } |
169 | } |
170 | |
171 | /* Notify clients not managed by pm framework about core resume */ |
172 | sof_resume_clients(sdev); |
173 | |
174 | /* notify DSP of system resume */ |
175 | if (pm_ops && pm_ops->ctx_restore) { |
176 | ret = pm_ops->ctx_restore(sdev); |
177 | if (ret < 0) |
178 | dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n" , ret); |
179 | } |
180 | |
181 | setup_fail: |
182 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) |
183 | if (ret < 0) { |
184 | /* |
185 | * Debugfs cannot be read in runtime suspend, so cache |
186 | * the contents upon failure. This allows to capture |
187 | * possible DSP coredump information. |
188 | */ |
189 | sof_cache_debugfs(sdev); |
190 | } |
191 | #endif |
192 | |
193 | return ret; |
194 | } |
195 | |
196 | static int sof_suspend(struct device *dev, bool runtime_suspend) |
197 | { |
198 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
199 | const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); |
200 | const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); |
201 | pm_message_t pm_state; |
202 | u32 target_state = snd_sof_dsp_power_target(sdev); |
203 | u32 old_state = sdev->dsp_power_state.state; |
204 | int ret; |
205 | |
206 | /* do nothing if dsp suspend callback is not set */ |
207 | if (!runtime_suspend && !sof_ops(sdev)->suspend) |
208 | return 0; |
209 | |
210 | if (runtime_suspend && !sof_ops(sdev)->runtime_suspend) |
211 | return 0; |
212 | |
213 | /* we need to tear down pipelines only if the DSP hardware is |
214 | * active, which happens for PCI devices. if the device is |
215 | * suspended, it is brought back to full power and then |
216 | * suspended again |
217 | */ |
218 | if (tplg_ops && tplg_ops->tear_down_all_pipelines && (old_state == SOF_DSP_PM_D0)) |
219 | tplg_ops->tear_down_all_pipelines(sdev, false); |
220 | |
221 | if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) |
222 | goto suspend; |
223 | |
224 | /* prepare for streams to be resumed properly upon resume */ |
225 | if (!runtime_suspend) { |
226 | ret = snd_sof_dsp_hw_params_upon_resume(sdev); |
227 | if (ret < 0) { |
228 | dev_err(sdev->dev, |
229 | "error: setting hw_params flag during suspend %d\n" , |
230 | ret); |
231 | return ret; |
232 | } |
233 | } |
234 | |
235 | pm_state.event = target_state; |
236 | |
237 | /* suspend DMA trace */ |
238 | sof_fw_trace_suspend(sdev, pm_state); |
239 | |
240 | /* Notify clients not managed by pm framework about core suspend */ |
241 | sof_suspend_clients(sdev, state: pm_state); |
242 | |
243 | /* Skip to platform-specific suspend if DSP is entering D0 */ |
244 | if (target_state == SOF_DSP_PM_D0) |
245 | goto suspend; |
246 | |
247 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) |
248 | /* cache debugfs contents during runtime suspend */ |
249 | if (runtime_suspend) |
250 | sof_cache_debugfs(sdev); |
251 | #endif |
252 | /* notify DSP of upcoming power down */ |
253 | if (pm_ops && pm_ops->ctx_save) { |
254 | ret = pm_ops->ctx_save(sdev); |
255 | if (ret == -EBUSY || ret == -EAGAIN) { |
256 | /* |
257 | * runtime PM has logic to handle -EBUSY/-EAGAIN so |
258 | * pass these errors up |
259 | */ |
260 | dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n" , ret); |
261 | return ret; |
262 | } else if (ret < 0) { |
263 | /* FW in unexpected state, continue to power down */ |
264 | dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n" , |
265 | ret); |
266 | } |
267 | } |
268 | |
269 | suspend: |
270 | |
271 | /* return if the DSP was not probed successfully */ |
272 | if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) |
273 | return 0; |
274 | |
275 | /* platform-specific suspend */ |
276 | if (runtime_suspend) |
277 | ret = snd_sof_dsp_runtime_suspend(sdev); |
278 | else |
279 | ret = snd_sof_dsp_suspend(sdev, target_state); |
280 | if (ret < 0) |
281 | dev_err(sdev->dev, |
282 | "error: failed to power down DSP during suspend %d\n" , |
283 | ret); |
284 | |
285 | /* Do not reset FW state if DSP is in D0 */ |
286 | if (target_state == SOF_DSP_PM_D0) |
287 | return ret; |
288 | |
289 | /* reset FW state */ |
290 | sof_set_fw_state(sdev, new_state: SOF_FW_BOOT_NOT_STARTED); |
291 | sdev->enabled_cores_mask = 0; |
292 | |
293 | return ret; |
294 | } |
295 | |
296 | int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) |
297 | { |
298 | const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); |
299 | |
300 | /* Notify DSP of upcoming power down */ |
301 | if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save) |
302 | return pm_ops->ctx_save(sdev); |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | int snd_sof_runtime_suspend(struct device *dev) |
308 | { |
309 | return sof_suspend(dev, runtime_suspend: true); |
310 | } |
311 | EXPORT_SYMBOL(snd_sof_runtime_suspend); |
312 | |
313 | int snd_sof_runtime_idle(struct device *dev) |
314 | { |
315 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
316 | |
317 | return snd_sof_dsp_runtime_idle(sdev); |
318 | } |
319 | EXPORT_SYMBOL(snd_sof_runtime_idle); |
320 | |
321 | int snd_sof_runtime_resume(struct device *dev) |
322 | { |
323 | return sof_resume(dev, runtime_resume: true); |
324 | } |
325 | EXPORT_SYMBOL(snd_sof_runtime_resume); |
326 | |
327 | int snd_sof_resume(struct device *dev) |
328 | { |
329 | return sof_resume(dev, runtime_resume: false); |
330 | } |
331 | EXPORT_SYMBOL(snd_sof_resume); |
332 | |
333 | int snd_sof_suspend(struct device *dev) |
334 | { |
335 | return sof_suspend(dev, runtime_suspend: false); |
336 | } |
337 | EXPORT_SYMBOL(snd_sof_suspend); |
338 | |
339 | int snd_sof_prepare(struct device *dev) |
340 | { |
341 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
342 | const struct sof_dev_desc *desc = sdev->pdata->desc; |
343 | |
344 | /* will suspend to S3 by default */ |
345 | sdev->system_suspend_target = SOF_SUSPEND_S3; |
346 | |
347 | /* |
348 | * if the firmware is crashed or boot failed then we try to aim for S3 |
349 | * to reboot the firmware |
350 | */ |
351 | if (sdev->fw_state == SOF_FW_CRASHED || |
352 | sdev->fw_state == SOF_FW_BOOT_FAILED) |
353 | return 0; |
354 | |
355 | if (!desc->use_acpi_target_states) |
356 | return 0; |
357 | |
358 | #if defined(CONFIG_ACPI) |
359 | switch (acpi_target_system_state()) { |
360 | case ACPI_STATE_S0: |
361 | sdev->system_suspend_target = SOF_SUSPEND_S0IX; |
362 | break; |
363 | case ACPI_STATE_S1: |
364 | case ACPI_STATE_S2: |
365 | case ACPI_STATE_S3: |
366 | sdev->system_suspend_target = SOF_SUSPEND_S3; |
367 | break; |
368 | case ACPI_STATE_S4: |
369 | sdev->system_suspend_target = SOF_SUSPEND_S4; |
370 | break; |
371 | case ACPI_STATE_S5: |
372 | sdev->system_suspend_target = SOF_SUSPEND_S5; |
373 | break; |
374 | default: |
375 | break; |
376 | } |
377 | #endif |
378 | |
379 | return 0; |
380 | } |
381 | EXPORT_SYMBOL(snd_sof_prepare); |
382 | |
383 | void snd_sof_complete(struct device *dev) |
384 | { |
385 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
386 | |
387 | sdev->system_suspend_target = SOF_SUSPEND_NONE; |
388 | } |
389 | EXPORT_SYMBOL(snd_sof_complete); |
390 | |