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) 2021 Intel Corporation. All rights reserved. |
7 | // |
8 | // |
9 | |
10 | #include <sound/pcm_params.h> |
11 | #include "ipc3-priv.h" |
12 | #include "ops.h" |
13 | #include "sof-priv.h" |
14 | #include "sof-audio.h" |
15 | |
16 | static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, |
17 | struct snd_pcm_substream *substream) |
18 | { |
19 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(c: component); |
20 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
21 | struct sof_ipc_stream stream; |
22 | struct snd_sof_pcm *spcm; |
23 | |
24 | spcm = snd_sof_find_spcm_dai(scomp: component, rtd); |
25 | if (!spcm) |
26 | return -EINVAL; |
27 | |
28 | if (!spcm->prepared[substream->stream]) |
29 | return 0; |
30 | |
31 | stream.hdr.size = sizeof(stream); |
32 | stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; |
33 | stream.comp_id = spcm->stream[substream->stream].comp_id; |
34 | |
35 | /* send IPC to the DSP */ |
36 | return sof_ipc_tx_message_no_reply(ipc: sdev->ipc, msg_data: &stream, msg_bytes: sizeof(stream)); |
37 | } |
38 | |
39 | static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, |
40 | struct snd_pcm_substream *substream, |
41 | struct snd_pcm_hw_params *params, |
42 | struct snd_sof_platform_stream_params *platform_params) |
43 | { |
44 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(c: component); |
45 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
46 | struct sof_ipc_fw_version *v = &sdev->fw_ready.version; |
47 | struct snd_pcm_runtime *runtime = substream->runtime; |
48 | struct sof_ipc_pcm_params_reply ipc_params_reply; |
49 | struct sof_ipc_pcm_params pcm; |
50 | struct snd_sof_pcm *spcm; |
51 | int ret; |
52 | |
53 | spcm = snd_sof_find_spcm_dai(scomp: component, rtd); |
54 | if (!spcm) |
55 | return -EINVAL; |
56 | |
57 | memset(&pcm, 0, sizeof(pcm)); |
58 | |
59 | /* number of pages should be rounded up */ |
60 | pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes); |
61 | |
62 | /* set IPC PCM parameters */ |
63 | pcm.hdr.size = sizeof(pcm); |
64 | pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; |
65 | pcm.comp_id = spcm->stream[substream->stream].comp_id; |
66 | pcm.params.hdr.size = sizeof(pcm.params); |
67 | pcm.params.buffer.phy_addr = spcm->stream[substream->stream].page_table.addr; |
68 | pcm.params.buffer.size = runtime->dma_bytes; |
69 | pcm.params.direction = substream->stream; |
70 | pcm.params.sample_valid_bytes = params_width(p: params) >> 3; |
71 | pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; |
72 | pcm.params.rate = params_rate(p: params); |
73 | pcm.params.channels = params_channels(p: params); |
74 | pcm.params.host_period_bytes = params_period_bytes(p: params); |
75 | |
76 | /* container size */ |
77 | ret = snd_pcm_format_physical_width(format: params_format(p: params)); |
78 | if (ret < 0) |
79 | return ret; |
80 | pcm.params.sample_container_bytes = ret >> 3; |
81 | |
82 | /* format */ |
83 | switch (params_format(p: params)) { |
84 | case SNDRV_PCM_FORMAT_S16: |
85 | pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; |
86 | break; |
87 | case SNDRV_PCM_FORMAT_S24: |
88 | pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; |
89 | break; |
90 | case SNDRV_PCM_FORMAT_S32: |
91 | pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; |
92 | break; |
93 | case SNDRV_PCM_FORMAT_FLOAT: |
94 | pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; |
95 | break; |
96 | default: |
97 | return -EINVAL; |
98 | } |
99 | |
100 | /* Update the IPC message with information from the platform */ |
101 | pcm.params.stream_tag = platform_params->stream_tag; |
102 | |
103 | if (platform_params->use_phy_address) |
104 | pcm.params.buffer.phy_addr = platform_params->phy_addr; |
105 | |
106 | if (platform_params->no_ipc_position) { |
107 | /* For older ABIs set host_period_bytes to zero to inform |
108 | * FW we don't want position updates. Newer versions use |
109 | * no_stream_position for this purpose. |
110 | */ |
111 | if (v->abi_version < SOF_ABI_VER(3, 10, 0)) |
112 | pcm.params.host_period_bytes = 0; |
113 | else |
114 | pcm.params.no_stream_position = 1; |
115 | } |
116 | |
117 | if (platform_params->cont_update_posn) |
118 | pcm.params.cont_update_posn = 1; |
119 | |
120 | dev_dbg(component->dev, "stream_tag %d" , pcm.params.stream_tag); |
121 | |
122 | /* send hw_params IPC to the DSP */ |
123 | ret = sof_ipc_tx_message(ipc: sdev->ipc, msg_data: &pcm, msg_bytes: sizeof(pcm), |
124 | reply_data: &ipc_params_reply, reply_bytes: sizeof(ipc_params_reply)); |
125 | if (ret < 0) { |
126 | dev_err(component->dev, "HW params ipc failed for stream %d\n" , |
127 | pcm.params.stream_tag); |
128 | return ret; |
129 | } |
130 | |
131 | ret = snd_sof_set_stream_data_offset(sdev, sps: &spcm->stream[substream->stream], |
132 | posn_offset: ipc_params_reply.posn_offset); |
133 | if (ret < 0) { |
134 | dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n" , |
135 | __func__, spcm->pcm.pcm_id); |
136 | return ret; |
137 | } |
138 | |
139 | return ret; |
140 | } |
141 | |
142 | static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, |
143 | struct snd_pcm_substream *substream, int cmd) |
144 | { |
145 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
146 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(c: component); |
147 | struct sof_ipc_stream stream; |
148 | struct snd_sof_pcm *spcm; |
149 | |
150 | spcm = snd_sof_find_spcm_dai(scomp: component, rtd); |
151 | if (!spcm) |
152 | return -EINVAL; |
153 | |
154 | stream.hdr.size = sizeof(stream); |
155 | stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; |
156 | stream.comp_id = spcm->stream[substream->stream].comp_id; |
157 | |
158 | switch (cmd) { |
159 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
160 | stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; |
161 | break; |
162 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
163 | stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; |
164 | break; |
165 | case SNDRV_PCM_TRIGGER_START: |
166 | stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; |
167 | break; |
168 | case SNDRV_PCM_TRIGGER_SUSPEND: |
169 | fallthrough; |
170 | case SNDRV_PCM_TRIGGER_STOP: |
171 | stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; |
172 | break; |
173 | default: |
174 | dev_err(component->dev, "Unhandled trigger cmd %d\n" , cmd); |
175 | return -EINVAL; |
176 | } |
177 | |
178 | /* send IPC to the DSP */ |
179 | return sof_ipc_tx_message_no_reply(ipc: sdev->ipc, msg_data: &stream, msg_bytes: sizeof(stream)); |
180 | } |
181 | |
182 | static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, |
183 | struct snd_pcm_hw_params *params) |
184 | { |
185 | struct sof_ipc_dai_config *config; |
186 | struct snd_sof_dai *dai; |
187 | int i; |
188 | |
189 | /* |
190 | * Search for all matching DAIs as we can have both playback and capture DAI |
191 | * associated with the same link. |
192 | */ |
193 | list_for_each_entry(dai, &sdev->dai_list, list) { |
194 | if (!dai->name || strcmp(link_name, dai->name)) |
195 | continue; |
196 | for (i = 0; i < dai->number_configs; i++) { |
197 | struct sof_dai_private_data *private = dai->private; |
198 | |
199 | config = &private->dai_config[i]; |
200 | if (config->ssp.fsync_rate == params_rate(p: params)) { |
201 | dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n" , i); |
202 | dai->current_config = i; |
203 | break; |
204 | } |
205 | } |
206 | } |
207 | } |
208 | |
209 | static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, |
210 | struct snd_pcm_hw_params *params) |
211 | { |
212 | struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); |
213 | struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); |
214 | struct snd_sof_dai *dai = snd_sof_find_dai(scomp: component, name: (char *)rtd->dai_link->name); |
215 | struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); |
216 | struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); |
217 | struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(c: component); |
218 | struct sof_dai_private_data *private; |
219 | struct snd_soc_dpcm *dpcm; |
220 | |
221 | if (!dai) { |
222 | dev_err(component->dev, "%s: No DAI found with name %s\n" , __func__, |
223 | rtd->dai_link->name); |
224 | return -EINVAL; |
225 | } |
226 | |
227 | private = dai->private; |
228 | if (!private) { |
229 | dev_err(component->dev, "%s: No private data found for DAI %s\n" , __func__, |
230 | rtd->dai_link->name); |
231 | return -EINVAL; |
232 | } |
233 | |
234 | /* read format from topology */ |
235 | snd_mask_none(mask: fmt); |
236 | |
237 | switch (private->comp_dai->config.frame_fmt) { |
238 | case SOF_IPC_FRAME_S16_LE: |
239 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S16_LE); |
240 | break; |
241 | case SOF_IPC_FRAME_S24_4LE: |
242 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S24_LE); |
243 | break; |
244 | case SOF_IPC_FRAME_S32_LE: |
245 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S32_LE); |
246 | break; |
247 | default: |
248 | dev_err(component->dev, "No available DAI format!\n" ); |
249 | return -EINVAL; |
250 | } |
251 | |
252 | /* read rate and channels from topology */ |
253 | switch (private->dai_config->type) { |
254 | case SOF_DAI_INTEL_SSP: |
255 | /* search for config to pcm params match, if not found use default */ |
256 | ssp_dai_config_pcm_params_match(sdev, link_name: (char *)rtd->dai_link->name, params); |
257 | |
258 | rate->min = private->dai_config[dai->current_config].ssp.fsync_rate; |
259 | rate->max = private->dai_config[dai->current_config].ssp.fsync_rate; |
260 | channels->min = private->dai_config[dai->current_config].ssp.tdm_slots; |
261 | channels->max = private->dai_config[dai->current_config].ssp.tdm_slots; |
262 | |
263 | dev_dbg(component->dev, "rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
264 | dev_dbg(component->dev, "channels_min: %d channels_max: %d\n" , |
265 | channels->min, channels->max); |
266 | |
267 | break; |
268 | case SOF_DAI_INTEL_DMIC: |
269 | /* DMIC only supports 16 or 32 bit formats */ |
270 | if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { |
271 | dev_err(component->dev, "Invalid fmt %d for DAI type %d\n" , |
272 | private->comp_dai->config.frame_fmt, |
273 | private->dai_config->type); |
274 | } |
275 | break; |
276 | case SOF_DAI_INTEL_HDA: |
277 | /* |
278 | * HDAudio does not follow the default trigger |
279 | * sequence due to firmware implementation |
280 | */ |
281 | for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { |
282 | struct snd_soc_pcm_runtime *fe = dpcm->fe; |
283 | |
284 | fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = |
285 | SND_SOC_DPCM_TRIGGER_POST; |
286 | } |
287 | break; |
288 | case SOF_DAI_INTEL_ALH: |
289 | /* |
290 | * Dai could run with different channel count compared with |
291 | * front end, so get dai channel count from topology |
292 | */ |
293 | channels->min = private->dai_config->alh.channels; |
294 | channels->max = private->dai_config->alh.channels; |
295 | break; |
296 | case SOF_DAI_IMX_ESAI: |
297 | rate->min = private->dai_config->esai.fsync_rate; |
298 | rate->max = private->dai_config->esai.fsync_rate; |
299 | channels->min = private->dai_config->esai.tdm_slots; |
300 | channels->max = private->dai_config->esai.tdm_slots; |
301 | |
302 | dev_dbg(component->dev, "rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
303 | dev_dbg(component->dev, "channels_min: %d channels_max: %d\n" , |
304 | channels->min, channels->max); |
305 | break; |
306 | case SOF_DAI_MEDIATEK_AFE: |
307 | rate->min = private->dai_config->afe.rate; |
308 | rate->max = private->dai_config->afe.rate; |
309 | channels->min = private->dai_config->afe.channels; |
310 | channels->max = private->dai_config->afe.channels; |
311 | |
312 | snd_mask_none(mask: fmt); |
313 | |
314 | switch (private->dai_config->afe.format) { |
315 | case SOF_IPC_FRAME_S16_LE: |
316 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S16_LE); |
317 | break; |
318 | case SOF_IPC_FRAME_S24_4LE: |
319 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S24_LE); |
320 | break; |
321 | case SOF_IPC_FRAME_S32_LE: |
322 | snd_mask_set_format(mask: fmt, SNDRV_PCM_FORMAT_S32_LE); |
323 | break; |
324 | default: |
325 | dev_err(component->dev, "Not available format!\n" ); |
326 | return -EINVAL; |
327 | } |
328 | |
329 | dev_dbg(component->dev, "rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
330 | dev_dbg(component->dev, "channels_min: %d channels_max: %d\n" , |
331 | channels->min, channels->max); |
332 | break; |
333 | case SOF_DAI_IMX_SAI: |
334 | rate->min = private->dai_config->sai.fsync_rate; |
335 | rate->max = private->dai_config->sai.fsync_rate; |
336 | channels->min = private->dai_config->sai.tdm_slots; |
337 | channels->max = private->dai_config->sai.tdm_slots; |
338 | |
339 | dev_dbg(component->dev, "rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
340 | dev_dbg(component->dev, "channels_min: %d channels_max: %d\n" , |
341 | channels->min, channels->max); |
342 | break; |
343 | case SOF_DAI_AMD_BT: |
344 | rate->min = private->dai_config->acpbt.fsync_rate; |
345 | rate->max = private->dai_config->acpbt.fsync_rate; |
346 | channels->min = private->dai_config->acpbt.tdm_slots; |
347 | channels->max = private->dai_config->acpbt.tdm_slots; |
348 | |
349 | dev_dbg(component->dev, |
350 | "AMD_BT rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
351 | dev_dbg(component->dev, "AMD_BT channels_min: %d channels_max: %d\n" , |
352 | channels->min, channels->max); |
353 | break; |
354 | case SOF_DAI_AMD_SP: |
355 | case SOF_DAI_AMD_SP_VIRTUAL: |
356 | rate->min = private->dai_config->acpsp.fsync_rate; |
357 | rate->max = private->dai_config->acpsp.fsync_rate; |
358 | channels->min = private->dai_config->acpsp.tdm_slots; |
359 | channels->max = private->dai_config->acpsp.tdm_slots; |
360 | |
361 | dev_dbg(component->dev, |
362 | "AMD_SP rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
363 | dev_dbg(component->dev, "AMD_SP channels_min: %d channels_max: %d\n" , |
364 | channels->min, channels->max); |
365 | break; |
366 | case SOF_DAI_AMD_HS: |
367 | case SOF_DAI_AMD_HS_VIRTUAL: |
368 | rate->min = private->dai_config->acphs.fsync_rate; |
369 | rate->max = private->dai_config->acphs.fsync_rate; |
370 | channels->min = private->dai_config->acphs.tdm_slots; |
371 | channels->max = private->dai_config->acphs.tdm_slots; |
372 | |
373 | dev_dbg(component->dev, |
374 | "AMD_HS channel_max: %d rate_max: %d\n" , channels->max, rate->max); |
375 | break; |
376 | case SOF_DAI_AMD_DMIC: |
377 | rate->min = private->dai_config->acpdmic.pdm_rate; |
378 | rate->max = private->dai_config->acpdmic.pdm_rate; |
379 | channels->min = private->dai_config->acpdmic.pdm_ch; |
380 | channels->max = private->dai_config->acpdmic.pdm_ch; |
381 | |
382 | dev_dbg(component->dev, |
383 | "AMD_DMIC rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
384 | dev_dbg(component->dev, "AMD_DMIC channels_min: %d channels_max: %d\n" , |
385 | channels->min, channels->max); |
386 | break; |
387 | case SOF_DAI_IMX_MICFIL: |
388 | rate->min = private->dai_config->micfil.pdm_rate; |
389 | rate->max = private->dai_config->micfil.pdm_rate; |
390 | channels->min = private->dai_config->micfil.pdm_ch; |
391 | channels->max = private->dai_config->micfil.pdm_ch; |
392 | |
393 | dev_dbg(component->dev, |
394 | "MICFIL PDM rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
395 | dev_dbg(component->dev, "MICFIL PDM channels_min: %d channels_max: %d\n" , |
396 | channels->min, channels->max); |
397 | break; |
398 | case SOF_DAI_AMD_SDW: |
399 | /* change the default trigger sequence as per HW implementation */ |
400 | for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { |
401 | struct snd_soc_pcm_runtime *fe = dpcm->fe; |
402 | |
403 | fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = |
404 | SND_SOC_DPCM_TRIGGER_POST; |
405 | } |
406 | |
407 | for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { |
408 | struct snd_soc_pcm_runtime *fe = dpcm->fe; |
409 | |
410 | fe->dai_link->trigger[SNDRV_PCM_STREAM_CAPTURE] = |
411 | SND_SOC_DPCM_TRIGGER_POST; |
412 | } |
413 | rate->min = private->dai_config->acp_sdw.rate; |
414 | rate->max = private->dai_config->acp_sdw.rate; |
415 | channels->min = private->dai_config->acp_sdw.channels; |
416 | channels->max = private->dai_config->acp_sdw.channels; |
417 | |
418 | dev_dbg(component->dev, |
419 | "AMD_SDW rate_min: %d rate_max: %d\n" , rate->min, rate->max); |
420 | dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n" , |
421 | channels->min, channels->max); |
422 | break; |
423 | default: |
424 | dev_err(component->dev, "Invalid DAI type %d\n" , private->dai_config->type); |
425 | break; |
426 | } |
427 | |
428 | return 0; |
429 | } |
430 | |
431 | const struct sof_ipc_pcm_ops ipc3_pcm_ops = { |
432 | .hw_params = sof_ipc3_pcm_hw_params, |
433 | .hw_free = sof_ipc3_pcm_hw_free, |
434 | .trigger = sof_ipc3_pcm_trigger, |
435 | .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup, |
436 | .reset_hw_params_during_stop = true, |
437 | }; |
438 | |