diff options
Diffstat (limited to 'sound/soc/sof')
-rw-r--r-- | sound/soc/sof/core.c | 19 | ||||
-rw-r--r-- | sound/soc/sof/debug.c | 2 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-compress.c | 4 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-dsp.c | 24 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-loader.c | 17 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-pcm.c | 18 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-stream.c | 38 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda-trace.c | 8 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda.c | 277 | ||||
-rw-r--r-- | sound/soc/sof/intel/hda.h | 7 | ||||
-rw-r--r-- | sound/soc/sof/intel/tgl.c | 3 | ||||
-rw-r--r-- | sound/soc/sof/ipc.c | 4 | ||||
-rw-r--r-- | sound/soc/sof/loader.c | 8 | ||||
-rw-r--r-- | sound/soc/sof/ops.h | 43 | ||||
-rw-r--r-- | sound/soc/sof/pcm.c | 7 | ||||
-rw-r--r-- | sound/soc/sof/pm.c | 1 | ||||
-rw-r--r-- | sound/soc/sof/sof-pci-dev.c | 22 | ||||
-rw-r--r-- | sound/soc/sof/sof-priv.h | 8 | ||||
-rw-r--r-- | sound/soc/sof/topology.c | 14 |
19 files changed, 317 insertions, 207 deletions
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index adc7c37145d6..6d8f7d9fd192 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -246,6 +246,8 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (plat_data->sof_probe_complete) plat_data->sof_probe_complete(sdev->dev); + sdev->probe_completed = true; + return 0; fw_trace_err: @@ -316,6 +318,7 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->route_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); + mutex_init(&sdev->power_state_access); if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) INIT_WORK(&sdev->probe_work, sof_probe_work); @@ -339,6 +342,14 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) } EXPORT_SYMBOL(snd_sof_device_probe); +bool snd_sof_device_probe_completed(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + return sdev->probe_completed; +} +EXPORT_SYMBOL(snd_sof_device_probe_completed); + int snd_sof_device_remove(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); @@ -384,6 +395,14 @@ int snd_sof_device_remove(struct device *dev) } EXPORT_SYMBOL(snd_sof_device_remove); +int snd_sof_device_shutdown(struct device *dev) +{ + struct snd_sof_dev *sdev = dev_get_drvdata(dev); + + return snd_sof_shutdown(sdev); +} +EXPORT_SYMBOL(snd_sof_device_shutdown); + MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index 30213a1beaaa..715a374b33cf 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -352,7 +352,7 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, char *string; int ret; - string = kzalloc(count, GFP_KERNEL); + string = kzalloc(count+1, GFP_KERNEL); if (!string) return -ENOMEM; diff --git a/sound/soc/sof/intel/hda-compress.c b/sound/soc/sof/intel/hda-compress.c index 53c08034fa22..fe2f3f7d236b 100644 --- a/sound/soc/sof/intel/hda-compress.c +++ b/sound/soc/sof/intel/hda-compress.c @@ -25,7 +25,7 @@ int hda_probe_compr_assign(struct snd_sof_dev *sdev, { struct hdac_ext_stream *stream; - stream = hda_dsp_stream_get(sdev, cstream->direction); + stream = hda_dsp_stream_get(sdev, cstream->direction, 0); if (!stream) return -EBUSY; @@ -82,7 +82,7 @@ int hda_probe_compr_set_params(struct snd_sof_dev *sdev, ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); if (ret < 0) { - dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; } diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 1c5e05b88a90..5788fe356960 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -624,7 +624,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) #endif /* power down DSP */ - ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + ret = snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); if (ret < 0) { dev_err(sdev->dev, "error: failed to power down core during suspend\n"); @@ -732,7 +732,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev) ret = snd_hdac_ext_bus_link_power_up(hlink); if (ret < 0) { dev_dbg(sdev->dev, - "error %x in %s: failed to power up links", + "error %d in %s: failed to power up links", ret, __func__); return ret; } @@ -802,11 +802,15 @@ int hda_dsp_runtime_idle(struct snd_sof_dev *sdev) int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_dsp_power_state target_state = { .state = SOF_DSP_PM_D3, }; int ret; + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); + /* stop hda controller and power dsp off */ ret = hda_suspend(sdev, true); if (ret < 0) @@ -930,19 +934,15 @@ void hda_dsp_d0i3_work(struct work_struct *work) d0i3_work.work); struct hdac_bus *bus = &hdev->hbus.core; struct snd_sof_dev *sdev = dev_get_drvdata(bus->dev); - struct sof_dsp_power_state target_state; + struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + .substate = SOF_HDA_DSP_PM_D0I3, + }; int ret; - target_state.state = SOF_DSP_PM_D0; - /* DSP can enter D0I3 iff only D0I3-compatible streams are active */ - if (snd_sof_dsp_only_d0i3_compatible_stream_active(sdev)) - target_state.substate = SOF_HDA_DSP_PM_D0I3; - else - target_state.substate = SOF_HDA_DSP_PM_D0I0; - - /* remain in D0I0 */ - if (target_state.substate == SOF_HDA_DSP_PM_D0I0) + if (!snd_sof_dsp_only_d0i3_compatible_stream_active(sdev)) + /* remain in D0I0 */ return; /* This can fail but error cannot be propagated */ diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index ed773696b495..fc25ee8f68dc 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -35,7 +35,7 @@ static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsig struct pci_dev *pci = to_pci_dev(sdev->dev); int ret; - dsp_stream = hda_dsp_stream_get(sdev, direction); + dsp_stream = hda_dsp_stream_get(sdev, direction, 0); if (!dsp_stream) { dev_err(sdev->dev, "error: no stream available\n"); @@ -47,7 +47,7 @@ static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsig /* allocate DMA buffer */ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, &pci->dev, size, dmab); if (ret < 0) { - dev_err(sdev->dev, "error: memory alloc failed: %x\n", ret); + dev_err(sdev->dev, "error: memory alloc failed: %d\n", ret); goto error; } @@ -58,13 +58,13 @@ static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsig if (direction == SNDRV_PCM_STREAM_CAPTURE) { ret = hda_dsp_iccmax_stream_hw_params(sdev, dsp_stream, dmab, NULL); if (ret < 0) { - dev_err(sdev->dev, "error: iccmax stream prepare failed: %x\n", ret); + dev_err(sdev->dev, "error: iccmax stream prepare failed: %d\n", ret); goto error; } } else { ret = hda_dsp_stream_hw_params(sdev, dsp_stream, dmab, NULL); if (ret < 0) { - dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); goto error; } hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size); @@ -93,7 +93,7 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) int i; /* step 1: power up corex */ - ret = hda_dsp_core_power_up(sdev, chip->host_managed_cores_mask); + ret = snd_sof_dsp_core_power_up(sdev, chip->host_managed_cores_mask); if (ret < 0) { if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n"); @@ -147,8 +147,9 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) chip->ipc_ack_mask, chip->ipc_ack_mask); - /* step 5: power down corex */ - ret = hda_dsp_core_power_down(sdev, chip->host_managed_cores_mask & ~(BIT(0))); + /* step 5: power down cores that are no longer needed */ + ret = snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask & + ~(chip->init_core_mask)); if (ret < 0) { if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, @@ -183,7 +184,7 @@ err: flags |= SOF_DBG_DUMP_FORCE_ERR_LEVEL; hda_dsp_dump(sdev, flags); - hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); return ret; } diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index 5d35bb18660a..df00db8369c7 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -111,7 +111,7 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params); if (ret < 0) { - dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; } @@ -215,11 +215,25 @@ found: int hda_dsp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_component *scomp = sdev->component; struct hdac_ext_stream *dsp_stream; + struct snd_sof_pcm *spcm; int direction = substream->stream; + u32 flags = 0; + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_err(sdev->dev, "error: can't find PCM with DAI ID %d\n", rtd->dai_link->id); + return -EINVAL; + } - dsp_stream = hda_dsp_stream_get(sdev, direction); + /* All playback and D0i3 compatible streams are DMI L1 capable */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK || + spcm->stream[substream->stream].d0i3_compatible) + flags |= SOF_HDA_STREAM_DMI_L1_COMPATIBLE; + dsp_stream = hda_dsp_stream_get(sdev, direction, flags); if (!dsp_stream) { dev_err(sdev->dev, "error: no stream available\n"); return -ENODEV; diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 0e09ede922c7..40a3993ae2cb 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -155,7 +155,7 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, /* get next unused stream */ struct hdac_ext_stream * -hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) +hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) { struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; @@ -183,18 +183,22 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) spin_unlock_irq(&bus->reg_lock); /* stream found ? */ - if (!stream) + if (!stream) { dev_err(sdev->dev, "error: no free %s streams\n", direction == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture"); + return stream; + } + + hda_stream->flags = flags; /* - * Disable DMI Link L1 entry when capture stream is opened. + * Prevent DMI Link L1 entry for streams that don't support it. * Workaround to address a known issue with host DMA that results * in xruns during pause/release in capture scenarios. */ if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1)) - if (stream && direction == SNDRV_PCM_STREAM_CAPTURE) + if (stream && !(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, 0); @@ -206,37 +210,39 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction) int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) { struct hdac_bus *bus = sof_to_bus(sdev); + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_stream *stream; struct hdac_stream *s; - bool active_capture_stream = false; + bool dmi_l1_enable = true; bool found = false; spin_lock_irq(&bus->reg_lock); /* - * close stream matching the stream tag - * and check if there are any open capture streams. + * close stream matching the stream tag and check if there are any open streams + * that are DMI L1 incompatible. */ list_for_each_entry(s, &bus->stream_list, list) { + stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(stream, struct sof_intel_hda_stream, hda_stream); + if (!s->opened) continue; if (s->direction == direction && s->stream_tag == stream_tag) { s->opened = false; found = true; - } else if (s->direction == SNDRV_PCM_STREAM_CAPTURE) { - active_capture_stream = true; + } else if (!(hda_stream->flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) { + dmi_l1_enable = false; } } spin_unlock_irq(&bus->reg_lock); - /* Enable DMI L1 entry if there are no capture streams open */ - if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1)) - if (!active_capture_stream) - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, - HDA_VS_INTEL_EM2, - HDA_VS_INTEL_EM2_L1SEN, - HDA_VS_INTEL_EM2_L1SEN); + /* Enable DMI L1 if permitted */ + if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1) && dmi_l1_enable) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); if (!found) { dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag); diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c index 1eb746d5adeb..29e3da3c63db 100644 --- a/sound/soc/sof/intel/hda-trace.c +++ b/sound/soc/sof/intel/hda-trace.c @@ -32,7 +32,7 @@ static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev) ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); if (ret < 0) - dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret); + dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; } @@ -42,8 +42,8 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; int ret; - hda->dtrace_stream = hda_dsp_stream_get(sdev, - SNDRV_PCM_STREAM_CAPTURE); + hda->dtrace_stream = hda_dsp_stream_get(sdev, SNDRV_PCM_STREAM_CAPTURE, + SOF_HDA_STREAM_DMI_L1_COMPATIBLE); if (!hda->dtrace_stream) { dev_err(sdev->dev, @@ -59,7 +59,7 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) */ ret = hda_dsp_trace_prepare(sdev); if (ret < 0) { - dev_err(sdev->dev, "error: hdac trace init failed: %x\n", ret); + dev_err(sdev->dev, "error: hdac trace init failed: %d\n", ret); hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag); hda->dtrace_stream = NULL; *stream_tag = 0; diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 509a9b256423..0dc3a8c0f5e3 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -285,11 +285,13 @@ static char *hda_model; module_param(hda_model, charp, 0444); MODULE_PARM_DESC(hda_model, "Use the given HDA board model."); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) static int hda_dmic_num = -1; module_param_named(dmic_num, hda_dmic_num, int, 0444); MODULE_PARM_DESC(dmic_num, "SOF HDA DMIC number"); +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) static bool hda_codec_use_common_hdmi = IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI); module_param_named(use_common_hdmi, hda_codec_use_common_hdmi, bool, 0444); MODULE_PARM_DESC(use_common_hdmi, "SOF HDA use common HDMI codec driver"); @@ -317,26 +319,6 @@ static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = { {HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"}, }; -static void hda_dsp_get_status_skl(struct snd_sof_dev *sdev) -{ - u32 status; - int i; - - status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, - HDA_ADSP_FW_STATUS_SKL); - - for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { - if (status == hda_dsp_rom_msg[i].code) { - dev_err(sdev->dev, "%s - code %8.8x\n", - hda_dsp_rom_msg[i].msg, status); - return; - } - } - - /* not for us, must be generic sof message */ - dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); -} - static void hda_dsp_get_status(struct snd_sof_dev *sdev) { u32 status; @@ -385,36 +367,6 @@ static void hda_dsp_get_registers(struct snd_sof_dev *sdev, stack_words * sizeof(u32)); } -void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags) -{ - struct sof_ipc_dsp_oops_xtensa xoops; - struct sof_ipc_panic_info panic_info; - u32 stack[HDA_DSP_STACK_DUMP_SIZE]; - u32 status, panic; - - /* try APL specific status message types first */ - hda_dsp_get_status_skl(sdev); - - /* now try generic SOF status messages */ - status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, - HDA_ADSP_ERROR_CODE_SKL); - - /*TODO: Check: there is no define in spec, but it is used in the code*/ - panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, - HDA_ADSP_ERROR_CODE_SKL + 0x4); - - if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { - hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, - HDA_DSP_STACK_DUMP_SIZE); - snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, - stack, HDA_DSP_STACK_DUMP_SIZE); - } else { - dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n", - status, panic); - hda_dsp_get_status_skl(sdev); - } -} - /* dump the first 8 dwords representing the extended ROM status */ static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, u32 flags) { @@ -555,7 +507,7 @@ static int hda_init(struct snd_sof_dev *sdev) return ret; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) static int check_nhlt_dmic(struct snd_sof_dev *sdev) { @@ -579,25 +531,76 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev, const char *dmic_str) { const char *tplg_filename = NULL; - char *filename; - char *split_ext; + char *filename, *tmp; + const char *split_ext; - filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL); + filename = kstrdup(sof_tplg_filename, GFP_KERNEL); if (!filename) return NULL; /* this assumes a .tplg extension */ - split_ext = strsep(&filename, "."); - if (split_ext) { + tmp = filename; + split_ext = strsep(&tmp, "."); + if (split_ext) tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, "%s%s%s.tplg", split_ext, idisp_str, dmic_str); - if (!tplg_filename) - return NULL; - } + kfree(filename); + return tplg_filename; } +static int dmic_topology_fixup(struct snd_sof_dev *sdev, + const char **tplg_filename, + const char *idisp_str, + int *dmic_found) +{ + const char *default_tplg_filename = *tplg_filename; + const char *fixed_tplg_filename; + const char *dmic_str; + int dmic_num; + + /* first check NHLT for DMICs */ + dmic_num = check_nhlt_dmic(sdev); + + /* allow for module parameter override */ + if (hda_dmic_num != -1) { + dev_dbg(sdev->dev, + "overriding DMICs detected in NHLT tables %d by kernel param %d\n", + dmic_num, hda_dmic_num); + dmic_num = hda_dmic_num; + } + + switch (dmic_num) { + case 1: + dmic_str = "-1ch"; + break; + case 2: + dmic_str = "-2ch"; + break; + case 3: + dmic_str = "-3ch"; + break; + case 4: + dmic_str = "-4ch"; + break; + default: + dmic_num = 0; + dmic_str = ""; + break; + } + + fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename, + idisp_str, dmic_str); + if (!fixed_tplg_filename) + return -ENOMEM; + + dev_info(sdev->dev, "DMICs detected in NHLT tables: %d\n", dmic_num); + *dmic_found = dmic_num; + *tplg_filename = fixed_tplg_filename; + + return 0; +} #endif static int hda_init_caps(struct snd_sof_dev *sdev) @@ -809,13 +812,9 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) sdev->mailbox_bar = HDA_DSP_BAR; /* allow 64bit DMA address if supported by H/W */ - if (!dma_set_mask(&pci->dev, DMA_BIT_MASK(64))) { - dev_dbg(sdev->dev, "DMA mask is 64 bit\n"); - dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(64)); - } else { + if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(64))) { dev_dbg(sdev->dev, "DMA mask is 32 bit\n"); - dma_set_mask(&pci->dev, DMA_BIT_MASK(32)); - dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)); + dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)); } /* init streams */ @@ -932,7 +931,7 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) /* disable cores */ if (chip) - hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); /* disable DSP */ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, @@ -967,9 +966,9 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) struct snd_sof_pdata *pdata = sdev->pdata; const char *tplg_filename; const char *idisp_str; - const char *dmic_str; int dmic_num = 0; int codec_num = 0; + int ret; int i; /* codec detection */ @@ -994,10 +993,6 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) if (!pdata->machine && codec_num <= 2) { hda_mach = snd_soc_acpi_intel_hda_machines; - /* topology: use the info from hda_machines */ - pdata->tplg_filename = - hda_mach->sof_tplg_filename; - dev_info(bus->dev, "using HDA machine driver %s now\n", hda_mach->drv_name); @@ -1006,42 +1001,13 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) else idisp_str = ""; - /* first check NHLT for DMICs */ - dmic_num = check_nhlt_dmic(sdev); - - /* allow for module parameter override */ - if (hda_dmic_num != -1) - dmic_num = hda_dmic_num; - - switch (dmic_num) { - case 1: - dmic_str = "-1ch"; - break; - case 2: - dmic_str = "-2ch"; - break; - case 3: - dmic_str = "-3ch"; - break; - case 4: - dmic_str = "-4ch"; - break; - default: - dmic_num = 0; - dmic_str = ""; - break; - } - - tplg_filename = pdata->tplg_filename; - tplg_filename = fixup_tplg_name(sdev, tplg_filename, - idisp_str, dmic_str); - if (!tplg_filename) - return -EINVAL; - - dev_info(bus->dev, - "DMICs detected in NHLT tables: %d\n", - dmic_num); + /* topology: use the info from hda_machines */ + tplg_filename = hda_mach->sof_tplg_filename; + ret = dmic_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num); + if (ret < 0) + return ret; + hda_mach->mach_params.dmic_num = dmic_num; pdata->machine = hda_mach; pdata->tplg_filename = tplg_filename; } @@ -1053,7 +1019,6 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) &pdata->machine->mach_params; mach_params->codec_mask = bus->codec_mask; mach_params->common_hdmi_codec_drv = hda_codec_use_common_hdmi; - mach_params->dmic_num = dmic_num; } return 0; @@ -1075,32 +1040,63 @@ static bool link_slaves_found(struct snd_sof_dev *sdev, struct sdw_intel_slave_id *ids = sdw->ids; int num_slaves = sdw->num_slaves; unsigned int part_id, link_id, unique_id, mfg_id; - int i, j; + int i, j, k; for (i = 0; i < link->num_adr; i++) { u64 adr = link->adr_d[i].adr; + int reported_part_count = 0; mfg_id = SDW_MFG_ID(adr); part_id = SDW_PART_ID(adr); link_id = SDW_DISCO_LINK_ID(adr); + + for (j = 0; j < num_slaves; j++) { + /* find out how many identical parts were reported on that link */ + if (ids[j].link_id == link_id && + ids[j].id.part_id == part_id && + ids[j].id.mfg_id == mfg_id) + reported_part_count++; + } + for (j = 0; j < num_slaves; j++) { + int expected_part_count = 0; + if (ids[j].link_id != link_id || ids[j].id.part_id != part_id || ids[j].id.mfg_id != mfg_id) continue; - /* - * we have to check unique id - * if there is more than one - * Slave on the link - */ - unique_id = SDW_UNIQUE_ID(adr); - if (link->num_adr == 1 || - ids[j].id.unique_id == SDW_IGNORED_UNIQUE_ID || - ids[j].id.unique_id == unique_id) { - dev_dbg(bus->dev, - "found %x at link %d\n", - part_id, link_id); - break; + + /* find out how many identical parts are expected */ + for (k = 0; k < link->num_adr; k++) { + u64 adr2 = link->adr_d[i].adr; + unsigned int part_id2, link_id2, mfg_id2; + + mfg_id2 = SDW_MFG_ID(adr2); + part_id2 = SDW_PART_ID(adr2); + link_id2 = SDW_DISCO_LINK_ID(adr2); + + if (link_id2 == link_id && + part_id2 == part_id && + mfg_id2 == mfg_id) + expected_part_count++; + } + + if (reported_part_count == expected_part_count) { + /* + * we have to check unique id + * if there is more than one + * Slave on the link + */ + unique_id = SDW_UNIQUE_ID(adr); + if (reported_part_count == 1 || + ids[j].id.unique_id == unique_id) { + dev_dbg(bus->dev, "found %x at link %d\n", + part_id, link_id); + break; + } + } else { + dev_dbg(bus->dev, "part %x reported %d expected %d on link %d, skipping\n", + part_id, reported_part_count, expected_part_count, link_id); } } if (j == num_slaves) { @@ -1117,7 +1113,6 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev) { struct snd_sof_pdata *pdata = sdev->pdata; const struct snd_soc_acpi_link_adr *link; - struct hdac_bus *bus = sof_to_bus(sdev); struct snd_soc_acpi_mach *mach; struct sof_intel_hda_dev *hdev; u32 link_mask; @@ -1165,16 +1160,42 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev) break; } if (mach && mach->link_mask) { - dev_dbg(bus->dev, - "SoundWire machine driver %s topology %s\n", - mach->drv_name, - mach->sof_tplg_filename); + int dmic_num = 0; + pdata->machine = mach; mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; mach->mach_params.platform = dev_name(sdev->dev); - pdata->fw_filename = mach->sof_fw_filename; + if (mach->sof_fw_filename) + pdata->fw_filename = mach->sof_fw_filename; + else + pdata->fw_filename = pdata->desc->default_fw_filename; pdata->tplg_filename = mach->sof_tplg_filename; + + /* + * DMICs use up to 4 pins and are typically pin-muxed with SoundWire + * link 2 and 3, thus we only try to enable dmics if all conditions + * are true: + * a) link 2 and 3 are not used by SoundWire + * b) the NHLT table reports the presence of microphones + */ + if (!(mach->link_mask & GENMASK(3, 2))) { + const char *tplg_filename = mach->sof_tplg_filename; + int ret; + + ret = dmic_topology_fixup(sdev, &tplg_filename, "", &dmic_num); + + if (ret < 0) + return ret; + + pdata->tplg_filename = tplg_filename; + } + mach->mach_params.dmic_num = dmic_num; + + dev_dbg(sdev->dev, + "SoundWire machine driver %s topology %s\n", + mach->drv_name, + pdata->tplg_filename); } else { dev_info(sdev->dev, "No SoundWire machine driver found\n"); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 1d9b38e6ed40..d1c38c37bc9d 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -403,6 +403,9 @@ struct sof_intel_dsp_bdl { #define SOF_HDA_PLAYBACK 0 #define SOF_HDA_CAPTURE 1 +/* stream flags */ +#define SOF_HDA_STREAM_DMI_L1_COMPATIBLE 1 + /* * Time in ms for opportunistic D0I3 entry delay. * This has been deliberately chosen to be long to avoid race conditions. @@ -472,6 +475,7 @@ struct sof_intel_hda_stream { struct hdac_ext_stream hda_stream; struct sof_intel_stream stream; int host_reserved; /* reserve host DMA channel */ + u32 flags; }; #define hstream_to_sof_hda_stream(hstream) \ @@ -514,7 +518,6 @@ int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev); int hda_dsp_runtime_resume(struct snd_sof_dev *sdev); int hda_dsp_runtime_idle(struct snd_sof_dev *sdev); int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev); -void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags); void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags); void hda_ipc_dump(struct snd_sof_dev *sdev); void hda_ipc_irq_dump(struct snd_sof_dev *sdev); @@ -563,7 +566,7 @@ bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev); bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev); struct hdac_ext_stream * - hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction); + hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags); int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag); int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 2252ca38ff4b..419f05ba1920 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -22,9 +22,10 @@ static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = { /* Tigerlake ops */ const struct snd_sof_dsp_ops sof_tgl_ops = { - /* probe and remove */ + /* probe/remove/shutdown */ .probe = hda_dsp_probe, .remove = hda_dsp_remove, + .shutdown = hda_dsp_remove, /* Register IO */ .write = sof_io_write, diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index fc13bb06dbf3..c2d07b783f60 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -106,6 +106,8 @@ static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) str2 = "CLK_REQ"; break; case SOF_IPC_PM_CORE_ENABLE: str2 = "CORE_ENABLE"; break; + case SOF_IPC_PM_GATE: + str2 = "GATE"; break; default: str2 = "unknown type"; break; } @@ -796,7 +798,7 @@ int snd_sof_ipc_valid(struct snd_sof_dev *sdev) return -EINVAL; } - if (v->abi_version > SOF_ABI_VERSION) { + if (SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) { if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n"); } else { diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 08a17abb63ff..6efaf766f2ab 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -731,6 +731,8 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: request firmware %s failed err: %d\n", fw_filename, ret); + dev_err(sdev->dev, + "you may need to download the firmware from https://github.com/thesofproject/sof-bin/\n"); goto err; } else { dev_dbg(sdev->dev, "request_firmware %s successful\n", @@ -811,7 +813,6 @@ EXPORT_SYMBOL(snd_sof_load_firmware); int snd_sof_run_firmware(struct snd_sof_dev *sdev) { int ret; - int init_core_mask; init_waitqueue_head(&sdev->boot_wait); @@ -843,8 +844,6 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) return ret; } - init_core_mask = ret; - /* * now wait for the DSP to boot. There are 3 possible outcomes: * 1. Boot wait times out indicating FW boot failure. @@ -874,9 +873,6 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) return ret; } - /* fw boot is complete. Update the active cores mask */ - sdev->enabled_cores_mask = init_core_mask; - return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 95e748b36903..5099ad03df72 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -37,6 +37,14 @@ static inline int snd_sof_remove(struct snd_sof_dev *sdev) return 0; } +static inline int snd_sof_shutdown(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev)->shutdown) + return sof_ops(sdev)->shutdown(sdev); + + return 0; +} + /* control */ /* @@ -68,19 +76,31 @@ static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) { - if (sof_ops(sdev)->core_power_up) - return sof_ops(sdev)->core_power_up(sdev, core_mask); + int ret = 0; - return 0; + core_mask &= ~sdev->enabled_cores_mask; + if (sof_ops(sdev)->core_power_up && core_mask) { + ret = sof_ops(sdev)->core_power_up(sdev, core_mask); + if (!ret) + sdev->enabled_cores_mask |= core_mask; + } + + return ret; } static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask) { - if (sof_ops(sdev)->core_power_down) - return sof_ops(sdev)->core_power_down(sdev, core_mask); + int ret = 0; - return 0; + core_mask &= sdev->enabled_cores_mask; + if (sof_ops(sdev)->core_power_down && core_mask) { + ret = sof_ops(sdev)->core_power_down(sdev, core_mask); + if (!ret) + sdev->enabled_cores_mask &= ~core_mask; + } + + return ret; } /* pre/post fw load */ @@ -208,11 +228,16 @@ static inline int snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev, const struct sof_dsp_power_state *target_state) { + int ret = 0; + + mutex_lock(&sdev->power_state_access); + if (sof_ops(sdev)->set_power_state) - return sof_ops(sdev)->set_power_state(sdev, target_state); + ret = sof_ops(sdev)->set_power_state(sdev, target_state); - /* D0 substate is not supported, do nothing here. */ - return 0; + mutex_unlock(&sdev->power_state_access); + + return ret; } /* debug */ diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 0dc39fbcd81d..61c3fe17342d 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -707,7 +707,12 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa } break; case SOF_DAI_INTEL_ALH: - /* do nothing for ALH dai_link */ + /* + * Dai could run with different channel count compared with + * front end, so get dai channel count from topology + */ + channels->min = dai->dai_config->alh.channels; + channels->max = dai->dai_config->alh.channels; break; case SOF_DAI_IMX_ESAI: rate->min = dai->dai_config->esai.fsync_rate; diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index c83fb6255961..fd265803f7bc 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -256,7 +256,6 @@ suspend: /* reset FW state */ sdev->fw_state = SOF_FW_BOOT_NOT_STARTED; - sdev->enabled_cores_mask = 0; return ret; } diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index 215711ac7450..fd1f0d8c2853 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -66,6 +66,13 @@ static const struct dmi_system_id community_key_platforms[] = { } }, { + .ident = "Up Extreme", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + DMI_MATCH(DMI_BOARD_NAME, "UP-WHL01"), + } + }, + { .ident = "Google Chromebooks", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), @@ -213,7 +220,7 @@ static const struct sof_dev_desc icl_desc = { }; #endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) || IS_ENABLED(CONFIG_SND_SOC_SOF_ALDERLAKE) static const struct sof_dev_desc tgl_desc = { .machines = snd_soc_acpi_intel_tgl_machines, .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines, @@ -230,7 +237,9 @@ static const struct sof_dev_desc tgl_desc = { .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_tgl_ops, }; +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE) static const struct sof_dev_desc tglh_desc = { .machines = snd_soc_acpi_intel_tgl_machines, .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines, @@ -445,13 +454,19 @@ static void sof_pci_remove(struct pci_dev *pci) snd_sof_device_remove(&pci->dev); /* follow recommendation in pci-driver.c to increment usage counter */ - if (!(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME)) + if (snd_sof_device_probe_completed(&pci->dev) && + !(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME)) pm_runtime_get_noresume(&pci->dev); /* release pci regions and disable device */ pci_release_regions(pci); } +static void sof_pci_shutdown(struct pci_dev *pci) +{ + snd_sof_device_shutdown(&pci->dev); +} + /* PCI IDs */ static const struct pci_device_id sof_pci_ids[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD) @@ -514,6 +529,8 @@ static const struct pci_device_id sof_pci_ids[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_ALDERLAKE) { PCI_DEVICE(0x8086, 0x7ad0), .driver_data = (unsigned long)&adls_desc}, + { PCI_DEVICE(0x8086, 0x51c8), + .driver_data = (unsigned long)&tgl_desc}, #endif { 0, } }; @@ -525,6 +542,7 @@ static struct pci_driver snd_sof_pci_driver = { .id_table = sof_pci_ids, .probe = sof_pci_probe, .remove = sof_pci_remove, + .shutdown = sof_pci_shutdown, .driver = { .pm = &sof_pci_pm, }, diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 68da8f797403..ad0d7ba2708c 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -98,9 +98,10 @@ struct snd_sof_pdata; */ struct snd_sof_dsp_ops { - /* probe and remove */ + /* probe/remove/shutdown */ int (*probe)(struct snd_sof_dev *sof_dev); /* mandatory */ int (*remove)(struct snd_sof_dev *sof_dev); /* optional */ + int (*shutdown)(struct snd_sof_dev *sof_dev); /* optional */ /* DSP core boot / reset */ int (*run)(struct snd_sof_dev *sof_dev); /* mandatory */ @@ -375,6 +376,8 @@ struct snd_sof_dev { /* current DSP power state */ struct sof_dsp_power_state dsp_power_state; + /* mutex to protect the dsp_power_state access */ + struct mutex power_state_access; /* Intended power target of system suspend */ enum sof_system_suspend_state system_suspend_target; @@ -386,6 +389,7 @@ struct snd_sof_dev { /* work queue in case the probe is implemented in two steps */ struct work_struct probe_work; + bool probe_completed; /* DSP HW differentiation */ struct snd_sof_pdata *pdata; @@ -460,6 +464,8 @@ struct snd_sof_dev { int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data); int snd_sof_device_remove(struct device *dev); +int snd_sof_device_shutdown(struct device *dev); +bool snd_sof_device_probe_completed(struct device *dev); int snd_sof_runtime_suspend(struct device *dev); int snd_sof_runtime_resume(struct device *dev); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index b6b32a7a91f8..10f99620eb31 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1073,7 +1073,7 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, scontrol->cmd = SOF_CTRL_CMD_VOLUME; /* extract tlv data */ - if (get_tlv_data(kc->tlv.p, tlv) < 0) { + if (!kc->tlv.p || get_tlv_data(kc->tlv.p, tlv) < 0) { dev_err(scomp->dev, "error: invalid TLV data\n"); ret = -EINVAL; goto out_free; @@ -1352,10 +1352,6 @@ static int sof_core_enable(struct snd_sof_dev *sdev, int core) core, ret); goto err; } - - /* update enabled cores mask */ - sdev->enabled_cores_mask |= BIT(core); - return ret; err: /* power down core if it is host managed and return the original error if this fails too */ @@ -2603,10 +2599,6 @@ static int sof_widget_unload(struct snd_soc_component *scomp, if (ret < 0) dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n", pipeline->core); - - /* update enabled cores mask */ - sdev->enabled_cores_mask &= ~(1 << pipeline->core); - break; default: break; @@ -3666,7 +3658,7 @@ static int sof_manifest(struct snd_soc_component *scomp, int index, return -EINVAL; } - if (abi_version > SOF_ABI_VERSION) { + if (SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) { if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n"); } else { @@ -3740,6 +3732,8 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) if (ret < 0) { dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n", file, ret); + dev_err(scomp->dev, + "you may need to download the firmware from https://github.com/thesofproject/sof-bin/\n"); return ret; } |