]>
Commit | Line | Data |
---|---|---|
c16211d6 LG |
1 | // SPDX-License-Identifier: (GPL-2.0 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) 2018 Intel Corporation. All rights reserved. | |
7 | // | |
8 | // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> | |
9 | // | |
10 | ||
11 | #include <linux/firmware.h> | |
12 | #include <linux/module.h> | |
13 | #include <asm/unaligned.h> | |
14 | #include <sound/soc.h> | |
15 | #include <sound/sof.h> | |
16 | #include "sof-priv.h" | |
17 | #include "ops.h" | |
18 | ||
2ab4c50f PLB |
19 | /* see SOF_DBG_ flags */ |
20 | int sof_core_debug; | |
21 | module_param_named(sof_debug, sof_core_debug, int, 0444); | |
22 | MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); | |
23 | ||
c16211d6 | 24 | /* SOF defaults if not provided by the platform in ms */ |
c9b54a33 KV |
25 | #define TIMEOUT_DEFAULT_IPC_MS 500 |
26 | #define TIMEOUT_DEFAULT_BOOT_MS 2000 | |
c16211d6 LG |
27 | |
28 | /* | |
29 | * Generic object lookup APIs. | |
30 | */ | |
31 | ||
32 | struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, | |
33 | const char *name) | |
34 | { | |
35 | struct snd_sof_pcm *spcm; | |
36 | ||
37 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
38 | /* match with PCM dai name */ | |
39 | if (strcmp(spcm->pcm.dai_name, name) == 0) | |
40 | return spcm; | |
41 | ||
42 | /* match with playback caps name if set */ | |
43 | if (*spcm->pcm.caps[0].name && | |
44 | !strcmp(spcm->pcm.caps[0].name, name)) | |
45 | return spcm; | |
46 | ||
47 | /* match with capture caps name if set */ | |
48 | if (*spcm->pcm.caps[1].name && | |
49 | !strcmp(spcm->pcm.caps[1].name, name)) | |
50 | return spcm; | |
51 | } | |
52 | ||
53 | return NULL; | |
54 | } | |
55 | ||
56 | struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, | |
57 | unsigned int comp_id, | |
58 | int *direction) | |
59 | { | |
60 | struct snd_sof_pcm *spcm; | |
61 | ||
62 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
63 | if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) { | |
64 | *direction = SNDRV_PCM_STREAM_PLAYBACK; | |
65 | return spcm; | |
66 | } | |
67 | if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { | |
68 | *direction = SNDRV_PCM_STREAM_CAPTURE; | |
69 | return spcm; | |
70 | } | |
71 | } | |
72 | ||
73 | return NULL; | |
74 | } | |
75 | ||
76 | struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, | |
77 | unsigned int pcm_id) | |
78 | { | |
79 | struct snd_sof_pcm *spcm; | |
80 | ||
81 | list_for_each_entry(spcm, &sdev->pcm_list, list) { | |
82 | if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) | |
83 | return spcm; | |
84 | } | |
85 | ||
86 | return NULL; | |
87 | } | |
88 | ||
89 | struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, | |
90 | const char *name) | |
91 | { | |
92 | struct snd_sof_widget *swidget; | |
93 | ||
94 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
95 | if (strcmp(name, swidget->widget->name) == 0) | |
96 | return swidget; | |
97 | } | |
98 | ||
99 | return NULL; | |
100 | } | |
101 | ||
102 | /* find widget by stream name and direction */ | |
103 | struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, | |
104 | const char *pcm_name, int dir) | |
105 | { | |
106 | struct snd_sof_widget *swidget; | |
107 | enum snd_soc_dapm_type type; | |
108 | ||
109 | if (dir == SNDRV_PCM_STREAM_PLAYBACK) | |
110 | type = snd_soc_dapm_aif_in; | |
111 | else | |
112 | type = snd_soc_dapm_aif_out; | |
113 | ||
114 | list_for_each_entry(swidget, &sdev->widget_list, list) { | |
115 | if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type) | |
116 | return swidget; | |
117 | } | |
118 | ||
119 | return NULL; | |
120 | } | |
121 | ||
122 | struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, | |
123 | const char *name) | |
124 | { | |
125 | struct snd_sof_dai *dai; | |
126 | ||
127 | list_for_each_entry(dai, &sdev->dai_list, list) { | |
128 | if (dai->name && (strcmp(name, dai->name) == 0)) | |
129 | return dai; | |
130 | } | |
131 | ||
132 | return NULL; | |
133 | } | |
134 | ||
135 | /* | |
136 | * FW Panic/fault handling. | |
137 | */ | |
138 | ||
139 | struct sof_panic_msg { | |
140 | u32 id; | |
141 | const char *msg; | |
142 | }; | |
143 | ||
144 | /* standard FW panic types */ | |
145 | static const struct sof_panic_msg panic_msg[] = { | |
146 | {SOF_IPC_PANIC_MEM, "out of memory"}, | |
147 | {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, | |
148 | {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, | |
149 | {SOF_IPC_PANIC_ARCH, "arch init failed"}, | |
150 | {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, | |
151 | {SOF_IPC_PANIC_TASK, "scheduler init failed"}, | |
152 | {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, | |
153 | {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, | |
154 | {SOF_IPC_PANIC_STACK, "stack overflow"}, | |
155 | {SOF_IPC_PANIC_IDLE, "can't enter idle"}, | |
156 | {SOF_IPC_PANIC_WFI, "invalid wait state"}, | |
157 | {SOF_IPC_PANIC_ASSERT, "assertion failed"}, | |
158 | }; | |
159 | ||
160 | /* | |
161 | * helper to be called from .dbg_dump callbacks. No error code is | |
162 | * provided, it's left as an exercise for the caller of .dbg_dump | |
163 | * (typically IPC or loader) | |
164 | */ | |
165 | void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, | |
166 | u32 tracep_code, void *oops, | |
167 | struct sof_ipc_panic_info *panic_info, | |
168 | void *stack, size_t stack_words) | |
169 | { | |
170 | u32 code; | |
171 | int i; | |
172 | ||
173 | /* is firmware dead ? */ | |
174 | if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { | |
175 | dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", | |
176 | panic_code, tracep_code); | |
177 | return; /* no fault ? */ | |
178 | } | |
179 | ||
180 | code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); | |
181 | ||
182 | for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { | |
183 | if (panic_msg[i].id == code) { | |
184 | dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); | |
185 | dev_err(sdev->dev, "error: trace point %8.8x\n", | |
186 | tracep_code); | |
187 | goto out; | |
188 | } | |
189 | } | |
190 | ||
191 | /* unknown error */ | |
192 | dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); | |
193 | dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); | |
194 | ||
195 | out: | |
196 | dev_err(sdev->dev, "error: panic at %s:%d\n", | |
197 | panic_info->filename, panic_info->linenum); | |
198 | sof_oops(sdev, oops); | |
199 | sof_stack(sdev, oops, stack, stack_words); | |
200 | } | |
201 | EXPORT_SYMBOL(snd_sof_get_status); | |
202 | ||
203 | /* | |
204 | * Generic buffer page table creation. | |
205 | * Take the each physical page address and drop the least significant unused | |
206 | * bits from each (based on PAGE_SIZE). Then pack valid page address bits | |
207 | * into compressed page table. | |
208 | */ | |
209 | ||
210 | int snd_sof_create_page_table(struct snd_sof_dev *sdev, | |
211 | struct snd_dma_buffer *dmab, | |
212 | unsigned char *page_table, size_t size) | |
213 | { | |
214 | int i, pages; | |
215 | ||
216 | pages = snd_sgbuf_aligned_pages(size); | |
217 | ||
218 | dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", | |
219 | dmab->area, size, pages); | |
220 | ||
221 | for (i = 0; i < pages; i++) { | |
222 | /* | |
223 | * The number of valid address bits for each page is 20. | |
224 | * idx determines the byte position within page_table | |
225 | * where the current page's address is stored | |
226 | * in the compressed page_table. | |
227 | * This can be calculated by multiplying the page number by 2.5. | |
228 | */ | |
229 | u32 idx = (5 * i) >> 1; | |
230 | u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; | |
231 | u8 *pg_table; | |
232 | ||
233 | dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); | |
234 | ||
235 | pg_table = (u8 *)(page_table + idx); | |
236 | ||
237 | /* | |
238 | * pagetable compression: | |
239 | * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 | |
240 | * ___________pfn 0__________ __________pfn 1___________ _pfn 2... | |
241 | * .... .... .... .... .... .... .... .... .... .... .... | |
242 | * It is created by: | |
243 | * 1. set current location to 0, PFN index i to 0 | |
244 | * 2. put pfn[i] at current location in Little Endian byte order | |
245 | * 3. calculate an intermediate value as | |
246 | * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) | |
247 | * 4. put x at offset (current location + 2) in LE byte order | |
248 | * 5. increment current location by 5 bytes, increment i by 2 | |
249 | * 6. continue to (2) | |
250 | */ | |
251 | if (i & 1) | |
252 | put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, | |
253 | pg_table); | |
254 | else | |
255 | put_unaligned_le32(pfn, pg_table); | |
256 | } | |
257 | ||
258 | return pages; | |
259 | } | |
260 | ||
261 | /* | |
262 | * SOF Driver enumeration. | |
263 | */ | |
264 | static int sof_machine_check(struct snd_sof_dev *sdev) | |
265 | { | |
266 | struct snd_sof_pdata *plat_data = sdev->pdata; | |
7f6647ce | 267 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) |
c16211d6 LG |
268 | struct snd_soc_acpi_mach *machine; |
269 | int ret; | |
7f6647ce | 270 | #endif |
c16211d6 LG |
271 | |
272 | if (plat_data->machine) | |
273 | return 0; | |
274 | ||
ce38a750 PLB |
275 | #if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) |
276 | dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); | |
277 | return -ENODEV; | |
278 | #else | |
c16211d6 LG |
279 | /* fallback to nocodec mode */ |
280 | dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n"); | |
281 | machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL); | |
282 | if (!machine) | |
283 | return -ENOMEM; | |
284 | ||
285 | ret = sof_nocodec_setup(sdev->dev, plat_data, machine, | |
286 | plat_data->desc, plat_data->desc->ops); | |
287 | if (ret < 0) | |
288 | return ret; | |
289 | ||
290 | plat_data->machine = machine; | |
291 | ||
292 | return 0; | |
ce38a750 | 293 | #endif |
c16211d6 LG |
294 | } |
295 | ||
296 | static int sof_probe_continue(struct snd_sof_dev *sdev) | |
297 | { | |
298 | struct snd_sof_pdata *plat_data = sdev->pdata; | |
299 | const char *drv_name; | |
300 | const void *mach; | |
301 | int size; | |
302 | int ret; | |
303 | ||
304 | /* probe the DSP hardware */ | |
305 | ret = snd_sof_probe(sdev); | |
306 | if (ret < 0) { | |
307 | dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); | |
308 | return ret; | |
309 | } | |
310 | ||
311 | /* check machine info */ | |
312 | ret = sof_machine_check(sdev); | |
313 | if (ret < 0) { | |
314 | dev_err(sdev->dev, "error: failed to get machine info %d\n", | |
315 | ret); | |
316 | goto dbg_err; | |
317 | } | |
318 | ||
319 | /* set up platform component driver */ | |
320 | snd_sof_new_platform_drv(sdev); | |
321 | ||
322 | /* register any debug/trace capabilities */ | |
323 | ret = snd_sof_dbg_init(sdev); | |
324 | if (ret < 0) { | |
325 | /* | |
326 | * debugfs issues are suppressed in snd_sof_dbg_init() since | |
327 | * we cannot rely on debugfs | |
328 | * here we trap errors due to memory allocation only. | |
329 | */ | |
330 | dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", | |
331 | ret); | |
332 | goto dbg_err; | |
333 | } | |
334 | ||
335 | /* init the IPC */ | |
336 | sdev->ipc = snd_sof_ipc_init(sdev); | |
337 | if (!sdev->ipc) { | |
338 | dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); | |
339 | goto ipc_err; | |
340 | } | |
341 | ||
342 | /* load the firmware */ | |
343 | ret = snd_sof_load_firmware(sdev); | |
344 | if (ret < 0) { | |
345 | dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", | |
346 | ret); | |
347 | goto fw_load_err; | |
348 | } | |
349 | ||
350 | /* boot the firmware */ | |
351 | ret = snd_sof_run_firmware(sdev); | |
352 | if (ret < 0) { | |
353 | dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", | |
354 | ret); | |
355 | goto fw_run_err; | |
356 | } | |
357 | ||
2ab4c50f PLB |
358 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || |
359 | (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { | |
360 | sdev->dtrace_is_supported = true; | |
361 | ||
362 | /* init DMA trace */ | |
363 | ret = snd_sof_init_trace(sdev); | |
364 | if (ret < 0) { | |
365 | /* non fatal */ | |
366 | dev_warn(sdev->dev, | |
367 | "warning: failed to initialize trace %d\n", | |
368 | ret); | |
369 | } | |
370 | } else { | |
371 | dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); | |
c16211d6 LG |
372 | } |
373 | ||
374 | /* hereafter all FW boot flows are for PM reasons */ | |
375 | sdev->first_boot = false; | |
376 | ||
377 | /* now register audio DSP platform driver and dai */ | |
378 | ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, | |
379 | sof_ops(sdev)->drv, | |
380 | sof_ops(sdev)->num_drv); | |
381 | if (ret < 0) { | |
382 | dev_err(sdev->dev, | |
383 | "error: failed to register DSP DAI driver %d\n", ret); | |
384 | goto fw_run_err; | |
385 | } | |
386 | ||
387 | drv_name = plat_data->machine->drv_name; | |
388 | mach = (const void *)plat_data->machine; | |
389 | size = sizeof(*plat_data->machine); | |
390 | ||
391 | /* register machine driver, pass machine info as pdata */ | |
392 | plat_data->pdev_mach = | |
393 | platform_device_register_data(sdev->dev, drv_name, | |
394 | PLATFORM_DEVID_NONE, mach, size); | |
395 | ||
396 | if (IS_ERR(plat_data->pdev_mach)) { | |
397 | ret = PTR_ERR(plat_data->pdev_mach); | |
13931ae3 | 398 | goto fw_run_err; |
c16211d6 LG |
399 | } |
400 | ||
401 | dev_dbg(sdev->dev, "created machine %s\n", | |
402 | dev_name(&plat_data->pdev_mach->dev)); | |
403 | ||
404 | if (plat_data->sof_probe_complete) | |
405 | plat_data->sof_probe_complete(sdev->dev); | |
406 | ||
407 | return 0; | |
408 | ||
0bce512e | 409 | #if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) |
c16211d6 LG |
410 | fw_run_err: |
411 | snd_sof_fw_unload(sdev); | |
412 | fw_load_err: | |
413 | snd_sof_ipc_free(sdev); | |
414 | ipc_err: | |
415 | snd_sof_free_debug(sdev); | |
416 | dbg_err: | |
417 | snd_sof_remove(sdev); | |
0bce512e PLB |
418 | #else |
419 | ||
420 | /* | |
421 | * when the probe_continue is handled in a work queue, the | |
422 | * probe does not fail so we don't release resources here. | |
423 | * They will be released with an explicit call to | |
424 | * snd_sof_device_remove() when the PCI/ACPI device is removed | |
425 | */ | |
426 | ||
427 | fw_run_err: | |
428 | fw_load_err: | |
429 | ipc_err: | |
430 | dbg_err: | |
431 | ||
432 | #endif | |
c16211d6 LG |
433 | |
434 | return ret; | |
435 | } | |
436 | ||
437 | static void sof_probe_work(struct work_struct *work) | |
438 | { | |
439 | struct snd_sof_dev *sdev = | |
440 | container_of(work, struct snd_sof_dev, probe_work); | |
441 | int ret; | |
442 | ||
443 | ret = sof_probe_continue(sdev); | |
444 | if (ret < 0) { | |
445 | /* errors cannot be propagated, log */ | |
446 | dev_err(sdev->dev, "error: %s failed err: %d\n", __func__, ret); | |
447 | } | |
448 | } | |
449 | ||
450 | int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) | |
451 | { | |
452 | struct snd_sof_dev *sdev; | |
453 | ||
454 | sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); | |
455 | if (!sdev) | |
456 | return -ENOMEM; | |
457 | ||
458 | /* initialize sof device */ | |
459 | sdev->dev = dev; | |
460 | ||
461 | sdev->pdata = plat_data; | |
462 | sdev->first_boot = true; | |
463 | dev_set_drvdata(dev, sdev); | |
464 | ||
465 | /* check all mandatory ops */ | |
466 | if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || | |
467 | !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || | |
468 | !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || | |
469 | !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params) | |
470 | return -EINVAL; | |
471 | ||
472 | INIT_LIST_HEAD(&sdev->pcm_list); | |
473 | INIT_LIST_HEAD(&sdev->kcontrol_list); | |
474 | INIT_LIST_HEAD(&sdev->widget_list); | |
475 | INIT_LIST_HEAD(&sdev->dai_list); | |
476 | INIT_LIST_HEAD(&sdev->route_list); | |
477 | spin_lock_init(&sdev->ipc_lock); | |
478 | spin_lock_init(&sdev->hw_lock); | |
479 | ||
480 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) | |
481 | INIT_WORK(&sdev->probe_work, sof_probe_work); | |
482 | ||
483 | /* set default timeouts if none provided */ | |
484 | if (plat_data->desc->ipc_timeout == 0) | |
485 | sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; | |
486 | else | |
487 | sdev->ipc_timeout = plat_data->desc->ipc_timeout; | |
488 | if (plat_data->desc->boot_timeout == 0) | |
489 | sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; | |
490 | else | |
491 | sdev->boot_timeout = plat_data->desc->boot_timeout; | |
492 | ||
493 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { | |
494 | schedule_work(&sdev->probe_work); | |
495 | return 0; | |
496 | } | |
497 | ||
498 | return sof_probe_continue(sdev); | |
499 | } | |
500 | EXPORT_SYMBOL(snd_sof_device_probe); | |
501 | ||
502 | int snd_sof_device_remove(struct device *dev) | |
503 | { | |
504 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
505 | struct snd_sof_pdata *pdata = sdev->pdata; | |
506 | ||
507 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) | |
508 | cancel_work_sync(&sdev->probe_work); | |
509 | ||
510 | snd_sof_fw_unload(sdev); | |
511 | snd_sof_ipc_free(sdev); | |
512 | snd_sof_free_debug(sdev); | |
513 | snd_sof_free_trace(sdev); | |
c16211d6 LG |
514 | |
515 | /* | |
516 | * Unregister machine driver. This will unbind the snd_card which | |
517 | * will remove the component driver and unload the topology | |
518 | * before freeing the snd_card. | |
519 | */ | |
520 | if (!IS_ERR_OR_NULL(pdata->pdev_mach)) | |
521 | platform_device_unregister(pdata->pdev_mach); | |
522 | ||
b85459aa RS |
523 | /* |
524 | * Unregistering the machine driver results in unloading the topology. | |
525 | * Some widgets, ex: scheduler, attempt to power down the core they are | |
526 | * scheduled on, when they are unloaded. Therefore, the DSP must be | |
527 | * removed only after the topology has been unloaded. | |
528 | */ | |
529 | snd_sof_remove(sdev); | |
530 | ||
c16211d6 LG |
531 | /* release firmware */ |
532 | release_firmware(pdata->fw); | |
533 | pdata->fw = NULL; | |
534 | ||
535 | return 0; | |
536 | } | |
537 | EXPORT_SYMBOL(snd_sof_device_remove); | |
538 | ||
539 | MODULE_AUTHOR("Liam Girdwood"); | |
540 | MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); | |
541 | MODULE_LICENSE("Dual BSD/GPL"); | |
542 | MODULE_ALIAS("platform:sof-audio"); |