Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

soundwire: intel: improve suspend flows

This patch provides both a simplification of the suspend flows and a
better balanced operation during suspend/resume transition, as part of
the transition of Sound Open Firmware (SOF) to dynamic pipelines: the
DSP resources are only enabled when required instead of enabled on
startup.

The exiting code relies on a convoluted way of dealing with suspend
signals. Since there is no .suspend DAI callback, we used the
component .suspend and marked all the component DAI dmas as
'suspended'. The information was used in the .prepare stage to
differentiate resume operations from xrun handling, and only
reinitialize SHIM registers and DMA in the former case.

While this solution has been working reliably for about 2 years, there
is a much better solution consisting in trapping the TRIGGER_SUSPEND
in the .trigger DAI ops. The DMA is still marked in the same way for
the .prepare op to run, but in addition the callbacks sent to DSP
firmware are now balanced.

Normal operation:
hw_params -> intel_params_stream
hw_free -> intel_free_stream

suspend -> intel_free_stream
prepare -> intel_params_stream

This balanced operation was not required with existing SOF firmware
relying on static pipelines instantiated at every boot. With the
on-going transition to dynamic pipelines, it's however a requirement
to keep the use count for the DAI widget balanced across all
transitions.

The component suspend is not removed but instead modified to deal with
a corner case: when a substream is PAUSED, the ALSA core does not
throw the TRIGGER_SUSPEND. This is problematic since the refcount for
all pipelines and widgets is not balanced, leading to issues on
resume. The trigger callback keeps track of the 'paused' state with a
new flag, which is tested during the component suspend called later to
release the remaining DSP resources. These resources will be
re-enabled in the .prepare step.

The IPC used in the TRIGGER_SUSPEND to release DSP resources is not a
problem since the BE dailink is already marked as non-atomic.

Co-developed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
Acked-By: Vinod Koul <vkoul@kernel.org>
Link: https://lore.kernel.org/r/20211224021034.26635-4-yung-chuan.liao@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Ranjani Sridharan and committed by
Mark Brown
8ddeafb9 b86947b5

+89 -23
+2
drivers/soundwire/cadence_master.h
··· 86 86 * @link_id: Master link id 87 87 * @hw_params: hw_params to be applied in .prepare step 88 88 * @suspended: status set when suspended, to be used in .prepare 89 + * @paused: status set in .trigger, to be used in suspend 89 90 */ 90 91 struct sdw_cdns_dma_data { 91 92 char *name; ··· 97 96 int link_id; 98 97 struct snd_pcm_hw_params *hw_params; 99 98 bool suspended; 99 + bool paused; 100 100 }; 101 101 102 102 /**
+87 -23
drivers/soundwire/intel.c
··· 871 871 sdw_cdns_config_stream(cdns, ch, dir, pdi); 872 872 873 873 /* store pdi and hw_params, may be needed in prepare step */ 874 + dma->paused = false; 874 875 dma->suspended = false; 875 876 dma->pdi = pdi; 876 877 dma->hw_params = params; ··· 1009 1008 pm_runtime_put_autosuspend(cdns->dev); 1010 1009 } 1011 1010 1012 - static int intel_component_dais_suspend(struct snd_soc_component *component) 1013 - { 1014 - struct sdw_cdns_dma_data *dma; 1015 - struct snd_soc_dai *dai; 1016 - 1017 - for_each_component_dais(component, dai) { 1018 - /* 1019 - * we don't have a .suspend dai_ops, and we don't have access 1020 - * to the substream, so let's mark both capture and playback 1021 - * DMA contexts as suspended 1022 - */ 1023 - dma = dai->playback_dma_data; 1024 - if (dma) 1025 - dma->suspended = true; 1026 - 1027 - dma = dai->capture_dma_data; 1028 - if (dma) 1029 - dma->suspended = true; 1030 - } 1031 - 1032 - return 0; 1033 - } 1034 - 1035 1011 static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, 1036 1012 void *stream, int direction) 1037 1013 { ··· 1037 1059 return dma->stream; 1038 1060 } 1039 1061 1062 + static int intel_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) 1063 + { 1064 + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 1065 + struct sdw_intel *sdw = cdns_to_intel(cdns); 1066 + struct sdw_cdns_dma_data *dma; 1067 + int ret = 0; 1068 + 1069 + dma = snd_soc_dai_get_dma_data(dai, substream); 1070 + if (!dma) { 1071 + dev_err(dai->dev, "failed to get dma data in %s\n", 1072 + __func__); 1073 + return -EIO; 1074 + } 1075 + 1076 + switch (cmd) { 1077 + case SNDRV_PCM_TRIGGER_SUSPEND: 1078 + 1079 + /* 1080 + * The .prepare callback is used to deal with xruns and resume operations. 1081 + * In the case of xruns, the DMAs and SHIM registers cannot be touched, 1082 + * but for resume operations the DMAs and SHIM registers need to be initialized. 1083 + * the .trigger callback is used to track the suspend case only. 1084 + */ 1085 + 1086 + dma->suspended = true; 1087 + 1088 + ret = intel_free_stream(sdw, substream->stream, dai, sdw->instance); 1089 + break; 1090 + 1091 + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 1092 + dma->paused = true; 1093 + break; 1094 + case SNDRV_PCM_TRIGGER_STOP: 1095 + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 1096 + dma->paused = false; 1097 + break; 1098 + default: 1099 + break; 1100 + } 1101 + 1102 + return ret; 1103 + } 1104 + 1105 + static int intel_component_dais_suspend(struct snd_soc_component *component) 1106 + { 1107 + struct snd_soc_dai *dai; 1108 + 1109 + /* 1110 + * In the corner case where a SUSPEND happens during a PAUSE, the ALSA core 1111 + * does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state. 1112 + * Since the component suspend is called last, we can trap this corner case 1113 + * and force the DAIs to release their resources. 1114 + */ 1115 + for_each_component_dais(component, dai) { 1116 + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); 1117 + struct sdw_intel *sdw = cdns_to_intel(cdns); 1118 + struct sdw_cdns_dma_data *dma; 1119 + int stream; 1120 + int ret; 1121 + 1122 + dma = dai->playback_dma_data; 1123 + stream = SNDRV_PCM_STREAM_PLAYBACK; 1124 + if (!dma) { 1125 + dma = dai->capture_dma_data; 1126 + stream = SNDRV_PCM_STREAM_CAPTURE; 1127 + } 1128 + 1129 + if (!dma) 1130 + continue; 1131 + 1132 + if (dma->suspended) 1133 + continue; 1134 + 1135 + if (dma->paused) { 1136 + dma->suspended = true; 1137 + 1138 + ret = intel_free_stream(sdw, stream, dai, sdw->instance); 1139 + if (ret < 0) 1140 + return ret; 1141 + } 1142 + } 1143 + 1144 + return 0; 1145 + } 1146 + 1040 1147 static const struct snd_soc_dai_ops intel_pcm_dai_ops = { 1041 1148 .startup = intel_startup, 1042 1149 .hw_params = intel_hw_params, 1043 1150 .prepare = intel_prepare, 1044 1151 .hw_free = intel_hw_free, 1152 + .trigger = intel_trigger, 1045 1153 .shutdown = intel_shutdown, 1046 1154 .set_sdw_stream = intel_pcm_set_sdw_stream, 1047 1155 .get_sdw_stream = intel_get_sdw_stream,