]>
Commit | Line | Data |
---|---|---|
e149ca29 | 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
c16211d6 LG |
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> | |
c16211d6 LG |
13 | #include <sound/soc.h> |
14 | #include <sound/sof.h> | |
15 | #include "sof-priv.h" | |
16 | #include "ops.h" | |
e145e9af CR |
17 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) |
18 | #include "probe.h" | |
19 | #endif | |
c16211d6 | 20 | |
2ab4c50f PLB |
21 | /* see SOF_DBG_ flags */ |
22 | int sof_core_debug; | |
23 | module_param_named(sof_debug, sof_core_debug, int, 0444); | |
24 | MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); | |
25 | ||
c16211d6 | 26 | /* SOF defaults if not provided by the platform in ms */ |
c9b54a33 KV |
27 | #define TIMEOUT_DEFAULT_IPC_MS 500 |
28 | #define TIMEOUT_DEFAULT_BOOT_MS 2000 | |
c16211d6 | 29 | |
c16211d6 LG |
30 | /* |
31 | * FW Panic/fault handling. | |
32 | */ | |
33 | ||
34 | struct sof_panic_msg { | |
35 | u32 id; | |
36 | const char *msg; | |
37 | }; | |
38 | ||
39 | /* standard FW panic types */ | |
40 | static const struct sof_panic_msg panic_msg[] = { | |
41 | {SOF_IPC_PANIC_MEM, "out of memory"}, | |
42 | {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, | |
43 | {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, | |
44 | {SOF_IPC_PANIC_ARCH, "arch init failed"}, | |
45 | {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, | |
46 | {SOF_IPC_PANIC_TASK, "scheduler init failed"}, | |
47 | {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, | |
48 | {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, | |
49 | {SOF_IPC_PANIC_STACK, "stack overflow"}, | |
50 | {SOF_IPC_PANIC_IDLE, "can't enter idle"}, | |
51 | {SOF_IPC_PANIC_WFI, "invalid wait state"}, | |
52 | {SOF_IPC_PANIC_ASSERT, "assertion failed"}, | |
53 | }; | |
54 | ||
55 | /* | |
56 | * helper to be called from .dbg_dump callbacks. No error code is | |
57 | * provided, it's left as an exercise for the caller of .dbg_dump | |
58 | * (typically IPC or loader) | |
59 | */ | |
60 | void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, | |
61 | u32 tracep_code, void *oops, | |
62 | struct sof_ipc_panic_info *panic_info, | |
63 | void *stack, size_t stack_words) | |
64 | { | |
65 | u32 code; | |
66 | int i; | |
67 | ||
68 | /* is firmware dead ? */ | |
69 | if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { | |
70 | dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", | |
71 | panic_code, tracep_code); | |
72 | return; /* no fault ? */ | |
73 | } | |
74 | ||
75 | code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); | |
76 | ||
77 | for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { | |
78 | if (panic_msg[i].id == code) { | |
79 | dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); | |
80 | dev_err(sdev->dev, "error: trace point %8.8x\n", | |
81 | tracep_code); | |
82 | goto out; | |
83 | } | |
84 | } | |
85 | ||
86 | /* unknown error */ | |
87 | dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); | |
88 | dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); | |
89 | ||
90 | out: | |
91 | dev_err(sdev->dev, "error: panic at %s:%d\n", | |
92 | panic_info->filename, panic_info->linenum); | |
93 | sof_oops(sdev, oops); | |
94 | sof_stack(sdev, oops, stack, stack_words); | |
95 | } | |
96 | EXPORT_SYMBOL(snd_sof_get_status); | |
97 | ||
6ca5cecb RS |
98 | /* |
99 | * FW Boot State Transition Diagram | |
100 | * | |
101 | * +-----------------------------------------------------------------------+ | |
102 | * | | | |
103 | * ------------------ ------------------ | | |
104 | * | | | | | | |
105 | * | BOOT_FAILED | | READY_FAILED |-------------------------+ | | |
106 | * | | | | | | | |
107 | * ------------------ ------------------ | | | |
108 | * ^ ^ | | | |
109 | * | | | | | |
110 | * (FW Boot Timeout) (FW_READY FAIL) | | | |
111 | * | | | | | |
112 | * | | | | | |
113 | * ------------------ | ------------------ | | | |
114 | * | | | | | | | | |
115 | * | IN_PROGRESS |---------------+------------->| COMPLETE | | | | |
116 | * | | (FW Boot OK) (FW_READY OK) | | | | | |
117 | * ------------------ ------------------ | | | |
118 | * ^ | | | | |
119 | * | | | | | |
120 | * (FW Loading OK) (System Suspend/Runtime Suspend) | |
121 | * | | | | | |
122 | * | | | | | |
123 | * ------------------ ------------------ | | | | |
124 | * | | | |<-----+ | | | |
125 | * | PREPARE | | NOT_STARTED |<---------------------+ | | |
126 | * | | | |<---------------------------+ | |
127 | * ------------------ ------------------ | |
128 | * | ^ | ^ | |
129 | * | | | | | |
130 | * | +-----------------------+ | | |
131 | * | (DSP Probe OK) | | |
132 | * | | | |
133 | * | | | |
134 | * +------------------------------------+ | |
135 | * (System Suspend/Runtime Suspend) | |
136 | */ | |
137 | ||
c16211d6 LG |
138 | static int sof_probe_continue(struct snd_sof_dev *sdev) |
139 | { | |
140 | struct snd_sof_pdata *plat_data = sdev->pdata; | |
c16211d6 LG |
141 | int ret; |
142 | ||
143 | /* probe the DSP hardware */ | |
144 | ret = snd_sof_probe(sdev); | |
145 | if (ret < 0) { | |
146 | dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); | |
147 | return ret; | |
148 | } | |
149 | ||
6ca5cecb RS |
150 | sdev->fw_state = SOF_FW_BOOT_PREPARE; |
151 | ||
c16211d6 LG |
152 | /* check machine info */ |
153 | ret = sof_machine_check(sdev); | |
154 | if (ret < 0) { | |
155 | dev_err(sdev->dev, "error: failed to get machine info %d\n", | |
156 | ret); | |
157 | goto dbg_err; | |
158 | } | |
159 | ||
160 | /* set up platform component driver */ | |
161 | snd_sof_new_platform_drv(sdev); | |
162 | ||
163 | /* register any debug/trace capabilities */ | |
164 | ret = snd_sof_dbg_init(sdev); | |
165 | if (ret < 0) { | |
166 | /* | |
167 | * debugfs issues are suppressed in snd_sof_dbg_init() since | |
168 | * we cannot rely on debugfs | |
169 | * here we trap errors due to memory allocation only. | |
170 | */ | |
171 | dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", | |
172 | ret); | |
173 | goto dbg_err; | |
174 | } | |
175 | ||
176 | /* init the IPC */ | |
177 | sdev->ipc = snd_sof_ipc_init(sdev); | |
178 | if (!sdev->ipc) { | |
7d8785bc | 179 | ret = -ENOMEM; |
c16211d6 LG |
180 | dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); |
181 | goto ipc_err; | |
182 | } | |
183 | ||
184 | /* load the firmware */ | |
185 | ret = snd_sof_load_firmware(sdev); | |
186 | if (ret < 0) { | |
187 | dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", | |
188 | ret); | |
189 | goto fw_load_err; | |
190 | } | |
191 | ||
6ca5cecb RS |
192 | sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; |
193 | ||
194 | /* | |
195 | * Boot the firmware. The FW boot status will be modified | |
196 | * in snd_sof_run_firmware() depending on the outcome. | |
197 | */ | |
c16211d6 LG |
198 | ret = snd_sof_run_firmware(sdev); |
199 | if (ret < 0) { | |
200 | dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", | |
201 | ret); | |
202 | goto fw_run_err; | |
203 | } | |
204 | ||
2ab4c50f PLB |
205 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) || |
206 | (sof_core_debug & SOF_DBG_ENABLE_TRACE)) { | |
207 | sdev->dtrace_is_supported = true; | |
208 | ||
209 | /* init DMA trace */ | |
210 | ret = snd_sof_init_trace(sdev); | |
211 | if (ret < 0) { | |
212 | /* non fatal */ | |
213 | dev_warn(sdev->dev, | |
214 | "warning: failed to initialize trace %d\n", | |
215 | ret); | |
216 | } | |
217 | } else { | |
218 | dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); | |
c16211d6 LG |
219 | } |
220 | ||
221 | /* hereafter all FW boot flows are for PM reasons */ | |
222 | sdev->first_boot = false; | |
223 | ||
224 | /* now register audio DSP platform driver and dai */ | |
225 | ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, | |
226 | sof_ops(sdev)->drv, | |
227 | sof_ops(sdev)->num_drv); | |
228 | if (ret < 0) { | |
229 | dev_err(sdev->dev, | |
230 | "error: failed to register DSP DAI driver %d\n", ret); | |
37e97e6f | 231 | goto fw_trace_err; |
c16211d6 LG |
232 | } |
233 | ||
285880a2 DB |
234 | ret = snd_sof_machine_register(sdev, plat_data); |
235 | if (ret < 0) | |
37e97e6f | 236 | goto fw_trace_err; |
c16211d6 | 237 | |
8c583f52 RS |
238 | /* |
239 | * Some platforms in SOF, ex: BYT, may not have their platform PM | |
240 | * callbacks set. Increment the usage count so as to | |
241 | * prevent the device from entering runtime suspend. | |
242 | */ | |
243 | if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume) | |
244 | pm_runtime_get_noresume(sdev->dev); | |
245 | ||
c16211d6 LG |
246 | if (plat_data->sof_probe_complete) |
247 | plat_data->sof_probe_complete(sdev->dev); | |
248 | ||
249 | return 0; | |
250 | ||
37e97e6f PLB |
251 | fw_trace_err: |
252 | snd_sof_free_trace(sdev); | |
c16211d6 LG |
253 | fw_run_err: |
254 | snd_sof_fw_unload(sdev); | |
255 | fw_load_err: | |
256 | snd_sof_ipc_free(sdev); | |
257 | ipc_err: | |
258 | snd_sof_free_debug(sdev); | |
259 | dbg_err: | |
260 | snd_sof_remove(sdev); | |
0bce512e | 261 | |
410e5e55 PLB |
262 | /* all resources freed, update state to match */ |
263 | sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; | |
264 | sdev->first_boot = true; | |
c16211d6 LG |
265 | |
266 | return ret; | |
267 | } | |
268 | ||
269 | static void sof_probe_work(struct work_struct *work) | |
270 | { | |
271 | struct snd_sof_dev *sdev = | |
272 | container_of(work, struct snd_sof_dev, probe_work); | |
273 | int ret; | |
274 | ||
275 | ret = sof_probe_continue(sdev); | |
276 | if (ret < 0) { | |
277 | /* errors cannot be propagated, log */ | |
278 | dev_err(sdev->dev, "error: %s failed err: %d\n", __func__, ret); | |
279 | } | |
280 | } | |
281 | ||
282 | int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) | |
283 | { | |
284 | struct snd_sof_dev *sdev; | |
285 | ||
286 | sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); | |
287 | if (!sdev) | |
288 | return -ENOMEM; | |
289 | ||
290 | /* initialize sof device */ | |
291 | sdev->dev = dev; | |
292 | ||
61e285ca RS |
293 | /* initialize default DSP power state */ |
294 | sdev->dsp_power_state.state = SOF_DSP_PM_D0; | |
09fe6b52 | 295 | |
c16211d6 LG |
296 | sdev->pdata = plat_data; |
297 | sdev->first_boot = true; | |
6ca5cecb | 298 | sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; |
e145e9af CR |
299 | #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) |
300 | sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID; | |
301 | #endif | |
c16211d6 LG |
302 | dev_set_drvdata(dev, sdev); |
303 | ||
304 | /* check all mandatory ops */ | |
305 | if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || | |
306 | !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || | |
307 | !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || | |
8692d498 RS |
308 | !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params || |
309 | !sof_ops(sdev)->fw_ready) | |
c16211d6 LG |
310 | return -EINVAL; |
311 | ||
312 | INIT_LIST_HEAD(&sdev->pcm_list); | |
313 | INIT_LIST_HEAD(&sdev->kcontrol_list); | |
314 | INIT_LIST_HEAD(&sdev->widget_list); | |
315 | INIT_LIST_HEAD(&sdev->dai_list); | |
316 | INIT_LIST_HEAD(&sdev->route_list); | |
317 | spin_lock_init(&sdev->ipc_lock); | |
318 | spin_lock_init(&sdev->hw_lock); | |
319 | ||
320 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) | |
321 | INIT_WORK(&sdev->probe_work, sof_probe_work); | |
322 | ||
323 | /* set default timeouts if none provided */ | |
324 | if (plat_data->desc->ipc_timeout == 0) | |
325 | sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; | |
326 | else | |
327 | sdev->ipc_timeout = plat_data->desc->ipc_timeout; | |
328 | if (plat_data->desc->boot_timeout == 0) | |
329 | sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; | |
330 | else | |
331 | sdev->boot_timeout = plat_data->desc->boot_timeout; | |
332 | ||
333 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { | |
334 | schedule_work(&sdev->probe_work); | |
335 | return 0; | |
336 | } | |
337 | ||
338 | return sof_probe_continue(sdev); | |
339 | } | |
340 | EXPORT_SYMBOL(snd_sof_device_probe); | |
341 | ||
342 | int snd_sof_device_remove(struct device *dev) | |
343 | { | |
344 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
345 | struct snd_sof_pdata *pdata = sdev->pdata; | |
9f369f7e MR |
346 | int ret; |
347 | ||
c16211d6 LG |
348 | if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) |
349 | cancel_work_sync(&sdev->probe_work); | |
350 | ||
410e5e55 | 351 | if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { |
eceb5437 RS |
352 | ret = snd_sof_dsp_power_down_notify(sdev); |
353 | if (ret < 0) | |
354 | dev_warn(dev, "error: %d failed to prepare DSP for device removal", | |
355 | ret); | |
356 | ||
410e5e55 PLB |
357 | snd_sof_fw_unload(sdev); |
358 | snd_sof_ipc_free(sdev); | |
359 | snd_sof_free_debug(sdev); | |
360 | snd_sof_free_trace(sdev); | |
361 | } | |
c16211d6 LG |
362 | |
363 | /* | |
364 | * Unregister machine driver. This will unbind the snd_card which | |
365 | * will remove the component driver and unload the topology | |
366 | * before freeing the snd_card. | |
367 | */ | |
285880a2 | 368 | snd_sof_machine_unregister(sdev, pdata); |
410e5e55 | 369 | |
b85459aa RS |
370 | /* |
371 | * Unregistering the machine driver results in unloading the topology. | |
372 | * Some widgets, ex: scheduler, attempt to power down the core they are | |
373 | * scheduled on, when they are unloaded. Therefore, the DSP must be | |
374 | * removed only after the topology has been unloaded. | |
375 | */ | |
410e5e55 PLB |
376 | if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) |
377 | snd_sof_remove(sdev); | |
b85459aa | 378 | |
c16211d6 LG |
379 | /* release firmware */ |
380 | release_firmware(pdata->fw); | |
381 | pdata->fw = NULL; | |
382 | ||
383 | return 0; | |
384 | } | |
385 | EXPORT_SYMBOL(snd_sof_device_remove); | |
386 | ||
a7c7a871 KJ |
387 | int snd_sof_device_shutdown(struct device *dev) |
388 | { | |
389 | struct snd_sof_dev *sdev = dev_get_drvdata(dev); | |
390 | ||
391 | return snd_sof_shutdown(sdev); | |
392 | } | |
393 | EXPORT_SYMBOL(snd_sof_device_shutdown); | |
394 | ||
c16211d6 LG |
395 | MODULE_AUTHOR("Liam Girdwood"); |
396 | MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); | |
397 | MODULE_LICENSE("Dual BSD/GPL"); | |
398 | MODULE_ALIAS("platform:sof-audio"); |