1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // Copyright(c) 2021-2022 Intel Corporation. All rights reserved. |
4 | // |
5 | // Authors: Cezary Rojewski <cezary.rojewski@intel.com> |
6 | // Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com> |
7 | // |
8 | |
9 | #include <linux/debugfs.h> |
10 | #include <linux/device.h> |
11 | #include <sound/hda_register.h> |
12 | #include <sound/hdaudio_ext.h> |
13 | #include <sound/pcm_params.h> |
14 | #include <sound/soc-acpi.h> |
15 | #include <sound/soc-acpi-intel-match.h> |
16 | #include <sound/soc-component.h> |
17 | #include "avs.h" |
18 | #include "path.h" |
19 | #include "topology.h" |
20 | #include "../../codecs/hda.h" |
21 | |
22 | struct avs_dma_data { |
23 | struct avs_tplg_path_template *template; |
24 | struct avs_path *path; |
25 | /* |
26 | * link stream is stored within substream's runtime |
27 | * private_data to fulfill the needs of codec BE path |
28 | * |
29 | * host stream assigned |
30 | */ |
31 | struct hdac_ext_stream *host_stream; |
32 | |
33 | struct snd_pcm_substream *substream; |
34 | }; |
35 | |
36 | static struct avs_tplg_path_template * |
37 | avs_dai_find_path_template(struct snd_soc_dai *dai, bool is_fe, int direction) |
38 | { |
39 | struct snd_soc_dapm_widget *dw = snd_soc_dai_get_widget(dai, stream: direction); |
40 | struct snd_soc_dapm_path *dp; |
41 | enum snd_soc_dapm_direction dir; |
42 | |
43 | if (direction == SNDRV_PCM_STREAM_CAPTURE) { |
44 | dir = is_fe ? SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN; |
45 | } else { |
46 | dir = is_fe ? SND_SOC_DAPM_DIR_IN : SND_SOC_DAPM_DIR_OUT; |
47 | } |
48 | |
49 | dp = list_first_entry_or_null(&dw->edges[dir], typeof(*dp), list_node[dir]); |
50 | if (!dp) |
51 | return NULL; |
52 | |
53 | /* Get the other widget, with actual path template data */ |
54 | dw = (dp->source == dw) ? dp->sink : dp->source; |
55 | |
56 | return dw->priv; |
57 | } |
58 | |
59 | static int avs_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, bool is_fe, |
60 | const struct snd_soc_dai_ops *ops) |
61 | { |
62 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
63 | struct avs_dev *adev = to_avs_dev(dai->dev); |
64 | struct avs_tplg_path_template *template; |
65 | struct avs_dma_data *data; |
66 | |
67 | template = avs_dai_find_path_template(dai, is_fe, direction: substream->stream); |
68 | if (!template) { |
69 | dev_err(dai->dev, "no %s path for dai %s, invalid tplg?\n" , |
70 | snd_pcm_stream_str(substream), dai->name); |
71 | return -EINVAL; |
72 | } |
73 | |
74 | data = kzalloc(size: sizeof(*data), GFP_KERNEL); |
75 | if (!data) |
76 | return -ENOMEM; |
77 | |
78 | data->substream = substream; |
79 | data->template = template; |
80 | snd_soc_dai_set_dma_data(dai, substream, data); |
81 | |
82 | if (rtd->dai_link->ignore_suspend) |
83 | adev->num_lp_paths++; |
84 | |
85 | return 0; |
86 | } |
87 | |
88 | static int avs_dai_hw_params(struct snd_pcm_substream *substream, |
89 | struct snd_pcm_hw_params *fe_hw_params, |
90 | struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, |
91 | int dma_id) |
92 | { |
93 | struct avs_dma_data *data; |
94 | struct avs_path *path; |
95 | struct avs_dev *adev = to_avs_dev(dai->dev); |
96 | int ret; |
97 | |
98 | data = snd_soc_dai_get_dma_data(dai, substream); |
99 | |
100 | dev_dbg(dai->dev, "%s FE hw_params str %p rtd %p" , |
101 | __func__, substream, substream->runtime); |
102 | dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n" , |
103 | params_rate(fe_hw_params), params_channels(fe_hw_params), |
104 | params_width(fe_hw_params), params_physical_width(fe_hw_params)); |
105 | |
106 | dev_dbg(dai->dev, "%s BE hw_params str %p rtd %p" , |
107 | __func__, substream, substream->runtime); |
108 | dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n" , |
109 | params_rate(be_hw_params), params_channels(be_hw_params), |
110 | params_width(be_hw_params), params_physical_width(be_hw_params)); |
111 | |
112 | path = avs_path_create(adev, dma_id, template: data->template, fe_params: fe_hw_params, be_params: be_hw_params); |
113 | if (IS_ERR(ptr: path)) { |
114 | ret = PTR_ERR(ptr: path); |
115 | dev_err(dai->dev, "create path failed: %d\n" , ret); |
116 | return ret; |
117 | } |
118 | |
119 | data->path = path; |
120 | return 0; |
121 | } |
122 | |
123 | static int avs_dai_be_hw_params(struct snd_pcm_substream *substream, |
124 | struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai, |
125 | int dma_id) |
126 | { |
127 | struct snd_pcm_hw_params *fe_hw_params = NULL; |
128 | struct snd_soc_pcm_runtime *fe, *be; |
129 | struct snd_soc_dpcm *dpcm; |
130 | |
131 | be = snd_soc_substream_to_rtd(substream); |
132 | for_each_dpcm_fe(be, substream->stream, dpcm) { |
133 | fe = dpcm->fe; |
134 | fe_hw_params = &fe->dpcm[substream->stream].hw_params; |
135 | } |
136 | |
137 | return avs_dai_hw_params(substream, fe_hw_params, be_hw_params, dai, dma_id); |
138 | } |
139 | |
140 | static int avs_dai_prepare(struct avs_dev *adev, struct snd_pcm_substream *substream, |
141 | struct snd_soc_dai *dai) |
142 | { |
143 | struct avs_dma_data *data; |
144 | int ret; |
145 | |
146 | data = snd_soc_dai_get_dma_data(dai, substream); |
147 | if (!data->path) |
148 | return 0; |
149 | |
150 | ret = avs_path_reset(path: data->path); |
151 | if (ret < 0) { |
152 | dev_err(dai->dev, "reset path failed: %d\n" , ret); |
153 | return ret; |
154 | } |
155 | |
156 | ret = avs_path_pause(path: data->path); |
157 | if (ret < 0) |
158 | dev_err(dai->dev, "pause path failed: %d\n" , ret); |
159 | return ret; |
160 | } |
161 | |
162 | static const struct snd_soc_dai_ops avs_dai_nonhda_be_ops; |
163 | |
164 | static int avs_dai_nonhda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
165 | { |
166 | return avs_dai_startup(substream, dai, is_fe: false, ops: &avs_dai_nonhda_be_ops); |
167 | } |
168 | |
169 | static void avs_dai_nonhda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
170 | { |
171 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
172 | struct avs_dev *adev = to_avs_dev(dai->dev); |
173 | struct avs_dma_data *data; |
174 | |
175 | if (rtd->dai_link->ignore_suspend) |
176 | adev->num_lp_paths--; |
177 | |
178 | data = snd_soc_dai_get_dma_data(dai, substream); |
179 | |
180 | snd_soc_dai_set_dma_data(dai, substream, NULL); |
181 | kfree(objp: data); |
182 | } |
183 | |
184 | static int avs_dai_nonhda_be_hw_params(struct snd_pcm_substream *substream, |
185 | struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) |
186 | { |
187 | struct avs_dma_data *data; |
188 | |
189 | data = snd_soc_dai_get_dma_data(dai, substream); |
190 | if (data->path) |
191 | return 0; |
192 | |
193 | /* Actual port-id comes from topology. */ |
194 | return avs_dai_be_hw_params(substream, be_hw_params: hw_params, dai, dma_id: 0); |
195 | } |
196 | |
197 | static int avs_dai_nonhda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
198 | { |
199 | struct avs_dma_data *data; |
200 | |
201 | dev_dbg(dai->dev, "%s: %s\n" , __func__, dai->name); |
202 | |
203 | data = snd_soc_dai_get_dma_data(dai, substream); |
204 | if (data->path) { |
205 | avs_path_free(path: data->path); |
206 | data->path = NULL; |
207 | } |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | static int avs_dai_nonhda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
213 | { |
214 | return avs_dai_prepare(to_avs_dev(dai->dev), substream, dai); |
215 | } |
216 | |
217 | static int avs_dai_nonhda_be_trigger(struct snd_pcm_substream *substream, int cmd, |
218 | struct snd_soc_dai *dai) |
219 | { |
220 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
221 | struct avs_dma_data *data; |
222 | int ret = 0; |
223 | |
224 | data = snd_soc_dai_get_dma_data(dai, substream); |
225 | |
226 | switch (cmd) { |
227 | case SNDRV_PCM_TRIGGER_RESUME: |
228 | if (rtd->dai_link->ignore_suspend) |
229 | break; |
230 | fallthrough; |
231 | case SNDRV_PCM_TRIGGER_START: |
232 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
233 | ret = avs_path_pause(path: data->path); |
234 | if (ret < 0) { |
235 | dev_err(dai->dev, "pause BE path failed: %d\n" , ret); |
236 | break; |
237 | } |
238 | |
239 | ret = avs_path_run(path: data->path, trigger: AVS_TPLG_TRIGGER_AUTO); |
240 | if (ret < 0) |
241 | dev_err(dai->dev, "run BE path failed: %d\n" , ret); |
242 | break; |
243 | |
244 | case SNDRV_PCM_TRIGGER_SUSPEND: |
245 | if (rtd->dai_link->ignore_suspend) |
246 | break; |
247 | fallthrough; |
248 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
249 | case SNDRV_PCM_TRIGGER_STOP: |
250 | ret = avs_path_pause(path: data->path); |
251 | if (ret < 0) |
252 | dev_err(dai->dev, "pause BE path failed: %d\n" , ret); |
253 | |
254 | ret = avs_path_reset(path: data->path); |
255 | if (ret < 0) |
256 | dev_err(dai->dev, "reset BE path failed: %d\n" , ret); |
257 | break; |
258 | |
259 | default: |
260 | ret = -EINVAL; |
261 | break; |
262 | } |
263 | |
264 | return ret; |
265 | } |
266 | |
267 | static const struct snd_soc_dai_ops avs_dai_nonhda_be_ops = { |
268 | .startup = avs_dai_nonhda_be_startup, |
269 | .shutdown = avs_dai_nonhda_be_shutdown, |
270 | .hw_params = avs_dai_nonhda_be_hw_params, |
271 | .hw_free = avs_dai_nonhda_be_hw_free, |
272 | .prepare = avs_dai_nonhda_be_prepare, |
273 | .trigger = avs_dai_nonhda_be_trigger, |
274 | }; |
275 | |
276 | static const struct snd_soc_dai_ops avs_dai_hda_be_ops; |
277 | |
278 | static int avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
279 | { |
280 | return avs_dai_startup(substream, dai, is_fe: false, ops: &avs_dai_hda_be_ops); |
281 | } |
282 | |
283 | static void avs_dai_hda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
284 | { |
285 | return avs_dai_nonhda_be_shutdown(substream, dai); |
286 | } |
287 | |
288 | static int avs_dai_hda_be_hw_params(struct snd_pcm_substream *substream, |
289 | struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) |
290 | { |
291 | struct avs_dma_data *data; |
292 | struct hdac_ext_stream *link_stream; |
293 | |
294 | data = snd_soc_dai_get_dma_data(dai, substream); |
295 | if (data->path) |
296 | return 0; |
297 | |
298 | link_stream = substream->runtime->private_data; |
299 | |
300 | return avs_dai_be_hw_params(substream, be_hw_params: hw_params, dai, |
301 | hdac_stream(link_stream)->stream_tag - 1); |
302 | } |
303 | |
304 | static int avs_dai_hda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
305 | { |
306 | struct avs_dma_data *data; |
307 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
308 | struct hdac_ext_stream *link_stream; |
309 | struct hdac_ext_link *link; |
310 | struct hda_codec *codec; |
311 | |
312 | dev_dbg(dai->dev, "%s: %s\n" , __func__, dai->name); |
313 | |
314 | data = snd_soc_dai_get_dma_data(dai, substream); |
315 | if (!data->path) |
316 | return 0; |
317 | |
318 | link_stream = substream->runtime->private_data; |
319 | link_stream->link_prepared = false; |
320 | avs_path_free(path: data->path); |
321 | data->path = NULL; |
322 | |
323 | /* clear link <-> stream mapping */ |
324 | codec = dev_to_hda_codec(snd_soc_rtd_to_codec(rtd, 0)->dev); |
325 | link = snd_hdac_ext_bus_get_hlink_by_addr(bus: &codec->bus->core, addr: codec->core.addr); |
326 | if (!link) |
327 | return -EINVAL; |
328 | |
329 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
330 | snd_hdac_ext_bus_link_clear_stream_id(hlink: link, hdac_stream(link_stream)->stream_tag); |
331 | |
332 | return 0; |
333 | } |
334 | |
335 | static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
336 | { |
337 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
338 | struct snd_pcm_runtime *runtime = substream->runtime; |
339 | struct snd_soc_pcm_stream *stream_info; |
340 | struct hdac_ext_stream *link_stream; |
341 | struct hdac_ext_link *link; |
342 | struct hda_codec *codec; |
343 | struct hdac_bus *bus; |
344 | unsigned int format_val; |
345 | unsigned int bits; |
346 | int ret; |
347 | |
348 | link_stream = runtime->private_data; |
349 | if (link_stream->link_prepared) |
350 | return 0; |
351 | |
352 | codec = dev_to_hda_codec(snd_soc_rtd_to_codec(rtd, 0)->dev); |
353 | bus = &codec->bus->core; |
354 | stream_info = snd_soc_dai_get_pcm_stream(dai, stream: substream->stream); |
355 | bits = snd_hdac_stream_format_bits(format: runtime->format, subformat: runtime->subformat, |
356 | maxbits: stream_info->sig_bits); |
357 | format_val = snd_hdac_stream_format(channels: runtime->channels, bits, rate: runtime->rate); |
358 | |
359 | snd_hdac_ext_stream_reset(hext_stream: link_stream); |
360 | snd_hdac_ext_stream_setup(hext_stream: link_stream, fmt: format_val); |
361 | |
362 | link = snd_hdac_ext_bus_get_hlink_by_addr(bus, addr: codec->core.addr); |
363 | if (!link) |
364 | return -EINVAL; |
365 | |
366 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
367 | snd_hdac_ext_bus_link_set_stream_id(hlink: link, hdac_stream(link_stream)->stream_tag); |
368 | |
369 | ret = avs_dai_prepare(to_avs_dev(dai->dev), substream, dai); |
370 | if (ret) |
371 | return ret; |
372 | |
373 | link_stream->link_prepared = true; |
374 | return 0; |
375 | } |
376 | |
377 | static int avs_dai_hda_be_trigger(struct snd_pcm_substream *substream, int cmd, |
378 | struct snd_soc_dai *dai) |
379 | { |
380 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
381 | struct hdac_ext_stream *link_stream; |
382 | struct avs_dma_data *data; |
383 | int ret = 0; |
384 | |
385 | dev_dbg(dai->dev, "entry %s cmd=%d\n" , __func__, cmd); |
386 | |
387 | data = snd_soc_dai_get_dma_data(dai, substream); |
388 | link_stream = substream->runtime->private_data; |
389 | |
390 | switch (cmd) { |
391 | case SNDRV_PCM_TRIGGER_RESUME: |
392 | if (rtd->dai_link->ignore_suspend) |
393 | break; |
394 | fallthrough; |
395 | case SNDRV_PCM_TRIGGER_START: |
396 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
397 | snd_hdac_ext_stream_start(hext_stream: link_stream); |
398 | |
399 | ret = avs_path_pause(path: data->path); |
400 | if (ret < 0) { |
401 | dev_err(dai->dev, "pause BE path failed: %d\n" , ret); |
402 | break; |
403 | } |
404 | |
405 | ret = avs_path_run(path: data->path, trigger: AVS_TPLG_TRIGGER_AUTO); |
406 | if (ret < 0) |
407 | dev_err(dai->dev, "run BE path failed: %d\n" , ret); |
408 | break; |
409 | |
410 | case SNDRV_PCM_TRIGGER_SUSPEND: |
411 | if (rtd->dai_link->ignore_suspend) |
412 | break; |
413 | fallthrough; |
414 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
415 | case SNDRV_PCM_TRIGGER_STOP: |
416 | ret = avs_path_pause(path: data->path); |
417 | if (ret < 0) |
418 | dev_err(dai->dev, "pause BE path failed: %d\n" , ret); |
419 | |
420 | snd_hdac_ext_stream_clear(hext_stream: link_stream); |
421 | |
422 | ret = avs_path_reset(path: data->path); |
423 | if (ret < 0) |
424 | dev_err(dai->dev, "reset BE path failed: %d\n" , ret); |
425 | break; |
426 | |
427 | default: |
428 | ret = -EINVAL; |
429 | break; |
430 | } |
431 | |
432 | return ret; |
433 | } |
434 | |
435 | static const struct snd_soc_dai_ops avs_dai_hda_be_ops = { |
436 | .startup = avs_dai_hda_be_startup, |
437 | .shutdown = avs_dai_hda_be_shutdown, |
438 | .hw_params = avs_dai_hda_be_hw_params, |
439 | .hw_free = avs_dai_hda_be_hw_free, |
440 | .prepare = avs_dai_hda_be_prepare, |
441 | .trigger = avs_dai_hda_be_trigger, |
442 | }; |
443 | |
444 | static const unsigned int rates[] = { |
445 | 8000, 11025, 12000, 16000, |
446 | 22050, 24000, 32000, 44100, |
447 | 48000, 64000, 88200, 96000, |
448 | 128000, 176400, 192000, |
449 | }; |
450 | |
451 | static const struct snd_pcm_hw_constraint_list hw_rates = { |
452 | .count = ARRAY_SIZE(rates), |
453 | .list = rates, |
454 | .mask = 0, |
455 | }; |
456 | |
457 | const struct snd_soc_dai_ops avs_dai_fe_ops; |
458 | |
459 | static int avs_dai_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
460 | { |
461 | struct snd_pcm_runtime *runtime = substream->runtime; |
462 | struct avs_dma_data *data; |
463 | struct avs_dev *adev = to_avs_dev(dai->dev); |
464 | struct hdac_bus *bus = &adev->base.core; |
465 | struct hdac_ext_stream *host_stream; |
466 | int ret; |
467 | |
468 | ret = avs_dai_startup(substream, dai, is_fe: true, ops: &avs_dai_fe_ops); |
469 | if (ret) |
470 | return ret; |
471 | |
472 | data = snd_soc_dai_get_dma_data(dai, substream); |
473 | |
474 | host_stream = snd_hdac_ext_stream_assign(bus, substream, type: HDAC_EXT_STREAM_TYPE_HOST); |
475 | if (!host_stream) { |
476 | ret = -EBUSY; |
477 | goto err; |
478 | } |
479 | |
480 | data->host_stream = host_stream; |
481 | ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
482 | if (ret < 0) |
483 | goto err; |
484 | |
485 | /* avoid wrap-around with wall-clock */ |
486 | ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, min: 20, max: 178000000); |
487 | if (ret < 0) |
488 | goto err; |
489 | |
490 | ret = snd_pcm_hw_constraint_list(runtime, cond: 0, SNDRV_PCM_HW_PARAM_RATE, l: &hw_rates); |
491 | if (ret < 0) |
492 | goto err; |
493 | |
494 | snd_pcm_set_sync(substream); |
495 | |
496 | dev_dbg(dai->dev, "%s fe STARTUP tag %d str %p" , |
497 | __func__, hdac_stream(host_stream)->stream_tag, substream); |
498 | |
499 | return 0; |
500 | |
501 | err: |
502 | kfree(objp: data); |
503 | return ret; |
504 | } |
505 | |
506 | static void avs_dai_fe_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
507 | { |
508 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
509 | struct avs_dev *adev = to_avs_dev(dai->dev); |
510 | struct avs_dma_data *data; |
511 | |
512 | if (rtd->dai_link->ignore_suspend) |
513 | adev->num_lp_paths--; |
514 | |
515 | data = snd_soc_dai_get_dma_data(dai, substream); |
516 | |
517 | snd_soc_dai_set_dma_data(dai, substream, NULL); |
518 | snd_hdac_ext_stream_release(hext_stream: data->host_stream, type: HDAC_EXT_STREAM_TYPE_HOST); |
519 | kfree(objp: data); |
520 | } |
521 | |
522 | static int avs_dai_fe_hw_params(struct snd_pcm_substream *substream, |
523 | struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai) |
524 | { |
525 | struct snd_pcm_hw_params *be_hw_params = NULL; |
526 | struct snd_soc_pcm_runtime *fe, *be; |
527 | struct snd_soc_dpcm *dpcm; |
528 | struct avs_dma_data *data; |
529 | struct hdac_ext_stream *host_stream; |
530 | int ret; |
531 | |
532 | data = snd_soc_dai_get_dma_data(dai, substream); |
533 | if (data->path) |
534 | return 0; |
535 | |
536 | host_stream = data->host_stream; |
537 | |
538 | hdac_stream(host_stream)->bufsize = 0; |
539 | hdac_stream(host_stream)->period_bytes = 0; |
540 | hdac_stream(host_stream)->format_val = 0; |
541 | |
542 | fe = snd_soc_substream_to_rtd(substream); |
543 | for_each_dpcm_be(fe, substream->stream, dpcm) { |
544 | be = dpcm->be; |
545 | be_hw_params = &be->dpcm[substream->stream].hw_params; |
546 | } |
547 | |
548 | ret = avs_dai_hw_params(substream, fe_hw_params: hw_params, be_hw_params, dai, |
549 | hdac_stream(host_stream)->stream_tag - 1); |
550 | if (ret) |
551 | goto create_err; |
552 | |
553 | ret = avs_path_bind(path: data->path); |
554 | if (ret < 0) { |
555 | dev_err(dai->dev, "bind FE <-> BE failed: %d\n" , ret); |
556 | goto bind_err; |
557 | } |
558 | |
559 | return 0; |
560 | |
561 | bind_err: |
562 | avs_path_free(path: data->path); |
563 | data->path = NULL; |
564 | create_err: |
565 | snd_pcm_lib_free_pages(substream); |
566 | return ret; |
567 | } |
568 | |
569 | static int __avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
570 | { |
571 | struct avs_dma_data *data; |
572 | struct hdac_ext_stream *host_stream; |
573 | int ret; |
574 | |
575 | dev_dbg(dai->dev, "%s fe HW_FREE str %p rtd %p" , |
576 | __func__, substream, substream->runtime); |
577 | |
578 | data = snd_soc_dai_get_dma_data(dai, substream); |
579 | if (!data->path) |
580 | return 0; |
581 | |
582 | host_stream = data->host_stream; |
583 | |
584 | ret = avs_path_unbind(path: data->path); |
585 | if (ret < 0) |
586 | dev_err(dai->dev, "unbind FE <-> BE failed: %d\n" , ret); |
587 | |
588 | avs_path_free(path: data->path); |
589 | data->path = NULL; |
590 | snd_hdac_stream_cleanup(hdac_stream(host_stream)); |
591 | hdac_stream(host_stream)->prepared = false; |
592 | |
593 | return ret; |
594 | } |
595 | |
596 | static int avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
597 | { |
598 | int ret; |
599 | |
600 | ret = __avs_dai_fe_hw_free(substream, dai); |
601 | snd_pcm_lib_free_pages(substream); |
602 | |
603 | return ret; |
604 | } |
605 | |
606 | static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) |
607 | { |
608 | struct snd_pcm_runtime *runtime = substream->runtime; |
609 | struct snd_soc_pcm_stream *stream_info; |
610 | struct avs_dma_data *data; |
611 | struct avs_dev *adev = to_avs_dev(dai->dev); |
612 | struct hdac_ext_stream *host_stream; |
613 | unsigned int format_val; |
614 | unsigned int bits; |
615 | int ret; |
616 | |
617 | data = snd_soc_dai_get_dma_data(dai, substream); |
618 | host_stream = data->host_stream; |
619 | |
620 | if (hdac_stream(host_stream)->prepared) |
621 | return 0; |
622 | |
623 | snd_hdac_stream_reset(hdac_stream(host_stream)); |
624 | |
625 | stream_info = snd_soc_dai_get_pcm_stream(dai, stream: substream->stream); |
626 | bits = snd_hdac_stream_format_bits(format: runtime->format, subformat: runtime->subformat, |
627 | maxbits: stream_info->sig_bits); |
628 | format_val = snd_hdac_stream_format(channels: runtime->channels, bits, rate: runtime->rate); |
629 | |
630 | ret = snd_hdac_stream_set_params(hdac_stream(host_stream), format_val); |
631 | if (ret < 0) |
632 | return ret; |
633 | |
634 | ret = snd_hdac_ext_host_stream_setup(hext_stream: host_stream, code_loading: false); |
635 | if (ret < 0) |
636 | return ret; |
637 | |
638 | ret = avs_dai_prepare(adev, substream, dai); |
639 | if (ret) |
640 | return ret; |
641 | |
642 | hdac_stream(host_stream)->prepared = true; |
643 | return 0; |
644 | } |
645 | |
646 | static void avs_hda_stream_start(struct hdac_bus *bus, struct hdac_ext_stream *host_stream) |
647 | { |
648 | struct hdac_stream *first_running = NULL; |
649 | struct hdac_stream *pos; |
650 | struct avs_dev *adev = hdac_to_avs(bus); |
651 | |
652 | list_for_each_entry(pos, &bus->stream_list, list) { |
653 | if (pos->running) { |
654 | if (first_running) |
655 | break; /* more than one running */ |
656 | first_running = pos; |
657 | } |
658 | } |
659 | |
660 | /* |
661 | * If host_stream is a CAPTURE stream and will be the only one running, |
662 | * disable L1SEN to avoid sound clipping. |
663 | */ |
664 | if (!first_running) { |
665 | if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE) |
666 | avs_hda_l1sen_enable(adev, enable: false); |
667 | snd_hdac_stream_start(hdac_stream(host_stream)); |
668 | return; |
669 | } |
670 | |
671 | snd_hdac_stream_start(hdac_stream(host_stream)); |
672 | /* |
673 | * If host_stream is the first stream to break the rule above, |
674 | * re-enable L1SEN. |
675 | */ |
676 | if (list_entry_is_head(pos, &bus->stream_list, list) && |
677 | first_running->direction == SNDRV_PCM_STREAM_CAPTURE) |
678 | avs_hda_l1sen_enable(adev, enable: true); |
679 | } |
680 | |
681 | static void avs_hda_stream_stop(struct hdac_bus *bus, struct hdac_ext_stream *host_stream) |
682 | { |
683 | struct hdac_stream *first_running = NULL; |
684 | struct hdac_stream *pos; |
685 | struct avs_dev *adev = hdac_to_avs(bus); |
686 | |
687 | list_for_each_entry(pos, &bus->stream_list, list) { |
688 | if (pos == hdac_stream(host_stream)) |
689 | continue; /* ignore stream that is about to be stopped */ |
690 | if (pos->running) { |
691 | if (first_running) |
692 | break; /* more than one running */ |
693 | first_running = pos; |
694 | } |
695 | } |
696 | |
697 | /* |
698 | * If host_stream is a CAPTURE stream and is the only one running, |
699 | * re-enable L1SEN. |
700 | */ |
701 | if (!first_running) { |
702 | snd_hdac_stream_stop(hdac_stream(host_stream)); |
703 | if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE) |
704 | avs_hda_l1sen_enable(adev, enable: true); |
705 | return; |
706 | } |
707 | |
708 | /* |
709 | * If by stopping host_stream there is only a single, CAPTURE stream running |
710 | * left, disable L1SEN to avoid sound clipping. |
711 | */ |
712 | if (list_entry_is_head(pos, &bus->stream_list, list) && |
713 | first_running->direction == SNDRV_PCM_STREAM_CAPTURE) |
714 | avs_hda_l1sen_enable(adev, enable: false); |
715 | |
716 | snd_hdac_stream_stop(hdac_stream(host_stream)); |
717 | } |
718 | |
719 | static int avs_dai_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) |
720 | { |
721 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
722 | struct avs_dma_data *data; |
723 | struct hdac_ext_stream *host_stream; |
724 | struct hdac_bus *bus; |
725 | unsigned long flags; |
726 | int ret = 0; |
727 | |
728 | data = snd_soc_dai_get_dma_data(dai, substream); |
729 | host_stream = data->host_stream; |
730 | bus = hdac_stream(host_stream)->bus; |
731 | |
732 | switch (cmd) { |
733 | case SNDRV_PCM_TRIGGER_RESUME: |
734 | if (rtd->dai_link->ignore_suspend) |
735 | break; |
736 | fallthrough; |
737 | case SNDRV_PCM_TRIGGER_START: |
738 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
739 | spin_lock_irqsave(&bus->reg_lock, flags); |
740 | avs_hda_stream_start(bus, host_stream); |
741 | spin_unlock_irqrestore(lock: &bus->reg_lock, flags); |
742 | |
743 | /* Timeout on DRSM poll shall not stop the resume so ignore the result. */ |
744 | if (cmd == SNDRV_PCM_TRIGGER_RESUME) |
745 | snd_hdac_stream_wait_drsm(hdac_stream(host_stream)); |
746 | |
747 | ret = avs_path_pause(path: data->path); |
748 | if (ret < 0) { |
749 | dev_err(dai->dev, "pause FE path failed: %d\n" , ret); |
750 | break; |
751 | } |
752 | |
753 | ret = avs_path_run(path: data->path, trigger: AVS_TPLG_TRIGGER_AUTO); |
754 | if (ret < 0) |
755 | dev_err(dai->dev, "run FE path failed: %d\n" , ret); |
756 | |
757 | break; |
758 | |
759 | case SNDRV_PCM_TRIGGER_SUSPEND: |
760 | if (rtd->dai_link->ignore_suspend) |
761 | break; |
762 | fallthrough; |
763 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
764 | case SNDRV_PCM_TRIGGER_STOP: |
765 | ret = avs_path_pause(path: data->path); |
766 | if (ret < 0) |
767 | dev_err(dai->dev, "pause FE path failed: %d\n" , ret); |
768 | |
769 | spin_lock_irqsave(&bus->reg_lock, flags); |
770 | avs_hda_stream_stop(bus, host_stream); |
771 | spin_unlock_irqrestore(lock: &bus->reg_lock, flags); |
772 | |
773 | ret = avs_path_reset(path: data->path); |
774 | if (ret < 0) |
775 | dev_err(dai->dev, "reset FE path failed: %d\n" , ret); |
776 | break; |
777 | |
778 | default: |
779 | ret = -EINVAL; |
780 | break; |
781 | } |
782 | |
783 | return ret; |
784 | } |
785 | |
786 | const struct snd_soc_dai_ops avs_dai_fe_ops = { |
787 | .startup = avs_dai_fe_startup, |
788 | .shutdown = avs_dai_fe_shutdown, |
789 | .hw_params = avs_dai_fe_hw_params, |
790 | .hw_free = avs_dai_fe_hw_free, |
791 | .prepare = avs_dai_fe_prepare, |
792 | .trigger = avs_dai_fe_trigger, |
793 | }; |
794 | |
795 | static ssize_t topology_name_read(struct file *file, char __user *user_buf, size_t count, |
796 | loff_t *ppos) |
797 | { |
798 | struct snd_soc_component *component = file->private_data; |
799 | struct snd_soc_card *card = component->card; |
800 | struct snd_soc_acpi_mach *mach = dev_get_platdata(dev: card->dev); |
801 | char buf[64]; |
802 | size_t len; |
803 | |
804 | len = scnprintf(buf, size: sizeof(buf), fmt: "%s/%s\n" , component->driver->topology_name_prefix, |
805 | mach->tplg_filename); |
806 | |
807 | return simple_read_from_buffer(to: user_buf, count, ppos, from: buf, available: len); |
808 | } |
809 | |
810 | static const struct file_operations topology_name_fops = { |
811 | .open = simple_open, |
812 | .read = topology_name_read, |
813 | .llseek = default_llseek, |
814 | }; |
815 | |
816 | static int avs_component_load_libraries(struct avs_soc_component *acomp) |
817 | { |
818 | struct avs_tplg *tplg = acomp->tplg; |
819 | struct avs_dev *adev = to_avs_dev(acomp->base.dev); |
820 | int ret; |
821 | |
822 | if (!tplg->num_libs) |
823 | return 0; |
824 | |
825 | /* Parent device may be asleep and library loading involves IPCs. */ |
826 | ret = pm_runtime_resume_and_get(dev: adev->dev); |
827 | if (ret < 0) |
828 | return ret; |
829 | |
830 | avs_hda_power_gating_enable(adev, enable: false); |
831 | avs_hda_clock_gating_enable(adev, enable: false); |
832 | avs_hda_l1sen_enable(adev, enable: false); |
833 | |
834 | ret = avs_dsp_load_libraries(adev, libs: tplg->libs, num_libs: tplg->num_libs); |
835 | |
836 | avs_hda_l1sen_enable(adev, enable: true); |
837 | avs_hda_clock_gating_enable(adev, enable: true); |
838 | avs_hda_power_gating_enable(adev, enable: true); |
839 | |
840 | if (!ret) |
841 | ret = avs_module_info_init(adev, purge: false); |
842 | |
843 | pm_runtime_mark_last_busy(dev: adev->dev); |
844 | pm_runtime_put_autosuspend(dev: adev->dev); |
845 | |
846 | return ret; |
847 | } |
848 | |
849 | static int avs_component_probe(struct snd_soc_component *component) |
850 | { |
851 | struct snd_soc_card *card = component->card; |
852 | struct snd_soc_acpi_mach *mach; |
853 | struct avs_soc_component *acomp; |
854 | struct avs_dev *adev; |
855 | char *filename; |
856 | int ret; |
857 | |
858 | dev_dbg(card->dev, "probing %s card %s\n" , component->name, card->name); |
859 | mach = dev_get_platdata(dev: card->dev); |
860 | acomp = to_avs_soc_component(component); |
861 | adev = to_avs_dev(component->dev); |
862 | |
863 | acomp->tplg = avs_tplg_new(comp: component); |
864 | if (!acomp->tplg) |
865 | return -ENOMEM; |
866 | |
867 | if (!mach->tplg_filename) |
868 | goto finalize; |
869 | |
870 | /* Load specified topology and create debugfs for it. */ |
871 | filename = kasprintf(GFP_KERNEL, fmt: "%s/%s" , component->driver->topology_name_prefix, |
872 | mach->tplg_filename); |
873 | if (!filename) |
874 | return -ENOMEM; |
875 | |
876 | ret = avs_load_topology(comp: component, filename); |
877 | kfree(objp: filename); |
878 | if (ret == -ENOENT && !strncmp(mach->tplg_filename, "hda-" , 4)) { |
879 | unsigned int vendor_id; |
880 | |
881 | if (sscanf(mach->tplg_filename, "hda-%08x-tplg.bin" , &vendor_id) != 1) |
882 | return ret; |
883 | |
884 | if (((vendor_id >> 16) & 0xFFFF) == 0x8086) |
885 | mach->tplg_filename = devm_kasprintf(dev: adev->dev, GFP_KERNEL, |
886 | fmt: "hda-8086-generic-tplg.bin" ); |
887 | else |
888 | mach->tplg_filename = devm_kasprintf(dev: adev->dev, GFP_KERNEL, |
889 | fmt: "hda-generic-tplg.bin" ); |
890 | |
891 | filename = kasprintf(GFP_KERNEL, fmt: "%s/%s" , component->driver->topology_name_prefix, |
892 | mach->tplg_filename); |
893 | if (!filename) |
894 | return -ENOMEM; |
895 | |
896 | dev_info(card->dev, "trying to load fallback topology %s\n" , mach->tplg_filename); |
897 | ret = avs_load_topology(comp: component, filename); |
898 | kfree(objp: filename); |
899 | } |
900 | if (ret < 0) |
901 | return ret; |
902 | |
903 | ret = avs_component_load_libraries(acomp); |
904 | if (ret < 0) { |
905 | dev_err(card->dev, "libraries loading failed: %d\n" , ret); |
906 | goto err_load_libs; |
907 | } |
908 | |
909 | finalize: |
910 | debugfs_create_file(name: "topology_name" , mode: 0444, parent: component->debugfs_root, data: component, |
911 | fops: &topology_name_fops); |
912 | |
913 | mutex_lock(&adev->comp_list_mutex); |
914 | list_add_tail(new: &acomp->node, head: &adev->comp_list); |
915 | mutex_unlock(lock: &adev->comp_list_mutex); |
916 | |
917 | return 0; |
918 | |
919 | err_load_libs: |
920 | avs_remove_topology(comp: component); |
921 | return ret; |
922 | } |
923 | |
924 | static void avs_component_remove(struct snd_soc_component *component) |
925 | { |
926 | struct avs_soc_component *acomp = to_avs_soc_component(component); |
927 | struct snd_soc_acpi_mach *mach; |
928 | struct avs_dev *adev = to_avs_dev(component->dev); |
929 | int ret; |
930 | |
931 | mach = dev_get_platdata(dev: component->card->dev); |
932 | |
933 | mutex_lock(&adev->comp_list_mutex); |
934 | list_del(entry: &acomp->node); |
935 | mutex_unlock(lock: &adev->comp_list_mutex); |
936 | |
937 | if (mach->tplg_filename) { |
938 | ret = avs_remove_topology(comp: component); |
939 | if (ret < 0) |
940 | dev_err(component->dev, "unload topology failed: %d\n" , ret); |
941 | } |
942 | } |
943 | |
944 | static int avs_dai_resume_hw_params(struct snd_soc_dai *dai, struct avs_dma_data *data) |
945 | { |
946 | struct snd_pcm_substream *substream; |
947 | struct snd_soc_pcm_runtime *rtd; |
948 | int ret; |
949 | |
950 | substream = data->substream; |
951 | rtd = snd_soc_substream_to_rtd(substream); |
952 | |
953 | ret = dai->driver->ops->hw_params(substream, &rtd->dpcm[substream->stream].hw_params, dai); |
954 | if (ret) |
955 | dev_err(dai->dev, "hw_params on resume failed: %d\n" , ret); |
956 | |
957 | return ret; |
958 | } |
959 | |
960 | static int avs_dai_resume_fe_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data) |
961 | { |
962 | struct hdac_ext_stream *host_stream; |
963 | struct hdac_stream *hstream; |
964 | struct hdac_bus *bus; |
965 | int ret; |
966 | |
967 | host_stream = data->host_stream; |
968 | hstream = hdac_stream(host_stream); |
969 | bus = hdac_stream(host_stream)->bus; |
970 | |
971 | /* Set DRSM before programming stream and position registers. */ |
972 | snd_hdac_stream_drsm_enable(bus, enable: true, index: hstream->index); |
973 | |
974 | ret = dai->driver->ops->prepare(data->substream, dai); |
975 | if (ret) { |
976 | dev_err(dai->dev, "prepare FE on resume failed: %d\n" , ret); |
977 | return ret; |
978 | } |
979 | |
980 | writel(val: host_stream->pphcllpl, addr: host_stream->pphc_addr + AZX_REG_PPHCLLPL); |
981 | writel(val: host_stream->pphcllpu, addr: host_stream->pphc_addr + AZX_REG_PPHCLLPU); |
982 | writel(val: host_stream->pphcldpl, addr: host_stream->pphc_addr + AZX_REG_PPHCLDPL); |
983 | writel(val: host_stream->pphcldpu, addr: host_stream->pphc_addr + AZX_REG_PPHCLDPU); |
984 | |
985 | /* As per HW spec recommendation, program LPIB and DPIB to the same value. */ |
986 | snd_hdac_stream_set_lpib(azx_dev: hstream, value: hstream->lpib); |
987 | snd_hdac_stream_set_dpibr(bus, azx_dev: hstream, value: hstream->lpib); |
988 | |
989 | return 0; |
990 | } |
991 | |
992 | static int avs_dai_resume_be_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data) |
993 | { |
994 | int ret; |
995 | |
996 | ret = dai->driver->ops->prepare(data->substream, dai); |
997 | if (ret) |
998 | dev_err(dai->dev, "prepare BE on resume failed: %d\n" , ret); |
999 | |
1000 | return ret; |
1001 | } |
1002 | |
1003 | static int avs_dai_suspend_fe_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data) |
1004 | { |
1005 | struct hdac_ext_stream *host_stream; |
1006 | int ret; |
1007 | |
1008 | host_stream = data->host_stream; |
1009 | |
1010 | /* Store position addresses so we can resume from them later on. */ |
1011 | hdac_stream(host_stream)->lpib = snd_hdac_stream_get_pos_lpib(hdac_stream(host_stream)); |
1012 | host_stream->pphcllpl = readl(addr: host_stream->pphc_addr + AZX_REG_PPHCLLPL); |
1013 | host_stream->pphcllpu = readl(addr: host_stream->pphc_addr + AZX_REG_PPHCLLPU); |
1014 | host_stream->pphcldpl = readl(addr: host_stream->pphc_addr + AZX_REG_PPHCLDPL); |
1015 | host_stream->pphcldpu = readl(addr: host_stream->pphc_addr + AZX_REG_PPHCLDPU); |
1016 | |
1017 | ret = __avs_dai_fe_hw_free(substream: data->substream, dai); |
1018 | if (ret < 0) |
1019 | dev_err(dai->dev, "hw_free FE on suspend failed: %d\n" , ret); |
1020 | |
1021 | return ret; |
1022 | } |
1023 | |
1024 | static int avs_dai_suspend_be_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data) |
1025 | { |
1026 | int ret; |
1027 | |
1028 | ret = dai->driver->ops->hw_free(data->substream, dai); |
1029 | if (ret < 0) |
1030 | dev_err(dai->dev, "hw_free BE on suspend failed: %d\n" , ret); |
1031 | |
1032 | return ret; |
1033 | } |
1034 | |
1035 | static int avs_component_pm_op(struct snd_soc_component *component, bool be, |
1036 | int (*op)(struct snd_soc_dai *, struct avs_dma_data *)) |
1037 | { |
1038 | struct snd_soc_pcm_runtime *rtd; |
1039 | struct avs_dma_data *data; |
1040 | struct snd_soc_dai *dai; |
1041 | int ret; |
1042 | |
1043 | for_each_component_dais(component, dai) { |
1044 | data = snd_soc_dai_dma_data_get_playback(dai); |
1045 | if (data) { |
1046 | rtd = snd_soc_substream_to_rtd(data->substream); |
1047 | if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) { |
1048 | ret = op(dai, data); |
1049 | if (ret < 0) { |
1050 | __snd_pcm_set_state(runtime: data->substream->runtime, |
1051 | SNDRV_PCM_STATE_DISCONNECTED); |
1052 | return ret; |
1053 | } |
1054 | } |
1055 | } |
1056 | |
1057 | data = snd_soc_dai_dma_data_get_capture(dai); |
1058 | if (data) { |
1059 | rtd = snd_soc_substream_to_rtd(data->substream); |
1060 | if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) { |
1061 | ret = op(dai, data); |
1062 | if (ret < 0) { |
1063 | __snd_pcm_set_state(runtime: data->substream->runtime, |
1064 | SNDRV_PCM_STATE_DISCONNECTED); |
1065 | return ret; |
1066 | } |
1067 | } |
1068 | } |
1069 | } |
1070 | |
1071 | return 0; |
1072 | } |
1073 | |
1074 | static int avs_component_resume_hw_params(struct snd_soc_component *component, bool be) |
1075 | { |
1076 | return avs_component_pm_op(component, be, op: &avs_dai_resume_hw_params); |
1077 | } |
1078 | |
1079 | static int avs_component_resume_prepare(struct snd_soc_component *component, bool be) |
1080 | { |
1081 | int (*prepare_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data); |
1082 | |
1083 | if (be) |
1084 | prepare_cb = &avs_dai_resume_be_prepare; |
1085 | else |
1086 | prepare_cb = &avs_dai_resume_fe_prepare; |
1087 | |
1088 | return avs_component_pm_op(component, be, op: prepare_cb); |
1089 | } |
1090 | |
1091 | static int avs_component_suspend_hw_free(struct snd_soc_component *component, bool be) |
1092 | { |
1093 | int (*hw_free_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data); |
1094 | |
1095 | if (be) |
1096 | hw_free_cb = &avs_dai_suspend_be_hw_free; |
1097 | else |
1098 | hw_free_cb = &avs_dai_suspend_fe_hw_free; |
1099 | |
1100 | return avs_component_pm_op(component, be, op: hw_free_cb); |
1101 | } |
1102 | |
1103 | static int avs_component_suspend(struct snd_soc_component *component) |
1104 | { |
1105 | int ret; |
1106 | |
1107 | /* |
1108 | * When freeing paths, FEs need to be first as they perform |
1109 | * path unbinding. |
1110 | */ |
1111 | ret = avs_component_suspend_hw_free(component, be: false); |
1112 | if (ret) |
1113 | return ret; |
1114 | |
1115 | return avs_component_suspend_hw_free(component, be: true); |
1116 | } |
1117 | |
1118 | static int avs_component_resume(struct snd_soc_component *component) |
1119 | { |
1120 | int ret; |
1121 | |
1122 | /* |
1123 | * When creating paths, FEs need to be last as they perform |
1124 | * path binding. |
1125 | */ |
1126 | ret = avs_component_resume_hw_params(component, be: true); |
1127 | if (ret) |
1128 | return ret; |
1129 | |
1130 | ret = avs_component_resume_hw_params(component, be: false); |
1131 | if (ret) |
1132 | return ret; |
1133 | |
1134 | /* It is expected that the LINK stream is prepared first. */ |
1135 | ret = avs_component_resume_prepare(component, be: true); |
1136 | if (ret) |
1137 | return ret; |
1138 | |
1139 | return avs_component_resume_prepare(component, be: false); |
1140 | } |
1141 | |
1142 | static const struct snd_pcm_hardware avs_pcm_hardware = { |
1143 | .info = SNDRV_PCM_INFO_MMAP | |
1144 | SNDRV_PCM_INFO_MMAP_VALID | |
1145 | SNDRV_PCM_INFO_INTERLEAVED | |
1146 | SNDRV_PCM_INFO_PAUSE | |
1147 | SNDRV_PCM_INFO_RESUME | |
1148 | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, |
1149 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
1150 | SNDRV_PCM_FMTBIT_S32_LE, |
1151 | .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | |
1152 | SNDRV_PCM_SUBFMTBIT_MSBITS_24 | |
1153 | SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, |
1154 | .buffer_bytes_max = AZX_MAX_BUF_SIZE, |
1155 | .period_bytes_min = 128, |
1156 | .period_bytes_max = AZX_MAX_BUF_SIZE / 2, |
1157 | .periods_min = 2, |
1158 | .periods_max = AZX_MAX_FRAG, |
1159 | .fifo_size = 0, |
1160 | }; |
1161 | |
1162 | static int avs_component_open(struct snd_soc_component *component, |
1163 | struct snd_pcm_substream *substream) |
1164 | { |
1165 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
1166 | |
1167 | /* only FE DAI links are handled here */ |
1168 | if (rtd->dai_link->no_pcm) |
1169 | return 0; |
1170 | |
1171 | return snd_soc_set_runtime_hwparams(substream, hw: &avs_pcm_hardware); |
1172 | } |
1173 | |
1174 | static unsigned int avs_hda_stream_dpib_read(struct hdac_ext_stream *stream) |
1175 | { |
1176 | return readl(hdac_stream(stream)->bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE + |
1177 | (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(stream)->index)); |
1178 | } |
1179 | |
1180 | static snd_pcm_uframes_t |
1181 | avs_component_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream) |
1182 | { |
1183 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
1184 | struct avs_dma_data *data; |
1185 | struct hdac_ext_stream *host_stream; |
1186 | unsigned int pos; |
1187 | |
1188 | data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); |
1189 | if (!data->host_stream) |
1190 | return 0; |
1191 | |
1192 | host_stream = data->host_stream; |
1193 | pos = avs_hda_stream_dpib_read(stream: host_stream); |
1194 | |
1195 | if (pos >= hdac_stream(host_stream)->bufsize) |
1196 | pos = 0; |
1197 | |
1198 | return bytes_to_frames(runtime: substream->runtime, size: pos); |
1199 | } |
1200 | |
1201 | static int avs_component_mmap(struct snd_soc_component *component, |
1202 | struct snd_pcm_substream *substream, |
1203 | struct vm_area_struct *vma) |
1204 | { |
1205 | return snd_pcm_lib_default_mmap(substream, area: vma); |
1206 | } |
1207 | |
1208 | #define MAX_PREALLOC_SIZE (32 * 1024 * 1024) |
1209 | |
1210 | static int avs_component_construct(struct snd_soc_component *component, |
1211 | struct snd_soc_pcm_runtime *rtd) |
1212 | { |
1213 | struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0); |
1214 | struct snd_pcm *pcm = rtd->pcm; |
1215 | |
1216 | if (dai->driver->playback.channels_min) |
1217 | snd_pcm_set_managed_buffer(substream: pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, |
1218 | SNDRV_DMA_TYPE_DEV_SG, data: component->dev, size: 0, |
1219 | MAX_PREALLOC_SIZE); |
1220 | |
1221 | if (dai->driver->capture.channels_min) |
1222 | snd_pcm_set_managed_buffer(substream: pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, |
1223 | SNDRV_DMA_TYPE_DEV_SG, data: component->dev, size: 0, |
1224 | MAX_PREALLOC_SIZE); |
1225 | |
1226 | return 0; |
1227 | } |
1228 | |
1229 | static const struct snd_soc_component_driver avs_component_driver = { |
1230 | .name = "avs-pcm" , |
1231 | .probe = avs_component_probe, |
1232 | .remove = avs_component_remove, |
1233 | .suspend = avs_component_suspend, |
1234 | .resume = avs_component_resume, |
1235 | .open = avs_component_open, |
1236 | .pointer = avs_component_pointer, |
1237 | .mmap = avs_component_mmap, |
1238 | .pcm_construct = avs_component_construct, |
1239 | .module_get_upon_open = 1, /* increment refcount when a pcm is opened */ |
1240 | .topology_name_prefix = "intel/avs" , |
1241 | }; |
1242 | |
1243 | int avs_soc_component_register(struct device *dev, const char *name, |
1244 | const struct snd_soc_component_driver *drv, |
1245 | struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais) |
1246 | { |
1247 | struct avs_soc_component *acomp; |
1248 | int ret; |
1249 | |
1250 | acomp = devm_kzalloc(dev, size: sizeof(*acomp), GFP_KERNEL); |
1251 | if (!acomp) |
1252 | return -ENOMEM; |
1253 | |
1254 | ret = snd_soc_component_initialize(component: &acomp->base, driver: drv, dev); |
1255 | if (ret < 0) |
1256 | return ret; |
1257 | |
1258 | /* force name change after ASoC is done with its init */ |
1259 | acomp->base.name = name; |
1260 | INIT_LIST_HEAD(list: &acomp->node); |
1261 | |
1262 | return snd_soc_add_component(component: &acomp->base, dai_drv: cpu_dais, num_dai: num_cpu_dais); |
1263 | } |
1264 | |
1265 | static struct snd_soc_dai_driver dmic_cpu_dais[] = { |
1266 | { |
1267 | .name = "DMIC Pin" , |
1268 | .ops = &avs_dai_nonhda_be_ops, |
1269 | .capture = { |
1270 | .stream_name = "DMIC Rx" , |
1271 | .channels_min = 1, |
1272 | .channels_max = 4, |
1273 | .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000, |
1274 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, |
1275 | }, |
1276 | }, |
1277 | { |
1278 | .name = "DMIC WoV Pin" , |
1279 | .ops = &avs_dai_nonhda_be_ops, |
1280 | .capture = { |
1281 | .stream_name = "DMIC WoV Rx" , |
1282 | .channels_min = 1, |
1283 | .channels_max = 4, |
1284 | .rates = SNDRV_PCM_RATE_16000, |
1285 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
1286 | }, |
1287 | }, |
1288 | }; |
1289 | |
1290 | int avs_dmic_platform_register(struct avs_dev *adev, const char *name) |
1291 | { |
1292 | return avs_soc_component_register(dev: adev->dev, name, drv: &avs_component_driver, cpu_dais: dmic_cpu_dais, |
1293 | ARRAY_SIZE(dmic_cpu_dais)); |
1294 | } |
1295 | |
1296 | static const struct snd_soc_dai_driver i2s_dai_template = { |
1297 | .ops = &avs_dai_nonhda_be_ops, |
1298 | .playback = { |
1299 | .channels_min = 1, |
1300 | .channels_max = 8, |
1301 | .rates = SNDRV_PCM_RATE_8000_192000 | |
1302 | SNDRV_PCM_RATE_KNOT, |
1303 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
1304 | SNDRV_PCM_FMTBIT_S32_LE, |
1305 | .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | |
1306 | SNDRV_PCM_SUBFMTBIT_MSBITS_24 | |
1307 | SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, |
1308 | }, |
1309 | .capture = { |
1310 | .channels_min = 1, |
1311 | .channels_max = 8, |
1312 | .rates = SNDRV_PCM_RATE_8000_192000 | |
1313 | SNDRV_PCM_RATE_KNOT, |
1314 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
1315 | SNDRV_PCM_FMTBIT_S32_LE, |
1316 | .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | |
1317 | SNDRV_PCM_SUBFMTBIT_MSBITS_24 | |
1318 | SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, |
1319 | }, |
1320 | }; |
1321 | |
1322 | int avs_i2s_platform_register(struct avs_dev *adev, const char *name, unsigned long port_mask, |
1323 | unsigned long *tdms) |
1324 | { |
1325 | struct snd_soc_dai_driver *cpus, *dai; |
1326 | size_t ssp_count, cpu_count; |
1327 | int i, j; |
1328 | |
1329 | ssp_count = adev->hw_cfg.i2s_caps.ctrl_count; |
1330 | |
1331 | cpu_count = 0; |
1332 | for_each_set_bit(i, &port_mask, ssp_count) |
1333 | if (!tdms || test_bit(0, &tdms[i])) |
1334 | cpu_count++; |
1335 | if (tdms) |
1336 | for_each_set_bit(i, &port_mask, ssp_count) |
1337 | cpu_count += hweight_long(w: tdms[i]); |
1338 | |
1339 | cpus = devm_kzalloc(dev: adev->dev, size: sizeof(*cpus) * cpu_count, GFP_KERNEL); |
1340 | if (!cpus) |
1341 | return -ENOMEM; |
1342 | |
1343 | dai = cpus; |
1344 | for_each_set_bit(i, &port_mask, ssp_count) { |
1345 | if (!tdms || test_bit(0, &tdms[i])) { |
1346 | memcpy(dai, &i2s_dai_template, sizeof(*dai)); |
1347 | |
1348 | dai->name = |
1349 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "SSP%d Pin" , i); |
1350 | dai->playback.stream_name = |
1351 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "ssp%d Tx" , i); |
1352 | dai->capture.stream_name = |
1353 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "ssp%d Rx" , i); |
1354 | |
1355 | if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) |
1356 | return -ENOMEM; |
1357 | dai++; |
1358 | } |
1359 | } |
1360 | |
1361 | if (!tdms) |
1362 | goto plat_register; |
1363 | |
1364 | for_each_set_bit(i, &port_mask, ssp_count) { |
1365 | for_each_set_bit(j, &tdms[i], ssp_count) { |
1366 | memcpy(dai, &i2s_dai_template, sizeof(*dai)); |
1367 | |
1368 | dai->name = |
1369 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "SSP%d:%d Pin" , i, j); |
1370 | dai->playback.stream_name = |
1371 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "ssp%d:%d Tx" , i, j); |
1372 | dai->capture.stream_name = |
1373 | devm_kasprintf(dev: adev->dev, GFP_KERNEL, fmt: "ssp%d:%d Rx" , i, j); |
1374 | |
1375 | if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name) |
1376 | return -ENOMEM; |
1377 | dai++; |
1378 | } |
1379 | } |
1380 | |
1381 | plat_register: |
1382 | return avs_soc_component_register(dev: adev->dev, name, drv: &avs_component_driver, cpu_dais: cpus, num_cpu_dais: cpu_count); |
1383 | } |
1384 | |
1385 | /* HD-Audio CPU DAI template */ |
1386 | static const struct snd_soc_dai_driver hda_cpu_dai = { |
1387 | .ops = &avs_dai_hda_be_ops, |
1388 | .playback = { |
1389 | .channels_min = 1, |
1390 | .channels_max = 8, |
1391 | .rates = SNDRV_PCM_RATE_8000_192000, |
1392 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
1393 | SNDRV_PCM_FMTBIT_S32_LE, |
1394 | .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | |
1395 | SNDRV_PCM_SUBFMTBIT_MSBITS_24 | |
1396 | SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, |
1397 | }, |
1398 | .capture = { |
1399 | .channels_min = 1, |
1400 | .channels_max = 8, |
1401 | .rates = SNDRV_PCM_RATE_8000_192000, |
1402 | .formats = SNDRV_PCM_FMTBIT_S16_LE | |
1403 | SNDRV_PCM_FMTBIT_S32_LE, |
1404 | .subformats = SNDRV_PCM_SUBFMTBIT_MSBITS_20 | |
1405 | SNDRV_PCM_SUBFMTBIT_MSBITS_24 | |
1406 | SNDRV_PCM_SUBFMTBIT_MSBITS_MAX, |
1407 | }, |
1408 | }; |
1409 | |
1410 | static void avs_component_hda_unregister_dais(struct snd_soc_component *component) |
1411 | { |
1412 | struct snd_soc_acpi_mach *mach; |
1413 | struct snd_soc_dai *dai, *save; |
1414 | struct hda_codec *codec; |
1415 | char name[32]; |
1416 | |
1417 | mach = dev_get_platdata(dev: component->card->dev); |
1418 | codec = mach->pdata; |
1419 | sprintf(buf: name, fmt: "%s-cpu" , dev_name(dev: &codec->core.dev)); |
1420 | |
1421 | for_each_component_dais_safe(component, dai, save) { |
1422 | int stream; |
1423 | |
1424 | if (!strstr(dai->driver->name, name)) |
1425 | continue; |
1426 | |
1427 | for_each_pcm_streams(stream) |
1428 | snd_soc_dapm_free_widget(w: snd_soc_dai_get_widget(dai, stream)); |
1429 | |
1430 | snd_soc_unregister_dai(dai); |
1431 | } |
1432 | } |
1433 | |
1434 | static int avs_component_hda_probe(struct snd_soc_component *component) |
1435 | { |
1436 | struct snd_soc_dapm_context *dapm; |
1437 | struct snd_soc_dai_driver *dais; |
1438 | struct snd_soc_acpi_mach *mach; |
1439 | struct hda_codec *codec; |
1440 | struct hda_pcm *pcm; |
1441 | const char *cname; |
1442 | int pcm_count = 0, ret, i; |
1443 | |
1444 | mach = dev_get_platdata(dev: component->card->dev); |
1445 | if (!mach) |
1446 | return -EINVAL; |
1447 | |
1448 | codec = mach->pdata; |
1449 | if (list_empty(head: &codec->pcm_list_head)) |
1450 | return -EINVAL; |
1451 | list_for_each_entry(pcm, &codec->pcm_list_head, list) |
1452 | pcm_count++; |
1453 | |
1454 | dais = devm_kcalloc(dev: component->dev, n: pcm_count, size: sizeof(*dais), |
1455 | GFP_KERNEL); |
1456 | if (!dais) |
1457 | return -ENOMEM; |
1458 | |
1459 | cname = dev_name(dev: &codec->core.dev); |
1460 | dapm = snd_soc_component_get_dapm(component); |
1461 | pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); |
1462 | |
1463 | for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { |
1464 | struct snd_soc_dai *dai; |
1465 | |
1466 | memcpy(&dais[i], &hda_cpu_dai, sizeof(*dais)); |
1467 | dais[i].id = i; |
1468 | dais[i].name = devm_kasprintf(dev: component->dev, GFP_KERNEL, |
1469 | fmt: "%s-cpu%d" , cname, i); |
1470 | if (!dais[i].name) { |
1471 | ret = -ENOMEM; |
1472 | goto exit; |
1473 | } |
1474 | |
1475 | if (pcm->stream[0].substreams) { |
1476 | dais[i].playback.stream_name = |
1477 | devm_kasprintf(dev: component->dev, GFP_KERNEL, |
1478 | fmt: "%s-cpu%d Tx" , cname, i); |
1479 | if (!dais[i].playback.stream_name) { |
1480 | ret = -ENOMEM; |
1481 | goto exit; |
1482 | } |
1483 | |
1484 | if (!hda_codec_is_display(codec)) { |
1485 | dais[i].playback.formats = pcm->stream[0].formats; |
1486 | dais[i].playback.subformats = pcm->stream[0].subformats; |
1487 | dais[i].playback.rates = pcm->stream[0].rates; |
1488 | dais[i].playback.channels_min = pcm->stream[0].channels_min; |
1489 | dais[i].playback.channels_max = pcm->stream[0].channels_max; |
1490 | dais[i].playback.sig_bits = pcm->stream[0].maxbps; |
1491 | } |
1492 | } |
1493 | |
1494 | if (pcm->stream[1].substreams) { |
1495 | dais[i].capture.stream_name = |
1496 | devm_kasprintf(dev: component->dev, GFP_KERNEL, |
1497 | fmt: "%s-cpu%d Rx" , cname, i); |
1498 | if (!dais[i].capture.stream_name) { |
1499 | ret = -ENOMEM; |
1500 | goto exit; |
1501 | } |
1502 | |
1503 | if (!hda_codec_is_display(codec)) { |
1504 | dais[i].capture.formats = pcm->stream[1].formats; |
1505 | dais[i].capture.subformats = pcm->stream[1].subformats; |
1506 | dais[i].capture.rates = pcm->stream[1].rates; |
1507 | dais[i].capture.channels_min = pcm->stream[1].channels_min; |
1508 | dais[i].capture.channels_max = pcm->stream[1].channels_max; |
1509 | dais[i].capture.sig_bits = pcm->stream[1].maxbps; |
1510 | } |
1511 | } |
1512 | |
1513 | dai = snd_soc_register_dai(component, dai_drv: &dais[i], legacy_dai_naming: false); |
1514 | if (!dai) { |
1515 | dev_err(component->dev, "register dai for %s failed\n" , |
1516 | pcm->name); |
1517 | ret = -EINVAL; |
1518 | goto exit; |
1519 | } |
1520 | |
1521 | ret = snd_soc_dapm_new_dai_widgets(dapm, dai); |
1522 | if (ret < 0) { |
1523 | dev_err(component->dev, "create widgets failed: %d\n" , |
1524 | ret); |
1525 | goto exit; |
1526 | } |
1527 | } |
1528 | |
1529 | ret = avs_component_probe(component); |
1530 | exit: |
1531 | if (ret) |
1532 | avs_component_hda_unregister_dais(component); |
1533 | |
1534 | return ret; |
1535 | } |
1536 | |
1537 | static void avs_component_hda_remove(struct snd_soc_component *component) |
1538 | { |
1539 | avs_component_hda_unregister_dais(component); |
1540 | avs_component_remove(component); |
1541 | } |
1542 | |
1543 | static int avs_component_hda_open(struct snd_soc_component *component, |
1544 | struct snd_pcm_substream *substream) |
1545 | { |
1546 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
1547 | struct hdac_ext_stream *link_stream; |
1548 | struct hda_codec *codec; |
1549 | |
1550 | if (!rtd->dai_link->no_pcm) { |
1551 | struct snd_pcm_hardware hwparams = avs_pcm_hardware; |
1552 | struct snd_soc_pcm_runtime *be; |
1553 | struct snd_soc_dpcm *dpcm; |
1554 | int dir = substream->stream; |
1555 | |
1556 | /* |
1557 | * Support the DPCM reparenting while still fulfilling expectations of HDAudio |
1558 | * common code - a valid stream pointer at substream->runtime->private_data - |
1559 | * by having all FEs point to the same private data. |
1560 | */ |
1561 | for_each_dpcm_be(rtd, dir, dpcm) { |
1562 | struct snd_pcm_substream *be_substream; |
1563 | |
1564 | be = dpcm->be; |
1565 | if (be->dpcm[dir].users == 1) |
1566 | break; |
1567 | |
1568 | be_substream = snd_soc_dpcm_get_substream(be, stream: dir); |
1569 | substream->runtime->private_data = be_substream->runtime->private_data; |
1570 | break; |
1571 | } |
1572 | |
1573 | /* RESUME unsupported for de-coupled HD-Audio capture. */ |
1574 | if (dir == SNDRV_PCM_STREAM_CAPTURE) |
1575 | hwparams.info &= ~SNDRV_PCM_INFO_RESUME; |
1576 | |
1577 | return snd_soc_set_runtime_hwparams(substream, hw: &hwparams); |
1578 | } |
1579 | |
1580 | codec = dev_to_hda_codec(snd_soc_rtd_to_codec(rtd, 0)->dev); |
1581 | link_stream = snd_hdac_ext_stream_assign(bus: &codec->bus->core, substream, |
1582 | type: HDAC_EXT_STREAM_TYPE_LINK); |
1583 | if (!link_stream) |
1584 | return -EBUSY; |
1585 | |
1586 | substream->runtime->private_data = link_stream; |
1587 | return 0; |
1588 | } |
1589 | |
1590 | static int avs_component_hda_close(struct snd_soc_component *component, |
1591 | struct snd_pcm_substream *substream) |
1592 | { |
1593 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
1594 | struct hdac_ext_stream *link_stream; |
1595 | |
1596 | /* only BE DAI links are handled here */ |
1597 | if (!rtd->dai_link->no_pcm) |
1598 | return 0; |
1599 | |
1600 | link_stream = substream->runtime->private_data; |
1601 | snd_hdac_ext_stream_release(hext_stream: link_stream, type: HDAC_EXT_STREAM_TYPE_LINK); |
1602 | substream->runtime->private_data = NULL; |
1603 | |
1604 | return 0; |
1605 | } |
1606 | |
1607 | static const struct snd_soc_component_driver avs_hda_component_driver = { |
1608 | .name = "avs-hda-pcm" , |
1609 | .probe = avs_component_hda_probe, |
1610 | .remove = avs_component_hda_remove, |
1611 | .suspend = avs_component_suspend, |
1612 | .resume = avs_component_resume, |
1613 | .open = avs_component_hda_open, |
1614 | .close = avs_component_hda_close, |
1615 | .pointer = avs_component_pointer, |
1616 | .mmap = avs_component_mmap, |
1617 | .pcm_construct = avs_component_construct, |
1618 | /* |
1619 | * hda platform component's probe() is dependent on |
1620 | * codec->pcm_list_head, it needs to be initialized after codec |
1621 | * component. remove_order is here for completeness sake |
1622 | */ |
1623 | .probe_order = SND_SOC_COMP_ORDER_LATE, |
1624 | .remove_order = SND_SOC_COMP_ORDER_EARLY, |
1625 | .module_get_upon_open = 1, |
1626 | .topology_name_prefix = "intel/avs" , |
1627 | }; |
1628 | |
1629 | int avs_hda_platform_register(struct avs_dev *adev, const char *name) |
1630 | { |
1631 | return avs_soc_component_register(dev: adev->dev, name, |
1632 | drv: &avs_hda_component_driver, NULL, num_cpu_dais: 0); |
1633 | } |
1634 | |