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