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) 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 <sound/soc.h> |
14 | #include <sound/sof.h> |
15 | #include "sof-priv.h" |
16 | #include "sof-of-dev.h" |
17 | #include "ops.h" |
18 | |
19 | #define CREATE_TRACE_POINTS |
20 | #include <trace/events/sof.h> |
21 | |
22 | /* see SOF_DBG_ flags */ |
23 | static int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); |
24 | module_param_named(sof_debug, sof_core_debug, int, 0444); |
25 | MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)" ); |
26 | |
27 | /* SOF defaults if not provided by the platform in ms */ |
28 | #define TIMEOUT_DEFAULT_IPC_MS 500 |
29 | #define TIMEOUT_DEFAULT_BOOT_MS 2000 |
30 | |
31 | /** |
32 | * sof_debug_check_flag - check if a given flag(s) is set in sof_core_debug |
33 | * @mask: Flag or combination of flags to check |
34 | * |
35 | * Returns true if all bits set in mask is also set in sof_core_debug, otherwise |
36 | * false |
37 | */ |
38 | bool sof_debug_check_flag(int mask) |
39 | { |
40 | if ((sof_core_debug & mask) == mask) |
41 | return true; |
42 | |
43 | return false; |
44 | } |
45 | EXPORT_SYMBOL(sof_debug_check_flag); |
46 | |
47 | /* |
48 | * FW Panic/fault handling. |
49 | */ |
50 | |
51 | struct sof_panic_msg { |
52 | u32 id; |
53 | const char *msg; |
54 | }; |
55 | |
56 | /* standard FW panic types */ |
57 | static const struct sof_panic_msg panic_msg[] = { |
58 | {SOF_IPC_PANIC_MEM, "out of memory" }, |
59 | {SOF_IPC_PANIC_WORK, "work subsystem init failed" }, |
60 | {SOF_IPC_PANIC_IPC, "IPC subsystem init failed" }, |
61 | {SOF_IPC_PANIC_ARCH, "arch init failed" }, |
62 | {SOF_IPC_PANIC_PLATFORM, "platform init failed" }, |
63 | {SOF_IPC_PANIC_TASK, "scheduler init failed" }, |
64 | {SOF_IPC_PANIC_EXCEPTION, "runtime exception" }, |
65 | {SOF_IPC_PANIC_DEADLOCK, "deadlock" }, |
66 | {SOF_IPC_PANIC_STACK, "stack overflow" }, |
67 | {SOF_IPC_PANIC_IDLE, "can't enter idle" }, |
68 | {SOF_IPC_PANIC_WFI, "invalid wait state" }, |
69 | {SOF_IPC_PANIC_ASSERT, "assertion failed" }, |
70 | }; |
71 | |
72 | /** |
73 | * sof_print_oops_and_stack - Handle the printing of DSP oops and stack trace |
74 | * @sdev: Pointer to the device's sdev |
75 | * @level: prink log level to use for the printing |
76 | * @panic_code: the panic code |
77 | * @tracep_code: tracepoint code |
78 | * @oops: Pointer to DSP specific oops data |
79 | * @panic_info: Pointer to the received panic information message |
80 | * @stack: Pointer to the call stack data |
81 | * @stack_words: Number of words in the stack data |
82 | * |
83 | * helper to be called from .dbg_dump callbacks. No error code is |
84 | * provided, it's left as an exercise for the caller of .dbg_dump |
85 | * (typically IPC or loader) |
86 | */ |
87 | void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level, |
88 | u32 panic_code, u32 tracep_code, void *oops, |
89 | struct sof_ipc_panic_info *panic_info, |
90 | void *stack, size_t stack_words) |
91 | { |
92 | u32 code; |
93 | int i; |
94 | |
95 | /* is firmware dead ? */ |
96 | if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { |
97 | dev_printk(level, sdev->dev, "unexpected fault %#010x trace %#010x\n" , |
98 | panic_code, tracep_code); |
99 | return; /* no fault ? */ |
100 | } |
101 | |
102 | code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); |
103 | |
104 | for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { |
105 | if (panic_msg[i].id == code) { |
106 | dev_printk(level, sdev->dev, "reason: %s (%#x)\n" , |
107 | panic_msg[i].msg, code & SOF_IPC_PANIC_CODE_MASK); |
108 | dev_printk(level, sdev->dev, "trace point: %#010x\n" , tracep_code); |
109 | goto out; |
110 | } |
111 | } |
112 | |
113 | /* unknown error */ |
114 | dev_printk(level, sdev->dev, "unknown panic code: %#x\n" , |
115 | code & SOF_IPC_PANIC_CODE_MASK); |
116 | dev_printk(level, sdev->dev, "trace point: %#010x\n" , tracep_code); |
117 | |
118 | out: |
119 | dev_printk(level, sdev->dev, "panic at %s:%d\n" , panic_info->filename, |
120 | panic_info->linenum); |
121 | sof_oops(sdev, level, oops); |
122 | sof_stack(sdev, level, oops, stack, stack_words); |
123 | } |
124 | EXPORT_SYMBOL(sof_print_oops_and_stack); |
125 | |
126 | /* Helper to manage DSP state */ |
127 | void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state) |
128 | { |
129 | if (sdev->fw_state == new_state) |
130 | return; |
131 | |
132 | dev_dbg(sdev->dev, "fw_state change: %d -> %d\n" , sdev->fw_state, new_state); |
133 | sdev->fw_state = new_state; |
134 | |
135 | switch (new_state) { |
136 | case SOF_FW_BOOT_NOT_STARTED: |
137 | case SOF_FW_BOOT_COMPLETE: |
138 | case SOF_FW_CRASHED: |
139 | sof_client_fw_state_dispatcher(sdev); |
140 | fallthrough; |
141 | default: |
142 | break; |
143 | } |
144 | } |
145 | EXPORT_SYMBOL(sof_set_fw_state); |
146 | |
147 | static struct snd_sof_of_mach *sof_of_machine_select(struct snd_sof_dev *sdev) |
148 | { |
149 | struct snd_sof_pdata *sof_pdata = sdev->pdata; |
150 | const struct sof_dev_desc *desc = sof_pdata->desc; |
151 | struct snd_sof_of_mach *mach = desc->of_machines; |
152 | |
153 | if (!mach) |
154 | return NULL; |
155 | |
156 | for (; mach->compatible; mach++) { |
157 | if (of_machine_is_compatible(compat: mach->compatible)) { |
158 | sof_pdata->tplg_filename = mach->sof_tplg_filename; |
159 | if (mach->fw_filename) |
160 | sof_pdata->fw_filename = mach->fw_filename; |
161 | |
162 | return mach; |
163 | } |
164 | } |
165 | |
166 | return NULL; |
167 | } |
168 | |
169 | /* SOF Driver enumeration */ |
170 | static int sof_machine_check(struct snd_sof_dev *sdev) |
171 | { |
172 | struct snd_sof_pdata *sof_pdata = sdev->pdata; |
173 | const struct sof_dev_desc *desc = sof_pdata->desc; |
174 | struct snd_soc_acpi_mach *mach; |
175 | |
176 | if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) { |
177 | const struct snd_sof_of_mach *of_mach; |
178 | |
179 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) && |
180 | sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC)) |
181 | goto nocodec; |
182 | |
183 | /* find machine */ |
184 | mach = snd_sof_machine_select(sdev); |
185 | if (mach) { |
186 | sof_pdata->machine = mach; |
187 | |
188 | if (sof_pdata->subsystem_id_set) { |
189 | mach->mach_params.subsystem_vendor = sof_pdata->subsystem_vendor; |
190 | mach->mach_params.subsystem_device = sof_pdata->subsystem_device; |
191 | mach->mach_params.subsystem_id_set = true; |
192 | } |
193 | |
194 | snd_sof_set_mach_params(mach, sdev); |
195 | return 0; |
196 | } |
197 | |
198 | of_mach = sof_of_machine_select(sdev); |
199 | if (of_mach) { |
200 | sof_pdata->of_machine = of_mach; |
201 | return 0; |
202 | } |
203 | |
204 | if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) { |
205 | dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n" ); |
206 | return -ENODEV; |
207 | } |
208 | } else { |
209 | dev_warn(sdev->dev, "Force to use nocodec mode\n" ); |
210 | } |
211 | |
212 | nocodec: |
213 | /* select nocodec mode */ |
214 | dev_warn(sdev->dev, "Using nocodec machine driver\n" ); |
215 | mach = devm_kzalloc(dev: sdev->dev, size: sizeof(*mach), GFP_KERNEL); |
216 | if (!mach) |
217 | return -ENOMEM; |
218 | |
219 | mach->drv_name = "sof-nocodec" ; |
220 | if (!sof_pdata->tplg_filename) |
221 | sof_pdata->tplg_filename = desc->nocodec_tplg_filename; |
222 | |
223 | sof_pdata->machine = mach; |
224 | snd_sof_set_mach_params(mach, sdev); |
225 | |
226 | return 0; |
227 | } |
228 | |
229 | static int sof_select_ipc_and_paths(struct snd_sof_dev *sdev) |
230 | { |
231 | struct snd_sof_pdata *plat_data = sdev->pdata; |
232 | struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base; |
233 | struct sof_loadable_file_profile out_profile; |
234 | struct device *dev = sdev->dev; |
235 | int ret; |
236 | |
237 | if (base_profile->ipc_type != plat_data->desc->ipc_default) |
238 | dev_info(dev, |
239 | "Module parameter used, overriding default IPC %d to %d\n" , |
240 | plat_data->desc->ipc_default, base_profile->ipc_type); |
241 | |
242 | if (base_profile->fw_path) |
243 | dev_dbg(dev, "Module parameter used, changed fw path to %s\n" , |
244 | base_profile->fw_path); |
245 | else if (base_profile->fw_path_postfix) |
246 | dev_dbg(dev, "Path postfix appended to default fw path: %s\n" , |
247 | base_profile->fw_path_postfix); |
248 | |
249 | if (base_profile->fw_lib_path) |
250 | dev_dbg(dev, "Module parameter used, changed fw_lib path to %s\n" , |
251 | base_profile->fw_lib_path); |
252 | else if (base_profile->fw_lib_path_postfix) |
253 | dev_dbg(dev, "Path postfix appended to default fw_lib path: %s\n" , |
254 | base_profile->fw_lib_path_postfix); |
255 | |
256 | if (base_profile->fw_name) |
257 | dev_dbg(dev, "Module parameter used, changed fw filename to %s\n" , |
258 | base_profile->fw_name); |
259 | |
260 | if (base_profile->tplg_path) |
261 | dev_dbg(dev, "Module parameter used, changed tplg path to %s\n" , |
262 | base_profile->tplg_path); |
263 | |
264 | if (base_profile->tplg_name) |
265 | dev_dbg(dev, "Module parameter used, changed tplg name to %s\n" , |
266 | base_profile->tplg_name); |
267 | |
268 | ret = sof_create_ipc_file_profile(sdev, base_profile, out_profile: &out_profile); |
269 | if (ret) |
270 | return ret; |
271 | |
272 | plat_data->ipc_type = out_profile.ipc_type; |
273 | plat_data->fw_filename = out_profile.fw_name; |
274 | plat_data->fw_filename_prefix = out_profile.fw_path; |
275 | plat_data->fw_lib_prefix = out_profile.fw_lib_path; |
276 | plat_data->tplg_filename_prefix = out_profile.tplg_path; |
277 | |
278 | return 0; |
279 | } |
280 | |
281 | static int validate_sof_ops(struct snd_sof_dev *sdev) |
282 | { |
283 | int ret; |
284 | |
285 | /* init ops, if necessary */ |
286 | ret = sof_ops_init(sdev); |
287 | if (ret < 0) |
288 | return ret; |
289 | |
290 | /* check all mandatory ops */ |
291 | if (!sof_ops(sdev) || !sof_ops(sdev)->probe) { |
292 | dev_err(sdev->dev, "missing mandatory ops\n" ); |
293 | sof_ops_free(sdev); |
294 | return -EINVAL; |
295 | } |
296 | |
297 | if (!sdev->dspless_mode_selected && |
298 | (!sof_ops(sdev)->run || !sof_ops(sdev)->block_read || |
299 | !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg || |
300 | !sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data)) { |
301 | dev_err(sdev->dev, "missing mandatory DSP ops\n" ); |
302 | sof_ops_free(sdev); |
303 | return -EINVAL; |
304 | } |
305 | |
306 | return 0; |
307 | } |
308 | |
309 | static int sof_init_sof_ops(struct snd_sof_dev *sdev) |
310 | { |
311 | struct snd_sof_pdata *plat_data = sdev->pdata; |
312 | struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base; |
313 | |
314 | /* check IPC support */ |
315 | if (!(BIT(base_profile->ipc_type) & plat_data->desc->ipc_supported_mask)) { |
316 | dev_err(sdev->dev, |
317 | "ipc_type %d is not supported on this platform, mask is %#x\n" , |
318 | base_profile->ipc_type, plat_data->desc->ipc_supported_mask); |
319 | return -EINVAL; |
320 | } |
321 | |
322 | /* |
323 | * Save the selected IPC type and a topology name override before |
324 | * selecting ops since platform code might need this information |
325 | */ |
326 | plat_data->ipc_type = base_profile->ipc_type; |
327 | plat_data->tplg_filename = base_profile->tplg_name; |
328 | |
329 | return validate_sof_ops(sdev); |
330 | } |
331 | |
332 | static int sof_init_environment(struct snd_sof_dev *sdev) |
333 | { |
334 | struct snd_sof_pdata *plat_data = sdev->pdata; |
335 | struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base; |
336 | int ret; |
337 | |
338 | /* probe the DSP hardware */ |
339 | ret = snd_sof_probe(sdev); |
340 | if (ret < 0) { |
341 | dev_err(sdev->dev, "failed to probe DSP %d\n" , ret); |
342 | goto err_sof_probe; |
343 | } |
344 | |
345 | /* check machine info */ |
346 | ret = sof_machine_check(sdev); |
347 | if (ret < 0) { |
348 | dev_err(sdev->dev, "failed to get machine info %d\n" , ret); |
349 | goto err_machine_check; |
350 | } |
351 | |
352 | ret = sof_select_ipc_and_paths(sdev); |
353 | if (!ret && plat_data->ipc_type != base_profile->ipc_type) { |
354 | /* IPC type changed, re-initialize the ops */ |
355 | sof_ops_free(sdev); |
356 | |
357 | ret = validate_sof_ops(sdev); |
358 | if (ret < 0) { |
359 | snd_sof_remove(sdev); |
360 | snd_sof_remove_late(sdev); |
361 | return ret; |
362 | } |
363 | } |
364 | |
365 | return 0; |
366 | |
367 | err_machine_check: |
368 | snd_sof_remove(sdev); |
369 | err_sof_probe: |
370 | snd_sof_remove_late(sdev); |
371 | sof_ops_free(sdev); |
372 | |
373 | return ret; |
374 | } |
375 | |
376 | /* |
377 | * FW Boot State Transition Diagram |
378 | * |
379 | * +----------------------------------------------------------------------+ |
380 | * | | |
381 | * ------------------ ------------------ | |
382 | * | | | | | |
383 | * | BOOT_FAILED |<-------| READY_FAILED | | |
384 | * | |<--+ | | ------------------ | |
385 | * ------------------ | ------------------ | | | |
386 | * ^ | ^ | CRASHED |---+ | |
387 | * | | | | | | | |
388 | * (FW Boot Timeout) | (FW_READY FAIL) ------------------ | | |
389 | * | | | ^ | | |
390 | * | | | |(DSP Panic) | | |
391 | * ------------------ | | ------------------ | | |
392 | * | | | | | | | | |
393 | * | IN_PROGRESS |---------------+------------->| COMPLETE | | | |
394 | * | | (FW Boot OK) (FW_READY OK) | | | | |
395 | * ------------------ | ------------------ | | |
396 | * ^ | | | | |
397 | * | | | | | |
398 | * (FW Loading OK) | (System Suspend/Runtime Suspend) |
399 | * | | | | | |
400 | * | (FW Loading Fail) | | | |
401 | * ------------------ | ------------------ | | | |
402 | * | | | | |<-----+ | | |
403 | * | PREPARE |---+ | NOT_STARTED |<---------------------+ | |
404 | * | | | |<--------------------------+ |
405 | * ------------------ ------------------ |
406 | * | ^ | ^ |
407 | * | | | | |
408 | * | +-----------------------+ | |
409 | * | (DSP Probe OK) | |
410 | * | | |
411 | * | | |
412 | * +------------------------------------+ |
413 | * (System Suspend/Runtime Suspend) |
414 | */ |
415 | |
416 | static int sof_probe_continue(struct snd_sof_dev *sdev) |
417 | { |
418 | struct snd_sof_pdata *plat_data = sdev->pdata; |
419 | int ret; |
420 | |
421 | /* Initialize loadable file paths and check the environment validity */ |
422 | ret = sof_init_environment(sdev); |
423 | if (ret) |
424 | return ret; |
425 | |
426 | sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); |
427 | |
428 | /* set up platform component driver */ |
429 | snd_sof_new_platform_drv(sdev); |
430 | |
431 | if (sdev->dspless_mode_selected) { |
432 | sof_set_fw_state(sdev, SOF_DSPLESS_MODE); |
433 | goto skip_dsp_init; |
434 | } |
435 | |
436 | /* register any debug/trace capabilities */ |
437 | ret = snd_sof_dbg_init(sdev); |
438 | if (ret < 0) { |
439 | /* |
440 | * debugfs issues are suppressed in snd_sof_dbg_init() since |
441 | * we cannot rely on debugfs |
442 | * here we trap errors due to memory allocation only. |
443 | */ |
444 | dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n" , |
445 | ret); |
446 | goto dbg_err; |
447 | } |
448 | |
449 | /* init the IPC */ |
450 | sdev->ipc = snd_sof_ipc_init(sdev); |
451 | if (!sdev->ipc) { |
452 | ret = -ENOMEM; |
453 | dev_err(sdev->dev, "error: failed to init DSP IPC %d\n" , ret); |
454 | goto ipc_err; |
455 | } |
456 | |
457 | /* load the firmware */ |
458 | ret = snd_sof_load_firmware(sdev); |
459 | if (ret < 0) { |
460 | dev_err(sdev->dev, "error: failed to load DSP firmware %d\n" , |
461 | ret); |
462 | sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); |
463 | goto fw_load_err; |
464 | } |
465 | |
466 | sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS); |
467 | |
468 | /* |
469 | * Boot the firmware. The FW boot status will be modified |
470 | * in snd_sof_run_firmware() depending on the outcome. |
471 | */ |
472 | ret = snd_sof_run_firmware(sdev); |
473 | if (ret < 0) { |
474 | dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n" , |
475 | ret); |
476 | sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); |
477 | goto fw_run_err; |
478 | } |
479 | |
480 | if (sof_debug_check_flag(SOF_DBG_ENABLE_TRACE)) { |
481 | sdev->fw_trace_is_supported = true; |
482 | |
483 | /* init firmware tracing */ |
484 | ret = sof_fw_trace_init(sdev); |
485 | if (ret < 0) { |
486 | /* non fatal */ |
487 | dev_warn(sdev->dev, "failed to initialize firmware tracing %d\n" , |
488 | ret); |
489 | } |
490 | } else { |
491 | dev_dbg(sdev->dev, "SOF firmware trace disabled\n" ); |
492 | } |
493 | |
494 | skip_dsp_init: |
495 | /* hereafter all FW boot flows are for PM reasons */ |
496 | sdev->first_boot = false; |
497 | |
498 | /* now register audio DSP platform driver and dai */ |
499 | ret = devm_snd_soc_register_component(dev: sdev->dev, component_driver: &sdev->plat_drv, |
500 | sof_ops(sdev)->drv, |
501 | sof_ops(sdev)->num_drv); |
502 | if (ret < 0) { |
503 | dev_err(sdev->dev, |
504 | "error: failed to register DSP DAI driver %d\n" , ret); |
505 | goto fw_trace_err; |
506 | } |
507 | |
508 | ret = snd_sof_machine_register(sdev, pdata: plat_data); |
509 | if (ret < 0) { |
510 | dev_err(sdev->dev, |
511 | "error: failed to register machine driver %d\n" , ret); |
512 | goto fw_trace_err; |
513 | } |
514 | |
515 | ret = sof_register_clients(sdev); |
516 | if (ret < 0) { |
517 | dev_err(sdev->dev, "failed to register clients %d\n" , ret); |
518 | goto sof_machine_err; |
519 | } |
520 | |
521 | /* |
522 | * Some platforms in SOF, ex: BYT, may not have their platform PM |
523 | * callbacks set. Increment the usage count so as to |
524 | * prevent the device from entering runtime suspend. |
525 | */ |
526 | if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) |
527 | pm_runtime_get_noresume(dev: sdev->dev); |
528 | |
529 | if (plat_data->sof_probe_complete) |
530 | plat_data->sof_probe_complete(sdev->dev); |
531 | |
532 | sdev->probe_completed = true; |
533 | |
534 | return 0; |
535 | |
536 | sof_machine_err: |
537 | snd_sof_machine_unregister(sdev, pdata: plat_data); |
538 | fw_trace_err: |
539 | sof_fw_trace_free(sdev); |
540 | fw_run_err: |
541 | snd_sof_fw_unload(sdev); |
542 | fw_load_err: |
543 | snd_sof_ipc_free(sdev); |
544 | ipc_err: |
545 | dbg_err: |
546 | snd_sof_free_debug(sdev); |
547 | snd_sof_remove(sdev); |
548 | snd_sof_remove_late(sdev); |
549 | sof_ops_free(sdev); |
550 | |
551 | /* all resources freed, update state to match */ |
552 | sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); |
553 | sdev->first_boot = true; |
554 | |
555 | return ret; |
556 | } |
557 | |
558 | static void sof_probe_work(struct work_struct *work) |
559 | { |
560 | struct snd_sof_dev *sdev = |
561 | container_of(work, struct snd_sof_dev, probe_work); |
562 | int ret; |
563 | |
564 | ret = sof_probe_continue(sdev); |
565 | if (ret < 0) { |
566 | /* errors cannot be propagated, log */ |
567 | dev_err(sdev->dev, "error: %s failed err: %d\n" , __func__, ret); |
568 | } |
569 | } |
570 | |
571 | int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) |
572 | { |
573 | struct snd_sof_dev *sdev; |
574 | int ret; |
575 | |
576 | sdev = devm_kzalloc(dev, size: sizeof(*sdev), GFP_KERNEL); |
577 | if (!sdev) |
578 | return -ENOMEM; |
579 | |
580 | /* initialize sof device */ |
581 | sdev->dev = dev; |
582 | |
583 | /* initialize default DSP power state */ |
584 | sdev->dsp_power_state.state = SOF_DSP_PM_D0; |
585 | |
586 | sdev->pdata = plat_data; |
587 | sdev->first_boot = true; |
588 | dev_set_drvdata(dev, data: sdev); |
589 | |
590 | if (sof_core_debug) |
591 | dev_info(dev, "sof_debug value: %#x\n" , sof_core_debug); |
592 | |
593 | if (sof_debug_check_flag(SOF_DBG_DSPLESS_MODE)) { |
594 | if (plat_data->desc->dspless_mode_supported) { |
595 | dev_info(dev, "Switching to DSPless mode\n" ); |
596 | sdev->dspless_mode_selected = true; |
597 | } else { |
598 | dev_info(dev, "DSPless mode is not supported by the platform\n" ); |
599 | } |
600 | } |
601 | |
602 | /* Initialize sof_ops based on the initial selected IPC version */ |
603 | ret = sof_init_sof_ops(sdev); |
604 | if (ret) |
605 | return ret; |
606 | |
607 | INIT_LIST_HEAD(list: &sdev->pcm_list); |
608 | INIT_LIST_HEAD(list: &sdev->kcontrol_list); |
609 | INIT_LIST_HEAD(list: &sdev->widget_list); |
610 | INIT_LIST_HEAD(list: &sdev->pipeline_list); |
611 | INIT_LIST_HEAD(list: &sdev->dai_list); |
612 | INIT_LIST_HEAD(list: &sdev->dai_link_list); |
613 | INIT_LIST_HEAD(list: &sdev->route_list); |
614 | INIT_LIST_HEAD(list: &sdev->ipc_client_list); |
615 | INIT_LIST_HEAD(list: &sdev->ipc_rx_handler_list); |
616 | INIT_LIST_HEAD(list: &sdev->fw_state_handler_list); |
617 | spin_lock_init(&sdev->ipc_lock); |
618 | spin_lock_init(&sdev->hw_lock); |
619 | mutex_init(&sdev->power_state_access); |
620 | mutex_init(&sdev->ipc_client_mutex); |
621 | mutex_init(&sdev->client_event_handler_mutex); |
622 | |
623 | /* set default timeouts if none provided */ |
624 | if (plat_data->desc->ipc_timeout == 0) |
625 | sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; |
626 | else |
627 | sdev->ipc_timeout = plat_data->desc->ipc_timeout; |
628 | if (plat_data->desc->boot_timeout == 0) |
629 | sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; |
630 | else |
631 | sdev->boot_timeout = plat_data->desc->boot_timeout; |
632 | |
633 | sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); |
634 | |
635 | /* |
636 | * first pass of probe which isn't allowed to run in a work-queue, |
637 | * typically to rely on -EPROBE_DEFER dependencies |
638 | */ |
639 | ret = snd_sof_probe_early(sdev); |
640 | if (ret < 0) |
641 | return ret; |
642 | |
643 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { |
644 | INIT_WORK(&sdev->probe_work, sof_probe_work); |
645 | schedule_work(work: &sdev->probe_work); |
646 | return 0; |
647 | } |
648 | |
649 | return sof_probe_continue(sdev); |
650 | } |
651 | EXPORT_SYMBOL(snd_sof_device_probe); |
652 | |
653 | bool snd_sof_device_probe_completed(struct device *dev) |
654 | { |
655 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
656 | |
657 | return sdev->probe_completed; |
658 | } |
659 | EXPORT_SYMBOL(snd_sof_device_probe_completed); |
660 | |
661 | int snd_sof_device_remove(struct device *dev) |
662 | { |
663 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
664 | struct snd_sof_pdata *pdata = sdev->pdata; |
665 | int ret; |
666 | bool aborted = false; |
667 | |
668 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) |
669 | aborted = cancel_work_sync(work: &sdev->probe_work); |
670 | |
671 | /* |
672 | * Unregister any registered client device first before IPC and debugfs |
673 | * to allow client drivers to be removed cleanly |
674 | */ |
675 | sof_unregister_clients(sdev); |
676 | |
677 | /* |
678 | * Unregister machine driver. This will unbind the snd_card which |
679 | * will remove the component driver and unload the topology |
680 | * before freeing the snd_card. |
681 | */ |
682 | snd_sof_machine_unregister(sdev, pdata); |
683 | |
684 | /* |
685 | * Balance the runtime pm usage count in case we are faced with an |
686 | * exception and we forcably prevented D3 power state to preserve |
687 | * context |
688 | */ |
689 | if (sdev->d3_prevented) { |
690 | sdev->d3_prevented = false; |
691 | pm_runtime_put_noidle(dev: sdev->dev); |
692 | } |
693 | |
694 | if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { |
695 | sof_fw_trace_free(sdev); |
696 | ret = snd_sof_dsp_power_down_notify(sdev); |
697 | if (ret < 0) |
698 | dev_warn(dev, "error: %d failed to prepare DSP for device removal" , |
699 | ret); |
700 | |
701 | snd_sof_ipc_free(sdev); |
702 | snd_sof_free_debug(sdev); |
703 | snd_sof_remove(sdev); |
704 | snd_sof_remove_late(sdev); |
705 | sof_ops_free(sdev); |
706 | } else if (aborted) { |
707 | /* probe_work never ran */ |
708 | snd_sof_remove_late(sdev); |
709 | sof_ops_free(sdev); |
710 | } |
711 | |
712 | /* release firmware */ |
713 | snd_sof_fw_unload(sdev); |
714 | |
715 | return 0; |
716 | } |
717 | EXPORT_SYMBOL(snd_sof_device_remove); |
718 | |
719 | int snd_sof_device_shutdown(struct device *dev) |
720 | { |
721 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); |
722 | |
723 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) |
724 | cancel_work_sync(work: &sdev->probe_work); |
725 | |
726 | if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { |
727 | sof_fw_trace_free(sdev); |
728 | return snd_sof_shutdown(sdev); |
729 | } |
730 | |
731 | return 0; |
732 | } |
733 | EXPORT_SYMBOL(snd_sof_device_shutdown); |
734 | |
735 | /* Machine driver registering and unregistering */ |
736 | int sof_machine_register(struct snd_sof_dev *sdev, void *pdata) |
737 | { |
738 | struct snd_sof_pdata *plat_data = pdata; |
739 | const char *drv_name; |
740 | const void *mach; |
741 | int size; |
742 | |
743 | drv_name = plat_data->machine->drv_name; |
744 | mach = plat_data->machine; |
745 | size = sizeof(*plat_data->machine); |
746 | |
747 | /* register machine driver, pass machine info as pdata */ |
748 | plat_data->pdev_mach = |
749 | platform_device_register_data(parent: sdev->dev, name: drv_name, |
750 | PLATFORM_DEVID_NONE, data: mach, size); |
751 | if (IS_ERR(ptr: plat_data->pdev_mach)) |
752 | return PTR_ERR(ptr: plat_data->pdev_mach); |
753 | |
754 | dev_dbg(sdev->dev, "created machine %s\n" , |
755 | dev_name(&plat_data->pdev_mach->dev)); |
756 | |
757 | return 0; |
758 | } |
759 | EXPORT_SYMBOL(sof_machine_register); |
760 | |
761 | void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) |
762 | { |
763 | struct snd_sof_pdata *plat_data = pdata; |
764 | |
765 | platform_device_unregister(plat_data->pdev_mach); |
766 | } |
767 | EXPORT_SYMBOL(sof_machine_unregister); |
768 | |
769 | MODULE_AUTHOR("Liam Girdwood" ); |
770 | MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core" ); |
771 | MODULE_LICENSE("Dual BSD/GPL" ); |
772 | MODULE_ALIAS("platform:sof-audio" ); |
773 | MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); |
774 | |