// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2018 Intel Corporation. All rights reserved. // // Author: Liam Girdwood // #include #include #include #include #include #include "sof-priv.h" #include "ops.h" /* SOF defaults if not provided by the platform in ms */ #define TIMEOUT_DEFAULT_IPC_MS 500 #define TIMEOUT_DEFAULT_BOOT_MS 2000 /* * Generic object lookup APIs. */ struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev, const char *name) { struct snd_sof_pcm *spcm; list_for_each_entry(spcm, &sdev->pcm_list, list) { /* match with PCM dai name */ if (strcmp(spcm->pcm.dai_name, name) == 0) return spcm; /* match with playback caps name if set */ if (*spcm->pcm.caps[0].name && !strcmp(spcm->pcm.caps[0].name, name)) return spcm; /* match with capture caps name if set */ if (*spcm->pcm.caps[1].name && !strcmp(spcm->pcm.caps[1].name, name)) return spcm; } return NULL; } struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev, unsigned int comp_id, int *direction) { struct snd_sof_pcm *spcm; list_for_each_entry(spcm, &sdev->pcm_list, list) { if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) { *direction = SNDRV_PCM_STREAM_PLAYBACK; return spcm; } if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) { *direction = SNDRV_PCM_STREAM_CAPTURE; return spcm; } } return NULL; } struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev, unsigned int pcm_id) { struct snd_sof_pcm *spcm; list_for_each_entry(spcm, &sdev->pcm_list, list) { if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) return spcm; } return NULL; } struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev, const char *name) { struct snd_sof_widget *swidget; list_for_each_entry(swidget, &sdev->widget_list, list) { if (strcmp(name, swidget->widget->name) == 0) return swidget; } return NULL; } /* find widget by stream name and direction */ struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev, const char *pcm_name, int dir) { struct snd_sof_widget *swidget; enum snd_soc_dapm_type type; if (dir == SNDRV_PCM_STREAM_PLAYBACK) type = snd_soc_dapm_aif_in; else type = snd_soc_dapm_aif_out; list_for_each_entry(swidget, &sdev->widget_list, list) { if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type) return swidget; } return NULL; } struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev, const char *name) { struct snd_sof_dai *dai; list_for_each_entry(dai, &sdev->dai_list, list) { if (dai->name && (strcmp(name, dai->name) == 0)) return dai; } return NULL; } /* * FW Panic/fault handling. */ struct sof_panic_msg { u32 id; const char *msg; }; /* standard FW panic types */ static const struct sof_panic_msg panic_msg[] = { {SOF_IPC_PANIC_MEM, "out of memory"}, {SOF_IPC_PANIC_WORK, "work subsystem init failed"}, {SOF_IPC_PANIC_IPC, "IPC subsystem init failed"}, {SOF_IPC_PANIC_ARCH, "arch init failed"}, {SOF_IPC_PANIC_PLATFORM, "platform init failed"}, {SOF_IPC_PANIC_TASK, "scheduler init failed"}, {SOF_IPC_PANIC_EXCEPTION, "runtime exception"}, {SOF_IPC_PANIC_DEADLOCK, "deadlock"}, {SOF_IPC_PANIC_STACK, "stack overflow"}, {SOF_IPC_PANIC_IDLE, "can't enter idle"}, {SOF_IPC_PANIC_WFI, "invalid wait state"}, {SOF_IPC_PANIC_ASSERT, "assertion failed"}, }; /* * helper to be called from .dbg_dump callbacks. No error code is * provided, it's left as an exercise for the caller of .dbg_dump * (typically IPC or loader) */ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, u32 tracep_code, void *oops, struct sof_ipc_panic_info *panic_info, void *stack, size_t stack_words) { u32 code; int i; /* is firmware dead ? */ if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n", panic_code, tracep_code); return; /* no fault ? */ } code = panic_code & (SOF_IPC_PANIC_MAGIC_MASK | SOF_IPC_PANIC_CODE_MASK); for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { if (panic_msg[i].id == code) { dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg); dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); goto out; } } /* unknown error */ dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code); dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code); out: dev_err(sdev->dev, "error: panic at %s:%d\n", panic_info->filename, panic_info->linenum); sof_oops(sdev, oops); sof_stack(sdev, oops, stack, stack_words); } EXPORT_SYMBOL(snd_sof_get_status); /* * Generic buffer page table creation. * Take the each physical page address and drop the least significant unused * bits from each (based on PAGE_SIZE). Then pack valid page address bits * into compressed page table. */ int snd_sof_create_page_table(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, unsigned char *page_table, size_t size) { int i, pages; pages = snd_sgbuf_aligned_pages(size); dev_dbg(sdev->dev, "generating page table for %p size 0x%zx pages %d\n", dmab->area, size, pages); for (i = 0; i < pages; i++) { /* * The number of valid address bits for each page is 20. * idx determines the byte position within page_table * where the current page's address is stored * in the compressed page_table. * This can be calculated by multiplying the page number by 2.5. */ u32 idx = (5 * i) >> 1; u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; u8 *pg_table; dev_vdbg(sdev->dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn); pg_table = (u8 *)(page_table + idx); /* * pagetable compression: * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 * ___________pfn 0__________ __________pfn 1___________ _pfn 2... * .... .... .... .... .... .... .... .... .... .... .... * It is created by: * 1. set current location to 0, PFN index i to 0 * 2. put pfn[i] at current location in Little Endian byte order * 3. calculate an intermediate value as * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) * 4. put x at offset (current location + 2) in LE byte order * 5. increment current location by 5 bytes, increment i by 2 * 6. continue to (2) */ if (i & 1) put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, pg_table); else put_unaligned_le32(pfn, pg_table); } return pages; } /* * SOF Driver enumeration. */ static int sof_machine_check(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; #if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) struct snd_soc_acpi_mach *machine; int ret; #endif if (plat_data->machine) return 0; #if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC) dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n"); return -ENODEV; #else /* fallback to nocodec mode */ dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n"); machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL); if (!machine) return -ENOMEM; ret = sof_nocodec_setup(sdev->dev, plat_data, machine, plat_data->desc, plat_data->desc->ops); if (ret < 0) return ret; plat_data->machine = machine; return 0; #endif } /* * FW Boot State Transition Diagram * * +-----------------------------------------------------------------------+ * | | * ------------------ ------------------ | * | | | | | * | BOOT_FAILED | | READY_FAILED |-------------------------+ | * | | | | | | * ------------------ ------------------ | | * ^ ^ | | * | | | | * (FW Boot Timeout) (FW_READY FAIL) | | * | | | | * | | | | * ------------------ | ------------------ | | * | | | | | | | * | IN_PROGRESS |---------------+------------->| COMPLETE | | | * | | (FW Boot OK) (FW_READY OK) | | | | * ------------------ ------------------ | | * ^ | | | * | | | | * (FW Loading OK) (System Suspend/Runtime Suspend) * | | | | * | | | | * ------------------ ------------------ | | | * | | | |<-----+ | | * | PREPARE | | NOT_STARTED |<---------------------+ | * | | | |<---------------------------+ * ------------------ ------------------ * | ^ | ^ * | | | | * | +-----------------------+ | * | (DSP Probe OK) | * | | * | | * +------------------------------------+ * (System Suspend/Runtime Suspend) */ static int sof_probe_continue(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; const char *drv_name; const void *mach; int size; int ret; /* probe the DSP hardware */ ret = snd_sof_probe(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); return ret; } sdev->fw_state = SOF_FW_BOOT_PREPARE; /* check machine info */ ret = sof_machine_check(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to get machine info %d\n", ret); goto dbg_err; } /* set up platform component driver */ snd_sof_new_platform_drv(sdev); /* register any debug/trace capabilities */ ret = snd_sof_dbg_init(sdev); if (ret < 0) { /* * debugfs issues are suppressed in snd_sof_dbg_init() since * we cannot rely on debugfs * here we trap errors due to memory allocation only. */ dev_err(sdev->dev, "error: failed to init DSP trace/debug %d\n", ret); goto dbg_err; } /* init the IPC */ sdev->ipc = snd_sof_ipc_init(sdev); if (!sdev->ipc) { dev_err(sdev->dev, "error: failed to init DSP IPC %d\n", ret); goto ipc_err; } /* load the firmware */ ret = snd_sof_load_firmware(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", ret); goto fw_load_err; } sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS; /* * Boot the firmware. The FW boot status will be modified * in snd_sof_run_firmware() depending on the outcome. */ ret = snd_sof_run_firmware(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", ret); goto fw_run_err; } /* init DMA trace */ ret = snd_sof_init_trace(sdev); if (ret < 0) { /* non fatal */ dev_warn(sdev->dev, "warning: failed to initialize trace %d\n", ret); } /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; /* now register audio DSP platform driver and dai */ ret = devm_snd_soc_register_component(sdev->dev, &sdev->plat_drv, sof_ops(sdev)->drv, sof_ops(sdev)->num_drv); if (ret < 0) { dev_err(sdev->dev, "error: failed to register DSP DAI driver %d\n", ret); goto fw_trace_err; } drv_name = plat_data->machine->drv_name; mach = (const void *)plat_data->machine; size = sizeof(*plat_data->machine); /* register machine driver, pass machine info as pdata */ plat_data->pdev_mach = platform_device_register_data(sdev->dev, drv_name, PLATFORM_DEVID_NONE, mach, size); if (IS_ERR(plat_data->pdev_mach)) { ret = PTR_ERR(plat_data->pdev_mach); goto fw_trace_err; } dev_dbg(sdev->dev, "created machine %s\n", dev_name(&plat_data->pdev_mach->dev)); if (plat_data->sof_probe_complete) plat_data->sof_probe_complete(sdev->dev); return 0; fw_trace_err: snd_sof_free_trace(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: snd_sof_ipc_free(sdev); ipc_err: snd_sof_free_debug(sdev); dbg_err: snd_sof_remove(sdev); /* all resources freed, update state to match */ sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; sdev->first_boot = true; return ret; } static void sof_probe_work(struct work_struct *work) { struct snd_sof_dev *sdev = container_of(work, struct snd_sof_dev, probe_work); int ret; ret = sof_probe_continue(sdev); if (ret < 0) { /* errors cannot be propagated, log */ dev_err(sdev->dev, "error: %s failed err: %d\n", __func__, ret); } } int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) { struct snd_sof_dev *sdev; sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); if (!sdev) return -ENOMEM; /* initialize sof device */ sdev->dev = dev; sdev->pdata = plat_data; sdev->first_boot = true; sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; dev_set_drvdata(dev, sdev); /* check all mandatory ops */ if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params) return -EINVAL; INIT_LIST_HEAD(&sdev->pcm_list); INIT_LIST_HEAD(&sdev->kcontrol_list); INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); INIT_LIST_HEAD(&sdev->route_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) INIT_WORK(&sdev->probe_work, sof_probe_work); /* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) sdev->ipc_timeout = TIMEOUT_DEFAULT_IPC_MS; else sdev->ipc_timeout = plat_data->desc->ipc_timeout; if (plat_data->desc->boot_timeout == 0) sdev->boot_timeout = TIMEOUT_DEFAULT_BOOT_MS; else sdev->boot_timeout = plat_data->desc->boot_timeout; if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { schedule_work(&sdev->probe_work); return 0; } return sof_probe_continue(sdev); } EXPORT_SYMBOL(snd_sof_device_probe); int snd_sof_device_remove(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_sof_pdata *pdata = sdev->pdata; if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { snd_sof_fw_unload(sdev); snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); snd_sof_free_trace(sdev); } /* * Unregister machine driver. This will unbind the snd_card which * will remove the component driver and unload the topology * before freeing the snd_card. */ if (!IS_ERR_OR_NULL(pdata->pdev_mach)) platform_device_unregister(pdata->pdev_mach); /* * Unregistering the machine driver results in unloading the topology. * Some widgets, ex: scheduler, attempt to power down the core they are * scheduled on, when they are unloaded. Therefore, the DSP must be * removed only after the topology has been unloaded. */ if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) snd_sof_remove(sdev); /* release firmware */ release_firmware(pdata->fw); pdata->fw = NULL; return 0; } EXPORT_SYMBOL(snd_sof_device_remove); MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:sof-audio");