18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// This file is provided under a dual BSD/GPLv2 license. When using or 48c2ecf20Sopenharmony_ci// redistributing this file, you may do so under either license. 58c2ecf20Sopenharmony_ci// 68c2ecf20Sopenharmony_ci// Copyright(c) 2018 Intel Corporation. All rights reserved. 78c2ecf20Sopenharmony_ci// 88c2ecf20Sopenharmony_ci// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> 98c2ecf20Sopenharmony_ci// 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include "ops.h" 128c2ecf20Sopenharmony_ci#include "sof-priv.h" 138c2ecf20Sopenharmony_ci#include "sof-audio.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci/* 168c2ecf20Sopenharmony_ci * Helper function to determine the target DSP state during 178c2ecf20Sopenharmony_ci * system suspend. This function only cares about the device 188c2ecf20Sopenharmony_ci * D-states. Platform-specific substates, if any, should be 198c2ecf20Sopenharmony_ci * handled by the platform-specific parts. 208c2ecf20Sopenharmony_ci */ 218c2ecf20Sopenharmony_cistatic u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci u32 target_dsp_state; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci switch (sdev->system_suspend_target) { 268c2ecf20Sopenharmony_ci case SOF_SUSPEND_S3: 278c2ecf20Sopenharmony_ci /* DSP should be in D3 if the system is suspending to S3 */ 288c2ecf20Sopenharmony_ci target_dsp_state = SOF_DSP_PM_D3; 298c2ecf20Sopenharmony_ci break; 308c2ecf20Sopenharmony_ci case SOF_SUSPEND_S0IX: 318c2ecf20Sopenharmony_ci /* 328c2ecf20Sopenharmony_ci * Currently, the only criterion for retaining the DSP in D0 338c2ecf20Sopenharmony_ci * is that there are streams that ignored the suspend trigger. 348c2ecf20Sopenharmony_ci * Additional criteria such Soundwire clock-stop mode and 358c2ecf20Sopenharmony_ci * device suspend latency considerations will be added later. 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_ci if (snd_sof_stream_suspend_ignored(sdev)) 388c2ecf20Sopenharmony_ci target_dsp_state = SOF_DSP_PM_D0; 398c2ecf20Sopenharmony_ci else 408c2ecf20Sopenharmony_ci target_dsp_state = SOF_DSP_PM_D3; 418c2ecf20Sopenharmony_ci break; 428c2ecf20Sopenharmony_ci default: 438c2ecf20Sopenharmony_ci /* This case would be during runtime suspend */ 448c2ecf20Sopenharmony_ci target_dsp_state = SOF_DSP_PM_D3; 458c2ecf20Sopenharmony_ci break; 468c2ecf20Sopenharmony_ci } 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci return target_dsp_state; 498c2ecf20Sopenharmony_ci} 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci struct sof_ipc_pm_ctx pm_ctx; 548c2ecf20Sopenharmony_ci struct sof_ipc_reply reply; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci memset(&pm_ctx, 0, sizeof(pm_ctx)); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci /* configure ctx save ipc message */ 598c2ecf20Sopenharmony_ci pm_ctx.hdr.size = sizeof(pm_ctx); 608c2ecf20Sopenharmony_ci pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* send ctx save ipc to dsp */ 638c2ecf20Sopenharmony_ci return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, 648c2ecf20Sopenharmony_ci sizeof(pm_ctx), &reply, sizeof(reply)); 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 688c2ecf20Sopenharmony_cistatic void sof_cache_debugfs(struct snd_sof_dev *sdev) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct snd_sof_dfsentry *dfse; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci list_for_each_entry(dfse, &sdev->dfsentry_list, list) { 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* nothing to do if debugfs buffer is not IO mem */ 758c2ecf20Sopenharmony_ci if (dfse->type == SOF_DFSENTRY_TYPE_BUF) 768c2ecf20Sopenharmony_ci continue; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci /* cache memory that is only accessible in D0 */ 798c2ecf20Sopenharmony_ci if (dfse->access_type == SOF_DEBUGFS_ACCESS_D0_ONLY) 808c2ecf20Sopenharmony_ci memcpy_fromio(dfse->cache_buf, dfse->io_mem, 818c2ecf20Sopenharmony_ci dfse->size); 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci#endif 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int sof_resume(struct device *dev, bool runtime_resume) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = dev_get_drvdata(dev); 898c2ecf20Sopenharmony_ci u32 old_state = sdev->dsp_power_state.state; 908c2ecf20Sopenharmony_ci int ret; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci /* do nothing if dsp resume callbacks are not set */ 938c2ecf20Sopenharmony_ci if (!runtime_resume && !sof_ops(sdev)->resume) 948c2ecf20Sopenharmony_ci return 0; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (runtime_resume && !sof_ops(sdev)->runtime_resume) 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* DSP was never successfully started, nothing to resume */ 1008c2ecf20Sopenharmony_ci if (sdev->first_boot) 1018c2ecf20Sopenharmony_ci return 0; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci /* 1048c2ecf20Sopenharmony_ci * if the runtime_resume flag is set, call the runtime_resume routine 1058c2ecf20Sopenharmony_ci * or else call the system resume routine 1068c2ecf20Sopenharmony_ci */ 1078c2ecf20Sopenharmony_ci if (runtime_resume) 1088c2ecf20Sopenharmony_ci ret = snd_sof_dsp_runtime_resume(sdev); 1098c2ecf20Sopenharmony_ci else 1108c2ecf20Sopenharmony_ci ret = snd_sof_dsp_resume(sdev); 1118c2ecf20Sopenharmony_ci if (ret < 0) { 1128c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1138c2ecf20Sopenharmony_ci "error: failed to power up DSP after resume\n"); 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* 1188c2ecf20Sopenharmony_ci * Nothing further to be done for platforms that support the low power 1198c2ecf20Sopenharmony_ci * D0 substate. 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_ci if (!runtime_resume && sof_ops(sdev)->set_power_state && 1228c2ecf20Sopenharmony_ci old_state == SOF_DSP_PM_D0) 1238c2ecf20Sopenharmony_ci return 0; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci sdev->fw_state = SOF_FW_BOOT_PREPARE; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci /* load the firmware */ 1288c2ecf20Sopenharmony_ci ret = snd_sof_load_firmware(sdev); 1298c2ecf20Sopenharmony_ci if (ret < 0) { 1308c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1318c2ecf20Sopenharmony_ci "error: failed to load DSP firmware after resume %d\n", 1328c2ecf20Sopenharmony_ci ret); 1338c2ecf20Sopenharmony_ci return ret; 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* 1398c2ecf20Sopenharmony_ci * Boot the firmware. The FW boot status will be modified 1408c2ecf20Sopenharmony_ci * in snd_sof_run_firmware() depending on the outcome. 1418c2ecf20Sopenharmony_ci */ 1428c2ecf20Sopenharmony_ci ret = snd_sof_run_firmware(sdev); 1438c2ecf20Sopenharmony_ci if (ret < 0) { 1448c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1458c2ecf20Sopenharmony_ci "error: failed to boot DSP firmware after resume %d\n", 1468c2ecf20Sopenharmony_ci ret); 1478c2ecf20Sopenharmony_ci return ret; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* resume DMA trace, only need send ipc */ 1518c2ecf20Sopenharmony_ci ret = snd_sof_init_trace_ipc(sdev); 1528c2ecf20Sopenharmony_ci if (ret < 0) { 1538c2ecf20Sopenharmony_ci /* non fatal */ 1548c2ecf20Sopenharmony_ci dev_warn(sdev->dev, 1558c2ecf20Sopenharmony_ci "warning: failed to init trace after resume %d\n", 1568c2ecf20Sopenharmony_ci ret); 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* restore pipelines */ 1608c2ecf20Sopenharmony_ci ret = sof_restore_pipelines(sdev->dev); 1618c2ecf20Sopenharmony_ci if (ret < 0) { 1628c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1638c2ecf20Sopenharmony_ci "error: failed to restore pipeline after resume %d\n", 1648c2ecf20Sopenharmony_ci ret); 1658c2ecf20Sopenharmony_ci return ret; 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* notify DSP of system resume */ 1698c2ecf20Sopenharmony_ci ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); 1708c2ecf20Sopenharmony_ci if (ret < 0) 1718c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1728c2ecf20Sopenharmony_ci "error: ctx_restore ipc error during resume %d\n", 1738c2ecf20Sopenharmony_ci ret); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci return ret; 1768c2ecf20Sopenharmony_ci} 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic int sof_suspend(struct device *dev, bool runtime_suspend) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = dev_get_drvdata(dev); 1818c2ecf20Sopenharmony_ci u32 target_state = 0; 1828c2ecf20Sopenharmony_ci int ret; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* do nothing if dsp suspend callback is not set */ 1858c2ecf20Sopenharmony_ci if (!runtime_suspend && !sof_ops(sdev)->suspend) 1868c2ecf20Sopenharmony_ci return 0; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (runtime_suspend && !sof_ops(sdev)->runtime_suspend) 1898c2ecf20Sopenharmony_ci return 0; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (sdev->fw_state != SOF_FW_BOOT_COMPLETE) 1928c2ecf20Sopenharmony_ci goto suspend; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* set restore_stream for all streams during system suspend */ 1958c2ecf20Sopenharmony_ci if (!runtime_suspend) { 1968c2ecf20Sopenharmony_ci ret = sof_set_hw_params_upon_resume(sdev->dev); 1978c2ecf20Sopenharmony_ci if (ret < 0) { 1988c2ecf20Sopenharmony_ci dev_err(sdev->dev, 1998c2ecf20Sopenharmony_ci "error: setting hw_params flag during suspend %d\n", 2008c2ecf20Sopenharmony_ci ret); 2018c2ecf20Sopenharmony_ci return ret; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci target_state = snd_sof_dsp_power_target(sdev); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* Skip to platform-specific suspend if DSP is entering D0 */ 2088c2ecf20Sopenharmony_ci if (target_state == SOF_DSP_PM_D0) 2098c2ecf20Sopenharmony_ci goto suspend; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* release trace */ 2128c2ecf20Sopenharmony_ci snd_sof_release_trace(sdev); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) 2158c2ecf20Sopenharmony_ci /* cache debugfs contents during runtime suspend */ 2168c2ecf20Sopenharmony_ci if (runtime_suspend) 2178c2ecf20Sopenharmony_ci sof_cache_debugfs(sdev); 2188c2ecf20Sopenharmony_ci#endif 2198c2ecf20Sopenharmony_ci /* notify DSP of upcoming power down */ 2208c2ecf20Sopenharmony_ci ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); 2218c2ecf20Sopenharmony_ci if (ret == -EBUSY || ret == -EAGAIN) { 2228c2ecf20Sopenharmony_ci /* 2238c2ecf20Sopenharmony_ci * runtime PM has logic to handle -EBUSY/-EAGAIN so 2248c2ecf20Sopenharmony_ci * pass these errors up 2258c2ecf20Sopenharmony_ci */ 2268c2ecf20Sopenharmony_ci dev_err(sdev->dev, 2278c2ecf20Sopenharmony_ci "error: ctx_save ipc error during suspend %d\n", 2288c2ecf20Sopenharmony_ci ret); 2298c2ecf20Sopenharmony_ci return ret; 2308c2ecf20Sopenharmony_ci } else if (ret < 0) { 2318c2ecf20Sopenharmony_ci /* FW in unexpected state, continue to power down */ 2328c2ecf20Sopenharmony_ci dev_warn(sdev->dev, 2338c2ecf20Sopenharmony_ci "ctx_save ipc error %d, proceeding with suspend\n", 2348c2ecf20Sopenharmony_ci ret); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cisuspend: 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci /* return if the DSP was not probed successfully */ 2408c2ecf20Sopenharmony_ci if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED) 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci /* platform-specific suspend */ 2448c2ecf20Sopenharmony_ci if (runtime_suspend) 2458c2ecf20Sopenharmony_ci ret = snd_sof_dsp_runtime_suspend(sdev); 2468c2ecf20Sopenharmony_ci else 2478c2ecf20Sopenharmony_ci ret = snd_sof_dsp_suspend(sdev, target_state); 2488c2ecf20Sopenharmony_ci if (ret < 0) 2498c2ecf20Sopenharmony_ci dev_err(sdev->dev, 2508c2ecf20Sopenharmony_ci "error: failed to power down DSP during suspend %d\n", 2518c2ecf20Sopenharmony_ci ret); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci /* Do not reset FW state if DSP is in D0 */ 2548c2ecf20Sopenharmony_ci if (target_state == SOF_DSP_PM_D0) 2558c2ecf20Sopenharmony_ci return ret; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci /* reset FW state */ 2588c2ecf20Sopenharmony_ci sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; 2598c2ecf20Sopenharmony_ci sdev->enabled_cores_mask = 0; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return ret; 2628c2ecf20Sopenharmony_ci} 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ciint snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci /* Notify DSP of upcoming power down */ 2678c2ecf20Sopenharmony_ci if (sof_ops(sdev)->remove) 2688c2ecf20Sopenharmony_ci return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return 0; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ciint snd_sof_runtime_suspend(struct device *dev) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci return sof_suspend(dev, true); 2768c2ecf20Sopenharmony_ci} 2778c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_runtime_suspend); 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ciint snd_sof_runtime_idle(struct device *dev) 2808c2ecf20Sopenharmony_ci{ 2818c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = dev_get_drvdata(dev); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci return snd_sof_dsp_runtime_idle(sdev); 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_runtime_idle); 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ciint snd_sof_runtime_resume(struct device *dev) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci return sof_resume(dev, true); 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_runtime_resume); 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ciint snd_sof_resume(struct device *dev) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci return sof_resume(dev, false); 2968c2ecf20Sopenharmony_ci} 2978c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_resume); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ciint snd_sof_suspend(struct device *dev) 3008c2ecf20Sopenharmony_ci{ 3018c2ecf20Sopenharmony_ci return sof_suspend(dev, false); 3028c2ecf20Sopenharmony_ci} 3038c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_suspend); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ciint snd_sof_prepare(struct device *dev) 3068c2ecf20Sopenharmony_ci{ 3078c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = dev_get_drvdata(dev); 3088c2ecf20Sopenharmony_ci const struct sof_dev_desc *desc = sdev->pdata->desc; 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci /* will suspend to S3 by default */ 3118c2ecf20Sopenharmony_ci sdev->system_suspend_target = SOF_SUSPEND_S3; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci if (!desc->use_acpi_target_states) 3148c2ecf20Sopenharmony_ci return 0; 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci#if defined(CONFIG_ACPI) 3178c2ecf20Sopenharmony_ci if (acpi_target_system_state() == ACPI_STATE_S0) 3188c2ecf20Sopenharmony_ci sdev->system_suspend_target = SOF_SUSPEND_S0IX; 3198c2ecf20Sopenharmony_ci#endif 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci return 0; 3228c2ecf20Sopenharmony_ci} 3238c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_prepare); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_civoid snd_sof_complete(struct device *dev) 3268c2ecf20Sopenharmony_ci{ 3278c2ecf20Sopenharmony_ci struct snd_sof_dev *sdev = dev_get_drvdata(dev); 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci sdev->system_suspend_target = SOF_SUSPEND_NONE; 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sof_complete); 332