2020-05-01 17:58:50 +03:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
2019-04-12 19:05:16 +03:00
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018 Intel Corporation. All rights reserved.
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
# include "ops.h"
# include "sof-priv.h"
2019-12-05 00:15:51 +03:00
# include "sof-audio.h"
2019-04-12 19:05:16 +03:00
2020-01-30 01:07:21 +03:00
/*
* Helper function to determine the target DSP state during
* system suspend . This function only cares about the device
* D - states . Platform - specific substates , if any , should be
* handled by the platform - specific parts .
*/
static u32 snd_sof_dsp_power_target ( struct snd_sof_dev * sdev )
{
u32 target_dsp_state ;
switch ( sdev - > system_suspend_target ) {
2022-06-16 23:18:17 +03:00
case SOF_SUSPEND_S5 :
case SOF_SUSPEND_S4 :
/* DSP should be in D3 if the system is suspending to S3+ */
2020-01-30 01:07:21 +03:00
case SOF_SUSPEND_S3 :
/* DSP should be in D3 if the system is suspending to S3 */
target_dsp_state = SOF_DSP_PM_D3 ;
break ;
case SOF_SUSPEND_S0IX :
/*
* Currently , the only criterion for retaining the DSP in D0
* is that there are streams that ignored the suspend trigger .
* Additional criteria such Soundwire clock - stop mode and
* device suspend latency considerations will be added later .
*/
if ( snd_sof_stream_suspend_ignored ( sdev ) )
target_dsp_state = SOF_DSP_PM_D0 ;
else
target_dsp_state = SOF_DSP_PM_D3 ;
break ;
default :
/* This case would be during runtime suspend */
target_dsp_state = SOF_DSP_PM_D3 ;
break ;
}
return target_dsp_state ;
}
2019-04-12 19:05:16 +03:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
static void sof_cache_debugfs ( struct snd_sof_dev * sdev )
{
struct snd_sof_dfsentry * dfse ;
list_for_each_entry ( dfse , & sdev - > dfsentry_list , list ) {
/* nothing to do if debugfs buffer is not IO mem */
if ( dfse - > type = = SOF_DFSENTRY_TYPE_BUF )
continue ;
/* cache memory that is only accessible in D0 */
if ( dfse - > access_type = = SOF_DEBUGFS_ACCESS_D0_ONLY )
memcpy_fromio ( dfse - > cache_buf , dfse - > io_mem ,
dfse - > size ) ;
}
}
# endif
static int sof_resume ( struct device * dev , bool runtime_resume )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
2022-12-21 13:23:25 +03:00
const struct sof_ipc_pm_ops * pm_ops = sof_ipc_get_ops ( sdev , pm ) ;
const struct sof_ipc_tplg_ops * tplg_ops = sof_ipc_get_ops ( sdev , tplg ) ;
2020-01-30 01:07:22 +03:00
u32 old_state = sdev - > dsp_power_state . state ;
2019-04-12 19:05:16 +03:00
int ret ;
/* do nothing if dsp resume callbacks are not set */
2020-05-15 16:59:51 +03:00
if ( ! runtime_resume & & ! sof_ops ( sdev ) - > resume )
return 0 ;
if ( runtime_resume & & ! sof_ops ( sdev ) - > runtime_resume )
2019-04-12 19:05:16 +03:00
return 0 ;
2020-01-25 00:36:21 +03:00
/* DSP was never successfully started, nothing to resume */
if ( sdev - > first_boot )
return 0 ;
2019-04-12 19:05:16 +03:00
/*
* if the runtime_resume flag is set , call the runtime_resume routine
* or else call the system resume routine
*/
if ( runtime_resume )
ret = snd_sof_dsp_runtime_resume ( sdev ) ;
else
ret = snd_sof_dsp_resume ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev ,
" error: failed to power up DSP after resume \n " ) ;
return ret ;
}
2023-04-04 12:21:06 +03:00
if ( sdev - > dspless_mode_selected ) {
sof_set_fw_state ( sdev , SOF_DSPLESS_MODE ) ;
return 0 ;
}
2020-05-26 23:36:34 +03:00
/*
* Nothing further to be done for platforms that support the low power
2022-03-30 23:19:22 +03:00
* D0 substate . Resume trace and return when resuming from
* low - power D0 substate
2020-05-26 23:36:34 +03:00
*/
if ( ! runtime_resume & & sof_ops ( sdev ) - > set_power_state & &
2022-03-30 23:19:22 +03:00
old_state = = SOF_DSP_PM_D0 ) {
2022-05-16 13:47:07 +03:00
ret = sof_fw_trace_resume ( sdev ) ;
2022-03-30 23:19:22 +03:00
if ( ret < 0 )
/* non fatal */
dev_warn ( sdev - > dev ,
" failed to enable trace after resume %d \n " , ret ) ;
2020-01-30 01:07:19 +03:00
return 0 ;
2022-03-30 23:19:22 +03:00
}
2020-01-30 01:07:19 +03:00
2021-10-06 14:06:40 +03:00
sof_set_fw_state ( sdev , SOF_FW_BOOT_PREPARE ) ;
2019-12-18 03:26:09 +03:00
2019-04-12 19:05:16 +03:00
/* load the firmware */
ret = snd_sof_load_firmware ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev ,
" error: failed to load DSP firmware after resume %d \n " ,
ret ) ;
2021-12-23 14:36:20 +03:00
sof_set_fw_state ( sdev , SOF_FW_BOOT_FAILED ) ;
2019-04-12 19:05:16 +03:00
return ret ;
}
2021-10-06 14:06:40 +03:00
sof_set_fw_state ( sdev , SOF_FW_BOOT_IN_PROGRESS ) ;
2019-12-18 03:26:09 +03:00
/*
* Boot the firmware . The FW boot status will be modified
* in snd_sof_run_firmware ( ) depending on the outcome .
*/
2019-04-12 19:05:16 +03:00
ret = snd_sof_run_firmware ( sdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev ,
" error: failed to boot DSP firmware after resume %d \n " ,
ret ) ;
2021-12-23 14:36:20 +03:00
sof_set_fw_state ( sdev , SOF_FW_BOOT_FAILED ) ;
2019-04-12 19:05:16 +03:00
return ret ;
}
2022-03-30 23:19:21 +03:00
/* resume DMA trace */
2022-05-16 13:47:07 +03:00
ret = sof_fw_trace_resume ( sdev ) ;
2019-04-12 19:05:16 +03:00
if ( ret < 0 ) {
/* non fatal */
dev_warn ( sdev - > dev ,
" warning: failed to init trace after resume %d \n " ,
ret ) ;
}
/* restore pipelines */
2022-12-21 13:23:25 +03:00
if ( tplg_ops & & tplg_ops - > set_up_all_pipelines ) {
2022-03-17 20:50:43 +03:00
ret = tplg_ops - > set_up_all_pipelines ( sdev , false ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " Failed to restore pipeline after resume %d \n " , ret ) ;
2023-05-12 13:46:38 +03:00
goto setup_fail ;
2022-03-17 20:50:43 +03:00
}
2019-04-12 19:05:16 +03:00
}
2022-02-10 18:05:22 +03:00
/* Notify clients not managed by pm framework about core resume */
sof_resume_clients ( sdev ) ;
2019-04-12 19:05:16 +03:00
/* notify DSP of system resume */
2022-03-17 20:50:28 +03:00
if ( pm_ops & & pm_ops - > ctx_restore ) {
ret = pm_ops - > ctx_restore ( sdev ) ;
if ( ret < 0 )
dev_err ( sdev - > dev , " ctx_restore IPC error during resume: %d \n " , ret ) ;
}
2019-04-12 19:05:16 +03:00
2023-05-12 13:46:38 +03:00
setup_fail :
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
if ( ret < 0 ) {
/*
* Debugfs cannot be read in runtime suspend , so cache
* the contents upon failure . This allows to capture
* possible DSP coredump information .
*/
sof_cache_debugfs ( sdev ) ;
}
# endif
2019-04-12 19:05:16 +03:00
return ret ;
}
static int sof_suspend ( struct device * dev , bool runtime_suspend )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
2022-12-21 13:23:25 +03:00
const struct sof_ipc_pm_ops * pm_ops = sof_ipc_get_ops ( sdev , pm ) ;
const struct sof_ipc_tplg_ops * tplg_ops = sof_ipc_get_ops ( sdev , tplg ) ;
2022-02-10 18:05:22 +03:00
pm_message_t pm_state ;
2022-12-20 15:56:27 +03:00
u32 target_state = snd_sof_dsp_power_target ( sdev ) ;
2023-04-05 12:26:55 +03:00
u32 old_state = sdev - > dsp_power_state . state ;
2019-04-12 19:05:16 +03:00
int ret ;
/* do nothing if dsp suspend callback is not set */
2020-05-15 16:59:51 +03:00
if ( ! runtime_suspend & & ! sof_ops ( sdev ) - > suspend )
return 0 ;
if ( runtime_suspend & & ! sof_ops ( sdev ) - > runtime_suspend )
2019-04-12 19:05:16 +03:00
return 0 ;
2023-04-05 12:26:55 +03:00
/* we need to tear down pipelines only if the DSP hardware is
* active , which happens for PCI devices . if the device is
* suspended , it is brought back to full power and then
* suspended again
*/
if ( tplg_ops & & tplg_ops - > tear_down_all_pipelines & & ( old_state = = SOF_DSP_PM_D0 ) )
2022-12-20 15:56:28 +03:00
tplg_ops - > tear_down_all_pipelines ( sdev , false ) ;
2019-12-18 03:26:09 +03:00
if ( sdev - > fw_state ! = SOF_FW_BOOT_COMPLETE )
2020-01-30 01:07:19 +03:00
goto suspend ;
2019-04-12 19:05:16 +03:00
2021-09-28 10:40:30 +03:00
/* prepare for streams to be resumed properly upon resume */
2019-06-12 20:23:38 +03:00
if ( ! runtime_suspend ) {
2022-04-21 23:31:48 +03:00
ret = snd_sof_dsp_hw_params_upon_resume ( sdev ) ;
2019-06-12 20:23:38 +03:00
if ( ret < 0 ) {
dev_err ( sdev - > dev ,
" error: setting hw_params flag during suspend %d \n " ,
ret ) ;
return ret ;
}
}
2019-04-12 19:05:16 +03:00
2022-02-10 18:05:22 +03:00
pm_state . event = target_state ;
2020-01-30 01:07:19 +03:00
2022-03-30 23:19:21 +03:00
/* suspend DMA trace */
2022-05-16 13:47:07 +03:00
sof_fw_trace_suspend ( sdev , pm_state ) ;
2020-01-30 01:07:19 +03:00
2022-02-10 18:05:22 +03:00
/* Notify clients not managed by pm framework about core suspend */
sof_suspend_clients ( sdev , pm_state ) ;
2023-06-16 13:00:38 +03:00
/* Skip to platform-specific suspend if DSP is entering D0 */
if ( target_state = = SOF_DSP_PM_D0 )
goto suspend ;
2019-04-12 19:05:16 +03:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
/* cache debugfs contents during runtime suspend */
if ( runtime_suspend )
sof_cache_debugfs ( sdev ) ;
# endif
/* notify DSP of upcoming power down */
2022-03-17 20:50:28 +03:00
if ( pm_ops & & pm_ops - > ctx_save ) {
ret = pm_ops - > ctx_save ( sdev ) ;
if ( ret = = - EBUSY | | ret = = - EAGAIN ) {
/*
* runtime PM has logic to handle - EBUSY / - EAGAIN so
* pass these errors up
*/
dev_err ( sdev - > dev , " ctx_save IPC error during suspend: %d \n " , ret ) ;
return ret ;
} else if ( ret < 0 ) {
/* FW in unexpected state, continue to power down */
dev_warn ( sdev - > dev , " ctx_save IPC error: %d, proceeding with suspend \n " ,
ret ) ;
}
2019-04-12 19:05:16 +03:00
}
2020-01-30 01:07:19 +03:00
suspend :
2019-12-18 03:26:09 +03:00
/* return if the DSP was not probed successfully */
if ( sdev - > fw_state = = SOF_FW_BOOT_NOT_STARTED )
return 0 ;
2020-01-30 01:07:19 +03:00
/* platform-specific suspend */
2019-04-12 19:05:16 +03:00
if ( runtime_suspend )
2019-07-22 17:13:50 +03:00
ret = snd_sof_dsp_runtime_suspend ( sdev ) ;
2019-04-12 19:05:16 +03:00
else
2020-01-30 01:07:22 +03:00
ret = snd_sof_dsp_suspend ( sdev , target_state ) ;
2019-04-12 19:05:16 +03:00
if ( ret < 0 )
dev_err ( sdev - > dev ,
" error: failed to power down DSP during suspend %d \n " ,
ret ) ;
2020-01-30 01:07:22 +03:00
/* Do not reset FW state if DSP is in D0 */
if ( target_state = = SOF_DSP_PM_D0 )
2020-01-30 01:07:19 +03:00
return ret ;
2019-12-18 03:26:09 +03:00
/* reset FW state */
2021-10-06 14:06:40 +03:00
sof_set_fw_state ( sdev , SOF_FW_BOOT_NOT_STARTED ) ;
2021-05-28 17:43:30 +03:00
sdev - > enabled_cores_mask = 0 ;
2019-12-18 03:26:09 +03:00
2019-04-12 19:05:16 +03:00
return ret ;
}
2020-05-15 16:59:52 +03:00
int snd_sof_dsp_power_down_notify ( struct snd_sof_dev * sdev )
{
2022-12-21 13:23:25 +03:00
const struct sof_ipc_pm_ops * pm_ops = sof_ipc_get_ops ( sdev , pm ) ;
2022-03-17 20:50:28 +03:00
2020-05-15 16:59:52 +03:00
/* Notify DSP of upcoming power down */
2022-03-17 20:50:28 +03:00
if ( sof_ops ( sdev ) - > remove & & pm_ops & & pm_ops - > ctx_save )
return pm_ops - > ctx_save ( sdev ) ;
2020-05-15 16:59:52 +03:00
return 0 ;
}
2019-04-12 19:05:16 +03:00
int snd_sof_runtime_suspend ( struct device * dev )
{
return sof_suspend ( dev , true ) ;
}
EXPORT_SYMBOL ( snd_sof_runtime_suspend ) ;
2019-07-02 16:24:27 +03:00
int snd_sof_runtime_idle ( struct device * dev )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
return snd_sof_dsp_runtime_idle ( sdev ) ;
}
EXPORT_SYMBOL ( snd_sof_runtime_idle ) ;
2019-04-12 19:05:16 +03:00
int snd_sof_runtime_resume ( struct device * dev )
{
return sof_resume ( dev , true ) ;
}
EXPORT_SYMBOL ( snd_sof_runtime_resume ) ;
int snd_sof_resume ( struct device * dev )
{
return sof_resume ( dev , false ) ;
}
EXPORT_SYMBOL ( snd_sof_resume ) ;
int snd_sof_suspend ( struct device * dev )
{
return sof_suspend ( dev , false ) ;
}
EXPORT_SYMBOL ( snd_sof_suspend ) ;
2019-10-26 01:41:17 +03:00
int snd_sof_prepare ( struct device * dev )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
2020-09-21 13:50:38 +03:00
const struct sof_dev_desc * desc = sdev - > pdata - > desc ;
/* will suspend to S3 by default */
sdev - > system_suspend_target = SOF_SUSPEND_S3 ;
2021-12-23 14:36:15 +03:00
/*
2021-12-23 14:36:21 +03:00
* if the firmware is crashed or boot failed then we try to aim for S3
* to reboot the firmware
2021-12-23 14:36:15 +03:00
*/
2021-12-23 14:36:21 +03:00
if ( sdev - > fw_state = = SOF_FW_CRASHED | |
sdev - > fw_state = = SOF_FW_BOOT_FAILED )
2021-12-23 14:36:15 +03:00
return 0 ;
2020-09-21 13:50:38 +03:00
if ( ! desc - > use_acpi_target_states )
return 0 ;
2019-10-26 01:41:17 +03:00
# if defined(CONFIG_ACPI)
2022-06-16 23:18:16 +03:00
switch ( acpi_target_system_state ( ) ) {
case ACPI_STATE_S0 :
2020-01-30 01:07:20 +03:00
sdev - > system_suspend_target = SOF_SUSPEND_S0IX ;
2022-06-16 23:18:16 +03:00
break ;
case ACPI_STATE_S1 :
case ACPI_STATE_S2 :
case ACPI_STATE_S3 :
sdev - > system_suspend_target = SOF_SUSPEND_S3 ;
break ;
2022-06-16 23:18:17 +03:00
case ACPI_STATE_S4 :
sdev - > system_suspend_target = SOF_SUSPEND_S4 ;
break ;
case ACPI_STATE_S5 :
sdev - > system_suspend_target = SOF_SUSPEND_S5 ;
break ;
2022-06-16 23:18:16 +03:00
default :
break ;
}
2019-10-26 01:41:17 +03:00
# endif
return 0 ;
}
EXPORT_SYMBOL ( snd_sof_prepare ) ;
void snd_sof_complete ( struct device * dev )
{
struct snd_sof_dev * sdev = dev_get_drvdata ( dev ) ;
2020-01-30 01:07:20 +03:00
sdev - > system_suspend_target = SOF_SUSPEND_NONE ;
2019-10-26 01:41:17 +03:00
}
EXPORT_SYMBOL ( snd_sof_complete ) ;