diff options
Diffstat (limited to 'sound/soc/sof')
58 files changed, 3732 insertions, 1600 deletions
diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index bd6c1b198736..df36b411a12e 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -18,22 +18,6 @@ #include "acp-dsp-offset.h" #include <sound/sof/xtensa.h> -int acp_dai_probe(struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); - unsigned int val; - - val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->i2s_pin_config_offset); - if (val != desc->i2s_mode) { - dev_err(sdev->dev, "I2S Mode is not supported (I2S_PIN_CONFIG: %#x)\n", val); - return -EINVAL; - } - - return 0; -} -EXPORT_SYMBOL_NS(acp_dai_probe, SND_SOC_SOF_AMD_COMMON); - /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. * @sdev: SOF device. @@ -187,6 +171,7 @@ struct snd_sof_dsp_ops sof_acp_common_ops = { .pcm_open = acp_pcm_open, .pcm_close = acp_pcm_close, .pcm_hw_params = acp_pcm_hw_params, + .pcm_pointer = acp_pcm_pointer, .hw_info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | diff --git a/sound/soc/sof/amd/acp-pcm.c b/sound/soc/sof/amd/acp-pcm.c index 727c3a784a20..0828245bbb99 100644 --- a/sound/soc/sof/amd/acp-pcm.c +++ b/sound/soc/sof/amd/acp-pcm.c @@ -39,6 +39,7 @@ int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substr platform_params->use_phy_address = true; platform_params->phy_addr = stream->reg_offset; platform_params->stream_tag = stream->stream_tag; + platform_params->cont_update_posn = 1; /* write buffer size of stream in scratch memory */ @@ -84,3 +85,36 @@ int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) return acp_dsp_stream_put(sdev, stream); } EXPORT_SYMBOL_NS(acp_pcm_close, SND_SOC_SOF_AMD_COMMON); + +snd_pcm_uframes_t acp_pcm_pointer(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 snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + snd_pcm_uframes_t pos; + int ret; + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + stream = &spcm->stream[substream->stream]; + ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return 0; + } + + memcpy(&stream->posn, &posn, sizeof(posn)); + pos = spcm->stream[substream->stream].posn.host_posn; + pos = bytes_to_frames(substream->runtime, pos); + + return pos; +} +EXPORT_SYMBOL_NS(acp_pcm_pointer, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index d5ccd4d09278..2ae76bcd3590 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -470,33 +470,39 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) unsigned int addr; int ret; + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); + return -EIO; + } adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), GFP_KERNEL); if (!adata) return -ENOMEM; adata->dev = sdev; + adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec", + PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(adata->dmic_dev)) { + dev_err(sdev->dev, "failed to register platform for dmic codec\n"); + return PTR_ERR(adata->dmic_dev); + } addr = pci_resource_start(pci, ACP_DSP_BAR); sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); if (!sdev->bar[ACP_DSP_BAR]) { dev_err(sdev->dev, "ioremap error\n"); - return -ENXIO; + ret = -ENXIO; + goto unregister_dev; } pci_set_master(pci); sdev->pdata->hw_pdata = adata; - - chip = get_chip_info(sdev->pdata); - if (!chip) { - dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); - return -EIO; - } - adata->smn_dev = pci_get_device(PCI_VENDOR_ID_AMD, chip->host_bridge_id, NULL); if (!adata->smn_dev) { dev_err(sdev->dev, "Failed to get host bridge device\n"); - return -ENODEV; + ret = -ENODEV; + goto unregister_dev; } sdev->ipc_irq = pci->irq; @@ -505,16 +511,12 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "failed to register IRQ %d\n", sdev->ipc_irq); - pci_dev_put(adata->smn_dev); - return ret; + goto free_smn_dev; } ret = acp_init(sdev); - if (ret < 0) { - free_irq(sdev->ipc_irq, sdev); - pci_dev_put(adata->smn_dev); - return ret; - } + if (ret < 0) + goto free_ipc_irq; sdev->dsp_box.offset = 0; sdev->dsp_box.size = BOX_SIZE_512; @@ -530,6 +532,14 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) acp_dsp_stream_init(sdev); return 0; + +free_ipc_irq: + free_irq(sdev->ipc_irq, sdev); +free_smn_dev: + pci_dev_put(adata->smn_dev); +unregister_dev: + platform_device_unregister(adata->dmic_dev); + return ret; } EXPORT_SYMBOL_NS(amd_sof_acp_probe, SND_SOC_SOF_AMD_COMMON); @@ -543,6 +553,9 @@ int amd_sof_acp_remove(struct snd_sof_dev *sdev) if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->dmic_dev) + platform_device_unregister(adata->dmic_dev); + return acp_reset(sdev); } EXPORT_SYMBOL_NS(amd_sof_acp_remove, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 39165ebf684b..1c535cc6c3a9 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -158,12 +158,10 @@ struct acp_dsp_stream { struct sof_amd_acp_desc { unsigned int rev; unsigned int host_bridge_id; - unsigned int i2s_mode; u32 pgfsm_base; u32 ext_intr_stat; u32 dsp_intr_base; u32 sram_pte_offset; - u32 i2s_pin_config_offset; u32 hw_semaphore_offset; u32 acp_clkmux_sel; u32 fusion_dsp_offset; @@ -172,6 +170,8 @@ struct sof_amd_acp_desc { /* Common device data struct for ACP devices */ struct acp_dev_data { struct snd_sof_dev *dev; + /* DMIC device */ + struct platform_device *dmic_dev; unsigned int fw_bin_size; unsigned int fw_data_bin_size; u32 fw_bin_page_count; @@ -238,6 +238,8 @@ int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_sof_platform_stream_params *platform_params); +snd_pcm_uframes_t acp_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); extern struct snd_sof_dsp_ops sof_acp_common_ops; @@ -246,7 +248,6 @@ int sof_renoir_ops_init(struct snd_sof_dev *sdev); extern struct snd_sof_dsp_ops sof_rembrandt_ops; int sof_rembrandt_ops_init(struct snd_sof_dev *sdev); -int acp_dai_probe(struct snd_soc_dai *dai); struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); /* Machine configuration */ int snd_amd_acp_find_config(struct pci_dev *pci); diff --git a/sound/soc/sof/amd/pci-rmb.c b/sound/soc/sof/amd/pci-rmb.c index 4e1de462b431..eaf70ea6e556 100644 --- a/sound/soc/sof/amd/pci-rmb.c +++ b/sound/soc/sof/amd/pci-rmb.c @@ -26,33 +26,13 @@ #define ACP6x_REG_START 0x1240000 #define ACP6x_REG_END 0x125C000 -static struct platform_device *dmic_dev; -static struct platform_device *pdev; - -static const struct resource rembrandt_res[] = { - { - .start = 0, - .end = ACP6x_REG_END - ACP6x_REG_START, - .name = "acp_mem", - .flags = IORESOURCE_MEM, - }, - { - .start = 0, - .end = 0, - .name = "acp_dai_irq", - .flags = IORESOURCE_IRQ, - }, -}; - static const struct sof_amd_acp_desc rembrandt_chip_info = { .rev = 6, .host_bridge_id = HOST_BRIDGE_RMB, - .i2s_mode = 0x0a, .pgfsm_base = ACP6X_PGFSM_BASE, .ext_intr_stat = ACP6X_EXT_INTR_STAT, .dsp_intr_base = ACP6X_DSP_SW_INTR_BASE, .sram_pte_offset = ACP6X_SRAM_PTE_OFFSET, - .i2s_pin_config_offset = ACP6X_I2S_PIN_CONFIG, .hw_semaphore_offset = ACP6X_AXI2DAGB_SEM_0, .acp_clkmux_sel = ACP6X_CLKMUX_SEL, .fusion_dsp_offset = ACP6X_DSP_FUSION_RUNSTALL, @@ -83,84 +63,17 @@ static const struct sof_dev_desc rembrandt_desc = { static int acp_pci_rmb_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { - struct platform_device_info pdevinfo; - struct device *dev = &pci->dev; - const struct resource *res_i2s; - struct resource *res; - unsigned int flag, i, addr; - int ret; + unsigned int flag; flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; - ret = sof_pci_probe(pci, pci_id); - if (ret != 0) - return ret; - - dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); - if (IS_ERR(dmic_dev)) { - dev_err(dev, "failed to create DMIC device\n"); - sof_pci_remove(pci); - return PTR_ERR(dmic_dev); - } - - /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */ - if (flag != FLAG_AMD_SOF_ONLY_DMIC) - return 0; - - addr = pci_resource_start(pci, 0); - res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(rembrandt_res), - GFP_KERNEL); - if (!res) { - platform_device_unregister(dmic_dev); - sof_pci_remove(pci); - return -ENOMEM; - } - - res_i2s = rembrandt_res; - for (i = 0; i < ARRAY_SIZE(rembrandt_res); i++, res_i2s++) { - res[i].name = res_i2s->name; - res[i].flags = res_i2s->flags; - res[i].start = addr + res_i2s->start; - res[i].end = addr + res_i2s->end; - if (res_i2s->flags == IORESOURCE_IRQ) { - res[i].start = pci->irq; - res[i].end = res[i].start; - } - } - - memset(&pdevinfo, 0, sizeof(pdevinfo)); - - /* - * We have common PCI driver probe for ACP device but we have to support I2S without SOF - * for some distributions. Register platform device that will be used to support non dsp - * ACP's audio ends points on some machines. - */ - pdevinfo.name = "acp_asoc_rembrandt"; - pdevinfo.id = 0; - pdevinfo.parent = &pci->dev; - pdevinfo.num_res = ARRAY_SIZE(rembrandt_res); - pdevinfo.res = &res[0]; - - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) { - dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name); - platform_device_unregister(dmic_dev); - sof_pci_remove(pci); - ret = PTR_ERR(pdev); - } - - return ret; + return sof_pci_probe(pci, pci_id); }; static void acp_pci_rmb_remove(struct pci_dev *pci) { - if (dmic_dev) - platform_device_unregister(dmic_dev); - if (pdev) - platform_device_unregister(pdev); - sof_pci_remove(pci); } diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c index fca40b261671..4809cb644152 100644 --- a/sound/soc/sof/amd/pci-rn.c +++ b/sound/soc/sof/amd/pci-rn.c @@ -26,33 +26,13 @@ #define ACP3x_REG_START 0x1240000 #define ACP3x_REG_END 0x125C000 -static struct platform_device *dmic_dev; -static struct platform_device *pdev; - -static const struct resource renoir_res[] = { - { - .start = 0, - .end = ACP3x_REG_END - ACP3x_REG_START, - .name = "acp_mem", - .flags = IORESOURCE_MEM, - }, - { - .start = 0, - .end = 0, - .name = "acp_dai_irq", - .flags = IORESOURCE_IRQ, - }, -}; - static const struct sof_amd_acp_desc renoir_chip_info = { .rev = 3, .host_bridge_id = HOST_BRIDGE_CZN, - .i2s_mode = 0x04, .pgfsm_base = ACP3X_PGFSM_BASE, .ext_intr_stat = ACP3X_EXT_INTR_STAT, .dsp_intr_base = ACP3X_DSP_SW_INTR_BASE, .sram_pte_offset = ACP3X_SRAM_PTE_OFFSET, - .i2s_pin_config_offset = ACP3X_I2S_PIN_CONFIG, .hw_semaphore_offset = ACP3X_AXI2DAGB_SEM_0, .acp_clkmux_sel = ACP3X_CLKMUX_SEL, }; @@ -83,84 +63,17 @@ static const struct sof_dev_desc renoir_desc = { static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { - struct platform_device_info pdevinfo; - struct device *dev = &pci->dev; - const struct resource *res_i2s; - struct resource *res; - unsigned int flag, i, addr; - int ret; + unsigned int flag; flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; - ret = sof_pci_probe(pci, pci_id); - if (ret != 0) - return ret; - - dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); - if (IS_ERR(dmic_dev)) { - dev_err(dev, "failed to create DMIC device\n"); - sof_pci_remove(pci); - return PTR_ERR(dmic_dev); - } - - /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */ - if (flag != FLAG_AMD_SOF_ONLY_DMIC) - return 0; - - addr = pci_resource_start(pci, 0); - res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(renoir_res), GFP_KERNEL); - if (!res) { - sof_pci_remove(pci); - platform_device_unregister(dmic_dev); - return -ENOMEM; - } - - res_i2s = renoir_res; - for (i = 0; i < ARRAY_SIZE(renoir_res); i++, res_i2s++) { - res[i].name = res_i2s->name; - res[i].flags = res_i2s->flags; - res[i].start = addr + res_i2s->start; - res[i].end = addr + res_i2s->end; - if (res_i2s->flags == IORESOURCE_IRQ) { - res[i].start = pci->irq; - res[i].end = res[i].start; - } - } - - memset(&pdevinfo, 0, sizeof(pdevinfo)); - - /* - * We have common PCI driver probe for ACP device but we have to support I2S without SOF - * for some distributions. Register platform device that will be used to support non dsp - * ACP's audio ends points on some machines. - */ - - pdevinfo.name = "acp_asoc_renoir"; - pdevinfo.id = 0; - pdevinfo.parent = &pci->dev; - pdevinfo.num_res = ARRAY_SIZE(renoir_res); - pdevinfo.res = &res[0]; - - pdev = platform_device_register_full(&pdevinfo); - if (IS_ERR(pdev)) { - dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name); - sof_pci_remove(pci); - platform_device_unregister(dmic_dev); - ret = PTR_ERR(pdev); - } - - return ret; + return sof_pci_probe(pci, pci_id); }; static void acp_pci_rn_remove(struct pci_dev *pci) { - if (dmic_dev) - platform_device_unregister(dmic_dev); - if (pdev) - platform_device_unregister(pdev); - return sof_pci_remove(pci); } diff --git a/sound/soc/sof/amd/rembrandt.c b/sound/soc/sof/amd/rembrandt.c index 5288ab882fc9..f1d1ba57ab3a 100644 --- a/sound/soc/sof/amd/rembrandt.c +++ b/sound/soc/sof/amd/rembrandt.c @@ -48,7 +48,6 @@ static struct snd_soc_dai_driver rembrandt_sof_dai[] = { .rate_min = 8000, .rate_max = 48000, }, - .probe = &acp_dai_probe, }, [I2S_BT_INSTANCE] = { @@ -73,7 +72,6 @@ static struct snd_soc_dai_driver rembrandt_sof_dai[] = { .rate_min = 8000, .rate_max = 48000, }, - .probe = &acp_dai_probe, }, [I2S_SP_INSTANCE] = { @@ -98,7 +96,6 @@ static struct snd_soc_dai_driver rembrandt_sof_dai[] = { .rate_min = 8000, .rate_max = 48000, }, - .probe = &acp_dai_probe, }, [PDM_DMIC_INSTANCE] = { @@ -126,7 +123,6 @@ static struct snd_soc_dai_driver rembrandt_sof_dai[] = { .rate_min = 8000, .rate_max = 96000, }, - .probe = &acp_dai_probe, }, }; diff --git a/sound/soc/sof/amd/renoir.c b/sound/soc/sof/amd/renoir.c index adade2e3d3be..47b863f6258c 100644 --- a/sound/soc/sof/amd/renoir.c +++ b/sound/soc/sof/amd/renoir.c @@ -47,7 +47,6 @@ static struct snd_soc_dai_driver renoir_sof_dai[] = { .rate_min = 8000, .rate_max = 48000, }, - .probe = &acp_dai_probe, }, [I2S_SP_INSTANCE] = { @@ -72,7 +71,6 @@ static struct snd_soc_dai_driver renoir_sof_dai[] = { .rate_min = 8000, .rate_max = 48000, }, - .probe = &acp_dai_probe, }, [PDM_DMIC_INSTANCE] = { @@ -100,7 +98,6 @@ static struct snd_soc_dai_driver renoir_sof_dai[] = { .rate_min = 8000, .rate_max = 96000, }, - .probe = &acp_dai_probe, }, }; diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 8d205eb16d2f..d7b044f33d79 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -135,7 +135,6 @@ static int sof_compr_free(struct snd_soc_component *component, struct sof_compr_stream *sstream = cstream->runtime->private_data; struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct sof_ipc_stream stream; - struct sof_ipc_reply reply; struct snd_sof_pcm *spcm; int ret = 0; @@ -148,8 +147,7 @@ static int sof_compr_free(struct snd_soc_component *component, stream.comp_id = spcm->stream[cstream->direction].comp_id; if (spcm->prepared[cstream->direction]) { - ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), - &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); if (!ret) spcm->prepared[cstream->direction] = false; } @@ -273,7 +271,6 @@ static int sof_compr_trigger(struct snd_soc_component *component, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct sof_ipc_stream stream; - struct sof_ipc_reply reply; struct snd_sof_pcm *spcm; spcm = snd_sof_find_spcm_dai(component, rtd); @@ -302,8 +299,7 @@ static int sof_compr_trigger(struct snd_soc_component *component, break; } - return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), - &reply, sizeof(reply)); + return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); } static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 7de8673a01ce..9a9d82220fd0 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -208,6 +208,11 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) /* set up platform component driver */ snd_sof_new_platform_drv(sdev); + if (sdev->dspless_mode_selected) { + sof_set_fw_state(sdev, SOF_DSPLESS_MODE); + goto skip_dsp_init; + } + /* register any debug/trace capabilities */ ret = snd_sof_dbg_init(sdev); if (ret < 0) { @@ -266,6 +271,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) dev_dbg(sdev->dev, "SOF firmware trace disabled\n"); } +skip_dsp_init: /* hereafter all FW boot flows are for PM reasons */ sdev->first_boot = false; @@ -365,6 +371,15 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) if (sof_core_debug) dev_info(dev, "sof_debug value: %#x\n", sof_core_debug); + if (sof_debug_check_flag(SOF_DBG_DSPLESS_MODE)) { + if (plat_data->desc->dspless_mode_supported) { + dev_info(dev, "Switching to DSPless mode\n"); + sdev->dspless_mode_selected = true; + } else { + dev_info(dev, "DSPless mode is not supported by the platform\n"); + } + } + /* check IPC support */ if (!(BIT(plat_data->ipc_type) & plat_data->desc->ipc_supported_mask)) { dev_err(dev, "ipc_type %d is not supported on this platform, mask is %#x\n", @@ -378,12 +393,18 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) return ret; /* 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) { + if (!sof_ops(sdev) || !sof_ops(sdev)->probe) { + sof_ops_free(sdev); + dev_err(dev, "missing mandatory ops\n"); + return -EINVAL; + } + + if (!sdev->dspless_mode_selected && + (!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_free(sdev); - dev_err(dev, "error: missing mandatory ops\n"); + dev_err(dev, "missing mandatory DSP ops\n"); return -EINVAL; } diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index ade0507328af..b42b5982cbbc 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -370,6 +370,7 @@ static const struct soc_fw_state_info { const char *name; } fw_state_dbg[] = { {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"}, + {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"}, {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"}, {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"}, {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"}, diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 715ba8a7f2f8..f4eeacf1f281 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -269,6 +269,13 @@ config SND_SOC_SOF_HDA_COMMON select SND_INTEL_DSP_CONFIG select SND_SOC_SOF_HDA_LINK_BASELINE select SND_SOC_SOF_HDA_PROBES + select SND_SOC_SOF_HDA_MLINK if SND_SOC_SOF_HDA_LINK + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_HDA_MLINK + tristate help This option is not user-selectable but automagically handled by 'select' statements at a higher level. diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index 8201cbdd654c..fdb463c12e91 100644 --- a/sound/soc/sof/intel/Makefile +++ b/sound/soc/sof/intel/Makefile @@ -5,10 +5,12 @@ snd-sof-acpi-intel-bdw-objs := bdw.o snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ - hda-dai.o hda-bus.o hda-mlink.o \ + hda-dai.o hda-dai-ops.o hda-bus.o \ skl.o hda-loader-skl.o \ apl.o cnl.o tgl.o icl.o mtl.o hda-common-ops.o +snd-sof-intel-hda-mlink-objs := hda-mlink.o + snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o snd-sof-intel-hda-objs := hda-codec.o @@ -19,6 +21,7 @@ obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-atom.o obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o +obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o snd-sof-pci-intel-tng-objs := pci-tng.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 0e7a7e4ad976..e1f25a8f0c32 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -48,6 +48,8 @@ int sof_apl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_apl_ops.ipc_dump = hda_ipc_dump; + + sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc3; } if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { @@ -73,6 +75,8 @@ int sof_apl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_apl_ops.ipc_dump = hda_ipc4_dump; + + sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc4; } /* set DAI driver ops */ diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index a08a77fa946b..a95222e53ecf 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -395,6 +395,8 @@ int sof_cnl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_cnl_ops.ipc_dump = cnl_ipc_dump; + + sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc3; } if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { @@ -420,6 +422,8 @@ int sof_cnl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_cnl_ops.ipc_dump = cnl_ipc4_dump; + + sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc4; } /* set DAI driver ops */ diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c index 397303b3ac9d..8e1cd0babd32 100644 --- a/sound/soc/sof/intel/hda-common-ops.c +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -89,7 +89,6 @@ struct snd_sof_dsp_ops sof_hda_common_ops = { .runtime_resume = hda_dsp_runtime_resume, .runtime_idle = hda_dsp_runtime_idle, .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, /* ALSA HW info flags */ .hw_info = SNDRV_PCM_INFO_MMAP | diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index f3bdeba28412..84bf01bd360a 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -19,6 +19,7 @@ #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> #include <sound/hda_component.h> +#include <sound/hda-mlink.h> #include "../ops.h" #include "hda.h" @@ -158,16 +159,18 @@ void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable) */ int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; u32 val; /* enable/disable audio dsp clock gating */ val = enable ? PCI_CGCTL_ADSPDCGE : 0; snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val); - /* enable/disable DMI Link L1 support */ + /* disable the DMI link when requested. But enable only if it wasn't disabled previously */ val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0; - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, - HDA_VS_INTEL_EM2_L1SEN, val); + if (!enable || !hda->l1_disabled) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, val); /* enable/disable audio dsp power gating */ val = enable ? 0 : PCI_PGCTL_ADSPPGD; diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c new file mode 100644 index 000000000000..4b39cecacd68 --- /dev/null +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: (GPL-2.0-only 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) 2022 Intel Corporation. All rights reserved. + +#include <sound/pcm_params.h> +#include <sound/hdaudio_ext.h> +#include <sound/sof/ipc4/header.h> +#include <uapi/sound/sof/header.h> +#include "../ipc4-priv.h" +#include "../ipc4-topology.h" +#include "../sof-priv.h" +#include "../sof-audio.h" +#include "hda.h" + +/* These ops are only applicable for the HDA DAI's in their current form */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +/* + * This function checks if the host dma channel corresponding + * to the link DMA stream_tag argument is assigned to one + * of the FEs connected to the BE DAI. + */ +static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, + int dir, int stream_tag) +{ + struct snd_pcm_substream *fe_substream; + struct hdac_stream *fe_hstream; + struct snd_soc_dpcm *dpcm; + + for_each_dpcm_fe(rtd, dir, dpcm) { + fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir); + fe_hstream = fe_substream->runtime->private_data; + if (fe_hstream->stream_tag == stream_tag) + return true; + } + + return false; +} + +static struct hdac_ext_stream * +hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; + struct hdac_ext_stream *res = NULL; + struct hdac_stream *hstream = NULL; + + int stream_dir = substream->stream; + + if (!bus->ppcap) { + dev_err(bus->dev, "stream type not supported\n"); + return NULL; + } + + spin_lock_irq(&bus->reg_lock); + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); + if (hstream->direction != substream->stream) + continue; + + hda_stream = hstream_to_sof_hda_stream(hext_stream); + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); + + /* check if link is available */ + if (!hext_stream->link_locked) { + /* + * choose the first available link for platforms that do not have the + * PROCEN_FMT_QUIRK set. + */ + if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) { + res = hext_stream; + break; + } + + if (hstream->opened) { + /* + * check if the stream tag matches the stream + * tag of one of the connected FEs + */ + if (hda_check_fes(rtd, stream_dir, + hstream->stream_tag)) { + res = hext_stream; + break; + } + } else { + res = hext_stream; + + /* + * This must be a hostless stream. + * So reserve the host DMA channel. + */ + hda_stream->host_reserved = 1; + break; + } + } + } + + if (res) { + /* Make sure that host and link DMA is decoupled. */ + snd_hdac_ext_stream_decouple_locked(bus, res, true); + + res->link_locked = 1; + res->link_substream = substream; + } + spin_unlock_irq(&bus->reg_lock); + + return res; +} + +static struct hdac_ext_stream *hda_get_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream) +{ + return snd_soc_dai_get_dma_data(cpu_dai, substream); +} + +static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *hext_stream; + + hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream); + if (!hext_stream) + return NULL; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream); + + return hext_stream; +} + +static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream); + + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); +} + +static void hda_setup_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, + unsigned int format_val) +{ + snd_hdac_ext_stream_setup(hext_stream, format_val); +} + +static void hda_reset_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream) +{ + snd_hdac_ext_stream_reset(hext_stream); +} + +static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + struct snd_sof_widget *swidget; + struct snd_soc_dapm_widget *w; + int ret; + + w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + swidget = w->dobj.private; + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + + pipeline->state = SOF_IPC4_PIPE_PAUSED; + break; + default: + dev_err(sdev->dev, "unknown trigger command %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd) +{ + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_stream_start(hext_stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_hdac_ext_stream_clear(hext_stream); + break; + default: + dev_err(sdev->dev, "unknown trigger command %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + struct snd_sof_widget *swidget; + struct snd_soc_dapm_widget *w; + int ret; + + w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + swidget = w->dobj.private; + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_PAUSED; + } + + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_RUNNING); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_RUNNING; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + { + ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, + SOF_IPC4_PIPE_RESET); + if (ret < 0) + return ret; + + pipeline->state = SOF_IPC4_PIPE_RESET; + break; + } + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + dev_err(sdev->dev, "unknown trigger command %d\n", cmd); + return -EINVAL; + } + + return 0; +} + +static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { + .get_hext_stream = hda_get_hext_stream, + .assign_hext_stream = hda_assign_hext_stream, + .release_hext_stream = hda_release_hext_stream, + .setup_hext_stream = hda_setup_hext_stream, + .reset_hext_stream = hda_reset_hext_stream, + .pre_trigger = hda_ipc4_pre_trigger, + .trigger = hda_trigger, + .post_trigger = hda_ipc4_post_trigger +}; + +static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { + .get_hext_stream = hda_get_hext_stream, + .assign_hext_stream = hda_assign_hext_stream, + .release_hext_stream = hda_release_hext_stream, + .setup_hext_stream = hda_setup_hext_stream, + .reset_hext_stream = hda_reset_hext_stream, + .trigger = hda_trigger, +}; + +static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + { + struct snd_sof_dai_config_data data = { 0 }; + + data.dai_data = DMA_CHAN_INVALID; + return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_FREE, &data); + } + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL); + default: + break; + } + + return 0; +} + +static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = { + .get_hext_stream = hda_get_hext_stream, + .assign_hext_stream = hda_assign_hext_stream, + .release_hext_stream = hda_release_hext_stream, + .setup_hext_stream = hda_setup_hext_stream, + .reset_hext_stream = hda_reset_hext_stream, + .trigger = hda_trigger, + .post_trigger = hda_ipc3_post_trigger, +}; + +static struct hdac_ext_stream * +hda_dspless_get_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + + return stream_to_hdac_ext_stream(hstream); +} + +static void hda_dspless_setup_hext_stream(struct snd_sof_dev *sdev, + struct hdac_ext_stream *hext_stream, + unsigned int format_val) +{ + /* + * Save the format_val which was adjusted by the maxbps of the codec. + * This information is not available on the FE side since there we are + * using dummy_codec. + */ + hext_stream->hstream.format_val = format_val; +} + +static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = { + .get_hext_stream = hda_dspless_get_hext_stream, + .setup_hext_stream = hda_dspless_setup_hext_stream, +}; + +#endif + +const struct hda_dai_widget_dma_ops * +hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + struct snd_sof_dai *sdai; + + if (sdev->dspless_mode_selected) + return &hda_dspless_dma_ops; + + sdai = swidget->private; + + switch (sdev->pdata->ipc_type) { + case SOF_IPC: + { + struct sof_dai_private_data *private = sdai->private; + + if (private->dai_config->type == SOF_DAI_INTEL_HDA) + return &hda_ipc3_dma_ops; + break; + } + case SOF_INTEL_IPC4: + { + struct sof_ipc4_copier *ipc4_copier = sdai->private; + + if (ipc4_copier->dai_type == SOF_DAI_INTEL_HDA) { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (pipeline->use_chain_dma) + return &hda_ipc4_chain_dma_ops; + + return &hda_ipc4_dma_ops; + } + break; + } + default: + break; + } +#endif + return NULL; +} diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 8d9c38d562d3..44a5d94c5050 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -27,119 +27,77 @@ static bool hda_use_tplg_nhlt; module_param_named(sof_use_tplg_nhlt, hda_use_tplg_nhlt, bool, 0444); MODULE_PARM_DESC(sof_use_tplg_nhlt, "SOF topology nhlt override"); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags, + struct snd_sof_dai_config_data *data) +{ + struct snd_sof_widget *swidget = w->dobj.private; + const struct sof_ipc_tplg_ops *tplg_ops; + struct snd_soc_component *component; + struct snd_sof_dev *sdev; + int ret; -struct hda_pipe_params { - u32 ch; - u32 s_freq; - snd_pcm_format_t format; - int link_index; - unsigned int link_bps; -}; + if (!swidget) + return 0; -/* - * This function checks if the host dma channel corresponding - * to the link DMA stream_tag argument is assigned to one - * of the FEs connected to the BE DAI. - */ -static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, - int dir, int stream_tag) -{ - struct snd_pcm_substream *fe_substream; - struct hdac_stream *fe_hstream; - struct snd_soc_dpcm *dpcm; - - for_each_dpcm_fe(rtd, dir, dpcm) { - fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir); - fe_hstream = fe_substream->runtime->private_data; - if (fe_hstream->stream_tag == stream_tag) - return true; + component = swidget->scomp; + sdev = snd_soc_component_get_drvdata(component); + tplg_ops = sof_ipc_get_ops(sdev, tplg); + + if (tplg_ops && tplg_ops->dai_config) { + ret = tplg_ops->dai_config(sdev, swidget, flags, data); + if (ret < 0) { + dev_err(sdev->dev, "DAI config with flags %x failed for widget %s\n", + flags, w->name); + return ret; + } } - return false; + return 0; } -static struct hdac_ext_stream * -hda_link_stream_assign(struct hdac_bus *bus, - struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct sof_intel_hda_stream *hda_stream; - const struct sof_intel_dsp_desc *chip; - struct snd_sof_dev *sdev; - struct hdac_ext_stream *res = NULL; - struct hdac_stream *hstream = NULL; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) - int stream_dir = substream->stream; +static const struct hda_dai_widget_dma_ops * +hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_sof_dai *sdai; - if (!bus->ppcap) { - dev_err(bus->dev, "stream type not supported\n"); - return NULL; - } + /* + * The swidget parameter of hda_select_dai_widget_ops() is ignored in + * case of DSPless mode + */ + if (sdev->dspless_mode_selected) + return hda_select_dai_widget_ops(sdev, NULL); - spin_lock_irq(&bus->reg_lock); - list_for_each_entry(hstream, &bus->stream_list, list) { - struct hdac_ext_stream *hext_stream = - stream_to_hdac_ext_stream(hstream); - if (hstream->direction != substream->stream) - continue; - - hda_stream = hstream_to_sof_hda_stream(hext_stream); - sdev = hda_stream->sdev; - chip = get_chip_info(sdev->pdata); - - /* check if link is available */ - if (!hext_stream->link_locked) { - /* - * choose the first available link for platforms that do not have the - * PROCEN_FMT_QUIRK set. - */ - if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) { - res = hext_stream; - break; - } + sdai = swidget->private; - if (hstream->opened) { - /* - * check if the stream tag matches the stream - * tag of one of the connected FEs - */ - if (hda_check_fes(rtd, stream_dir, - hstream->stream_tag)) { - res = hext_stream; - break; - } - } else { - res = hext_stream; - - /* - * This must be a hostless stream. - * So reserve the host DMA channel. - */ - hda_stream->host_reserved = 1; - break; - } - } - } + /* select and set the DAI widget ops if not set already */ + if (!sdai->platform_private) { + const struct hda_dai_widget_dma_ops *ops = + hda_select_dai_widget_ops(sdev, swidget); + if (!ops) + return NULL; - if (res) { - /* Make sure that host and link DMA is decoupled. */ - snd_hdac_ext_stream_decouple_locked(bus, res, true); + /* check if mandatory ops are set */ + if (!ops || !ops->get_hext_stream) + return NULL; - res->link_locked = 1; - res->link_substream = substream; + sdai->platform_private = ops; } - spin_unlock_irq(&bus->reg_lock); - return res; + return sdai->platform_private; } static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct hdac_ext_stream *hext_stream, struct snd_soc_dai *cpu_dai, - struct snd_soc_dai *codec_dai, - bool trigger_suspend_stop) + struct snd_soc_dai *codec_dai) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); struct hdac_stream *hstream = &hext_stream->hstream; struct hdac_bus *bus = hstream->bus; struct sof_intel_hda_stream *hda_stream; @@ -150,15 +108,14 @@ static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, if (!hlink) return -EINVAL; - if (trigger_suspend_stop) - snd_hdac_ext_stream_clear(hext_stream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { stream_tag = hdac_stream(hext_stream)->stream_tag; snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag); } - snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); - snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); + + if (ops->release_hext_stream) + ops->release_hext_stream(sdev, cpu_dai, substream); + hext_stream->link_prepared = 0; /* free the host DMA channel reserved by hostless streams */ @@ -168,50 +125,20 @@ static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, return 0; } -static int hda_link_dma_params(struct hdac_ext_stream *hext_stream, - struct hda_pipe_params *params) -{ - struct hdac_stream *hstream = &hext_stream->hstream; - unsigned char stream_tag = hstream->stream_tag; - struct hdac_bus *bus = hstream->bus; - struct hdac_ext_link *hlink; - unsigned int format_val; - - snd_hdac_ext_stream_reset(hext_stream); - - format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, - params->format, - params->link_bps, 0); - - dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", - format_val, params->s_freq, params->ch, params->format); - - snd_hdac_ext_stream_setup(hext_stream, format_val); - - if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { - list_for_each_entry(hlink, &bus->hlink_list, list) { - if (hlink->index == params->link_index) - snd_hdac_ext_bus_link_set_stream_id(hlink, - stream_tag); - } - } - - hext_stream->link_prepared = 1; - - return 0; -} - static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) + struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai) { + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct hda_pipe_params p_params = {0}; struct hdac_ext_stream *hext_stream; + struct hdac_stream *hstream; struct hdac_ext_link *hlink; struct snd_sof_dev *sdev; struct hdac_bus *bus; + unsigned int format_val; + unsigned int link_bps; + int stream_tag; sdev = snd_soc_component_get_drvdata(cpu_dai->component); bus = sof_to_bus(sdev); @@ -220,337 +147,196 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, if (!hlink) return -EINVAL; - hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); - if (!hext_stream) { - hext_stream = hda_link_stream_assign(bus, substream); - if (!hext_stream) - return -EBUSY; + hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream); - snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream); + if (!hext_stream) { + if (ops->assign_hext_stream) + hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream); } + if (!hext_stream) + return -EBUSY; + + hstream = &hext_stream->hstream; + stream_tag = hstream->stream_tag; + + if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) + snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag); + /* set the hdac_stream in the codec dai */ snd_soc_dai_set_stream(codec_dai, hdac_stream(hext_stream), substream->stream); - p_params.ch = params_channels(params); - p_params.s_freq = params_rate(params); - p_params.link_index = hlink->index; - p_params.format = params_format(params); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - p_params.link_bps = codec_dai->driver->playback.sig_bits; + link_bps = codec_dai->driver->playback.sig_bits; else - p_params.link_bps = codec_dai->driver->capture.sig_bits; + link_bps = codec_dai->driver->capture.sig_bits; - return hda_link_dma_params(hext_stream, &p_params); -} + if (ops->reset_hext_stream) + ops->reset_hext_stream(sdev, hext_stream); -static int hda_link_dma_prepare(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - int stream = substream->stream; + format_val = snd_hdac_calc_stream_format(params_rate(params), params_channels(params), + params_format(params), link_bps, 0); - return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params); -} + dev_dbg(bus->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val, + params_rate(params), params_channels(params), params_format(params)); -static int hda_link_dma_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); - int ret; + if (ops->setup_hext_stream) + ops->setup_hext_stream(sdev, hext_stream, format_val); - if (!hext_stream) - return 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - snd_hdac_ext_stream_start(hext_stream); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - ret = hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, true); - if (ret < 0) - return ret; - - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - snd_hdac_ext_stream_clear(hext_stream); + hext_stream->link_prepared = 1; - break; - default: - return -EINVAL; - } return 0; } -static int hda_link_dma_hw_free(struct snd_pcm_substream *substream) +static int hda_link_dma_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct hdac_ext_stream *hext_stream; - - hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); - if (!hext_stream) - return 0; - - return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, false); -} - -static int hda_dai_widget_update(struct snd_soc_dapm_widget *w, - int channel, bool widget_setup) -{ - struct snd_sof_dai_config_data data; - - data.dai_data = channel; - - /* set up/free DAI widget and send DAI_CONFIG IPC */ - if (widget_setup) - return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data); + int stream = substream->stream; - return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); + return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params, cpu_dai); } -static int hda_dai_hw_params_update(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct hdac_ext_stream *hext_stream; - struct snd_soc_dapm_widget *w; - int stream_tag; - hext_stream = snd_soc_dai_get_dma_data(dai, substream); - if (!hext_stream) + if (!ops) { + dev_err(sdev->dev, "DAI widget ops not set\n"); return -EINVAL; + } - stream_tag = hdac_stream(hext_stream)->stream_tag; - - w = snd_soc_dai_get_widget(dai, substream->stream); + hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream); + if (!hext_stream) + return 0; - /* set up the DAI widget and send the DAI_CONFIG with the new tag */ - return hda_dai_widget_update(w, stream_tag - 1, true); + return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai); } static int hda_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct hdac_ext_stream *hext_stream = - snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); + struct hdac_ext_stream *hext_stream; + struct snd_sof_dai_config_data data = { 0 }; + unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS; int ret; + if (!ops) { + dev_err(sdev->dev, "DAI widget ops not set\n"); + return -EINVAL; + } + + hext_stream = ops->get_hext_stream(sdev, dai, substream); if (hext_stream && hext_stream->link_prepared) return 0; - ret = hda_link_dma_hw_params(substream, params); + ret = hda_link_dma_hw_params(substream, params, dai); if (ret < 0) return ret; - return hda_dai_hw_params_update(substream, params, dai); -} - - -static int hda_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w) -{ - struct snd_sof_widget *swidget = w->dobj.private; - struct snd_soc_component *component = swidget->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - int ret = 0; + hext_stream = ops->get_hext_stream(sdev, dai, substream); - if (tplg_ops->dai_config) { - ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL); - if (ret < 0) - dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__, - w->name); - } + flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; + data.dai_data = hdac_stream(hext_stream)->stream_tag - 1; - return ret; + return hda_dai_config(w, flags, &data); } static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct hdac_ext_stream *hext_stream = - snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - int stream = substream->stream; + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); + struct hdac_ext_stream *hext_stream; + struct snd_sof_dai_config_data data = { 0 }; + unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS; int ret; + hext_stream = ops->get_hext_stream(sdev, dai, substream); if (hext_stream && hext_stream->link_prepared) return 0; dev_dbg(sdev->dev, "prepare stream dir %d\n", substream->stream); - ret = hda_link_dma_prepare(substream); - if (ret < 0) - return ret; - - return hda_dai_hw_params_update(substream, &rtd->dpcm[stream].hw_params, dai); -} - -static int hda_dai_hw_free_ipc(int stream, /* direction */ - struct snd_soc_dai *dai) -{ - struct snd_soc_dapm_widget *w; - - w = snd_soc_dai_get_widget(dai, stream); - - /* free the link DMA channel in the FW and the DAI widget */ - return hda_dai_widget_update(w, DMA_CHAN_INVALID, false); -} - -static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) -{ - struct snd_soc_dapm_widget *w; - int ret; - - dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, - dai->name, substream->stream); - - ret = hda_link_dma_trigger(substream, cmd); + ret = hda_link_dma_prepare(substream, dai); if (ret < 0) return ret; - w = snd_soc_dai_get_widget(dai, substream->stream); + hext_stream = ops->get_hext_stream(sdev, dai, substream); - switch (cmd) { - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - /* - * free DAI widget during stop/suspend to keep widget use_count's balanced. - */ - ret = hda_dai_hw_free_ipc(substream->stream, dai); - if (ret < 0) - return ret; - - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - ret = hda_dai_config_pause_push_ipc(w); - if (ret < 0) - return ret; - break; + flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; + data.dai_data = hdac_stream(hext_stream)->stream_tag - 1; - default: - break; - } - return 0; + return hda_dai_config(w, flags, &data); } /* * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes * (over IPC channel) and DMA state change (direct host register changes). */ -static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) +static int hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct snd_sof_widget *pipe_widget; - struct sof_ipc4_pipeline *pipeline; + const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); + struct hdac_ext_stream *hext_stream; struct snd_soc_pcm_runtime *rtd; - struct snd_sof_widget *swidget; - struct snd_soc_dapm_widget *w; struct snd_soc_dai *codec_dai; - struct snd_soc_dai *cpu_dai; int ret; dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, dai->name, substream->stream); + hext_stream = ops->get_hext_stream(sdev, dai, substream); + if (!hext_stream) + return -EINVAL; + rtd = asoc_substream_to_rtd(substream); - cpu_dai = asoc_rtd_to_cpu(rtd, 0); codec_dai = asoc_rtd_to_codec(rtd, 0); - w = snd_soc_dai_get_widget(dai, substream->stream); - swidget = w->dobj.private; - pipe_widget = swidget->spipe->pipe_widget; - pipeline = pipe_widget->private; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - snd_hdac_ext_stream_start(hext_stream); - if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); - if (ret < 0) - return ret; - pipeline->state = SOF_IPC4_PIPE_PAUSED; - } - - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_RUNNING); + if (ops->pre_trigger) { + ret = ops->pre_trigger(sdev, dai, substream, cmd); if (ret < 0) return ret; - pipeline->state = SOF_IPC4_PIPE_RUNNING; - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - { - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); + } + + if (ops->trigger) { + ret = ops->trigger(sdev, dai, substream, cmd); if (ret < 0) return ret; + } - pipeline->state = SOF_IPC4_PIPE_PAUSED; - - snd_hdac_ext_stream_clear(hext_stream); - - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_RESET); + if (ops->post_trigger) { + ret = ops->post_trigger(sdev, dai, substream, cmd); if (ret < 0) return ret; + } - pipeline->state = SOF_IPC4_PIPE_RESET; - - ret = hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai, false); + switch (cmd) { + case SNDRV_PCM_TRIGGER_SUSPEND: + ret = hda_link_dma_cleanup(substream, hext_stream, dai, codec_dai); if (ret < 0) { dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__); return ret; } break; - } - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - { - ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_PAUSED); - if (ret < 0) - return ret; - - pipeline->state = SOF_IPC4_PIPE_PAUSED; - - snd_hdac_ext_stream_clear(hext_stream); - break; - } default: - dev_err(sdev->dev, "%s: unknown trigger command %d\n", __func__, cmd); - return -EINVAL; + break; } return 0; } -static int hda_dai_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - int ret; - - ret = hda_link_dma_hw_free(substream); - if (ret < 0) - return ret; - - return hda_dai_hw_free_ipc(substream->stream, dai); -} - -static const struct snd_soc_dai_ops ipc3_hda_dai_ops = { +static const struct snd_soc_dai_ops hda_dai_ops = { .hw_params = hda_dai_hw_params, .hw_free = hda_dai_hw_free, - .trigger = ipc3_hda_dai_trigger, + .trigger = hda_dai_trigger, .prepare = hda_dai_prepare, }; @@ -573,186 +359,62 @@ static int hda_dai_suspend(struct hdac_bus *bus) * explicitly during suspend. */ if (hext_stream->link_substream) { - struct snd_soc_dai *cpu_dai; + const struct hda_dai_widget_dma_ops *ops; + struct snd_sof_widget *swidget; + struct snd_soc_dapm_widget *w; struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai; + struct snd_sof_dev *sdev; + struct snd_sof_dai *sdai; rtd = asoc_substream_to_rtd(hext_stream->link_substream); cpu_dai = asoc_rtd_to_cpu(rtd, 0); codec_dai = asoc_rtd_to_codec(rtd, 0); + w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction); + swidget = w->dobj.private; + sdev = snd_soc_component_get_drvdata(swidget->scomp); + sdai = swidget->private; + ops = sdai->platform_private; ret = hda_link_dma_cleanup(hext_stream->link_substream, hext_stream, - cpu_dai, codec_dai, false); + cpu_dai, codec_dai); if (ret < 0) return ret; - /* for consistency with TRIGGER_SUSPEND we free DAI resources */ - ret = hda_dai_hw_free_ipc(hdac_stream(hext_stream)->direction, cpu_dai); - if (ret < 0) - return ret; + /* for consistency with TRIGGER_SUSPEND */ + if (ops->post_trigger) { + ret = ops->post_trigger(sdev, cpu_dai, + hext_stream->link_substream, + SNDRV_PCM_TRIGGER_SUSPEND); + if (ret < 0) + return ret; + } } } return 0; } -static const struct snd_soc_dai_ops ipc4_hda_dai_ops = { - .hw_params = hda_dai_hw_params, - .hw_free = hda_dai_hw_free, - .trigger = ipc4_hda_dai_trigger, - .prepare = hda_dai_prepare, -}; - #endif -/* only one flag used so far to harden hw_params/hw_free/trigger/prepare */ -struct ssp_dai_dma_data { - bool setup; -}; - -static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, - bool setup) -{ - struct snd_soc_dapm_widget *w; - - w = snd_soc_dai_get_widget(dai, substream->stream); - - if (setup) - return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL); - - return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL); -} - -static int ssp_dai_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct ssp_dai_dma_data *dma_data; - - dma_data = kzalloc(sizeof(*dma_data), GFP_KERNEL); - if (!dma_data) - return -ENOMEM; - - snd_soc_dai_set_dma_data(dai, substream, dma_data); - - return 0; -} - -static int ssp_dai_setup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai, - bool setup) -{ - struct ssp_dai_dma_data *dma_data; - int ret = 0; - - dma_data = snd_soc_dai_get_dma_data(dai, substream); - if (!dma_data) { - dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); - return -EIO; - } - - if (dma_data->setup != setup) { - ret = ssp_dai_setup_or_free(substream, dai, setup); - if (!ret) - dma_data->setup = setup; - } - return ret; -} - -static int ssp_dai_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) -{ - /* params are ignored for now */ - return ssp_dai_setup(substream, dai, true); -} - -static int ssp_dai_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - /* - * the SSP will only be reconfigured during resume operations and - * not in case of xruns - */ - return ssp_dai_setup(substream, dai, true); -} - -static int ipc3_ssp_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) -{ - if (cmd != SNDRV_PCM_TRIGGER_SUSPEND) - return 0; - - return ssp_dai_setup(substream, dai, false); -} - -static int ssp_dai_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - return ssp_dai_setup(substream, dai, false); -} - -static void ssp_dai_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct ssp_dai_dma_data *dma_data; - - dma_data = snd_soc_dai_get_dma_data(dai, substream); - if (!dma_data) { - dev_err(dai->dev, "%s: failed to get dma_data\n", __func__); - return; - } - snd_soc_dai_set_dma_data(dai, substream, NULL); - kfree(dma_data); -} - -static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = { - .startup = ssp_dai_startup, - .hw_params = ssp_dai_hw_params, - .prepare = ssp_dai_prepare, - .trigger = ipc3_ssp_dai_trigger, - .hw_free = ssp_dai_hw_free, - .shutdown = ssp_dai_shutdown, -}; - void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) { int i; - switch (sdev->pdata->ipc_type) { - case SOF_IPC: - for (i = 0; i < ops->num_drv; i++) { - if (strstr(ops->drv[i].name, "SSP")) { - ops->drv[i].ops = &ipc3_ssp_dai_ops; - continue; - } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) - if (strstr(ops->drv[i].name, "iDisp") || - strstr(ops->drv[i].name, "Analog") || - strstr(ops->drv[i].name, "Digital")) - ops->drv[i].ops = &ipc3_hda_dai_ops; -#endif - } - break; - case SOF_INTEL_IPC4: - { - struct sof_ipc4_fw_data *ipc4_data = sdev->private; - - for (i = 0; i < ops->num_drv; i++) { + for (i = 0; i < ops->num_drv; i++) { #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) - if (strstr(ops->drv[i].name, "iDisp") || - strstr(ops->drv[i].name, "Analog") || - strstr(ops->drv[i].name, "Digital")) - ops->drv[i].ops = &ipc4_hda_dai_ops; + if (strstr(ops->drv[i].name, "iDisp") || + strstr(ops->drv[i].name, "Analog") || + strstr(ops->drv[i].name, "Digital")) + ops->drv[i].ops = &hda_dai_ops; #endif - } + } - if (!hda_use_tplg_nhlt) - ipc4_data->nhlt = intel_nhlt_init(sdev->dev); + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4 && !hda_use_tplg_nhlt) { + struct sof_ipc4_fw_data *ipc4_data = sdev->private; - break; - } - default: - break; + ipc4_data->nhlt = intel_nhlt_init(sdev->dev); } } diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index a6f2822401e0..44f39a520bb3 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> +#include <sound/hda-mlink.h> #include <trace/events/sof_intel.h> #include "../sof-audio.h" #include "../ops.h" @@ -321,6 +322,9 @@ void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; + if (sdev->dspless_mode_selected) + return; + /* enable IPC DONE and BUSY interrupts */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY, @@ -336,6 +340,9 @@ void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; + if (sdev->dspless_mode_selected) + return; + /* disable IPC interrupt */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, HDA_DSP_ADSPIC_IPC, 0); @@ -567,31 +574,11 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev) * is called again either because of a new IPC sent to the DSP or * during system suspend/resume. */ -int hda_dsp_set_power_state(struct snd_sof_dev *sdev, - const struct sof_dsp_power_state *target_state) +static int hda_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) { int ret = 0; - /* - * When the DSP is already in D0I3 and the target state is D0I3, - * it could be the case that the DSP is in D0I3 during S0 - * and the system is suspending to S0Ix. Therefore, - * hda_dsp_set_D0_state() must be called to disable trace DMA - * by sending the PM_GATE IPC to the FW. - */ - if (target_state->substate == SOF_HDA_DSP_PM_D0I3 && - sdev->system_suspend_target == SOF_SUSPEND_S0IX) - goto set_state; - - /* - * For all other cases, return without doing anything if - * the DSP is already in the target state. - */ - if (target_state->state == sdev->dsp_power_state.state && - target_state->substate == sdev->dsp_power_state.substate) - return 0; - -set_state: switch (target_state->state) { case SOF_DSP_PM_D0: ret = hda_dsp_set_D0_state(sdev, target_state); @@ -623,6 +610,42 @@ set_state: return ret; } +int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + /* + * When the DSP is already in D0I3 and the target state is D0I3, + * it could be the case that the DSP is in D0I3 during S0 + * and the system is suspending to S0Ix. Therefore, + * hda_dsp_set_D0_state() must be called to disable trace DMA + * by sending the PM_GATE IPC to the FW. + */ + if (target_state->substate == SOF_HDA_DSP_PM_D0I3 && + sdev->system_suspend_target == SOF_SUSPEND_S0IX) + return hda_dsp_set_power_state(sdev, target_state); + + /* + * For all other cases, return without doing anything if + * the DSP is already in the target state. + */ + if (target_state->state == sdev->dsp_power_state.state && + target_state->substate == sdev->dsp_power_state.substate) + return 0; + + return hda_dsp_set_power_state(sdev, target_state); +} + +int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + /* Return without doing anything if the DSP is already in the target state */ + if (target_state->state == sdev->dsp_power_state.state && + target_state->substate == sdev->dsp_power_state.substate) + return 0; + + return hda_dsp_set_power_state(sdev, target_state); +} + /* * Audio DSP states may transform as below:- * @@ -681,6 +704,9 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) /* power down all hda links */ hda_bus_ml_suspend(bus); + if (sdev->dspless_mode_selected) + goto skip_dsp; + ret = chip->power_down_dsp(sdev); if (ret < 0) { dev_err(sdev->dev, "failed to power down DSP during suspend\n"); @@ -694,6 +720,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) /* disable ppcap interrupt */ hda_dsp_ctrl_ppcap_enable(sdev, false); hda_dsp_ctrl_ppcap_int_enable(sdev, false); +skip_dsp: /* disable hda bus irq and streams */ hda_dsp_ctrl_stop_chip(sdev); @@ -744,9 +771,11 @@ static int hda_resume(struct snd_sof_dev *sdev, bool runtime_resume) hda_codec_jack_check(sdev); } - /* enable ppcap interrupt */ - hda_dsp_ctrl_ppcap_enable(sdev, true); - hda_dsp_ctrl_ppcap_int_enable(sdev, true); + if (!sdev->dspless_mode_selected) { + /* enable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, true); + hda_dsp_ctrl_ppcap_int_enable(sdev, true); + } cleanup: /* display codec can powered off after controller init */ @@ -788,7 +817,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev) } /* restore L1SEN bit */ - if (hda->l1_support_changed) + if (hda->l1_disabled) snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, 0); @@ -843,8 +872,10 @@ int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev) }; int ret; - /* cancel any attempt for DSP D0I3 */ - cancel_delayed_work_sync(&hda->d0i3_work); + if (!sdev->dspless_mode_selected) { + /* 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); @@ -866,8 +897,10 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) }; int ret; - /* cancel any attempt for DSP D0I3 */ - cancel_delayed_work_sync(&hda->d0i3_work); + if (!sdev->dspless_mode_selected) { + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); + } if (target_state == SOF_DSP_PM_D0) { /* Set DSP power state */ @@ -880,11 +913,9 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) } /* enable L1SEN to make sure the system can enter S0Ix */ - hda->l1_support_changed = - 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 (hda->l1_disabled) + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2, + HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); /* stop the CORB/RIRB DMA if it is On */ hda_codec_suspend_cmd_io(sdev); diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index df541b22b2d2..a838dddb1d32 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -355,6 +355,9 @@ bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) bool ret = false; u32 irq_status; + if (sdev->dspless_mode_selected) + return false; + /* store status */ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); trace_sof_intel_hda_irq_ipc_check(sdev, irq_status); diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index d680562edb35..50ce6b190002 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -321,13 +321,13 @@ int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) { struct hdac_ext_stream *iccmax_stream; - struct hdac_bus *bus = sof_to_bus(sdev); struct snd_dma_buffer dmab_bdl; int ret, ret1; u8 original_gb; /* save the original LTRP guardband value */ - original_gb = snd_hdac_chip_readb(bus, VS_LTRP) & HDA_VS_INTEL_LTRP_GB_MASK; + original_gb = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP) & + HDA_VS_INTEL_LTRP_GB_MASK; /* * Prepare capture stream for ICCMAX. We do not need to store @@ -356,7 +356,8 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) } /* restore the original guardband value after FW boot */ - snd_hdac_chip_updateb(bus, VS_LTRP, HDA_VS_INTEL_LTRP_GB_MASK, original_gb); + snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP, + HDA_VS_INTEL_LTRP_GB_MASK, original_gb); return ret; } @@ -556,7 +557,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, goto cleanup; } - ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); ret1 = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP); if (ret1 < 0) { diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c index 76ab9a2e7bb3..775582ab7494 100644 --- a/sound/soc/sof/intel/hda-mlink.c +++ b/sound/soc/sof/intel/hda-mlink.c @@ -12,49 +12,746 @@ #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> +#include <sound/hda-mlink.h> -#include <linux/acpi.h> +#include <linux/bitfield.h> #include <linux/module.h> -#include <linux/soundwire/sdw.h> -#include <linux/soundwire/sdw_intel.h> -#include <sound/intel-dsp-config.h> -#include <sound/intel-nhlt.h> -#include <sound/sof.h> -#include <sound/sof/xtensa.h> -#include "../sof-audio.h" -#include "../sof-pci-dev.h" -#include "../ops.h" -#include "hda.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK) -void hda_bus_ml_get_capabilities(struct hdac_bus *bus) +/** + * struct hdac_ext2_link - HDAudio extended+alternate link + * + * @hext_link: hdac_ext_link + * @alt: flag set for alternate extended links + * @intc: boolean for interrupt capable + * @ofls: boolean for offload support + * @lss: boolean for link synchronization capabilities + * @slcount: sublink count + * @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines) + * @elver: extended link version + * @leptr: extended link pointer + * @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits + * in LCTL register + * @base_ptr: pointer to shim/ip/shim_vs space + * @instance_offset: offset between each of @slcount instances managed by link + * @shim_offset: offset to SHIM register base + * @ip_offset: offset to IP register base + * @shim_vs_offset: offset to vendor-specific (VS) SHIM base + */ +struct hdac_ext2_link { + struct hdac_ext_link hext_link; + + /* read directly from LCAP register */ + bool alt; + bool intc; + bool ofls; + bool lss; + int slcount; + int elid; + int elver; + u32 leptr; + + struct mutex eml_lock; /* prevent concurrent access to e.g. CPA/SPA */ + + /* internal values computed from LCAP contents */ + void __iomem *base_ptr; + u32 instance_offset; + u32 shim_offset; + u32 ip_offset; + u32 shim_vs_offset; +}; + +#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link) + +#define AZX_REG_SDW_INSTANCE_OFFSET 0x8000 +#define AZX_REG_SDW_SHIM_OFFSET 0x0 +#define AZX_REG_SDW_IP_OFFSET 0x100 +#define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000 + +/* only one instance supported */ +#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0 +#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100 +#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000 + +#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000 +#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0 +#define AZX_REG_INTEL_SSP_IP_OFFSET 0x100 +#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00 + +/* only one instance supported */ +#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0 +#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100 +#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00 + +/* HDAML section - this part follows sequences in the hardware specification, + * including naming conventions and the use of the hdaml_ prefix. + * The code is intentionally minimal with limited dependencies on frameworks or + * helpers. Locking and scanning lists is handled at a higher level + */ + +static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link, + void __iomem *ml_addr, int link_idx) +{ + struct hdac_ext_link *hlink = &h2link->hext_link; + u32 base_offset; + + hlink->lcaps = readl(ml_addr + AZX_REG_ML_LCAP); + + h2link->alt = FIELD_GET(AZX_ML_HDA_LCAP_ALT, hlink->lcaps); + + /* handle alternate extensions */ + if (!h2link->alt) { + h2link->slcount = 1; + + /* + * LSDIID is initialized by hardware for HDaudio link, + * it needs to be setup by software for alternate links + */ + hlink->lsdiid = readw(ml_addr + AZX_REG_ML_LSDIID); + + dev_dbg(dev, "Link %d: HDAudio - lsdiid=%d\n", + link_idx, hlink->lsdiid); + + return 0; + } + + h2link->intc = FIELD_GET(AZX_ML_HDA_LCAP_INTC, hlink->lcaps); + h2link->ofls = FIELD_GET(AZX_ML_HDA_LCAP_OFLS, hlink->lcaps); + h2link->lss = FIELD_GET(AZX_ML_HDA_LCAP_LSS, hlink->lcaps); + + /* read slcount (increment due to zero-based hardware representation */ + h2link->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1; + dev_dbg(dev, "Link %d: HDAudio extended - sublink count %d\n", + link_idx, h2link->slcount); + + /* find IP ID and offsets */ + h2link->leptr = readl(hlink->ml_addr + AZX_REG_ML_LEPTR); + + h2link->elid = FIELD_GET(AZX_REG_ML_LEPTR_ID, h2link->leptr); + + base_offset = FIELD_GET(AZX_REG_ML_LEPTR_PTR, h2link->leptr); + h2link->base_ptr = hlink->ml_addr + base_offset; + + switch (h2link->elid) { + case AZX_REG_ML_LEPTR_ID_SDW: + h2link->shim_offset = AZX_REG_SDW_SHIM_OFFSET; + h2link->ip_offset = AZX_REG_SDW_IP_OFFSET; + h2link->shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET; + dev_dbg(dev, "Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x\n", + link_idx, base_offset); + break; + case AZX_REG_ML_LEPTR_ID_INTEL_DMIC: + h2link->shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET; + h2link->ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET; + h2link->shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET; + dev_dbg(dev, "Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x\n", + link_idx, base_offset); + break; + case AZX_REG_ML_LEPTR_ID_INTEL_SSP: + h2link->shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET; + h2link->ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET; + h2link->shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET; + dev_dbg(dev, "Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x\n", + link_idx, base_offset); + break; + case AZX_REG_ML_LEPTR_ID_INTEL_UAOL: + h2link->shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET; + h2link->ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET; + h2link->shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET; + dev_dbg(dev, "Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x\n", + link_idx, base_offset); + break; + default: + dev_err(dev, "Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value\n", + link_idx, h2link->elid); + return -EINVAL; + } + return 0; +} + +/* + * Hardware recommendations are to wait ~10us before checking any hardware transition + * reported by bits changing status. + * This value does not need to be super-precise, a slack of 5us is perfectly acceptable. + * The worst-case is about 1ms before reporting an issue + */ +#define HDAML_POLL_DELAY_MIN_US 10 +#define HDAML_POLL_DELAY_SLACK_US 5 +#define HDAML_POLL_DELAY_RETRY 100 + +static int check_sublink_power(u32 __iomem *lctl, int sublink, bool enabled) { - if (bus->mlcap) - snd_hdac_ext_bus_get_ml_capabilities(bus); + int mask = BIT(sublink) << AZX_ML_LCTL_CPA_SHIFT; + int retry = HDAML_POLL_DELAY_RETRY; + u32 val; + + usleep_range(HDAML_POLL_DELAY_MIN_US, + HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US); + do { + val = readl(lctl); + if (enabled) { + if (val & mask) + return 0; + } else { + if (!(val & mask)) + return 0; + } + usleep_range(HDAML_POLL_DELAY_MIN_US, + HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US); + + } while (--retry); + + return -EIO; } -void hda_bus_ml_free(struct hdac_bus *bus) +static int hdaml_link_init(u32 __iomem *lctl, int sublink) +{ + u32 val; + u32 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT; + + val = readl(lctl); + val |= mask; + + writel(val, lctl); + + return check_sublink_power(lctl, sublink, true); +} + +static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink) +{ + u32 val; + u32 mask; + + val = readl(lctl); + mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT; + val &= ~mask; + + writel(val, lctl); + + return check_sublink_power(lctl, sublink, false); +} + +static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable) +{ + u32 val; + + val = readl(lctl); + if (enable) + val |= AZX_ML_LCTL_INTEN; + else + val &= ~AZX_ML_LCTL_INTEN; + + writel(val, lctl); +} + +static bool hdaml_link_check_interrupt(u32 __iomem *lctl) +{ + u32 val; + + val = readl(lctl); + + return val & AZX_ML_LCTL_INTSTS; +} + +static int hdaml_wait_bit(void __iomem *base, int offset, u32 mask, u32 target) +{ + int timeout = HDAML_POLL_DELAY_RETRY; + u32 reg_read; + + do { + reg_read = readl(base + offset); + if ((reg_read & mask) == target) + return 0; + + timeout--; + usleep_range(HDAML_POLL_DELAY_MIN_US, + HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US); + } while (timeout != 0); + + return -EAGAIN; +} + +static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd) +{ + u32 val; + + val = readl(lsync); + val &= ~AZX_REG_ML_LSYNC_SYNCPRD; + val |= (syncprd & AZX_REG_ML_LSYNC_SYNCPRD); + + /* + * set SYNCPU but do not wait. The bit is cleared by hardware when + * the link becomes active. + */ + val |= AZX_REG_ML_LSYNC_SYNCPU; + + writel(val, lsync); +} + +static int hdaml_link_wait_syncpu(u32 __iomem *lsync) +{ + return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0); +} + +static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink) +{ + u32 val; + + val = readl(lsync); + val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink); + + writel(val, lsync); +} + +static void hdaml_link_sync_go(u32 __iomem *lsync) { + u32 val; + + val = readl(lsync); + val |= AZX_REG_ML_LSYNC_SYNCGO; + + writel(val, lsync); +} + +static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask) +{ + u32 val; + + val = readl(lsync); + + return !!(val & cmdsync_mask); +} + +static void hdaml_link_set_lsdiid(u32 __iomem *lsdiid, int dev_num) +{ + u32 val; + + val = readl(lsdiid); + val |= BIT(dev_num); + + writel(val, lsdiid); +} + +static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable) +{ + u32 val = readl(lctl); + + if (enable) + val |= AZX_ML_LCTL_OFLEN; + else + val &= ~AZX_ML_LCTL_OFLEN; + + writel(val, lctl); +} + +/* END HDAML section */ + +static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index) +{ + struct hdac_ext2_link *h2link; struct hdac_ext_link *hlink; + int ret; + + h2link = kzalloc(sizeof(*h2link), GFP_KERNEL); + if (!h2link) + return -ENOMEM; + + /* basic initialization */ + hlink = &h2link->hext_link; + + hlink->index = index; + hlink->bus = bus; + hlink->ml_addr = bus->mlcap + AZX_ML_BASE + (AZX_ML_INTERVAL * index); + + ret = hdaml_lnk_enum(bus->dev, h2link, hlink->ml_addr, index); + if (ret < 0) { + kfree(h2link); + return ret; + } + + mutex_init(&h2link->eml_lock); + + list_add_tail(&hlink->list, &bus->hlink_list); + + /* + * HDaudio regular links are powered-on by default, the + * refcount needs to be initialized. + */ + if (!h2link->alt) + hlink->ref_count = 1; + + return 0; +} + +int hda_bus_ml_init(struct hdac_bus *bus) +{ + u32 link_count; + int ret; + int i; + + if (!bus->mlcap) + return 0; + + link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count); + + for (i = 0; i < link_count; i++) { + ret = hda_ml_alloc_h2link(bus, i); + if (ret < 0) { + hda_bus_ml_free(bus); + return ret; + } + } + return 0; +} +EXPORT_SYMBOL_NS(hda_bus_ml_init, SND_SOC_SOF_HDA_MLINK); + +void hda_bus_ml_free(struct hdac_bus *bus) +{ + struct hdac_ext_link *hlink, *_h; + struct hdac_ext2_link *h2link; if (!bus->mlcap) return; - while (!list_empty(&bus->hlink_list)) { - hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list); + list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) { list_del(&hlink->list); - kfree(hlink); + h2link = hdac_ext_link_to_ext2(hlink); + + mutex_destroy(&h2link->eml_lock); + kfree(h2link); + } +} +EXPORT_SYMBOL_NS(hda_bus_ml_free, SND_SOC_SOF_HDA_MLINK); + +static struct hdac_ext2_link * +find_ext2_link(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext_link *hlink; + + list_for_each_entry(hlink, &bus->hlink_list, list) { + struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); + + if (h2link->alt == alt && h2link->elid == elid) + return h2link; } + + return NULL; +} + +int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return 0; + + return h2link->slcount; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, SND_SOC_SOF_HDA_MLINK); + +void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return; + + if (!h2link->intc) + return; + + hlink = &h2link->hext_link; + + mutex_lock(&h2link->eml_lock); + + hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable); + + mutex_unlock(&h2link->eml_lock); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, SND_SOC_SOF_HDA_MLINK); + +bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return false; + + if (!h2link->intc) + return false; + + hlink = &h2link->hext_link; + + return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return 0; + + if (!h2link->lss) + return 0; + + hlink = &h2link->hext_link; + + hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd); + + return 0; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd) +{ + return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return 0; + + if (!h2link->lss) + return 0; + + hlink = &h2link->hext_link; + + return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus) +{ + return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK); + +void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return; + + if (!h2link->lss) + return; + + hlink = &h2link->hext_link; + + hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK); + +void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink) +{ + hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return 0; + + if (!h2link->lss) + return 0; + + hlink = &h2link->hext_link; + + hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC); + + return 0; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus) +{ + return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK); + +bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + u32 cmdsync_mask; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return 0; + + if (!h2link->lss) + return 0; + + hlink = &h2link->hext_link; + + cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1, + AZX_REG_ML_LSYNC_CMDSYNC_SHIFT); + + return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC, + cmdsync_mask); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK); + +bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus) +{ + return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK); + +static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink, + bool eml_lock) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + int ret = 0; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return -ENODEV; + + if (sublink >= h2link->slcount) + return -EINVAL; + + hlink = &h2link->hext_link; + + if (eml_lock) + mutex_lock(&h2link->eml_lock); + + if (++hlink->ref_count > 1) + goto skip_init; + + ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink); + +skip_init: + if (eml_lock) + mutex_unlock(&h2link->eml_lock); + + return ret; +} + +int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink) +{ + return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true); } +EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink) +{ + return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, SND_SOC_SOF_HDA_MLINK); + +static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink, + bool eml_lock) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + int ret = 0; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return -ENODEV; + + if (sublink >= h2link->slcount) + return -EINVAL; + + hlink = &h2link->hext_link; + + if (eml_lock) + mutex_lock(&h2link->eml_lock); + + if (--hlink->ref_count > 0) + goto skip_shutdown; + + ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink); + +skip_shutdown: + if (eml_lock) + mutex_unlock(&h2link->eml_lock); + + return ret; +} + +int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink) +{ + return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink) +{ + return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink) +{ + return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink) +{ + return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink); +} +EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW); + if (!h2link) + return -ENODEV; + + hlink = &h2link->hext_link; + + mutex_lock(&h2link->eml_lock); + + hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num); + + mutex_unlock(&h2link->eml_lock); + + return 0; +} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, SND_SOC_SOF_HDA_MLINK); void hda_bus_ml_put_all(struct hdac_bus *bus) { struct hdac_ext_link *hlink; - list_for_each_entry(hlink, &bus->hlink_list, list) - snd_hdac_ext_bus_link_put(bus, hlink); + list_for_each_entry(hlink, &bus->hlink_list, list) { + struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); + + if (!h2link->alt) + snd_hdac_ext_bus_link_put(bus, hlink); + } } +EXPORT_SYMBOL_NS(hda_bus_ml_put_all, SND_SOC_SOF_HDA_MLINK); void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { @@ -64,6 +761,7 @@ void hda_bus_ml_reset_losidv(struct hdac_bus *bus) list_for_each_entry(hlink, &bus->hlink_list, list) writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV); } +EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, SND_SOC_SOF_HDA_MLINK); int hda_bus_ml_resume(struct hdac_bus *bus) { @@ -72,7 +770,9 @@ int hda_bus_ml_resume(struct hdac_bus *bus) /* power up links that were active before suspend */ list_for_each_entry(hlink, &bus->hlink_list, list) { - if (hlink->ref_count) { + struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); + + if (!h2link->alt && hlink->ref_count) { ret = snd_hdac_ext_bus_link_power_up(hlink); if (ret < 0) return ret; @@ -80,10 +780,86 @@ int hda_bus_ml_resume(struct hdac_bus *bus) } return 0; } +EXPORT_SYMBOL_NS(hda_bus_ml_resume, SND_SOC_SOF_HDA_MLINK); int hda_bus_ml_suspend(struct hdac_bus *bus) { - return snd_hdac_ext_bus_link_power_down_all(bus); + struct hdac_ext_link *hlink; + int ret; + + list_for_each_entry(hlink, &bus->hlink_list, list) { + struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink); + + if (!h2link->alt) { + ret = snd_hdac_ext_bus_link_power_down(hlink); + if (ret < 0) + return ret; + } + } + return 0; +} +EXPORT_SYMBOL_NS(hda_bus_ml_suspend, SND_SOC_SOF_HDA_MLINK); + +struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid) +{ + struct hdac_ext2_link *h2link; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return NULL; + + return &h2link->eml_lock; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, SND_SOC_SOF_HDA_MLINK); + +struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus) +{ + struct hdac_ext2_link *h2link; + + h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP); + if (!h2link) + return NULL; + + return &h2link->hext_link; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, SND_SOC_SOF_HDA_MLINK); + +struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus) +{ + struct hdac_ext2_link *h2link; + + h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC); + if (!h2link) + return NULL; + + return &h2link->hext_link; +} +EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, SND_SOC_SOF_HDA_MLINK); + +int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable) +{ + struct hdac_ext2_link *h2link; + struct hdac_ext_link *hlink; + + h2link = find_ext2_link(bus, alt, elid); + if (!h2link) + return -ENODEV; + + if (!h2link->ofls) + return 0; + + hlink = &h2link->hext_link; + + mutex_lock(&h2link->eml_lock); + + hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable); + + mutex_unlock(&h2link->eml_lock); + + return 0; } +EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, SND_SOC_SOF_HDA_MLINK); #endif + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index dc0b359ed9b6..981e7b699bdb 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -101,18 +101,23 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct snd_dma_buffer *dmab; int ret; - u32 size, rate, bits; - - size = params_buffer_bytes(params); - rate = hda_dsp_get_mult_div(sdev, params_rate(params)); - bits = hda_dsp_get_bits(sdev, params_width(params)); hstream->substream = substream; dmab = substream->runtime->dma_buffer_p; - hstream->format_val = rate | bits | (params_channels(params) - 1); - hstream->bufsize = size; + /* + * Use the codec required format val (which is link_bps adjusted) when + * the DSP is not in use + */ + if (!sdev->dspless_mode_selected) { + u32 rate = hda_dsp_get_mult_div(sdev, params_rate(params)); + u32 bits = hda_dsp_get_bits(sdev, params_width(params)); + + hstream->format_val = rate | bits | (params_channels(params) - 1); + } + + hstream->bufsize = params_buffer_bytes(params); hstream->period_bytes = params_period_bytes(params); hstream->no_period_wakeup = (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && @@ -249,6 +254,11 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS); + /* Only S16 and S32 supported by HDA hardware when used without DSP */ + if (sdev->dspless_mode_selected) + snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32); + /* binding pcm substream to hda stream */ substream->runtime->private_data = &dsp_stream->hstream; return 0; diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 7f0fd05a96e6..8de422604ad5 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -182,6 +182,8 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, struct hdac_ext_stream * hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) { + const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; struct hdac_ext_stream *hext_stream = NULL; @@ -220,12 +222,15 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) /* * 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. + * in xruns during pause/release in capture scenarios. This is not needed for the ACE IP. */ - if (!(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) + if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && + !(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); + hda->l1_disabled = true; + } return hext_stream; } @@ -233,6 +238,8 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) /* free a stream */ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) { + const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; struct hdac_ext_stream *hext_stream; @@ -264,9 +271,11 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) spin_unlock_irq(&bus->reg_lock); /* Enable DMI L1 if permitted */ - if (dmi_l1_enable) + if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && 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); + hda->l1_disabled = false; + } if (!found) { dev_err(sdev->dev, "%s: stream_tag %d not opened!\n", @@ -328,7 +337,13 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, /* cmd must be for audio stream */ switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!sdev->dspless_mode_selected) + break; + fallthrough; case SNDRV_PCM_TRIGGER_START: + if (hstream->running) + break; + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, 1 << hstream->index, 1 << hstream->index); @@ -351,8 +366,11 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, hstream->running = true; break; - case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!sdev->dspless_mode_selected) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, @@ -476,9 +494,8 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, { const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata); struct hdac_bus *bus = sof_to_bus(sdev); - struct hdac_stream *hstream = &hext_stream->hstream; - int sd_offset = SOF_STREAM_SD_OFFSET(hstream); - int ret; + struct hdac_stream *hstream; + int sd_offset, ret; u32 dma_start = SOF_HDA_SD_CTL_DMA_START; u32 mask; u32 run; @@ -493,10 +510,14 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, return -ENODEV; } - /* decouple host and link DMA */ - mask = 0x1 << hstream->index; - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, - mask, mask); + hstream = &hext_stream->hstream; + sd_offset = SOF_STREAM_SD_OFFSET(hstream); + mask = BIT(hstream->index); + + /* decouple host and link DMA if the DSP is used */ + if (!sdev->dspless_mode_selected) + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); /* clear stream status */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, @@ -597,11 +618,10 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, * enable decoupled mode */ - if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) { + if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) /* couple host and link DMA, disable DSP features */ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, 0); - } /* program stream format */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, @@ -609,11 +629,10 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, SOF_HDA_ADSP_REG_SD_FORMAT, 0xffff, hstream->format_val); - if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) { + if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) /* decouple host and link DMA, enable DSP features */ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, mask); - } /* program last valid index */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, @@ -666,20 +685,23 @@ int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream = container_of(hstream, struct hdac_ext_stream, hstream); - struct hdac_bus *bus = sof_to_bus(sdev); - u32 mask = 0x1 << hstream->index; int ret; ret = hda_dsp_stream_reset(sdev, hstream); if (ret < 0) return ret; - spin_lock_irq(&bus->reg_lock); - /* couple host and link DMA if link DMA channel is idle */ - if (!hext_stream->link_locked) - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, - SOF_HDA_REG_PP_PPCTL, mask, 0); - spin_unlock_irq(&bus->reg_lock); + if (!sdev->dspless_mode_selected) { + struct hdac_bus *bus = sof_to_bus(sdev); + u32 mask = BIT(hstream->index); + + spin_lock_irq(&bus->reg_lock); + /* couple host and link DMA if link DMA channel is idle */ + if (!hext_stream->link_locked) + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, + SOF_HDA_REG_PP_PPCTL, mask, 0); + spin_unlock_irq(&bus->reg_lock); + } hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); @@ -861,12 +883,14 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev) hext_stream = &hda_stream->hext_stream; - hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + - SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + if (sdev->bar[HDA_DSP_PP_BAR]) { + hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; - hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + - SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + - SOF_HDA_PPLC_INTERVAL * i; + hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + } hstream = &hext_stream->hstream; @@ -917,13 +941,14 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev) hext_stream = &hda_stream->hext_stream; - /* we always have DSP support */ - hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + - SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; + if (sdev->bar[HDA_DSP_PP_BAR]) { + hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; - hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + - SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + - SOF_HDA_PPLC_INTERVAL * i; + hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + + SOF_HDA_PPLC_INTERVAL * i; + } hstream = &hext_stream->hstream; diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 81c697e20108..3153e21f100a 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -26,6 +26,7 @@ #include <sound/intel-nhlt.h> #include <sound/sof.h> #include <sound/sof/xtensa.h> +#include <sound/hda-mlink.h> #include "../sof-audio.h" #include "../sof-pci-dev.h" #include "../ops.h" @@ -44,68 +45,37 @@ #define EXCEPT_MAX_HDR_SIZE 0x400 #define HDA_EXT_ROM_STATUS_SIZE 8 -int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, - struct snd_sof_dai_config_data *data) +static u32 hda_get_interface_mask(struct snd_sof_dev *sdev) { - struct snd_sof_widget *swidget = w->dobj.private; - struct snd_soc_component *component = swidget->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - struct snd_sof_dai *sof_dai = swidget->private; - int ret; - - if (!sof_dai) { - dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name); - return -EINVAL; - } - - if (tplg_ops->dai_config) { - unsigned int flags; - - /* set HW_PARAMS flag along with quirks */ - flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS | - quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; - - ret = tplg_ops->dai_config(sdev, swidget, flags, data); - if (ret < 0) { - dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__, - w->name); - return ret; - } - } - - return 0; -} - -int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, - struct snd_sof_dai_config_data *data) -{ - struct snd_sof_widget *swidget = w->dobj.private; - struct snd_soc_component *component = swidget->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - struct snd_sof_dai *sof_dai = swidget->private; - - if (!sof_dai) { - dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name); - return -EINVAL; - } - - if (tplg_ops->dai_config) { - unsigned int flags; - int ret; - - /* set HW_FREE flag along with any quirks */ - flags = SOF_DAI_CONFIG_FLAGS_HW_FREE | - quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; + const struct sof_intel_dsp_desc *chip; + u32 interface_mask[2] = { 0 }; - ret = tplg_ops->dai_config(sdev, swidget, flags, data); - if (ret < 0) - dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__, - w->name); + chip = get_chip_info(sdev->pdata); + switch (chip->hw_ip_version) { + case SOF_INTEL_TANGIER: + case SOF_INTEL_BAYTRAIL: + case SOF_INTEL_BROADWELL: + interface_mask[0] = BIT(SOF_DAI_INTEL_SSP); + break; + case SOF_INTEL_CAVS_1_5: + case SOF_INTEL_CAVS_1_5_PLUS: + interface_mask[0] = BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) | + BIT(SOF_DAI_INTEL_HDA); + interface_mask[1] = BIT(SOF_DAI_INTEL_HDA); + break; + case SOF_INTEL_CAVS_1_8: + case SOF_INTEL_CAVS_2_0: + case SOF_INTEL_CAVS_2_5: + case SOF_INTEL_ACE_1_0: + interface_mask[0] = BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) | + BIT(SOF_DAI_INTEL_HDA) | BIT(SOF_DAI_INTEL_ALH); + interface_mask[1] = BIT(SOF_DAI_INTEL_HDA); + break; + default: + break; } - return 0; + return interface_mask[sdev->dspless_mode_selected]; } #if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) @@ -124,35 +94,17 @@ static int sdw_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { struct snd_soc_dai *d = params_data->dai; - struct snd_sof_dai_config_data data; - struct snd_soc_dapm_widget *w; + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, params_data->stream); + struct snd_sof_dai_config_data data = { 0 }; - w = snd_soc_dai_get_widget(d, params_data->stream); data.dai_index = (params_data->link_id << 8) | d->id; data.dai_data = params_data->alh_stream_id; - return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); -} - -static int sdw_free_stream(struct device *dev, - struct sdw_intel_stream_free_data *free_data) -{ - struct snd_soc_dai *d = free_data->dai; - struct snd_sof_dai_config_data data; - struct snd_soc_dapm_widget *w; - - w = snd_soc_dai_get_widget(d, free_data->stream); - data.dai_index = (free_data->link_id << 8) | d->id; - - /* send invalid stream_id */ - data.dai_data = 0xFFFF; - - return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); + return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_PARAMS, &data); } struct sdw_intel_ops sdw_callback = { .params_stream = sdw_params_stream, - .free_stream = sdw_free_stream, }; void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable) @@ -171,8 +123,12 @@ void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable) void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) { + u32 interface_mask = hda_get_interface_mask(sdev); const struct sof_intel_dsp_desc *chip; + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + return; + chip = get_chip_info(sdev->pdata); if (chip && chip->enable_sdw_irq) chip->enable_sdw_irq(sdev, enable); @@ -180,10 +136,14 @@ void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable) static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); struct sof_intel_hda_dev *hdev; acpi_handle handle; int ret; + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + return -EINVAL; + handle = ACPI_HANDLE(sdev->dev); /* save ACPI info for the probe step */ @@ -254,8 +214,8 @@ int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev) /* Check HW supported vs property value */ if (caps < ctx->count) { dev_err(sdev->dev, - "BIOS master count %d is larger than hardware capabilities %d\n", - ctx->count, caps); + "%s: BIOS master count %d is larger than hardware capabilities %d\n", + __func__, ctx->count, caps); return -EINVAL; } @@ -337,8 +297,12 @@ out: static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); const struct sof_intel_dsp_desc *chip; + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + return false; + chip = get_chip_info(sdev->pdata); if (chip && chip->check_sdw_irq) return chip->check_sdw_irq(sdev); @@ -353,8 +317,12 @@ static irqreturn_t hda_dsp_sdw_thread(int irq, void *context) static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); struct sof_intel_hda_dev *hdev; + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + return false; + hdev = sdev->pdata->hw_pdata; if (hdev->sdw && snd_sof_dsp_read(sdev, HDA_DSP_BAR, @@ -366,8 +334,12 @@ static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) void hda_sdw_process_wakeen(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); struct sof_intel_hda_dev *hdev; + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + return; + hdev = sdev->pdata->hw_pdata; if (!hdev->sdw) return; @@ -927,6 +899,7 @@ static int dmic_detect_topology_fixup(struct snd_sof_dev *sdev, static int hda_init_caps(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); struct hdac_bus *bus = sof_to_bus(sdev); struct snd_sof_pdata *pdata = sdev->pdata; struct sof_intel_hda_dev *hdev = pdata->hw_pdata; @@ -945,7 +918,11 @@ static int hda_init_caps(struct snd_sof_dev *sdev) return ret; } - hda_bus_ml_get_capabilities(bus); + hda_bus_ml_init(bus); + + /* Skip SoundWire if it is not supported */ + if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH))) + goto skip_soundwire; /* scan SoundWire capabilities exposed by DSDT */ ret = hda_sdw_acpi_scan(sdev); @@ -1054,21 +1031,25 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) const struct sof_intel_dsp_desc *chip; int ret = 0; - /* - * detect DSP by checking class/subclass/prog-id information - * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required - * class=04 subclass 01 prog-if 00: DSP is present - * (and may be required e.g. for DMIC or SSP support) - * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works - */ - if (pci->class == 0x040300) { - dev_err(sdev->dev, "error: the DSP is not enabled on this platform, aborting probe\n"); - return -ENODEV; - } else if (pci->class != 0x040100 && pci->class != 0x040380) { - dev_err(sdev->dev, "error: unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", pci->class); - return -ENODEV; + if (!sdev->dspless_mode_selected) { + /* + * detect DSP by checking class/subclass/prog-id information + * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required + * class=04 subclass 01 prog-if 00: DSP is present + * (and may be required e.g. for DMIC or SSP support) + * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works + */ + if (pci->class == 0x040300) { + dev_err(sdev->dev, "the DSP is not enabled on this platform, aborting probe\n"); + return -ENODEV; + } else if (pci->class != 0x040100 && pci->class != 0x040380) { + dev_err(sdev->dev, "unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", + pci->class); + return -ENODEV; + } + dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", + pci->class); } - dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", pci->class); chip = get_chip_info(sdev->pdata); if (!chip) { @@ -1104,12 +1085,18 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) hdev->no_ipc_position = sof_ops(sdev)->pcm_pointer ? 1 : 0; #endif + if (sdev->dspless_mode_selected) + hdev->no_ipc_position = 1; + /* set up HDA base */ bus = sof_to_bus(sdev); ret = hda_init(sdev); if (ret < 0) goto hdac_bus_unmap; + if (sdev->dspless_mode_selected) + goto skip_dsp_setup; + /* DSP base */ sdev->bar[HDA_DSP_BAR] = pci_ioremap_bar(pci, HDA_DSP_BAR); if (!sdev->bar[HDA_DSP_BAR]) { @@ -1120,6 +1107,7 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) sdev->mmio_bar = HDA_DSP_BAR; sdev->mailbox_bar = HDA_DSP_BAR; +skip_dsp_setup: /* allow 64bit DMA address if supported by H/W */ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(64))) { @@ -1185,14 +1173,16 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) if (ret < 0) goto free_ipc_irq; - /* enable ppcap interrupt */ - hda_dsp_ctrl_ppcap_enable(sdev, true); - hda_dsp_ctrl_ppcap_int_enable(sdev, true); + if (!sdev->dspless_mode_selected) { + /* enable ppcap interrupt */ + hda_dsp_ctrl_ppcap_enable(sdev, true); + hda_dsp_ctrl_ppcap_int_enable(sdev, true); - /* set default mailbox offset for FW ready message */ - sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET; + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET; - INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work); + INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work); + } init_waitqueue_head(&hdev->waitq); @@ -1208,7 +1198,8 @@ free_irq_vector: free_streams: hda_dsp_stream_free(sdev); /* dsp_unmap: not currently used */ - iounmap(sdev->bar[HDA_DSP_BAR]); + if (!sdev->dspless_mode_selected) + iounmap(sdev->bar[HDA_DSP_BAR]); hdac_bus_unmap: platform_device_unregister(hdev->dmic_dev); iounmap(bus->remap_addr); @@ -1228,8 +1219,9 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) if (nhlt) intel_nhlt_free(nhlt); - /* cancel any attempt for DSP D0I3 */ - cancel_delayed_work_sync(&hda->d0i3_work); + if (!sdev->dspless_mode_selected) + /* cancel any attempt for DSP D0I3 */ + cancel_delayed_work_sync(&hda->d0i3_work); hda_codec_device_remove(sdev); @@ -1238,14 +1230,19 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) if (!IS_ERR_OR_NULL(hda->dmic_dev)) platform_device_unregister(hda->dmic_dev); - /* disable DSP IRQ */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, - SOF_HDA_PPCTL_PIE, 0); + if (!sdev->dspless_mode_selected) { + /* disable DSP IRQ */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + SOF_HDA_PPCTL_PIE, 0); + } /* disable CIE and GIE interrupts */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0); + if (sdev->dspless_mode_selected) + goto skip_disable_dsp; + /* no need to check for error as the DSP will be disabled anyway */ if (chip && chip->power_down_dsp) chip->power_down_dsp(sdev); @@ -1254,6 +1251,7 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, SOF_HDA_PPCTL_GPROCEN, 0); +skip_disable_dsp: free_irq(sdev->ipc_irq, sdev); if (sdev->msi_enabled) pci_free_irq_vectors(pci); @@ -1262,7 +1260,9 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) hda_bus_ml_free(sof_to_bus(sdev)); - iounmap(sdev->bar[HDA_DSP_BAR]); + if (!sdev->dspless_mode_selected) + iounmap(sdev->bar[HDA_DSP_BAR]); + iounmap(bus->remap_addr); sof_hda_bus_exit(sdev); @@ -1568,12 +1568,16 @@ void hda_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev) { + u32 interface_mask = hda_get_interface_mask(sdev); struct snd_sof_pdata *sof_pdata = sdev->pdata; const struct sof_dev_desc *desc = sof_pdata->desc; - struct snd_soc_acpi_mach *mach; + struct snd_soc_acpi_mach *mach = NULL; const char *tplg_filename; - mach = snd_soc_acpi_find_machine(desc->machines); + /* Try I2S or DMIC if it is supported */ + if (interface_mask & (BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC))) + mach = snd_soc_acpi_find_machine(desc->machines); + if (mach) { bool add_extension = false; bool tplg_fixup = false; @@ -1680,10 +1684,8 @@ struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev) } } - /* - * If I2S fails, try SoundWire - */ - if (!mach) + /* If I2S fails, try SoundWire if it is supported */ + if (!mach && (interface_mask & BIT(SOF_DAI_INTEL_ALH))) mach = hda_sdw_machine_select(sdev); /* @@ -1729,3 +1731,4 @@ MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); MODULE_IMPORT_NS(SND_INTEL_SOUNDWIRE_ACPI); MODULE_IMPORT_NS(SOUNDWIRE_INTEL_INIT); MODULE_IMPORT_NS(SOUNDWIRE_INTEL); +MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 45f9d4248f14..c4befacde23e 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -502,7 +502,7 @@ struct sof_intel_hda_dev { u32 stream_max; /* PM related */ - bool l1_support_changed;/* during suspend, is L1SEN changed or not */ + bool l1_disabled;/* is DMI link L1 disabled? */ /* DMIC device */ struct platform_device *dmic_dev; @@ -584,8 +584,10 @@ void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev); void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev); bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask); -int hda_dsp_set_power_state(struct snd_sof_dev *sdev, - const struct sof_dsp_power_state *target_state); +int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state); +int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state); int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state); int hda_dsp_resume(struct snd_sof_dev *sdev); @@ -763,26 +765,6 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } #endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - -void hda_bus_ml_get_capabilities(struct hdac_bus *bus); -void hda_bus_ml_free(struct hdac_bus *bus); -void hda_bus_ml_put_all(struct hdac_bus *bus); -void hda_bus_ml_reset_losidv(struct hdac_bus *bus); -int hda_bus_ml_resume(struct hdac_bus *bus); -int hda_bus_ml_suspend(struct hdac_bus *bus); - -#else - -static inline void hda_bus_ml_get_capabilities(struct hdac_bus *bus) { } -static inline void hda_bus_ml_free(struct hdac_bus *bus) { } -static inline void hda_bus_ml_put_all(struct hdac_bus *bus) { } -static inline void hda_bus_ml_reset_losidv(struct hdac_bus *bus) { } -static inline int hda_bus_ml_resume(struct hdac_bus *bus) { return 0; } -static inline int hda_bus_ml_suspend(struct hdac_bus *bus) { return 0; } - -#endif /* CONFIG_SND_SOC_SOF_HDA */ - /* * Trace Control. */ @@ -896,10 +878,6 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) struct snd_sof_dai; struct sof_ipc_dai_config; -int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, - struct snd_sof_dai_config_data *data); -int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, - struct snd_sof_dai_config_data *data); #define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */ #define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */ @@ -928,4 +906,41 @@ extern struct sdw_intel_ops sdw_callback; struct sof_ipc4_fw_library; int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); + +/** + * struct hda_dai_widget_dma_ops - DAI DMA ops optional by default unless specified otherwise + * @get_hext_stream: Mandatory function pointer to get the saved pointer to struct hdac_ext_stream + * @assign_hext_stream: Function pointer to assign a hdac_ext_stream + * @release_hext_stream: Function pointer to release the hdac_ext_stream + * @setup_hext_stream: Function pointer for hdac_ext_stream setup + * @reset_hext_stream: Function pointer for hdac_ext_stream reset + * @pre_trigger: Function pointer for DAI DMA pre-trigger actions + * @trigger: Function pointer for DAI DMA trigger actions + * @post_trigger: Function pointer for DAI DMA post-trigger actions + */ +struct hda_dai_widget_dma_ops { + struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev, + struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream); + void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, + unsigned int format_val); + void (*reset_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_sream); + int (*pre_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd); + int (*trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd); + int (*post_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, + struct snd_pcm_substream *substream, int cmd); +}; + +const struct hda_dai_widget_dma_ops * +hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); +int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags, + struct snd_sof_dai_config_data *data); + #endif diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 435941a1692f..0f249efc6a5a 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -116,6 +116,8 @@ int sof_icl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_icl_ops.ipc_dump = cnl_ipc_dump; + + sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc3; } if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { @@ -141,6 +143,8 @@ int sof_icl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_icl_ops.ipc_dump = cnl_ipc4_dump; + + sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc4; } /* debug */ diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 307faad2ecf4..46caf3ccde66 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -60,6 +60,9 @@ static bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev) u32 irq_status; u32 hfintipptr; + if (sdev->dspless_mode_selected) + return false; + /* read Interrupt IP Pointer */ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS); @@ -120,6 +123,9 @@ static void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; + if (sdev->dspless_mode_selected) + return; + /* enable IPC DONE and BUSY interrupts */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, @@ -131,6 +137,9 @@ static void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev) struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; + if (sdev->dspless_mode_selected) + return; + /* disable IPC DONE and BUSY interrupts */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0); @@ -143,6 +152,9 @@ static void mtl_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable) u32 val; int ret; + if (sdev->dspless_mode_selected) + return; + /* Enable/Disable SoundWire interrupt */ mask = MTL_DSP_REG_HfSNDWIE_IE_MASK; if (enable) @@ -170,6 +182,9 @@ static int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) u32 val; int ret; + if (sdev->dspless_mode_selected) + return 0; + /* read Interrupt IP Pointer */ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; @@ -217,6 +232,7 @@ static int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) /* pre fw run operations */ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; u32 dsphfpwrsts; u32 dsphfdsscs; u32 cpa; @@ -255,9 +271,11 @@ static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) if (ret < 0) dev_err(sdev->dev, "failed to power up gated DSP domain\n"); - /* make sure SoundWire is not power-gated */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, MTL_HFPWRCTL, - MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1)); + /* if SoundWire is used, make sure it is not power-gated */ + if (hdev->info.handle && hdev->info.link_mask > 0) + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL, + MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1)); + return ret; } @@ -650,6 +668,8 @@ int sof_mtl_ops_init(struct snd_sof_dev *sdev) /* set DAI ops */ hda_set_dai_drv_ops(sdev, &sof_mtl_ops); + sof_mtl_ops.set_power_state = hda_dsp_set_power_state_ipc4; + return 0; }; EXPORT_SYMBOL_NS(sof_mtl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c index aff6cb573c27..69cad5a6bc72 100644 --- a/sound/soc/sof/intel/pci-apl.c +++ b/sound/soc/sof/intel/pci-apl.c @@ -29,6 +29,7 @@ static const struct sof_dev_desc bxt_desc = { .chip_info = &apl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/apl", @@ -60,6 +61,7 @@ static const struct sof_dev_desc glk_desc = { .chip_info = &apl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/glk", diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c index 4c0c1c369dcd..8895508a0be6 100644 --- a/sound/soc/sof/intel/pci-cnl.c +++ b/sound/soc/sof/intel/pci-cnl.c @@ -30,6 +30,7 @@ static const struct sof_dev_desc cnl_desc = { .chip_info = &cnl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/cnl", @@ -62,6 +63,7 @@ static const struct sof_dev_desc cfl_desc = { .chip_info = &cnl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/cnl", @@ -94,6 +96,7 @@ static const struct sof_dev_desc cml_desc = { .chip_info = &cnl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/cnl", diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c index 6785669113b3..5fb5a820693e 100644 --- a/sound/soc/sof/intel/pci-icl.c +++ b/sound/soc/sof/intel/pci-icl.c @@ -30,6 +30,7 @@ static const struct sof_dev_desc icl_desc = { .chip_info = &icl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/icl", @@ -61,6 +62,7 @@ static const struct sof_dev_desc jsl_desc = { .chip_info = &jsl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/jsl", diff --git a/sound/soc/sof/intel/pci-mtl.c b/sound/soc/sof/intel/pci-mtl.c index b183dc0014b4..e276e1e37fed 100644 --- a/sound/soc/sof/intel/pci-mtl.c +++ b/sound/soc/sof/intel/pci-mtl.c @@ -31,6 +31,7 @@ static const struct sof_dev_desc mtl_desc = { .chip_info = &mtl_chip_info, .ipc_supported_mask = BIT(SOF_INTEL_IPC4), .ipc_default = SOF_INTEL_IPC4, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_INTEL_IPC4] = "intel/sof-ipc4/mtl", }, diff --git a/sound/soc/sof/intel/pci-skl.c b/sound/soc/sof/intel/pci-skl.c index 5b4bccf81965..5e69af6eed34 100644 --- a/sound/soc/sof/intel/pci-skl.c +++ b/sound/soc/sof/intel/pci-skl.c @@ -26,6 +26,7 @@ static struct sof_dev_desc skl_desc = { .irqindex_host_ipc = -1, .ipc_supported_mask = BIT(SOF_INTEL_IPC4), .ipc_default = SOF_INTEL_IPC4, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_INTEL_IPC4] = "intel/avs/skl", }, @@ -50,6 +51,7 @@ static struct sof_dev_desc kbl_desc = { .irqindex_host_ipc = -1, .ipc_supported_mask = BIT(SOF_INTEL_IPC4), .ipc_default = SOF_INTEL_IPC4, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_INTEL_IPC4] = "intel/avs/kbl", }, diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c index 22e769e0831d..ca37ff1bbd2a 100644 --- a/sound/soc/sof/intel/pci-tgl.c +++ b/sound/soc/sof/intel/pci-tgl.c @@ -30,6 +30,7 @@ static const struct sof_dev_desc tgl_desc = { .chip_info = &tgl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/tgl", @@ -62,6 +63,7 @@ static const struct sof_dev_desc tglh_desc = { .chip_info = &tglh_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/tgl-h", @@ -93,6 +95,7 @@ static const struct sof_dev_desc ehl_desc = { .chip_info = &ehl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/ehl", @@ -125,6 +128,7 @@ static const struct sof_dev_desc adls_desc = { .chip_info = &adls_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/adl-s", @@ -157,6 +161,7 @@ static const struct sof_dev_desc adl_desc = { .chip_info = &tgl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/adl", @@ -189,6 +194,7 @@ static const struct sof_dev_desc adl_n_desc = { .chip_info = &tgl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/adl-n", @@ -221,6 +227,7 @@ static const struct sof_dev_desc rpls_desc = { .chip_info = &adls_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/rpl-s", @@ -253,6 +260,7 @@ static const struct sof_dev_desc rpl_desc = { .chip_info = &tgl_chip_info, .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), .ipc_default = SOF_IPC, + .dspless_mode_supported = true, /* Only supported for HDaudio */ .default_fw_path = { [SOF_IPC] = "intel/sof", [SOF_INTEL_IPC4] = "intel/avs/rpl", diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 58ac3a46e6a7..2713b7dc7931 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -71,6 +71,8 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_tgl_ops.ipc_dump = cnl_ipc_dump; + + sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc3; } if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { @@ -96,6 +98,8 @@ int sof_tgl_ops_init(struct snd_sof_dev *sdev) /* debug */ sof_tgl_ops.ipc_dump = cnl_ipc4_dump; + + sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc4; } /* set DAI driver ops */ diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c index 217ac5501a98..ad040e7bb850 100644 --- a/sound/soc/sof/ipc3-control.c +++ b/sound/soc/sof/ipc3-control.c @@ -104,7 +104,7 @@ unlock: return ret; } -static void snd_sof_refresh_control(struct snd_sof_control *scontrol) +static void sof_ipc3_refresh_control(struct snd_sof_control *scontrol) { struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp; @@ -138,7 +138,7 @@ static int sof_ipc3_volume_get(struct snd_sof_control *scontrol, unsigned int channels = scontrol->num_channels; unsigned int i; - snd_sof_refresh_control(scontrol); + sof_ipc3_refresh_control(scontrol); /* read back each channel */ for (i = 0; i < channels; i++) @@ -189,7 +189,7 @@ static int sof_ipc3_switch_get(struct snd_sof_control *scontrol, unsigned int channels = scontrol->num_channels; unsigned int i; - snd_sof_refresh_control(scontrol); + sof_ipc3_refresh_control(scontrol); /* read back each channel */ for (i = 0; i < channels; i++) @@ -237,7 +237,7 @@ static int sof_ipc3_enum_get(struct snd_sof_control *scontrol, unsigned int channels = scontrol->num_channels; unsigned int i; - snd_sof_refresh_control(scontrol); + sof_ipc3_refresh_control(scontrol); /* read back each channel */ for (i = 0; i < channels; i++) @@ -286,7 +286,7 @@ static int sof_ipc3_bytes_get(struct snd_sof_control *scontrol, struct sof_abi_hdr *data = cdata->data; size_t size; - snd_sof_refresh_control(scontrol); + sof_ipc3_refresh_control(scontrol); if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) { dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n", @@ -343,55 +343,6 @@ static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol, return 0; } -static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol, - const unsigned int __user *binary_data, unsigned int size) -{ - struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; - struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; - struct snd_soc_component *scomp = scontrol->scomp; - struct snd_ctl_tlv header; - size_t data_size; - - snd_sof_refresh_control(scontrol); - - /* - * Decrement the limit by ext bytes header size to - * ensure the user space buffer is not exceeded. - */ - if (size < sizeof(struct snd_ctl_tlv)) - return -ENOSPC; - - size -= sizeof(struct snd_ctl_tlv); - - /* set the ABI header values */ - cdata->data->magic = SOF_ABI_MAGIC; - cdata->data->abi = SOF_ABI_VERSION; - - /* check data size doesn't exceed max coming from topology */ - if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { - dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n", - cdata->data->size, - scontrol->max_size - sizeof(struct sof_abi_hdr)); - return -EINVAL; - } - - data_size = cdata->data->size + sizeof(struct sof_abi_hdr); - - /* make sure we don't exceed size provided by user space for data */ - if (data_size > size) - return -ENOSPC; - - header.numid = cdata->cmd; - header.length = data_size; - if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) - return -EFAULT; - - if (copy_to_user(tlvd->tlv, cdata->data, data_size)) - return -EFAULT; - - return 0; -} - static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, const unsigned int __user *binary_data, unsigned int size) @@ -457,16 +408,15 @@ static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, return 0; } -static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, - const unsigned int __user *binary_data, - unsigned int size) +static int _sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size, bool from_dsp) { struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp; struct snd_ctl_tlv header; size_t data_size; - int ret; /* * Decrement the limit by ext bytes header size to @@ -482,9 +432,12 @@ static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, cdata->data->abi = SOF_ABI_VERSION; /* get all the component data from DSP */ - ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true); - if (ret < 0) - return ret; + if (from_dsp) { + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true); + + if (ret < 0) + return ret; + } /* check data size doesn't exceed max coming from topology */ if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { @@ -508,7 +461,20 @@ static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, if (copy_to_user(tlvd->tlv, cdata->data, data_size)) return -EFAULT; - return ret; + return 0; +} + +static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, unsigned int size) +{ + return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, false); +} + +static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, true); } static void snd_sof_update_control(struct snd_sof_control *scontrol, diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c index b815b0244d9e..1d3bca2d28dd 100644 --- a/sound/soc/sof/ipc3-dtrace.c +++ b/sound/soc/sof/ipc3-dtrace.c @@ -150,7 +150,6 @@ static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, struct sof_ipc_trace_filter_elem *elems) { struct sof_ipc_trace_filter *msg; - struct sof_ipc_reply reply; size_t size; int ret; @@ -172,13 +171,13 @@ static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, dev_err(sdev->dev, "enabling device failed: %d\n", ret); goto error; } - ret = sof_ipc_tx_message(sdev->ipc, msg, msg->hdr.size, &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, msg->hdr.size); pm_runtime_mark_last_busy(sdev->dev); pm_runtime_put_autosuspend(sdev->dev); error: kfree(msg); - return ret ? ret : reply.error; + return ret; } static ssize_t dfsentry_trace_filter_write(struct file *file, const char __user *from, @@ -434,7 +433,6 @@ static int ipc3_dtrace_enable(struct snd_sof_dev *sdev) struct sof_ipc_fw_ready *ready = &sdev->fw_ready; struct sof_ipc_fw_version *v = &ready->version; struct sof_ipc_dma_trace_params_ext params; - struct sof_ipc_reply ipc_reply; int ret; if (!sdev->fw_trace_is_supported) @@ -474,7 +472,7 @@ static int ipc3_dtrace_enable(struct snd_sof_dev *sdev) /* send IPC to the DSP */ priv->dtrace_state = SOF_DTRACE_INITIALIZING; - ret = sof_ipc_tx_message(sdev->ipc, ¶ms, sizeof(params), &ipc_reply, sizeof(ipc_reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, ¶ms, sizeof(params)); if (ret < 0) { dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret); goto trace_release; @@ -604,7 +602,6 @@ static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop) struct sof_ipc_fw_ready *ready = &sdev->fw_ready; struct sof_ipc_fw_version *v = &ready->version; struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply ipc_reply; int ret; if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED) @@ -623,8 +620,7 @@ static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop) hdr.size = sizeof(hdr); hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; - ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, - &ipc_reply, sizeof(ipc_reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &hdr, hdr.size); if (ret < 0) dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); } diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c index b29d93e0d216..304faf6425ab 100644 --- a/sound/soc/sof/ipc3-pcm.c +++ b/sound/soc/sof/ipc3-pcm.c @@ -19,7 +19,6 @@ static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sof_ipc_stream stream; - struct sof_ipc_reply reply; struct snd_sof_pcm *spcm; spcm = snd_sof_find_spcm_dai(component, rtd); @@ -34,7 +33,7 @@ static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, stream.comp_id = spcm->stream[substream->stream].comp_id; /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); + return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); } static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, @@ -146,7 +145,6 @@ static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct sof_ipc_stream stream; - struct sof_ipc_reply reply; struct snd_sof_pcm *spcm; spcm = snd_sof_find_spcm_dai(component, rtd); @@ -178,7 +176,7 @@ static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, } /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); + return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); } static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, @@ -382,4 +380,5 @@ const struct sof_ipc_pcm_ops ipc3_pcm_ops = { .hw_free = sof_ipc3_pcm_hw_free, .trigger = sof_ipc3_pcm_trigger, .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup, + .reset_hw_params_during_stop = true, }; diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c index b1f425b39db9..fc1eb8e2de2c 100644 --- a/sound/soc/sof/ipc3-topology.c +++ b/sound/soc/sof/ipc3-topology.c @@ -1627,7 +1627,6 @@ static void sof_ipc3_widget_free_comp_dai(struct snd_sof_widget *swidget) static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) { struct sof_ipc_pipe_comp_connect connect; - struct sof_ipc_reply reply; int ret; connect.hdr.size = sizeof(connect); @@ -1640,7 +1639,7 @@ static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * sroute->sink_widget->widget->name); /* send ipc */ - ret = sof_ipc_tx_message(sdev->ipc, &connect, sizeof(connect), &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &connect, sizeof(connect)); if (ret < 0) dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__, sroute->src_widget->widget->name, sroute->sink_widget->widget->name); @@ -1789,7 +1788,7 @@ static int sof_ipc3_control_free(struct snd_sof_dev *sdev, struct snd_sof_contro fcomp.id = scontrol->comp_id; /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, &fcomp, sizeof(fcomp), NULL, 0); + return sof_ipc_tx_message_no_reply(sdev->ipc, &fcomp, sizeof(fcomp)); } /* send pcm params ipc */ @@ -1797,7 +1796,6 @@ static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, in { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_pcm_params_reply ipc_params_reply; struct snd_pcm_hw_params *params; struct sof_ipc_pcm_params pcm; struct snd_sof_pcm *spcm; @@ -1841,8 +1839,7 @@ static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, in } /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), - &ipc_params_reply, sizeof(ipc_params_reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &pcm, sizeof(pcm)); if (ret < 0) dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__, swidget->widget->name); @@ -1856,7 +1853,6 @@ static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int c struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc_stream stream; - struct sof_ipc_reply reply; int ret; /* set IPC stream params */ @@ -1865,7 +1861,7 @@ static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int c stream.comp_id = swidget->comp_id; /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream)); if (ret < 0) dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name); @@ -1982,7 +1978,6 @@ static int sof_ipc3_widget_bind_event(struct snd_soc_component *scomp, static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc_pipe_ready ready; - struct sof_ipc_reply reply; int ret; dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", @@ -1993,7 +1988,7 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; ready.comp_id = swidget->comp_id; - ret = sof_ipc_tx_message(sdev->ipc, &ready, sizeof(ready), &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ready, sizeof(ready)); if (ret < 0) return ret; @@ -2009,7 +2004,6 @@ static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget }, .id = swidget->comp_id, }; - struct sof_ipc_reply reply; int ret; if (!swidget->private) @@ -2029,8 +2023,7 @@ static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget break; } - ret = sof_ipc_tx_message(sdev->ipc, &ipc_free, sizeof(ipc_free), - &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ipc_free, sizeof(ipc_free)); if (ret < 0) dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name); @@ -2044,7 +2037,6 @@ static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * struct snd_sof_dai *dai = swidget->private; struct sof_dai_private_data *private; struct sof_ipc_dai_config *config; - struct sof_ipc_reply reply; int ret = 0; if (!dai || !dai->private) { @@ -2118,8 +2110,7 @@ static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * /* only send the IPC if the widget is set up in the DSP */ if (swidget->use_count > 0) { - ret = sof_ipc_tx_message(sdev->ipc, config, config->hdr.size, - &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, config, config->hdr.size); if (ret < 0) dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name); @@ -2132,7 +2123,6 @@ static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { - struct sof_ipc_comp_reply reply; int ret; if (!swidget->private) @@ -2146,8 +2136,7 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_dai_private_data *dai_data = dai->private; struct sof_ipc_comp *comp = &dai_data->comp_dai->comp; - ret = sof_ipc_tx_message(sdev->ipc, dai_data->comp_dai, - comp->hdr.size, &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, dai_data->comp_dai, comp->hdr.size); break; } case snd_soc_dapm_scheduler: @@ -2155,8 +2144,7 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_ipc_pipe_new *pipeline; pipeline = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, pipeline, sizeof(*pipeline), - &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, pipeline, sizeof(*pipeline)); break; } default: @@ -2164,8 +2152,7 @@ static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_ipc_cmd_hdr *hdr; hdr = swidget->private; - ret = sof_ipc_tx_message(sdev->ipc, swidget->private, hdr->size, - &reply, sizeof(reply)); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, swidget->private, hdr->size); break; } } diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index 4493bbd7faf1..c67767742093 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -1044,15 +1044,13 @@ static int sof_ipc3_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool .hdr.size = sizeof(core_cfg), .hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE, }; - struct sof_ipc_reply reply; if (on) core_cfg.enable_mask = sdev->enabled_cores_mask | BIT(core_idx); else core_cfg.enable_mask = sdev->enabled_cores_mask & ~BIT(core_idx); - return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg), - &reply, sizeof(reply), false); + return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg), NULL, 0, false); } static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd) @@ -1061,11 +1059,9 @@ static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd) .hdr.size = sizeof(pm_ctx), .hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd, }; - struct sof_ipc_reply reply; /* send ctx save ipc to dsp */ - return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), - &reply, sizeof(reply), false); + return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), NULL, 0, false); } static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev) @@ -1081,7 +1077,6 @@ static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev) static int sof_ipc3_set_pm_gate(struct snd_sof_dev *sdev, u32 flags) { struct sof_ipc_pm_gate pm_gate; - struct sof_ipc_reply reply; memset(&pm_gate, 0, sizeof(pm_gate)); @@ -1091,8 +1086,7 @@ static int sof_ipc3_set_pm_gate(struct snd_sof_dev *sdev, u32 flags) pm_gate.flags = flags; /* send pm_gate ipc to dsp */ - return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate), - &reply, sizeof(reply)); + return sof_ipc_tx_message_no_pm_no_reply(sdev->ipc, &pm_gate, sizeof(pm_gate)); } static const struct sof_ipc_pm_ops ipc3_pm_ops = { diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c index 9a71af1a613a..6f0698be9451 100644 --- a/sound/soc/sof/ipc4-control.c +++ b/sound/soc/sof/ipc4-control.c @@ -181,21 +181,263 @@ static int sof_ipc4_volume_get(struct snd_sof_control *scontrol, return 0; } +static int sof_ipc4_set_get_bytes_data(struct snd_sof_dev *sdev, + struct snd_sof_control *scontrol, + bool set, bool lock) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct sof_abi_hdr *data = cdata->data; + struct sof_ipc4_msg *msg = &cdata->msg; + int ret = 0; + + /* Send the new data to the firmware only if it is powered up */ + if (set && !pm_runtime_active(sdev->dev)) + return 0; + + msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type); + + msg->data_ptr = data->data; + msg->data_size = data->size; + + ret = sof_ipc4_set_get_kcontrol_data(scontrol, set, lock); + if (ret < 0) + dev_err(sdev->dev, "Failed to %s for %s\n", + set ? "set bytes update" : "get bytes", + scontrol->name); + + msg->data_ptr = NULL; + msg->data_size = 0; + + return ret; +} + +static int sof_ipc4_bytes_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_abi_hdr *data = cdata->data; + size_t size; + + if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, + "data max %zu exceeds ucontrol data array size\n", + scontrol->max_size); + return -EINVAL; + } + + /* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (data->size > scontrol->max_size - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "data size too big %u bytes max is %zu\n", + data->size, scontrol->max_size - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, size); + + sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true); + + return 0; +} + +static int sof_ipc4_bytes_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_abi_hdr *data = cdata->data; + size_t size; + + if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n", + scontrol->max_size); + return -EINVAL; + } + + if (data->size > scontrol->max_size - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "%u bytes of control data is invalid, max is %zu\n", + data->size, scontrol->max_size - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + + return 0; +} + +static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_abi_hdr *data = cdata->data; + struct sof_abi_hdr abi_hdr; + struct snd_ctl_tlv header; + + /* + * The beginning of bytes data contains a header from where + * the length (as bytes) is needed to know the correct copy + * length of data from tlvd->tlv. + */ + if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv))) + return -EFAULT; + + /* make sure TLV info is consistent */ + if (header.length + sizeof(struct snd_ctl_tlv) > size) { + dev_err_ratelimited(scomp->dev, + "Inconsistent TLV, data %d + header %zu > %d\n", + header.length, sizeof(struct snd_ctl_tlv), size); + return -EINVAL; + } + + /* be->max is coming from topology */ + if (header.length > scontrol->max_size) { + dev_err_ratelimited(scomp->dev, + "Bytes data size %d exceeds max %zu\n", + header.length, scontrol->max_size); + return -EINVAL; + } + + /* Verify the ABI header first */ + if (copy_from_user(&abi_hdr, tlvd->tlv, sizeof(abi_hdr))) + return -EFAULT; + + if (abi_hdr.magic != SOF_IPC4_ABI_MAGIC) { + dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n", + abi_hdr.magic); + return -EINVAL; + } + + if (abi_hdr.size > scontrol->max_size - sizeof(abi_hdr)) { + dev_err_ratelimited(scomp->dev, + "%u bytes of control data is invalid, max is %zu\n", + abi_hdr.size, scontrol->max_size - sizeof(abi_hdr)); + return -EINVAL; + } + + /* Copy the whole binary data which includes the ABI header and the payload */ + if (copy_from_user(data, tlvd->tlv, header.length)) + return -EFAULT; + + sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true); + + return 0; +} + +static int _sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size, bool from_dsp) +{ + struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_abi_hdr *data = cdata->data; + struct snd_ctl_tlv header; + size_t data_size; + + /* + * Decrement the limit by ext bytes header size to ensure the user space + * buffer is not exceeded. + */ + if (size < sizeof(struct snd_ctl_tlv)) + return -ENOSPC; + + size -= sizeof(struct snd_ctl_tlv); + + /* get all the component data from DSP */ + if (from_dsp) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + int ret = sof_ipc4_set_get_bytes_data(sdev, scontrol, false, true); + + if (ret < 0) + return ret; + + /* Set the ABI magic (if the control is not initialized) */ + data->magic = SOF_IPC4_ABI_MAGIC; + } + + if (data->size > scontrol->max_size - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "%u bytes of control data is invalid, max is %zu\n", + data->size, scontrol->max_size - sizeof(*data)); + return -EINVAL; + } + + data_size = data->size + sizeof(struct sof_abi_hdr); + + /* make sure we don't exceed size provided by user space for data */ + if (data_size > size) + return -ENOSPC; + + header.numid = scontrol->comp_id; + header.length = data_size; + + if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) + return -EFAULT; + + if (copy_to_user(tlvd->tlv, data, data_size)) + return -EFAULT; + + return 0; +} + +static int sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, false); +} + +static int sof_ipc4_bytes_ext_volatile_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, true); +} + /* set up all controls for the widget */ static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct snd_sof_control *scontrol; - int ret; + int ret = 0; - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { if (scontrol->comp_id == swidget->comp_id) { - ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol, false); + switch (scontrol->info_type) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + ret = sof_ipc4_set_volume_data(sdev, swidget, + scontrol, false); + break; + case SND_SOC_TPLG_CTL_BYTES: + ret = sof_ipc4_set_get_bytes_data(sdev, scontrol, + true, false); + break; + default: + break; + } + if (ret < 0) { - dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n", - __func__, scontrol->comp_id, swidget->widget->name); + dev_err(sdev->dev, + "kcontrol %d set up failed for widget %s\n", + scontrol->comp_id, swidget->widget->name); return ret; } } + } return 0; } @@ -225,6 +467,11 @@ sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_I const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = { .volume_put = sof_ipc4_volume_put, .volume_get = sof_ipc4_volume_get, + .bytes_put = sof_ipc4_bytes_put, + .bytes_get = sof_ipc4_bytes_get, + .bytes_ext_put = sof_ipc4_bytes_ext_put, + .bytes_ext_get = sof_ipc4_bytes_ext_get, + .bytes_ext_volatile_get = sof_ipc4_bytes_ext_volatile_get, .widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup, .set_up_volume_table = sof_ipc4_set_up_volume_table, }; diff --git a/sound/soc/sof/ipc4-mtrace.c b/sound/soc/sof/ipc4-mtrace.c index 0ec6ef681012..2b4659a1768e 100644 --- a/sound/soc/sof/ipc4-mtrace.c +++ b/sound/soc/sof/ipc4-mtrace.c @@ -609,6 +609,16 @@ static void ipc4_mtrace_free(struct snd_sof_dev *sdev) ipc4_mtrace_disable(sdev); } +static int sof_ipc4_mtrace_update_pos_all_cores(struct snd_sof_dev *sdev) +{ + int i; + + for (i = 0; i < sdev->num_cores; i++) + sof_ipc4_mtrace_update_pos(sdev, i); + + return 0; +} + int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core) { struct sof_mtrace_priv *priv = sdev->fw_trace_data; @@ -642,6 +652,16 @@ int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core) return 0; } +static void ipc4_mtrace_fw_crashed(struct snd_sof_dev *sdev) +{ + /* + * The DSP might not be able to send SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS + * messages anymore, so check the log buffer status on all + * cores and process any pending messages. + */ + sof_ipc4_mtrace_update_pos_all_cores(sdev); +} + static int ipc4_mtrace_resume(struct snd_sof_dev *sdev) { return ipc4_mtrace_enable(sdev); @@ -655,6 +675,7 @@ static void ipc4_mtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops = { .init = ipc4_mtrace_init, .free = ipc4_mtrace_free, + .fw_crashed = ipc4_mtrace_fw_crashed, .suspend = ipc4_mtrace_suspend, .resume = ipc4_mtrace_resume, }; diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 68258767aace..9e2b6c45080d 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -39,7 +39,7 @@ static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state msg.data_size = ipc_size; msg.data_ptr = trigger_list; - return sof_ipc_tx_message(sdev->ipc, &msg, ipc_size, NULL, 0); + return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, ipc_size); } int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) @@ -57,7 +57,7 @@ int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) msg.primary = primary; - return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); } EXPORT_SYMBOL(sof_ipc4_set_pipeline_state); @@ -193,6 +193,88 @@ sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd, * prepare ioctl before the START trigger. */ +/* + * Chained DMA is a special case where there is no processing on + * DSP. The samples are just moved over by host side DMA to a single + * buffer on DSP and directly from there to link DMA. However, the + * model on SOF driver has two notional pipelines, one at host DAI, + * and another at link DAI. They both shall have the use_chain_dma + * attribute. + */ + +static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev, + struct snd_sof_pcm_stream_pipeline_list *pipeline_list, + int state, int cmd) +{ + bool allocate, enable, set_fifo_size; + struct sof_ipc4_msg msg = {{ 0 }}; + int i; + + switch (state) { + case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */ + allocate = true; + enable = true; + /* + * SOF assumes creation of a new stream from the presence of fifo_size + * in the message, so we must leave it out in pause release case. + */ + if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) + set_fifo_size = false; + else + set_fifo_size = true; + break; + case SOF_IPC4_PIPE_PAUSED: /* Disable chained DMA. */ + allocate = true; + enable = false; + set_fifo_size = false; + break; + case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */ + allocate = false; + enable = false; + set_fifo_size = false; + break; + default: + dev_err(sdev->dev, "Unexpected state %d", state); + return -EINVAL; + } + + msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + + /* + * To set-up the DMA chain, the host DMA ID and SCS setting + * are retrieved from the host pipeline configuration. Likewise + * the link DMA ID and fifo_size are retrieved from the link + * pipeline configuration. + */ + for (i = 0; i < pipeline_list->count; i++) { + struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i]; + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (!pipeline->use_chain_dma) { + dev_err(sdev->dev, + "All pipelines in chained DMA stream should have use_chain_dma attribute set."); + return -EINVAL; + } + + msg.primary |= pipeline->msg.primary; + + /* Add fifo_size (actually DMA buffer size) field to the message */ + if (set_fifo_size) + msg.extension |= pipeline->msg.extension; + } + + if (allocate) + msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK; + + if (enable) + msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK; + + return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); +} + static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, struct snd_pcm_substream *substream, int state, int cmd) { @@ -201,6 +283,8 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, struct snd_sof_pcm_stream_pipeline_list *pipeline_list; struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct ipc4_pipeline_set_state_data *trigger_list; + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; struct snd_sof_pipeline *spipe; struct snd_sof_pcm *spcm; int ret; @@ -218,6 +302,17 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (!pipeline_list->pipelines || !pipeline_list->count) return 0; + spipe = pipeline_list->pipelines[0]; + pipe_widget = spipe->pipe_widget; + pipeline = pipe_widget->private; + + /* + * If use_chain_dma attribute is set we proceed to chained DMA + * trigger function that handles the rest for the substream. + */ + if (pipeline->use_chain_dma) + return sof_ipc4_chain_dma_trigger(sdev, pipeline_list, state, cmd); + /* allocate memory for the pipeline data */ trigger_list = kzalloc(struct_size(trigger_list, pipeline_ids, pipeline_list->count), GFP_KERNEL); @@ -362,15 +457,70 @@ static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const } } +/* + * Fixup DAI link parameters for sampling rate based on + * DAI copier configuration. + */ +static int sof_ipc4_pcm_dai_link_fixup_rate(struct snd_sof_dev *sdev, + struct snd_pcm_hw_params *params, + struct sof_ipc4_copier *ipc4_copier) +{ + struct sof_ipc4_pin_format *pin_fmts = ipc4_copier->available_fmt.input_pin_fmts; + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + int num_input_formats = ipc4_copier->available_fmt.num_input_formats; + unsigned int fe_rate = params_rate(params); + bool fe_be_rate_match = false; + bool single_be_rate = true; + unsigned int be_rate; + int i; + + /* + * Copier does not change sampling rate, so we + * need to only consider the input pin information. + */ + for (i = 0; i < num_input_formats; i++) { + unsigned int val = pin_fmts[i].audio_fmt.sampling_frequency; + + if (i == 0) + be_rate = val; + else if (val != be_rate) + single_be_rate = false; + + if (val == fe_rate) { + fe_be_rate_match = true; + break; + } + } + + /* + * If rate is different than FE rate, topology must + * contain an SRC. But we do require topology to + * define a single rate in the DAI copier config in + * this case (FE rate may be variable). + */ + if (!fe_be_rate_match) { + if (!single_be_rate) { + dev_err(sdev->dev, "Unable to select sampling rate for DAI link\n"); + return -EINVAL; + } + + rate->min = be_rate; + rate->max = rate->min; + } + + return 0; +} + static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name); - struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); - struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); struct sof_ipc4_copier *ipc4_copier; + bool use_chain_dma = false; + int dir; if (!dai) { dev_err(component->dev, "%s: No DAI found with name %s\n", __func__, @@ -385,12 +535,26 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, return -EINVAL; } - /* always set BE format to 32-bits for both playback and capture */ - snd_mask_none(fmt); - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + for_each_pcm_streams(dir) { + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, dir); + + if (w) { + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (pipeline->use_chain_dma) + use_chain_dma = true; + } + } + + /* Chain DMA does not use copiers, so no fixup needed */ + if (!use_chain_dma) { + int ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier); - rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency; - rate->max = rate->min; + if (ret) + return ret; + } switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_SSP: @@ -671,5 +835,7 @@ const struct sof_ipc_pcm_ops ipc4_pcm_ops = { .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup, .pcm_setup = sof_ipc4_pcm_setup, .pcm_free = sof_ipc4_pcm_free, - .delay = sof_ipc4_pcm_delay + .delay = sof_ipc4_pcm_delay, + .ipc_first_on_start = true, + .platform_stop_during_hw_free = true, }; diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 3a5394c3dd83..059eebf0a687 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -6,6 +6,7 @@ // Copyright(c) 2022 Intel Corporation. All rights reserved. // // +#include <linux/bitfield.h> #include <uapi/sound/sof/tokens.h> #include <sound/pcm_params.h> #include <sound/sof/ext_manifest4.h> @@ -18,13 +19,18 @@ #define SOF_IPC4_GAIN_PARAM_ID 0 #define SOF_IPC4_TPLG_ABI_SIZE 6 +#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2 static DEFINE_IDA(alh_group_ida); static DEFINE_IDA(pipeline_ida); static const struct sof_topology_token ipc4_sched_tokens[] = { {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_pipeline, lp_mode)} + offsetof(struct sof_ipc4_pipeline, lp_mode)}, + {SOF_TKN_SCHED_USE_CHAIN_DMA, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct sof_ipc4_pipeline, use_chain_dma)}, + {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pipeline, core_id)}, }; static const struct sof_topology_token pipeline_tokens[] = { @@ -39,45 +45,48 @@ static const struct sof_topology_token ipc4_comp_tokens[] = { offsetof(struct sof_ipc4_base_module_cfg, is_pages)}, }; -static const struct sof_topology_token ipc4_audio_format_buffer_size_tokens[] = { - {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_base_module_cfg, ibs)}, - {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_base_module_cfg, obs)}, -}; - static const struct sof_topology_token ipc4_in_audio_format_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, bit_depth)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, ch_map)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, ch_cfg)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, + get_token_u32, offsetof(struct sof_ipc4_pin_format, + audio_fmt.interleaving_style)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pin_format, pin_index)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pin_format, buffer_size)}, }; static const struct sof_topology_token ipc4_out_audio_format_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, bit_depth)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, ch_map)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, ch_cfg)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, + get_token_u32, offsetof(struct sof_ipc4_pin_format, + audio_fmt.interleaving_style)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, + offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pin_format, pin_index)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pin_format, buffer_size)}, }; -static const struct sof_topology_token ipc4_copier_gateway_cfg_tokens[] = { - {SOF_TKN_CAVS_AUDIO_FORMAT_DMA_BUFFER_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, +static const struct sof_topology_token ipc4_copier_deep_buffer_tokens[] = { + {SOF_TKN_INTEL_COPIER_DEEP_BUFFER_DMA_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, }; static const struct sof_topology_token ipc4_copier_tokens[] = { @@ -85,8 +94,10 @@ static const struct sof_topology_token ipc4_copier_tokens[] = { }; static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = { - {SOF_TKN_COMP_NUM_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - 0}, + {SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_available_audio_format, num_input_formats)}, + {SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_available_audio_format, num_output_formats)}, }; static const struct sof_topology_token dai_tokens[] = { @@ -100,6 +111,8 @@ static const struct sof_topology_token dai_tokens[] = { static const struct sof_topology_token comp_ext_tokens[] = { {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid, offsetof(struct snd_sof_widget, uuid)}, + {SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct snd_sof_widget, core)}, }; static const struct sof_topology_token gain_tokens[] = { @@ -131,11 +144,8 @@ static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = { ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)}, [SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens", ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)}, - [SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS] = {"IPC4 Audio format buffer size tokens", - ipc4_audio_format_buffer_size_tokens, - ARRAY_SIZE(ipc4_audio_format_buffer_size_tokens)}, - [SOF_COPIER_GATEWAY_CFG_TOKENS] = {"IPC4 Copier gateway config tokens", - ipc4_copier_gateway_cfg_tokens, ARRAY_SIZE(ipc4_copier_gateway_cfg_tokens)}, + [SOF_COPIER_DEEP_BUFFER_TOKENS] = {"IPC4 Copier deep buffer tokens", + ipc4_copier_deep_buffer_tokens, ARRAY_SIZE(ipc4_copier_deep_buffer_tokens)}, [SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens, ARRAY_SIZE(ipc4_copier_tokens)}, [SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens", @@ -144,130 +154,150 @@ static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = { [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)}, }; -static void sof_ipc4_dbg_audio_format(struct device *dev, - struct sof_ipc4_audio_format *format, - size_t object_size, int num_format) +static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_format *pin_fmt, + int num_formats) { - struct sof_ipc4_audio_format *fmt; - void *ptr = format; int i; - for (i = 0; i < num_format; i++, ptr = (u8 *)ptr + object_size) { - fmt = ptr; + for (i = 0; i < num_formats; i++) { + struct sof_ipc4_audio_format *fmt = &pin_fmt[i].audio_fmt; dev_dbg(dev, - " #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x)\n", - i, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map, - fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg); + "Pin index #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x) buffer size %d\n", + pin_fmt[i].pin_index, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map, + fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg, + pin_fmt[i].buffer_size); } } +static const struct sof_ipc4_audio_format * +sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index) +{ + struct sof_ipc4_base_module_cfg_ext *base_cfg_ext; + struct sof_ipc4_process *process; + int i; + + if (swidget->id != snd_soc_dapm_effect) { + struct sof_ipc4_base_module_cfg *base = swidget->private; + + /* For non-process modules, base module config format is used for all input pins */ + return &base->audio_fmt; + } + + process = swidget->private; + base_cfg_ext = process->base_config_ext; + + /* + * If there are multiple input formats available for a pin, the first available format + * is chosen. + */ + for (i = 0; i < base_cfg_ext->num_input_pin_fmts; i++) { + struct sof_ipc4_pin_format *pin_format = &base_cfg_ext->pin_formats[i]; + + if (pin_format->pin_index == pin_index) + return &pin_format->audio_fmt; + } + + return NULL; +} + /** * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples * @scomp: pointer to pointer to SOC component * @swidget: pointer to struct snd_sof_widget containing tuples * @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in - * @has_out_format: true if available_fmt contains output format + * @module_base_cfg: Pointer to the base_config in the module init IPC payload * * Return: 0 if successful */ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt, - bool has_out_format) + struct sof_ipc4_base_module_cfg *module_base_cfg) { - struct sof_ipc4_base_module_cfg *base_config; - struct sof_ipc4_audio_format *out_format; - int audio_fmt_num = 0; - int ret, i; + struct sof_ipc4_pin_format *in_format = NULL; + struct sof_ipc4_pin_format *out_format; + int ret; - ret = sof_update_ipc_object(scomp, &audio_fmt_num, + ret = sof_update_ipc_object(scomp, available_fmt, SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(audio_fmt_num), 1); - if (ret || audio_fmt_num <= 0) { - dev_err(scomp->dev, "Invalid number of audio formats: %d\n", audio_fmt_num); - return -EINVAL; - } - available_fmt->audio_fmt_num = audio_fmt_num; - - dev_dbg(scomp->dev, "Number of audio formats: %d\n", available_fmt->audio_fmt_num); - - base_config = kcalloc(available_fmt->audio_fmt_num, sizeof(*base_config), GFP_KERNEL); - if (!base_config) - return -ENOMEM; - - /* set cpc and is_pages for all base_cfg */ - for (i = 0; i < available_fmt->audio_fmt_num; i++) { - ret = sof_update_ipc_object(scomp, &base_config[i], - SOF_COMP_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(*base_config), 1); - if (ret) { - dev_err(scomp->dev, "parse comp tokens failed %d\n", ret); - goto err_in; - } + swidget->num_tuples, sizeof(available_fmt), 1); + if (ret) { + dev_err(scomp->dev, "Failed to parse audio format token count\n"); + return ret; } - /* copy the ibs/obs for each base_cfg */ - ret = sof_update_ipc_object(scomp, base_config, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(*base_config), - available_fmt->audio_fmt_num); - if (ret) { - dev_err(scomp->dev, "parse buffer size tokens failed %d\n", ret); - goto err_in; + if (!available_fmt->num_input_formats && !available_fmt->num_output_formats) { + dev_err(scomp->dev, "No input/output pin formats set in topology\n"); + return -EINVAL; } - for (i = 0; i < available_fmt->audio_fmt_num; i++) - dev_dbg(scomp->dev, "%d: ibs: %d obs: %d cpc: %d is_pages: %d\n", i, - base_config[i].ibs, base_config[i].obs, - base_config[i].cpc, base_config[i].is_pages); + dev_dbg(scomp->dev, + "Number of input audio formats: %d. Number of output audio formats: %d\n", + available_fmt->num_input_formats, available_fmt->num_output_formats); - ret = sof_update_ipc_object(scomp, &base_config->audio_fmt, - SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(*base_config), - available_fmt->audio_fmt_num); + /* set cpc and is_pages in the module's base_config */ + ret = sof_update_ipc_object(scomp, module_base_cfg, SOF_COMP_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*module_base_cfg), 1); if (ret) { - dev_err(scomp->dev, "parse base_config audio_fmt tokens failed %d\n", ret); - goto err_in; + dev_err(scomp->dev, "parse comp tokens for %s failed, error: %d\n", + swidget->widget->name, ret); + return ret; } - dev_dbg(scomp->dev, "Get input audio formats for %s\n", swidget->widget->name); - sof_ipc4_dbg_audio_format(scomp->dev, &base_config->audio_fmt, - sizeof(*base_config), - available_fmt->audio_fmt_num); + dev_dbg(scomp->dev, "widget %s cpc: %d is_pages: %d\n", + swidget->widget->name, module_base_cfg->cpc, module_base_cfg->is_pages); - available_fmt->base_config = base_config; + if (available_fmt->num_input_formats) { + in_format = kcalloc(available_fmt->num_input_formats, + sizeof(*in_format), GFP_KERNEL); + if (!in_format) + return -ENOMEM; + available_fmt->input_pin_fmts = in_format; - if (!has_out_format) - return 0; + ret = sof_update_ipc_object(scomp, in_format, + SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*in_format), + available_fmt->num_input_formats); + if (ret) { + dev_err(scomp->dev, "parse input audio fmt tokens failed %d\n", ret); + goto err_in; + } - out_format = kcalloc(available_fmt->audio_fmt_num, sizeof(*out_format), GFP_KERNEL); - if (!out_format) { - ret = -ENOMEM; - goto err_in; + dev_dbg(scomp->dev, "Input audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(scomp->dev, in_format, + available_fmt->num_input_formats); } - ret = sof_update_ipc_object(scomp, out_format, - SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(*out_format), - available_fmt->audio_fmt_num); + if (available_fmt->num_output_formats) { + out_format = kcalloc(available_fmt->num_output_formats, sizeof(*out_format), + GFP_KERNEL); + if (!out_format) { + ret = -ENOMEM; + goto err_in; + } - if (ret) { - dev_err(scomp->dev, "parse output audio_fmt tokens failed\n"); - goto err_out; - } + ret = sof_update_ipc_object(scomp, out_format, + SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*out_format), + available_fmt->num_output_formats); + if (ret) { + dev_err(scomp->dev, "parse output audio fmt tokens failed\n"); + goto err_out; + } - available_fmt->out_audio_fmt = out_format; - dev_dbg(scomp->dev, "Get output audio formats for %s\n", swidget->widget->name); - sof_ipc4_dbg_audio_format(scomp->dev, out_format, sizeof(*out_format), - available_fmt->audio_fmt_num); + available_fmt->output_pin_fmts = out_format; + dev_dbg(scomp->dev, "Output audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(scomp->dev, out_format, + available_fmt->num_output_formats); + } return 0; err_out: kfree(out_format); err_in: - kfree(base_config); - + kfree(in_format); + available_fmt->input_pin_fmts = NULL; return ret; } @@ -275,10 +305,10 @@ err_in: static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt) { - kfree(available_fmt->base_config); - available_fmt->base_config = NULL; - kfree(available_fmt->out_audio_fmt); - available_fmt->out_audio_fmt = NULL; + kfree(available_fmt->output_pin_fmts); + available_fmt->output_pin_fmts = NULL; + kfree(available_fmt->input_pin_fmts); + available_fmt->input_pin_fmts = NULL; } static void sof_ipc4_widget_free_comp_pipeline(struct snd_sof_widget *swidget) @@ -326,13 +356,31 @@ static int sof_ipc4_widget_setup_msg(struct snd_sof_widget *swidget, struct sof_ return 0; } +static void sof_ipc4_widget_update_kcontrol_module_id(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_fw_module *fw_module = swidget->module_info; + struct snd_sof_control *scontrol; + + /* update module ID for all kcontrols for this widget */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + if (scontrol->comp_id == swidget->comp_id) { + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct sof_ipc4_msg *msg = &cdata->msg; + + msg->primary |= fw_module->man4_module_entry.id; + } + } +} + static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) { struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct sof_ipc4_copier *ipc4_copier; int node_type = 0; - int ret, i; + int ret; ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); if (!ipc4_copier) @@ -343,17 +391,11 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); - ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); + ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, + &ipc4_copier->data.base_config); if (ret) goto free_copier; - available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), - GFP_KERNEL); - if (!available_fmt->dma_buffer_size) { - ret = -ENOMEM; - goto free_available_fmt; - } - /* * This callback is used by host copier and module-to-module copier, * and only host copier needs to set gtw_cfg. @@ -361,21 +403,6 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) if (!WIDGET_IS_AIF(swidget->id)) goto skip_gtw_cfg; - ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, - SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(u32), - available_fmt->audio_fmt_num); - if (ret) { - dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", - swidget->widget->name); - goto err; - } - - dev_dbg(scomp->dev, "dma buffer size:\n"); - for (i = 0; i < available_fmt->audio_fmt_num; i++) - dev_dbg(scomp->dev, "%d: %u\n", i, - available_fmt->dma_buffer_size[i]); - ret = sof_update_ipc_object(scomp, &node_type, SOF_COPIER_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(node_type), 1); @@ -383,7 +410,7 @@ static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) if (ret) { dev_err(scomp->dev, "parse host copier node type token failed %d\n", ret); - goto err; + goto free_available_fmt; } dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type); @@ -391,7 +418,7 @@ skip_gtw_cfg: ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) { ret = -ENOMEM; - goto err; + goto free_available_fmt; } ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; @@ -422,8 +449,6 @@ skip_gtw_cfg: free_gtw_attr: kfree(ipc4_copier->gtw_attr); -err: - kfree(available_fmt->dma_buffer_size); free_available_fmt: sof_ipc4_free_audio_fmt(available_fmt); free_copier: @@ -441,9 +466,7 @@ static void sof_ipc4_widget_free_comp_pcm(struct snd_sof_widget *swidget) return; available_fmt = &ipc4_copier->available_fmt; - kfree(available_fmt->dma_buffer_size); - kfree(available_fmt->base_config); - kfree(available_fmt->out_audio_fmt); + kfree(available_fmt->output_pin_fmts); kfree(ipc4_copier->gtw_attr); kfree(ipc4_copier); swidget->private = NULL; @@ -455,8 +478,10 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_copier *ipc4_copier; + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; int node_type = 0; - int ret, i; + int ret; ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); if (!ipc4_copier) @@ -466,37 +491,17 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); - ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); + ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, + &ipc4_copier->data.base_config); if (ret) goto free_copier; - available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), - GFP_KERNEL); - if (!available_fmt->dma_buffer_size) { - ret = -ENOMEM; - goto free_available_fmt; - } - - ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, - SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, - swidget->num_tuples, sizeof(u32), - available_fmt->audio_fmt_num); - if (ret) { - dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", - swidget->widget->name); - goto err; - } - - for (i = 0; i < available_fmt->audio_fmt_num; i++) - dev_dbg(scomp->dev, "%d: dma buffer size: %u\n", i, - available_fmt->dma_buffer_size[i]); - ret = sof_update_ipc_object(scomp, &node_type, SOF_COPIER_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(node_type), 1); if (ret) { dev_err(scomp->dev, "parse dai node type failed %d\n", ret); - goto err; + goto free_available_fmt; } ret = sof_update_ipc_object(scomp, ipc4_copier, @@ -504,7 +509,7 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) swidget->num_tuples, sizeof(u32), 1); if (ret) { dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret); - goto err; + goto free_available_fmt; } dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name, @@ -512,17 +517,43 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type); + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + if (pipeline->use_chain_dma && ipc4_copier->dai_type != SOF_DAI_INTEL_HDA) { + dev_err(scomp->dev, + "Bad DAI type '%d', Chained DMA is only supported by HDA DAIs (%d).\n", + ipc4_copier->dai_type, SOF_DAI_INTEL_HDA); + ret = -ENODEV; + goto free_available_fmt; + } + switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_ALH: { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_alh_configuration_blob *blob; + struct snd_soc_dapm_path *p; struct snd_sof_widget *w; + int src_num = 0; + + snd_soc_dapm_widget_for_each_source_path(swidget->widget, p) + src_num++; + + if (swidget->id == snd_soc_dapm_dai_in && src_num == 0) { + /* + * The blob will not be used if the ALH copier is playback direction + * and doesn't connect to any source. + * It is fine to call kfree(ipc4_copier->copier_config) since + * ipc4_copier->copier_config is null. + */ + ret = 0; + break; + } blob = kzalloc(sizeof(*blob), GFP_KERNEL); if (!blob) { ret = -ENOMEM; - goto err; + goto free_available_fmt; } list_for_each_entry(w, &sdev->widget_list, list) { @@ -551,7 +582,7 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) { ret = -ENOMEM; - goto err; + goto free_available_fmt; } ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; @@ -572,8 +603,6 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) free_copier_config: kfree(ipc4_copier->copier_config); -err: - kfree(available_fmt->dma_buffer_size); free_available_fmt: sof_ipc4_free_audio_fmt(available_fmt); free_copier: @@ -601,9 +630,7 @@ static void sof_ipc4_widget_free_comp_dai(struct snd_sof_widget *swidget) ipc4_copier = dai->private; available_fmt = &ipc4_copier->available_fmt; - kfree(available_fmt->dma_buffer_size); - kfree(available_fmt->base_config); - kfree(available_fmt->out_audio_fmt); + kfree(available_fmt->output_pin_fmts); if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP && ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC) kfree(ipc4_copier->copier_config); @@ -629,6 +656,14 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) goto err; } + swidget->core = pipeline->core_id; + + if (pipeline->use_chain_dma) { + dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name); + swidget->private = pipeline; + return 0; + } + /* parse one set of pipeline tokens */ ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*swidget), 1); @@ -640,9 +675,9 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) /* TODO: Get priority from topology */ pipeline->priority = 0; - dev_dbg(scomp->dev, "pipeline '%s': id %d pri %d lp mode %d\n", + dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n", swidget->widget->name, swidget->pipeline_id, - pipeline->priority, pipeline->lp_mode); + pipeline->priority, pipeline->core_id, pipeline->lp_mode); swidget->private = pipeline; @@ -652,6 +687,7 @@ static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); pipeline->msg.extension = pipeline->lp_mode; + pipeline->msg.extension |= SOF_IPC4_GLB_PIPE_EXT_CORE_ID(pipeline->core_id); pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; return 0; @@ -663,9 +699,6 @@ err: static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc4_fw_module *fw_module; - struct snd_sof_control *scontrol; struct sof_ipc4_gain *gain; int ret; @@ -678,8 +711,7 @@ static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) gain->data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK; gain->data.init_val = SOF_IPC4_VOL_ZERO_DB; - /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ - ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, false); + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, &gain->base_config); if (ret) goto err; @@ -699,16 +731,7 @@ static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) if (ret) goto err; - fw_module = swidget->module_info; - - /* update module ID for all kcontrols for this widget */ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) - if (scontrol->comp_id == swidget->comp_id) { - struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; - struct sof_ipc4_msg *msg = &cdata->msg; - - msg->primary |= fw_module->man4_module_entry.id; - } + sof_ipc4_widget_update_kcontrol_module_id(swidget); return 0; err: @@ -744,8 +767,8 @@ static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget) swidget->private = mixer; - /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ - ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, false); + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, + &mixer->base_config); if (ret) goto err; @@ -775,8 +798,7 @@ static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) swidget->private = src; - /* The out_audio_fmt in topology is ignored as it is not required by SRC */ - ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, false); + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, &src->base_config); if (ret) goto err; @@ -825,6 +847,94 @@ static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget) swidget->private = NULL; } +/* + * Add the process modules support. The process modules are defined as snd_soc_dapm_effect modules. + */ +static int sof_ipc4_widget_setup_comp_process(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_fw_module *fw_module; + struct sof_ipc4_process *process; + void *cfg; + int ret; + + process = kzalloc(sizeof(*process), GFP_KERNEL); + if (!process) + return -ENOMEM; + + swidget->private = process; + + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &process->available_fmt, + &process->base_config); + if (ret) + goto err; + + ret = sof_ipc4_widget_setup_msg(swidget, &process->msg); + if (ret) + goto err; + + /* parse process init module payload config type from module info */ + fw_module = swidget->module_info; + process->init_config = FIELD_GET(SOF_IPC4_MODULE_INIT_CONFIG_MASK, + fw_module->man4_module_entry.type); + + process->ipc_config_size = sizeof(struct sof_ipc4_base_module_cfg); + + /* allocate memory for base config extension if needed */ + if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) { + struct sof_ipc4_base_module_cfg_ext *base_cfg_ext; + u32 ext_size = struct_size(base_cfg_ext, pin_formats, + swidget->num_input_pins + swidget->num_output_pins); + + base_cfg_ext = kzalloc(ext_size, GFP_KERNEL); + if (!base_cfg_ext) { + ret = -ENOMEM; + goto free_available_fmt; + } + + base_cfg_ext->num_input_pin_fmts = swidget->num_input_pins; + base_cfg_ext->num_output_pin_fmts = swidget->num_output_pins; + process->base_config_ext = base_cfg_ext; + process->base_config_ext_size = ext_size; + process->ipc_config_size += ext_size; + } + + cfg = kzalloc(process->ipc_config_size, GFP_KERNEL); + if (!cfg) { + ret = -ENOMEM; + goto free_base_cfg_ext; + } + + process->ipc_config_data = cfg; + + sof_ipc4_widget_update_kcontrol_module_id(swidget); + + return 0; +free_base_cfg_ext: + kfree(process->base_config_ext); + process->base_config_ext = NULL; +free_available_fmt: + sof_ipc4_free_audio_fmt(&process->available_fmt); +err: + kfree(process); + swidget->private = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_process(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_process *process = swidget->private; + + if (!process) + return; + + kfree(process->ipc_config_data); + kfree(process->base_config_ext); + sof_ipc4_free_audio_fmt(&process->available_fmt); + kfree(swidget->private); + swidget->private = NULL; +} + static void sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config) @@ -876,22 +986,62 @@ static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev, return 0; } +/* update hw_params based on the audio stream format */ +static int sof_ipc4_update_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params, + struct sof_ipc4_audio_format *fmt) +{ + snd_pcm_format_t snd_fmt; + struct snd_interval *i; + struct snd_mask *m; + int valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); + unsigned int channels, rate; + + switch (valid_bits) { + case 16: + snd_fmt = SNDRV_PCM_FORMAT_S16_LE; + break; + case 24: + snd_fmt = SNDRV_PCM_FORMAT_S24_LE; + break; + case 32: + snd_fmt = SNDRV_PCM_FORMAT_S32_LE; + break; + default: + dev_err(sdev->dev, "invalid PCM valid_bits %d\n", valid_bits); + return -EINVAL; + } + + m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(m); + snd_mask_set_format(m, snd_fmt); + + rate = fmt->sampling_frequency; + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + i->min = rate; + i->max = rate; + + channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); + i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + i->min = channels; + i->max = channels; + + return 0; +} + static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, - struct sof_ipc4_audio_format *out_format, struct snd_pcm_hw_params *params, struct sof_ipc4_available_audio_format *available_fmt, - size_t object_offset) + struct sof_ipc4_pin_format *pin_fmts, u32 pin_fmts_size) { - void *ptr = available_fmt->ref_audio_fmt; u32 valid_bits; u32 channels; u32 rate; int sample_valid_bits; int i; - if (!ptr) { + if (!pin_fmts) { dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name); return -EINVAL; } @@ -911,53 +1061,53 @@ static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; } - if (!available_fmt->audio_fmt_num) { + if (!pin_fmts_size) { dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name); return -EINVAL; } /* - * Search supported audio formats to match rate, channels ,and + * Search supported audio formats with pin index 0 to match rate, channels ,and * sample_valid_bytes from runtime params */ - for (i = 0; i < available_fmt->audio_fmt_num; i++, ptr = (u8 *)ptr + object_offset) { - struct sof_ipc4_audio_format *fmt = ptr; + for (i = 0; i < pin_fmts_size; i++) { + struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt; + + if (pin_fmts[i].pin_index) + continue; rate = fmt->sampling_frequency; channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); if (params_rate(params) == rate && params_channels(params) == channels && sample_valid_bits == valid_bits) { - dev_dbg(sdev->dev, "matching audio format index for %uHz, %ubit, %u channels: %d\n", + dev_dbg(sdev->dev, "matched audio format index for %uHz, %ubit, %u channels: %d\n", rate, valid_bits, channels, i); - - /* copy ibs/obs and input format */ - memcpy(base_config, &available_fmt->base_config[i], - sizeof(struct sof_ipc4_base_module_cfg)); - - /* copy output format */ - if (out_format) - memcpy(out_format, &available_fmt->out_audio_fmt[i], - sizeof(struct sof_ipc4_audio_format)); break; } } - if (i == available_fmt->audio_fmt_num) { + if (i == pin_fmts_size) { dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n", __func__, params_rate(params), sample_valid_bits, params_channels(params)); return -EINVAL; } - dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name); - sof_ipc4_dbg_audio_format(sdev->dev, &base_config->audio_fmt, - sizeof(*base_config), 1); - if (out_format) { - dev_dbg(sdev->dev, "Init output audio formats for %s\n", swidget->widget->name); - sof_ipc4_dbg_audio_format(sdev->dev, out_format, - sizeof(*out_format), 1); + /* copy input format */ + if (available_fmt->num_input_formats && i < available_fmt->num_input_formats) { + memcpy(&base_config->audio_fmt, &available_fmt->input_pin_fmts[i].audio_fmt, + sizeof(struct sof_ipc4_audio_format)); + + /* set base_cfg ibs/obs */ + base_config->ibs = available_fmt->input_pin_fmts[i].buffer_size; + + dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->input_pin_fmts[i], 1); } + if (available_fmt->num_output_formats && i < available_fmt->num_output_formats) + base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; + /* Return the index of the matched format */ return i; } @@ -974,11 +1124,21 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) pipeline->mem_usage = 0; if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) { + if (pipeline->use_chain_dma) { + pipeline->msg.primary = 0; + pipeline->msg.extension = 0; + } ipc4_copier = swidget->private; } else if (WIDGET_IS_DAI(swidget->id)) { struct snd_sof_dai *dai = swidget->private; ipc4_copier = dai->private; + + if (pipeline->use_chain_dma) { + pipeline->msg.primary = 0; + pipeline->msg.extension = 0; + } + if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data; struct sof_ipc4_alh_configuration_blob *blob; @@ -1109,6 +1269,69 @@ static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_s } #endif +static int ipc4_set_fmt_mask(struct snd_mask *fmt, unsigned int bit_depth) +{ + switch (bit_depth) { + case 16: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case 24: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + break; + case 32: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ipc4_copier_set_capture_fmt(struct snd_sof_dev *sdev, + struct snd_pcm_hw_params *pipeline_params, + struct snd_pcm_hw_params *fe_params, + struct sof_ipc4_available_audio_format *available_fmt) +{ + struct sof_ipc4_audio_format *audio_fmt; + unsigned int sample_valid_bits; + bool multiple_formats = false; + bool fe_format_match = false; + struct snd_mask *fmt; + int i; + + for (i = 0; i < available_fmt->num_output_formats; i++) { + unsigned int val; + + audio_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; + val = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(audio_fmt->fmt_cfg); + + if (i == 0) + sample_valid_bits = val; + else if (sample_valid_bits != val) + multiple_formats = true; + + if (snd_pcm_format_width(params_format(fe_params)) == val) + fe_format_match = true; + } + + fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(fmt); + + if (multiple_formats) { + if (fe_format_match) { + /* multiple formats defined and one matches FE */ + snd_mask_set_format(fmt, params_format(fe_params)); + return 0; + } + + dev_err(sdev->dev, "Multiple audio formats for single dai_out not supported\n"); + return -EINVAL; + } + + return ipc4_set_fmt_mask(fmt, sample_valid_bits); +} + static int sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, @@ -1118,17 +1341,19 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_pin_format *format_list_to_search; struct sof_ipc4_copier_data *copier_data; struct snd_pcm_hw_params *ref_params; struct sof_ipc4_copier *ipc4_copier; struct snd_sof_dai *dai; struct snd_mask *fmt; int out_sample_valid_bits; - size_t ref_audio_fmt_size; void **ipc_config_data; int *ipc_config_size; u32 **data; int ipc_size, ret; + u32 deep_buffer_dma_ms = 0; + u32 format_list_count; dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id); @@ -1140,25 +1365,66 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; - pipe_widget = swidget->spipe->pipe_widget; - pipeline = pipe_widget->private; + /* parse the deep buffer dma size */ + ret = sof_update_ipc_object(scomp, &deep_buffer_dma_ms, + SOF_COPIER_DEEP_BUFFER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(u32), 1); + if (ret) { + dev_err(scomp->dev, "Failed to parse deep buffer dma size for %s\n", + swidget->widget->name); + return ret; + } + ipc4_copier = (struct sof_ipc4_copier *)swidget->private; gtw_attr = ipc4_copier->gtw_attr; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + + if (pipeline->use_chain_dma) { + u32 host_dma_id; + u32 fifo_size; + + host_dma_id = platform_params->stream_tag - 1; + pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(host_dma_id); + + /* Set SCS bit for S16_LE format only */ + if (params_format(fe_params) == SNDRV_PCM_FORMAT_S16_LE) + pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_SCS_MASK; + + /* + * Despite its name the bitfield 'fifo_size' is used to define DMA buffer + * size. The expression calculates 2ms buffer size. + */ + fifo_size = DIV_ROUND_UP((SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS * + params_rate(fe_params) * + params_channels(fe_params) * + params_physical_width(fe_params)), 8000); + pipeline->msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(fifo_size); + + /* + * Chain DMA does not support stream timestamping, set node_id to invalid + * to skip the code in sof_ipc4_get_stream_start_offset(). + */ + copier_data->gtw_cfg.node_id = SOF_IPC4_INVALID_NODE_ID; + + return 0; + } + /* - * base_config->audio_fmt and out_audio_fmt represent the input and output audio - * formats. Use the input format as the reference to match pcm params for playback - * and the output format as reference for capture. + * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts + * for capture. */ if (dir == SNDRV_PCM_STREAM_PLAYBACK) { - available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; - ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); + format_list_to_search = available_fmt->input_pin_fmts; + format_list_count = available_fmt->num_input_formats; } else { - available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; - ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); + format_list_to_search = available_fmt->output_pin_fmts; + format_list_count = available_fmt->num_output_formats; } + copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1); @@ -1171,25 +1437,28 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: { + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (pipeline->use_chain_dma) + return 0; + dai = swidget->private; ipc4_copier = (struct sof_ipc4_copier *)dai->private; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; if (dir == SNDRV_PCM_STREAM_CAPTURE) { - available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; - ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); + format_list_to_search = available_fmt->output_pin_fmts; + format_list_count = available_fmt->num_output_formats; - /* - * modify the input params for the dai copier as it only supports - * 32-bit always - */ - fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); - snd_mask_none(fmt); - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + ret = ipc4_copier_set_capture_fmt(sdev, pipeline_params, fe_params, + available_fmt); + if (ret < 0) + return ret; } else { - available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; - ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); + format_list_to_search = available_fmt->input_pin_fmts; + format_list_count = available_fmt->num_input_formats; } ref_params = pipeline_params; @@ -1209,12 +1478,9 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; - /* - * base_config->audio_fmt represent the input audio formats. Use - * the input format as the reference to match pcm params - */ - available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; - ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); + /* Use the input formats to match pcm params */ + format_list_to_search = available_fmt->input_pin_fmts; + format_list_count = available_fmt->num_input_formats; ref_params = pipeline_params; break; @@ -1226,12 +1492,23 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, } /* set input and output audio formats */ - ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config, - &copier_data->out_format, ref_params, - available_fmt, ref_audio_fmt_size); + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config, ref_params, + available_fmt, format_list_to_search, format_list_count); if (ret < 0) return ret; + /* + * Set the output format. Current topology defines pin 0 input and output formats in pairs. + * This assumes that the pin 0 formats are defined before all other pins. + * So pick the output audio format with the same index as the chosen + * input format. This logic will need to be updated when the format definitions + * in topology change. + */ + memcpy(&copier_data->out_format, &available_fmt->output_pin_fmts[ret].audio_fmt, + sizeof(struct sof_ipc4_audio_format)); + dev_dbg(sdev->dev, "Output audio format for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->output_pin_fmts[ret], 1); + switch (swidget->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: @@ -1323,25 +1600,34 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, out_sample_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(copier_data->out_format.fmt_cfg); snd_mask_none(fmt); - switch (out_sample_valid_bits) { - case 16: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + ret = ipc4_set_fmt_mask(fmt, out_sample_valid_bits); + if (ret) + return ret; + + /* + * Set the gateway dma_buffer_size to 2ms buffer size to meet the FW expectation. In the + * deep buffer case, set the dma_buffer_size depending on the deep_buffer_dma_ms set + * in topology. + */ + switch (swidget->id) { + case snd_soc_dapm_dai_in: + copier_data->gtw_cfg.dma_buffer_size = + SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.ibs; break; - case 24: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + case snd_soc_dapm_aif_in: + copier_data->gtw_cfg.dma_buffer_size = + max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms) * + copier_data->base_config.ibs; break; - case 32: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + case snd_soc_dapm_dai_out: + case snd_soc_dapm_aif_out: + copier_data->gtw_cfg.dma_buffer_size = + SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.obs; break; default: - dev_err(sdev->dev, "invalid sample frame format %d\n", - params_format(pipeline_params)); - return -EINVAL; + break; } - /* set the gateway dma_buffer_size using the matched ID returned above */ - copier_data->gtw_cfg.dma_buffer_size = available_fmt->dma_buffer_size[ret]; - data = &ipc4_copier->copier_config; ipc_config_size = &ipc4_copier->ipc_config_size; ipc_config_data = &ipc4_copier->ipc_config_data; @@ -1377,14 +1663,13 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_gain *gain = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &gain->available_fmt; int ret; - gain->available_fmt.ref_audio_fmt = &gain->available_fmt.base_config->audio_fmt; - - /* output format is not required to be sent to the FW for gain */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config, - NULL, pipeline_params, &gain->available_fmt, - sizeof(gain->base_config)); + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1402,15 +1687,13 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_mixer *mixer = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &mixer->available_fmt; int ret; - /* only 32bit is supported by mixer */ - mixer->available_fmt.ref_audio_fmt = &mixer->available_fmt.base_config->audio_fmt; - - /* output format is not required to be sent to the FW for mixer */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config, - NULL, pipeline_params, &mixer->available_fmt, - sizeof(mixer->base_config)); + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1428,15 +1711,14 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_src *src = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &src->available_fmt; struct snd_interval *rate; int ret; - src->available_fmt.ref_audio_fmt = &src->available_fmt.base_config->audio_fmt; - - /* output format is not required to be sent to the FW for SRC */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config, - NULL, pipeline_params, &src->available_fmt, - sizeof(src->base_config)); + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1451,6 +1733,135 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, return 0; } +static int +sof_ipc4_process_set_pin_formats(struct snd_sof_widget *swidget, int pin_type) +{ + struct sof_ipc4_process *process = swidget->private; + struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext; + struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; + struct sof_ipc4_pin_format *pin_format, *format_list_to_search; + struct snd_soc_component *scomp = swidget->scomp; + int num_pins, format_list_count; + int pin_format_offset = 0; + int i, j; + + /* set number of pins, offset of pin format and format list to search based on pin type */ + if (pin_type == SOF_PIN_TYPE_INPUT) { + num_pins = swidget->num_input_pins; + format_list_to_search = available_fmt->input_pin_fmts; + format_list_count = available_fmt->num_input_formats; + } else { + num_pins = swidget->num_output_pins; + pin_format_offset = swidget->num_input_pins; + format_list_to_search = available_fmt->output_pin_fmts; + format_list_count = available_fmt->num_output_formats; + } + + for (i = pin_format_offset; i < num_pins + pin_format_offset; i++) { + pin_format = &base_cfg_ext->pin_formats[i]; + + /* Pin 0 audio formats are derived from the base config input/output format */ + if (i == pin_format_offset) { + if (pin_type == SOF_PIN_TYPE_INPUT) { + pin_format->buffer_size = process->base_config.ibs; + pin_format->audio_fmt = process->base_config.audio_fmt; + } else { + pin_format->buffer_size = process->base_config.obs; + pin_format->audio_fmt = process->output_format; + } + continue; + } + + /* + * For all other pins, find the pin formats from those set in topology. If there + * is more than one format specified for a pin, this will pick the first available + * one. + */ + for (j = 0; j < format_list_count; j++) { + struct sof_ipc4_pin_format *pin_format_item = &format_list_to_search[j]; + + if (pin_format_item->pin_index == i - pin_format_offset) { + *pin_format = *pin_format_item; + break; + } + } + + if (j == format_list_count) { + dev_err(scomp->dev, "%s pin %d format not found for %s\n", + (pin_type == SOF_PIN_TYPE_INPUT) ? "input" : "output", + i - pin_format_offset, swidget->widget->name); + return -EINVAL; + } + } + + return 0; +} + +static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget) +{ + int ret, i; + + /* copy input and output pin formats */ + for (i = 0; i <= SOF_PIN_TYPE_OUTPUT; i++) { + ret = sof_ipc4_process_set_pin_formats(swidget, i); + if (ret < 0) + return ret; + } + + return 0; +} + +static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_process *process = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; + void *cfg = process->ipc_config_data; + int ret; + + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &process->base_config, + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); + if (ret < 0) + return ret; + + /* copy Pin 0 output format */ + if (available_fmt->num_output_formats && ret < available_fmt->num_output_formats && + !available_fmt->output_pin_fmts[ret].pin_index) { + memcpy(&process->output_format, &available_fmt->output_pin_fmts[ret].audio_fmt, + sizeof(struct sof_ipc4_audio_format)); + + /* modify the pipeline params with the pin 0 output format */ + ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &process->output_format); + if (ret) + return ret; + } + + /* update pipeline memory usage */ + sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &process->base_config); + + /* ipc_config_data is composed of the base_config followed by an optional extension */ + memcpy(cfg, &process->base_config, sizeof(struct sof_ipc4_base_module_cfg)); + cfg += sizeof(struct sof_ipc4_base_module_cfg); + + if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) { + struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext; + + ret = sof_ipc4_process_add_base_cfg_extn(swidget); + if (ret < 0) + return ret; + + memcpy(cfg, base_cfg_ext, process->base_config_ext_size); + } + + return 0; +} + static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) { struct sof_ipc4_control_data *control_data; @@ -1483,6 +1894,71 @@ static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof return 0; } +static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc4_control_data *control_data; + struct sof_ipc4_msg *msg; + int ret; + + if (scontrol->max_size < (sizeof(*control_data) + sizeof(struct sof_abi_hdr))) { + dev_err(sdev->dev, "insufficient size for a bytes control %s: %zu.\n", + scontrol->name, scontrol->max_size); + return -EINVAL; + } + + if (scontrol->priv_size > scontrol->max_size - sizeof(*control_data)) { + dev_err(sdev->dev, "scontrol %s bytes data size %zu exceeds max %zu.\n", + scontrol->name, scontrol->priv_size, + scontrol->max_size - sizeof(*control_data)); + return -EINVAL; + } + + scontrol->size = sizeof(struct sof_ipc4_control_data) + scontrol->priv_size; + + scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL); + if (!scontrol->ipc_control_data) + return -ENOMEM; + + control_data = scontrol->ipc_control_data; + control_data->index = scontrol->index; + if (scontrol->priv_size > 0) { + memcpy(control_data->data, scontrol->priv, scontrol->priv_size); + kfree(scontrol->priv); + scontrol->priv = NULL; + + if (control_data->data->magic != SOF_IPC4_ABI_MAGIC) { + dev_err(sdev->dev, "Wrong ABI magic (%#x) for control: %s\n", + control_data->data->magic, scontrol->name); + ret = -EINVAL; + goto err; + } + + /* TODO: check the ABI version */ + + if (control_data->data->size + sizeof(struct sof_abi_hdr) != + scontrol->priv_size) { + dev_err(sdev->dev, "Control %s conflict in bytes %zu vs. priv size %zu.\n", + scontrol->name, + control_data->data->size + sizeof(struct sof_abi_hdr), + scontrol->priv_size); + ret = -EINVAL; + goto err; + } + } + + msg = &control_data->msg; + msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); + msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + + return 0; + +err: + kfree(scontrol->ipc_control_data); + scontrol->ipc_control_data = NULL; + return ret; +} + static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) { switch (scontrol->info_type) { @@ -1490,6 +1966,8 @@ static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_contr case SND_SOC_TPLG_CTL_VOLSW_SX: case SND_SOC_TPLG_CTL_VOLSW_XR_SX: return sof_ipc4_control_load_volume(sdev, scontrol); + case SND_SOC_TPLG_CTL_BYTES: + return sof_ipc4_control_load_bytes(sdev, scontrol); default: break; } @@ -1511,6 +1989,12 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget case snd_soc_dapm_scheduler: pipeline = swidget->private; + if (pipeline->use_chain_dma) { + dev_warn(sdev->dev, "use_chain_dma set for scheduler %s", + swidget->widget->name); + return 0; + } + dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id, pipeline->mem_usage); @@ -1533,6 +2017,10 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget { struct sof_ipc4_copier *ipc4_copier = swidget->private; + pipeline = pipe_widget->private; + if (pipeline->use_chain_dma) + return 0; + ipc_size = ipc4_copier->ipc_config_size; ipc_data = ipc4_copier->ipc_config_data; @@ -1545,6 +2033,10 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_copier *ipc4_copier = dai->private; + pipeline = pipe_widget->private; + if (pipeline->use_chain_dma) + return 0; + ipc_size = ipc4_copier->ipc_config_size; ipc_data = ipc4_copier->ipc_config_data; @@ -1582,6 +2074,22 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget msg = &src->msg; break; } + case snd_soc_dapm_effect: + { + struct sof_ipc4_process *process = swidget->private; + + if (!process->ipc_config_size) { + dev_err(sdev->dev, "module %s has no config data!\n", + swidget->widget->name); + return -EINVAL; + } + + ipc_size = process->ipc_config_size; + ipc_data = process->ipc_config_data; + + msg = &process->msg; + break; + } default: dev_err(sdev->dev, "widget type %d not supported", swidget->id); return -EINVAL; @@ -1610,7 +2118,7 @@ static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget msg->data_size = ipc_size; msg->data_ptr = ipc_data; - ret = sof_ipc_tx_message(sdev->ipc, msg, ipc_size, NULL, 0); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, ipc_size); if (ret < 0) { dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name); @@ -1640,6 +2148,13 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget struct sof_ipc4_msg msg = {{ 0 }}; u32 header; + if (pipeline->use_chain_dma) { + dev_warn(sdev->dev, "use_chain_dma set for scheduler %s", + swidget->widget->name); + mutex_unlock(&ipc4_data->pipeline_state_mutex); + return 0; + } + header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->instance_id); header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE); header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); @@ -1647,7 +2162,7 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget msg.primary = header; - ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); if (ret < 0) dev_err(sdev->dev, "failed to free pipeline widget %s\n", swidget->widget->name); @@ -1656,7 +2171,11 @@ static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; ida_free(&pipeline_ida, swidget->instance_id); } else { - ida_free(&fw_module->m_ida, swidget->instance_id); + struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + if (!pipeline->use_chain_dma) + ida_free(&fw_module->m_ida, swidget->instance_id); } mutex_unlock(&ipc4_data->pipeline_state_mutex); @@ -1675,17 +2194,17 @@ static int sof_ipc4_get_queue_id(struct snd_sof_widget *src_widget, u32 num_pins; int i; - if (pin_type == SOF_PIN_TYPE_SOURCE) { + if (pin_type == SOF_PIN_TYPE_OUTPUT) { current_swidget = src_widget; - pin_binding = src_widget->src_pin_binding; - queue_ida = &src_widget->src_queue_ida; - num_pins = src_widget->num_source_pins; + pin_binding = src_widget->output_pin_binding; + queue_ida = &src_widget->output_queue_ida; + num_pins = src_widget->num_output_pins; buddy_name = sink_widget->widget->name; } else { current_swidget = sink_widget; - pin_binding = sink_widget->sink_pin_binding; - queue_ida = &sink_widget->sink_queue_ida; - num_pins = sink_widget->num_sink_pins; + pin_binding = sink_widget->input_pin_binding; + queue_ida = &sink_widget->input_queue_ida; + num_pins = sink_widget->num_input_pins; buddy_name = src_widget->widget->name; } @@ -1693,12 +2212,12 @@ static int sof_ipc4_get_queue_id(struct snd_sof_widget *src_widget, if (num_pins < 1) { dev_err(scomp->dev, "invalid %s num_pins: %d for queue allocation for %s\n", - (pin_type == SOF_PIN_TYPE_SOURCE ? "source" : "sink"), + (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"), num_pins, current_swidget->widget->name); return -EINVAL; } - /* If there is only one sink/source pin, queue id must be 0 */ + /* If there is only one input/output pin, queue id must be 0 */ if (num_pins == 1) return 0; @@ -1713,7 +2232,7 @@ static int sof_ipc4_get_queue_id(struct snd_sof_widget *src_widget, * mixed use pin binding array and ida for queue ID allocation. */ dev_err(scomp->dev, "no %s queue id found from pin binding array for %s\n", - (pin_type == SOF_PIN_TYPE_SOURCE ? "source" : "sink"), + (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"), current_swidget->widget->name); return -EINVAL; } @@ -1729,14 +2248,14 @@ static void sof_ipc4_put_queue_id(struct snd_sof_widget *swidget, int queue_id, char **pin_binding; int num_pins; - if (pin_type == SOF_PIN_TYPE_SOURCE) { - pin_binding = swidget->src_pin_binding; - queue_ida = &swidget->src_queue_ida; - num_pins = swidget->num_source_pins; + if (pin_type == SOF_PIN_TYPE_OUTPUT) { + pin_binding = swidget->output_pin_binding; + queue_ida = &swidget->output_queue_ida; + num_pins = swidget->num_output_pins; } else { - pin_binding = swidget->sink_pin_binding; - queue_ida = &swidget->sink_queue_ida; - num_pins = swidget->num_sink_pins; + pin_binding = swidget->input_pin_binding; + queue_ida = &swidget->input_queue_ida; + num_pins = swidget->num_input_pins; } /* Nothing to free if queue ID is not allocated with ida. */ @@ -1751,9 +2270,9 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, struct snd_sof_widget *sink_widget, int sink_id) { - struct sof_ipc4_base_module_cfg *sink_config = sink_widget->private; - struct sof_ipc4_base_module_cfg *src_config; struct sof_ipc4_copier_config_set_sink_format format; + struct sof_ipc4_base_module_cfg *src_config; + const struct sof_ipc4_audio_format *pin_fmt; struct sof_ipc4_fw_module *fw_module; struct sof_ipc4_msg msg = {{ 0 }}; u32 header, extension; @@ -1773,7 +2292,16 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, format.sink_id = sink_id; memcpy(&format.source_fmt, &src_config->audio_fmt, sizeof(format.source_fmt)); - memcpy(&format.sink_fmt, &sink_config->audio_fmt, sizeof(format.sink_fmt)); + + pin_fmt = sof_ipc4_get_input_pin_audio_fmt(sink_widget, sink_id); + if (!pin_fmt) { + dev_err(sdev->dev, "Unable to get pin %d format for %s", + sink_id, sink_widget->widget->name); + return -EINVAL; + } + + memcpy(&format.sink_fmt, pin_fmt, sizeof(format.sink_fmt)); + msg.data_size = sizeof(format); msg.data_ptr = &format; @@ -1792,19 +2320,34 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, msg.primary = header; msg.extension = extension; - return sof_ipc_tx_message(sdev->ipc, &msg, msg.data_size, NULL, 0); + return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, msg.data_size); } static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) { struct snd_sof_widget *src_widget = sroute->src_widget; struct snd_sof_widget *sink_widget = sroute->sink_widget; + struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget; + struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget; struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; + struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private; + struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private; struct sof_ipc4_msg msg = {{ 0 }}; u32 header, extension; int ret; + /* no route set up if chain DMA is used */ + if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma) { + if (!src_pipeline->use_chain_dma || !sink_pipeline->use_chain_dma) { + dev_err(sdev->dev, + "use_chain_dma must be set for both src %s and sink %s pipelines\n", + src_widget->widget->name, sink_widget->widget->name); + return -EINVAL; + } + return 0; + } + if (!src_fw_module || !sink_fw_module) { dev_err(sdev->dev, "cannot bind %s -> %s, no firmware module for: %s%s\n", @@ -1816,7 +2359,7 @@ static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * } sroute->src_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget, - SOF_PIN_TYPE_SOURCE); + SOF_PIN_TYPE_OUTPUT); if (sroute->src_queue_id < 0) { dev_err(sdev->dev, "failed to get queue ID for source widget: %s\n", src_widget->widget->name); @@ -1824,12 +2367,12 @@ static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * } sroute->dst_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget, - SOF_PIN_TYPE_SINK); + SOF_PIN_TYPE_INPUT); if (sroute->dst_queue_id < 0) { dev_err(sdev->dev, "failed to get queue ID for sink widget: %s\n", sink_widget->widget->name); sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, - SOF_PIN_TYPE_SOURCE); + SOF_PIN_TYPE_OUTPUT); return sroute->dst_queue_id; } @@ -1862,7 +2405,7 @@ static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * msg.primary = header; msg.extension = extension; - ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); if (ret < 0) { dev_err(sdev->dev, "failed to bind modules %s:%d -> %s:%d\n", src_widget->widget->name, sroute->src_queue_id, @@ -1873,8 +2416,8 @@ static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route * return ret; out: - sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_SOURCE); - sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_SINK); + sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT); + sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT); return ret; } @@ -1885,9 +2428,17 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; struct sof_ipc4_msg msg = {{ 0 }}; + struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget; + struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget; + struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private; + struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private; u32 header, extension; int ret = 0; + /* no route is set up if chain DMA is used */ + if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma) + return 0; + dev_dbg(sdev->dev, "unbind modules %s:%d -> %s:%d\n", src_widget->widget->name, sroute->src_queue_id, sink_widget->widget->name, sroute->dst_queue_id); @@ -1913,14 +2464,14 @@ static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *s msg.primary = header; msg.extension = extension; - ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0); if (ret < 0) dev_err(sdev->dev, "failed to unbind modules %s:%d -> %s:%d\n", src_widget->widget->name, sroute->src_queue_id, sink_widget->widget->name, sroute->dst_queue_id); out: - sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_SINK); - sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_SOURCE); + sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT); + sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT); return ret; } @@ -1949,6 +2500,11 @@ static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_HDA: + if (pipeline->use_chain_dma) { + pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK; + pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(data->dai_data); + break; + } gtw_attr = ipc4_copier->gtw_attr; gtw_attr->lp_buffer_alloc = pipeline->lp_mode; pipeline->skip_during_fe_trigger = true; @@ -2149,10 +2705,9 @@ static int sof_ipc4_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link static enum sof_tokens common_copier_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_OUT_AUDIO_FORMAT_TOKENS, - SOF_COPIER_GATEWAY_CFG_TOKENS, + SOF_COPIER_DEEP_BUFFER_TOKENS, SOF_COPIER_TOKENS, SOF_COMP_EXT_TOKENS, }; @@ -2165,10 +2720,8 @@ static enum sof_tokens pipeline_token_list[] = { static enum sof_tokens dai_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_OUT_AUDIO_FORMAT_TOKENS, - SOF_COPIER_GATEWAY_CFG_TOKENS, SOF_COPIER_TOKENS, SOF_DAI_TOKENS, SOF_COMP_EXT_TOKENS, @@ -2178,8 +2731,8 @@ static enum sof_tokens pga_token_list[] = { SOF_COMP_TOKENS, SOF_GAIN_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, SOF_COMP_EXT_TOKENS, }; @@ -2187,7 +2740,7 @@ static enum sof_tokens mixer_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, SOF_COMP_EXT_TOKENS, }; @@ -2196,7 +2749,15 @@ static enum sof_tokens src_token_list[] = { SOF_SRC_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static enum sof_tokens process_token_list[] = { + SOF_COMP_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, SOF_COMP_EXT_TOKENS, }; @@ -2237,6 +2798,11 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TY src_token_list, ARRAY_SIZE(src_token_list), NULL, sof_ipc4_prepare_src_module, NULL}, + [snd_soc_dapm_effect] = {sof_ipc4_widget_setup_comp_process, + sof_ipc4_widget_free_comp_process, + process_token_list, ARRAY_SIZE(process_token_list), + NULL, sof_ipc4_prepare_process_module, + NULL}, }; const struct sof_ipc_tplg_ops ipc4_tplg_ops = { diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 123f1096f326..cf007282867b 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -26,6 +26,10 @@ #define SOF_IPC4_MODULE_LL BIT(5) #define SOF_IPC4_MODULE_DP BIT(6) #define SOF_IPC4_MODULE_LIB_CODE BIT(7) +#define SOF_IPC4_MODULE_INIT_CONFIG_MASK GENMASK(11, 8) + +#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG 0 +#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT 1 #define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12 #define SOF_IPC4_PIPELINE_OBJECT_SIZE 448 @@ -55,6 +59,9 @@ #define SOF_IPC4_INVALID_NODE_ID 0xffffffff +/* FW requires minimum 2ms DMA buffer size */ +#define SOF_IPC4_MIN_DMA_BUFFER_SIZE 2 + /* * The base of multi-gateways. Multi-gateways addressing starts from * ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources @@ -117,7 +124,9 @@ struct sof_ipc4_copier_config_set_sink_format { * @priority: Priority of this pipeline * @lp_mode: Low power mode * @mem_usage: Memory usage + * @core_id: Target core for the pipeline * @state: Pipeline state + * @use_chain_dma: flag to indicate if the firmware shall use chained DMA * @msg: message structure for pipeline * @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger */ @@ -125,7 +134,9 @@ struct sof_ipc4_pipeline { uint32_t priority; uint32_t lp_mode; uint32_t mem_usage; + uint32_t core_id; int state; + bool use_chain_dma; struct sof_ipc4_msg msg; bool skip_during_fe_trigger; }; @@ -141,19 +152,32 @@ struct ipc4_pipeline_set_state_data { } __packed; /** + * struct sof_ipc4_pin_format - Module pin format + * @pin_index: pin index + * @buffer_size: buffer size in bytes + * @audio_fmt: audio format for the pin + * + * This structure can be used for both output or input pins and the pin_index is relative to the + * pin type i.e output/input pin + */ +struct sof_ipc4_pin_format { + u32 pin_index; + u32 buffer_size; + struct sof_ipc4_audio_format audio_fmt; +}; + +/** * struct sof_ipc4_available_audio_format - Available audio formats - * @base_config: Available base config - * @out_audio_fmt: Available output audio format - * @ref_audio_fmt: Reference audio format to match runtime audio format - * @dma_buffer_size: Available Gateway DMA buffer size (in bytes) - * @audio_fmt_num: Number of available audio formats + * @output_pin_fmts: Available output pin formats + * @input_pin_fmts: Available input pin formats + * @num_input_formats: Number of input pin formats + * @num_output_formats: Number of output pin formats */ struct sof_ipc4_available_audio_format { - struct sof_ipc4_base_module_cfg *base_config; - struct sof_ipc4_audio_format *out_audio_fmt; - struct sof_ipc4_audio_format *ref_audio_fmt; - u32 *dma_buffer_size; - int audio_fmt_num; + struct sof_ipc4_pin_format *output_pin_fmts; + struct sof_ipc4_pin_format *input_pin_fmts; + u32 num_input_formats; + u32 num_output_formats; }; /** @@ -266,8 +290,8 @@ struct sof_ipc4_control_data { int index; union { - struct sof_ipc4_ctrl_value_chan chanv[0]; - struct sof_abi_hdr data[0]; + DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv); + DECLARE_FLEX_ARRAY(struct sof_abi_hdr, data); }; }; @@ -329,4 +353,45 @@ struct sof_ipc4_src { struct sof_ipc4_msg msg; }; +/** + * struct sof_ipc4_base_module_cfg_ext - base module config extension containing the pin format + * information for the module. Both @num_input_pin_fmts and @num_output_pin_fmts cannot be 0 for a + * module. + * @num_input_pin_fmts: number of input pin formats in the @pin_formats array + * @num_output_pin_fmts: number of output pin formats in the @pin_formats array + * @reserved: reserved for future use + * @pin_formats: flexible array consisting of @num_input_pin_fmts input pin format items followed + * by @num_output_pin_fmts output pin format items + */ +struct sof_ipc4_base_module_cfg_ext { + u16 num_input_pin_fmts; + u16 num_output_pin_fmts; + u8 reserved[12]; + DECLARE_FLEX_ARRAY(struct sof_ipc4_pin_format, pin_formats); +} __packed; + +/** + * struct sof_ipc4_process - process config data + * @base_config: IPC base config data + * @base_config_ext: Base config extension data for module init + * @output_format: Output audio format + * @available_fmt: Available audio format + * @ipc_config_data: Process module config data + * @ipc_config_size: Size of process module config data + * @msg: IPC4 message struct containing header and data info + * @base_config_ext_size: Size of the base config extension data in bytes + * @init_config: Module init config type (SOF_IPC4_MODULE_INIT_CONFIG_TYPE_*) + */ +struct sof_ipc4_process { + struct sof_ipc4_base_module_cfg base_config; + struct sof_ipc4_base_module_cfg_ext *base_config_ext; + struct sof_ipc4_audio_format output_format; + struct sof_ipc4_available_audio_format available_fmt; + void *ipc_config_data; + uint32_t ipc_config_size; + struct sof_ipc4_msg msg; + u32 base_config_ext_size; + u32 init_config; +}; + #endif diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index 81d202e5ce53..2f8555f11c03 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -123,7 +123,7 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) "fw_version", 0444); /* errors are only due to memory allocation, not debugfs */ if (ret < 0) { - dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n"); + dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n"); return ret; } } @@ -131,7 +131,7 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) /* perform pre fw run operations */ ret = snd_sof_dsp_pre_fw_run(sdev); if (ret < 0) { - dev_err(sdev->dev, "error: failed pre fw run op\n"); + dev_err(sdev->dev, "failed pre fw run op\n"); return ret; } diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index 445acb5c3a21..567db32173a8 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -211,16 +211,22 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, dev_dbg(component->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); - /* free PCM in the DSP */ - if (pcm_ops && pcm_ops->hw_free && spcm->prepared[substream->stream]) { - ret = pcm_ops->hw_free(component, substream); - if (ret < 0) - err = ret; + if (spcm->prepared[substream->stream]) { + /* stop DMA first if needed */ + if (pcm_ops && pcm_ops->platform_stop_during_hw_free) + snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP); + + /* free PCM in the DSP */ + if (pcm_ops && pcm_ops->hw_free) { + ret = pcm_ops->hw_free(component, substream); + if (ret < 0) + err = ret; + } spcm->prepared[substream->stream] = false; } - /* stop DMA */ + /* reset DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { dev_err(component->dev, "error: platform hw free failed\n"); @@ -301,6 +307,8 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ipc_first = true; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (pcm_ops && pcm_ops->ipc_first_on_start) + ipc_first = true; break; case SNDRV_PCM_TRIGGER_START: if (spcm->stream[substream->stream].suspend_ignored) { @@ -312,6 +320,9 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = false; return 0; } + + if (pcm_ops && pcm_ops->ipc_first_on_start) + ipc_first = true; break; case SNDRV_PCM_TRIGGER_SUSPEND: if (sdev->system_suspend_target == SOF_SUSPEND_S0IX && @@ -325,29 +336,45 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = true; return 0; } + + /* On suspend the DMA must be stopped in DSPless mode */ + if (sdev->dspless_mode_selected) + reset_hw_params = true; + fallthrough; case SNDRV_PCM_TRIGGER_STOP: ipc_first = true; - reset_hw_params = true; + if (pcm_ops && pcm_ops->reset_hw_params_during_stop) + reset_hw_params = true; break; default: dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } - /* - * DMA and IPC sequence is different for start and stop. Need to send - * STOP IPC before stop DMA - */ if (!ipc_first) snd_sof_pcm_platform_trigger(sdev, substream, cmd); if (pcm_ops && pcm_ops->trigger) ret = pcm_ops->trigger(component, substream, cmd); - /* need to STOP DMA even if trigger IPC failed */ - if (ipc_first) - snd_sof_pcm_platform_trigger(sdev, substream, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_START: + /* invoke platform trigger to start DMA only if pcm_ops is successful */ + if (ipc_first && !ret) + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + /* invoke platform trigger to stop DMA even if pcm_ops isn't set or if it failed */ + if (!pcm_ops || (pcm_ops && !pcm_ops->platform_stop_during_hw_free)) + snd_sof_pcm_platform_trigger(sdev, substream, cmd); + break; + default: + break; + } /* free PCM if reset_hw_params is set and the STOP IPC is successful */ if (!ret && reset_hw_params) @@ -690,7 +717,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; - pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; pd->be_pcm_base = SOF_BE_PCM_BASE; pd->use_dai_pcm_id = true; pd->topology_name_prefix = "sof"; @@ -699,4 +725,11 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->module_get_upon_open = 1; pd->legacy_dai_naming = 1; + + /* + * The fixup is only needed when the DSP is in use as with the DSPless + * mode we are directly using the audio interface + */ + if (!sdev->dspless_mode_selected) + pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; } diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 85412aeb1ca1..2fdbc53ca715 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -103,6 +103,11 @@ static int sof_resume(struct device *dev, bool runtime_resume) return ret; } + if (sdev->dspless_mode_selected) { + sof_set_fw_state(sdev, SOF_DSPLESS_MODE); + return 0; + } + /* * Nothing further to be done for platforms that support the low power * D0 substate. Resume trace and return when resuming from diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 6de388a8d0b8..1cbda595c518 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -280,9 +280,11 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, int dir) { + const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_soc_dapm_widget *widget; + struct snd_sof_route *sroute; struct snd_soc_dapm_path *p; - int ret; + int ret = 0; int i; /* @@ -325,6 +327,63 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, } } + /* + * The above loop handles connections between widgets that belong to the DAPM widget list. + * This is not sufficient to handle loopback cases between pipelines configured with + * different directions, e.g. a sidetone or an amplifier feedback connected to a speaker + * protection module. + */ + list_for_each_entry(sroute, &sdev->route_list, list) { + bool src_widget_in_dapm_list, sink_widget_in_dapm_list; + struct snd_sof_widget *swidget; + + if (sroute->setup) + continue; + + src_widget_in_dapm_list = widget_in_list(list, sroute->src_widget->widget); + sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget); + + /* + * if both source and sink are in the DAPM list, the route must already have been + * set up above. And if neither are in the DAPM list, the route shouldn't be + * handled now. + */ + if (src_widget_in_dapm_list == sink_widget_in_dapm_list) + continue; + + /* + * At this point either the source widget or the sink widget is in the DAPM list + * with a route that might need to be set up. Check the use_count of the widget + * that is not in the DAPM list to confirm if it is in use currently before setting + * up the route. + */ + if (src_widget_in_dapm_list) + swidget = sroute->sink_widget; + else + swidget = sroute->src_widget; + + mutex_lock(&swidget->setup_mutex); + if (!swidget->use_count) { + mutex_unlock(&swidget->setup_mutex); + continue; + } + + if (tplg_ops && tplg_ops->route_setup) { + /* + * this route will get freed when either the source widget or the sink + * widget is freed during hw_free + */ + ret = tplg_ops->route_setup(sdev, sroute); + if (!ret) + sroute->setup = true; + } + + mutex_unlock(&swidget->setup_mutex); + + if (ret < 0) + return ret; + } + return 0; } @@ -629,7 +688,7 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, struct snd_sof_widget *pipe_widget; struct snd_sof_pipeline *spipe; - if (!swidget) + if (!swidget || sdev->dspless_mode_selected) continue; spipe = swidget->spipe; @@ -746,16 +805,22 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm); int ret; - /* Send PCM_FREE IPC to reset pipeline */ - if (pcm_ops && pcm_ops->hw_free && spcm->prepared[substream->stream]) { - ret = pcm_ops->hw_free(sdev->component, substream); - if (ret < 0) - return ret; - } + if (spcm->prepared[substream->stream]) { + /* stop DMA first if needed */ + if (pcm_ops && pcm_ops->platform_stop_during_hw_free) + snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP); - spcm->prepared[substream->stream] = false; + /* Send PCM_FREE IPC to reset pipeline */ + if (pcm_ops && pcm_ops->hw_free) { + ret = pcm_ops->hw_free(sdev->component, substream); + if (ret < 0) + return ret; + } + + spcm->prepared[substream->stream] = false; + } - /* stop the DMA */ + /* reset the DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) return ret; diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index e0579af9d281..a090a9eb4828 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -30,9 +30,9 @@ */ #define SOF_WIDGET_MAX_NUM_PINS 8 -/* The type of a widget pin is either sink or source */ -#define SOF_PIN_TYPE_SINK 0 -#define SOF_PIN_TYPE_SOURCE 1 +/* Widget pin type */ +#define SOF_PIN_TYPE_INPUT 0 +#define SOF_PIN_TYPE_OUTPUT 1 /* max number of FE PCMs before BEs */ #define SOF_BE_PCM_BASE 16 @@ -104,6 +104,15 @@ struct snd_sof_dai_config_data { * @pcm_free: Function pointer for PCM free that can be used for freeing any * additional memory in the SOF PCM stream structure * @delay: Function pointer for pcm delay calculation + * @reset_hw_params_during_stop: Flag indicating whether the hw_params should be reset during the + * STOP pcm trigger + * @ipc_first_on_start: Send IPC before invoking platform trigger during + * START/PAUSE_RELEASE triggers + * @platform_stop_during_hw_free: Invoke the platform trigger during hw_free. This is needed for + * IPC4 where a pipeline is only paused during stop/pause/suspend + * triggers. The FW keeps the host DMA running in this case and + * therefore the host must do the same and should stop the DMA during + * hw_free. */ struct sof_ipc_pcm_ops { int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, @@ -117,6 +126,9 @@ struct sof_ipc_pcm_ops { void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm); snd_pcm_sframes_t (*delay)(struct snd_soc_component *component, struct snd_pcm_substream *substream); + bool reset_hw_params_during_stop; + bool ipc_first_on_start; + bool platform_stop_during_hw_free; }; /** @@ -256,8 +268,7 @@ enum sof_tokens { SOF_COMP_EXT_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_OUT_AUDIO_FORMAT_TOKENS, - SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, - SOF_COPIER_GATEWAY_CFG_TOKENS, + SOF_COPIER_DEEP_BUFFER_TOKENS, SOF_COPIER_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_COPIER_FORMAT_TOKENS, @@ -433,31 +444,31 @@ struct snd_sof_widget { struct snd_sof_tuple *tuples; /* - * The allowed range for num_sink/source_pins is [0, SOF_WIDGET_MAX_NUM_PINS]. - * Widgets may have zero sink or source pins, for example the tone widget has - * zero sink pins. + * The allowed range for num_input/output_pins is [0, SOF_WIDGET_MAX_NUM_PINS]. + * Widgets may have zero input or output pins, for example the tone widget has + * zero input pins. */ - u32 num_sink_pins; - u32 num_source_pins; + u32 num_input_pins; + u32 num_output_pins; /* - * The sink/source pin binding array, it takes the form of + * The input/output pin binding array, it takes the form of * [widget_name_connected_to_pin0, widget_name_connected_to_pin1, ...], * with the index as the queue ID. * * The array is used for special pin binding. Note that even if there - * is only one sink/source pin requires special pin binding, pin binding - * should be defined for all sink/source pins in topology, for pin(s) that + * is only one input/output pin requires special pin binding, pin binding + * should be defined for all input/output pins in topology, for pin(s) that * are not used, give the value "NotConnected". * * If pin binding is not defined in topology, nothing to parse in the kernel, - * sink_pin_binding and src_pin_binding shall be NULL. + * input_pin_binding and output_pin_binding shall be NULL. */ - char **sink_pin_binding; - char **src_pin_binding; + char **input_pin_binding; + char **output_pin_binding; - struct ida src_queue_ida; - struct ida sink_queue_ida; + struct ida output_queue_ida; + struct ida input_queue_ida; void *private; /* core does not touch this */ }; @@ -502,6 +513,8 @@ struct snd_sof_dai { int number_configs; int current_config; struct list_head list; /* list in sdev dai list */ + /* core should not touch this */ + const void *platform_private; void *private; }; diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c index 4bdecd80248a..c0d6723aed59 100644 --- a/sound/soc/sof/sof-client-ipc-flood-test.c +++ b/sound/soc/sof/sof-client-ipc-flood-test.c @@ -64,7 +64,6 @@ static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, struct sof_ipc_flood_priv *priv = cdev->data; struct device *dev = &cdev->auxdev.dev; struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply reply; u64 min_response_time = U64_MAX; ktime_t start, end, test_end; u64 avg_response_time = 0; @@ -84,7 +83,7 @@ static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, /* send test IPC's */ while (1) { start = ktime_get(); - ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message_no_reply(cdev, &hdr); end = ktime_get(); if (ret < 0) diff --git a/sound/soc/sof/sof-client-probes-ipc3.c b/sound/soc/sof/sof-client-probes-ipc3.c index ef768db5f04d..5e8eb19582a8 100644 --- a/sound/soc/sof/sof-client-probes-ipc3.c +++ b/sound/soc/sof/sof-client-probes-ipc3.c @@ -65,7 +65,6 @@ static int ipc3_probes_init(struct sof_client_dev *cdev, u32 stream_tag, { struct sof_ipc_probe_dma_add_params *msg; size_t size = struct_size(msg, dma, 1); - struct sof_ipc_reply reply; int ret; msg = kmalloc(size, GFP_KERNEL); @@ -77,7 +76,7 @@ static int ipc3_probes_init(struct sof_client_dev *cdev, u32 stream_tag, msg->dma[0].stream_tag = stream_tag; msg->dma[0].dma_buffer_size = buffer_size; - ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message_no_reply(cdev, msg); kfree(msg); return ret; } @@ -93,12 +92,11 @@ static int ipc3_probes_init(struct sof_client_dev *cdev, u32 stream_tag, static int ipc3_probes_deinit(struct sof_client_dev *cdev) { struct sof_ipc_cmd_hdr msg; - struct sof_ipc_reply reply; msg.size = sizeof(msg); msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; - return sof_client_ipc_tx_message(cdev, &msg, &reply, sizeof(reply)); + return sof_client_ipc_tx_message_no_reply(cdev, &msg); } static int ipc3_probes_info(struct sof_client_dev *cdev, unsigned int cmd, @@ -180,7 +178,6 @@ static int ipc3_probes_points_add(struct sof_client_dev *cdev, { struct sof_ipc_probe_point_add_params *msg; size_t size = struct_size(msg, desc, num_desc); - struct sof_ipc_reply reply; int ret; msg = kmalloc(size, GFP_KERNEL); @@ -191,7 +188,7 @@ static int ipc3_probes_points_add(struct sof_client_dev *cdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; memcpy(&msg->desc[0], desc, size - sizeof(*msg)); - ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message_no_reply(cdev, msg); kfree(msg); return ret; } @@ -211,7 +208,6 @@ static int ipc3_probes_points_remove(struct sof_client_dev *cdev, { struct sof_ipc_probe_point_remove_params *msg; size_t size = struct_size(msg, buffer_id, num_buffer_id); - struct sof_ipc_reply reply; int ret; msg = kmalloc(size, GFP_KERNEL); @@ -222,7 +218,7 @@ static int ipc3_probes_points_remove(struct sof_client_dev *cdev, msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); - ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + ret = sof_client_ipc_tx_message_no_reply(cdev, msg); kfree(msg); return ret; } diff --git a/sound/soc/sof/sof-client-probes-ipc4.c b/sound/soc/sof/sof-client-probes-ipc4.c index 66fa7c2f390a..ea21ef176c42 100644 --- a/sound/soc/sof/sof-client-probes-ipc4.c +++ b/sound/soc/sof/sof-client-probes-ipc4.c @@ -129,7 +129,7 @@ static int ipc4_probes_init(struct sof_client_dev *cdev, u32 stream_tag, msg.data_size = sizeof(cfg); msg.data_ptr = &cfg; - return sof_client_ipc_tx_message(cdev, &msg, NULL, 0); + return sof_client_ipc_tx_message_no_reply(cdev, &msg); } /** @@ -156,7 +156,7 @@ static int ipc4_probes_deinit(struct sof_client_dev *cdev) msg.data_size = 0; msg.data_ptr = NULL; - return sof_client_ipc_tx_message(cdev, &msg, NULL, 0); + return sof_client_ipc_tx_message_no_reply(cdev, &msg); } /** diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index 9017f0864cdd..d6b7caa0cf03 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -130,6 +130,9 @@ int sof_register_clients(struct snd_sof_dev *sdev) { int ret; + if (sdev->dspless_mode_selected) + return 0; + /* Register platform independent client devices */ ret = sof_register_ipc_flood_test(sdev); if (ret) { diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h index 2589714eaa91..10571d1ea9a7 100644 --- a/sound/soc/sof/sof-client.h +++ b/sound/soc/sof/sof-client.h @@ -39,6 +39,10 @@ struct sof_client_dev { int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, void *reply_data, size_t reply_bytes); +static inline int sof_client_ipc_tx_message_no_reply(struct sof_client_dev *cdev, void *ipc_msg) +{ + return sof_client_ipc_tx_message(cdev, ipc_msg, NULL, 0); +} int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg, bool set); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 5f919162a555..cd4f6ac126ec 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -48,6 +48,7 @@ struct snd_sof_pcm_stream; #define SOF_DBG_FORCE_NOCODEC BIT(10) /* ignore all codec-related * configurations */ +#define SOF_DBG_DSPLESS_MODE BIT(15) /* Do not initialize and use the DSP */ /* Flag definitions used for controlling the DSP dump behavior */ #define SOF_DBG_DUMP_REGS BIT(0) @@ -528,6 +529,16 @@ struct snd_sof_dev { spinlock_t ipc_lock; /* lock for IPC users */ spinlock_t hw_lock; /* lock for HW IO access */ + /* + * When true the DSP is not used. + * It is set under the following condition: + * User sets the SOF_DBG_DSPLESS_MODE flag in sof_debug module parameter + * and + * the platform advertises that it can support such mode + * pdata->desc->dspless_mode_supported is true. + */ + bool dspless_mode_selected; + /* Main, Base firmware image */ struct sof_firmware basefw; @@ -700,10 +711,20 @@ static inline void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) } int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); +static inline int sof_ipc_tx_message_no_reply(struct snd_sof_ipc *ipc, void *msg_data, + size_t msg_bytes) +{ + return sof_ipc_tx_message(ipc, msg_data, msg_bytes, NULL, 0); +} int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, bool set); int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); +static inline int sof_ipc_tx_message_no_pm_no_reply(struct snd_sof_ipc *ipc, void *msg_data, + size_t msg_bytes) +{ + return sof_ipc_tx_message_no_pm(ipc, msg_data, msg_bytes, NULL, 0); +} int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, size_t reply_bytes); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 9f3a038fe21a..d3d536b0a8f5 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -416,19 +416,19 @@ static const struct sof_topology_token led_tokens[] = { }; static const struct sof_topology_token comp_pin_tokens[] = { - {SOF_TKN_COMP_NUM_SINK_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct snd_sof_widget, num_sink_pins)}, - {SOF_TKN_COMP_NUM_SOURCE_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct snd_sof_widget, num_source_pins)}, + {SOF_TKN_COMP_NUM_INPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct snd_sof_widget, num_input_pins)}, + {SOF_TKN_COMP_NUM_OUTPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct snd_sof_widget, num_output_pins)}, }; -static const struct sof_topology_token comp_sink_pin_binding_tokens[] = { - {SOF_TKN_COMP_SINK_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING, +static const struct sof_topology_token comp_input_pin_binding_tokens[] = { + {SOF_TKN_COMP_INPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_string, 0}, }; -static const struct sof_topology_token comp_src_pin_binding_tokens[] = { - {SOF_TKN_COMP_SRC_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING, +static const struct sof_topology_token comp_output_pin_binding_tokens[] = { + {SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_string, 0}, }; @@ -1144,8 +1144,12 @@ static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, int dir) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *host_widget; + if (sdev->dspless_mode_selected) + return 0; + host_widget = snd_sof_find_swidget_sname(scomp, spcm->pcm.caps[dir].name, dir); @@ -1231,37 +1235,43 @@ static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_s continue; case SOF_IN_AUDIO_FORMAT_TOKENS: - case SOF_OUT_AUDIO_FORMAT_TOKENS: - case SOF_COPIER_GATEWAY_CFG_TOKENS: - case SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS: - num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_AUDIO_FORMATS, + num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS, swidget->tuples, swidget->num_tuples); - if (num_sets < 0) { - dev_err(sdev->dev, "Invalid audio format count for %s\n", + dev_err(sdev->dev, "Invalid input audio format count for %s\n", swidget->widget->name); ret = num_sets; goto err; } - - if (num_sets > 1) { - struct snd_sof_tuple *new_tuples; - - num_tuples += token_list[object_token_list[i]].count * num_sets; - new_tuples = krealloc(swidget->tuples, - sizeof(*new_tuples) * num_tuples, GFP_KERNEL); - if (!new_tuples) { - ret = -ENOMEM; - goto err; - } - - swidget->tuples = new_tuples; + break; + case SOF_OUT_AUDIO_FORMAT_TOKENS: + num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS, + swidget->tuples, swidget->num_tuples); + if (num_sets < 0) { + dev_err(sdev->dev, "Invalid output audio format count for %s\n", + swidget->widget->name); + ret = num_sets; + goto err; } break; default: break; } + if (num_sets > 1) { + struct snd_sof_tuple *new_tuples; + + num_tuples += token_list[object_token_list[i]].count * num_sets; + new_tuples = krealloc(swidget->tuples, + sizeof(*new_tuples) * num_tuples, GFP_KERNEL); + if (!new_tuples) { + ret = -ENOMEM; + goto err; + } + + swidget->tuples = new_tuples; + } + /* copy one set of tuples per token ID into swidget->tuples */ ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), object_token_list[i], num_sets, swidget->tuples, @@ -1286,12 +1296,12 @@ static void sof_free_pin_binding(struct snd_sof_widget *swidget, u32 num_pins; int i; - if (pin_type == SOF_PIN_TYPE_SINK) { - pin_binding = swidget->sink_pin_binding; - num_pins = swidget->num_sink_pins; + if (pin_type == SOF_PIN_TYPE_INPUT) { + pin_binding = swidget->input_pin_binding; + num_pins = swidget->num_input_pins; } else { - pin_binding = swidget->src_pin_binding; - num_pins = swidget->num_source_pins; + pin_binding = swidget->output_pin_binding; + num_pins = swidget->num_output_pins; } if (pin_binding) { @@ -1313,14 +1323,14 @@ static int sof_parse_pin_binding(struct snd_sof_widget *swidget, int ret; int i; - if (pin_type == SOF_PIN_TYPE_SINK) { - num_pins = swidget->num_sink_pins; - pin_binding_token = comp_sink_pin_binding_tokens; - token_count = ARRAY_SIZE(comp_sink_pin_binding_tokens); + if (pin_type == SOF_PIN_TYPE_INPUT) { + num_pins = swidget->num_input_pins; + pin_binding_token = comp_input_pin_binding_tokens; + token_count = ARRAY_SIZE(comp_input_pin_binding_tokens); } else { - num_pins = swidget->num_source_pins; - pin_binding_token = comp_src_pin_binding_tokens; - token_count = ARRAY_SIZE(comp_src_pin_binding_tokens); + num_pins = swidget->num_output_pins; + pin_binding_token = comp_output_pin_binding_tokens; + token_count = ARRAY_SIZE(comp_output_pin_binding_tokens); } memset(pin_binding, 0, SOF_WIDGET_MAX_NUM_PINS * sizeof(char *)); @@ -1337,10 +1347,10 @@ static int sof_parse_pin_binding(struct snd_sof_widget *swidget, ret = -ENOMEM; goto err; } - if (pin_type == SOF_PIN_TYPE_SINK) - swidget->sink_pin_binding = pb; + if (pin_type == SOF_PIN_TYPE_INPUT) + swidget->input_pin_binding = pb; else - swidget->src_pin_binding = pb; + swidget->output_pin_binding = pb; } return 0; @@ -1379,8 +1389,8 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, swidget->private = NULL; mutex_init(&swidget->setup_mutex); - ida_init(&swidget->src_queue_ida); - ida_init(&swidget->sink_queue_ida); + ida_init(&swidget->output_queue_ida); + ida_init(&swidget->input_queue_ida); ret = sof_parse_tokens(scomp, swidget, comp_pin_tokens, ARRAY_SIZE(comp_pin_tokens), priv->array, @@ -1391,29 +1401,29 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, goto widget_free; } - if (swidget->num_sink_pins > SOF_WIDGET_MAX_NUM_PINS || - swidget->num_source_pins > SOF_WIDGET_MAX_NUM_PINS) { - dev_err(scomp->dev, "invalid pins for %s: [sink: %d, src: %d]\n", - swidget->widget->name, swidget->num_sink_pins, swidget->num_source_pins); + if (swidget->num_input_pins > SOF_WIDGET_MAX_NUM_PINS || + swidget->num_output_pins > SOF_WIDGET_MAX_NUM_PINS) { + dev_err(scomp->dev, "invalid pins for %s: [input: %d, output: %d]\n", + swidget->widget->name, swidget->num_input_pins, swidget->num_output_pins); ret = -EINVAL; goto widget_free; } - if (swidget->num_sink_pins > 1) { - ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_SINK); + if (swidget->num_input_pins > 1) { + ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_INPUT); /* on parsing error, pin binding is not allocated, nothing to free. */ if (ret < 0) { - dev_err(scomp->dev, "failed to parse sink pin binding for %s\n", + dev_err(scomp->dev, "failed to parse input pin binding for %s\n", w->name); goto widget_free; } } - if (swidget->num_source_pins > 1) { - ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_SOURCE); + if (swidget->num_output_pins > 1) { + ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_OUTPUT); /* on parsing error, pin binding is not allocated, nothing to free. */ if (ret < 0) { - dev_err(scomp->dev, "failed to parse source pin binding for %s\n", + dev_err(scomp->dev, "failed to parse output pin binding for %s\n", w->name); goto widget_free; } @@ -1422,7 +1432,7 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, dev_dbg(scomp->dev, "tplg: widget %d (%s) is ready [type: %d, pipe: %d, pins: %d / %d, stream: %s]\n", swidget->comp_id, w->name, swidget->id, index, - swidget->num_sink_pins, swidget->num_source_pins, + swidget->num_input_pins, swidget->num_output_pins, strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 ? w->sname : "none"); widget_ops = tplg_ops ? tplg_ops->widget : NULL; @@ -1643,11 +1653,11 @@ out: if (widget_ops && widget_ops[swidget->id].ipc_free) widget_ops[swidget->id].ipc_free(swidget); - ida_destroy(&swidget->src_queue_ida); - ida_destroy(&swidget->sink_queue_ida); + ida_destroy(&swidget->output_queue_ida); + ida_destroy(&swidget->input_queue_ida); - sof_free_pin_binding(swidget, SOF_PIN_TYPE_SINK); - sof_free_pin_binding(swidget, SOF_PIN_TYPE_SOURCE); + sof_free_pin_binding(swidget, SOF_PIN_TYPE_INPUT); + sof_free_pin_binding(swidget, SOF_PIN_TYPE_OUTPUT); kfree(swidget->tuples); @@ -2120,7 +2130,6 @@ static int sof_complete(struct snd_soc_component *scomp) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); - struct snd_sof_widget *swidget, *comp_swidget; const struct sof_ipc_tplg_widget_ops *widget_ops; struct snd_sof_control *scontrol; struct snd_sof_pipeline *spipe; @@ -2139,37 +2148,38 @@ static int sof_complete(struct snd_soc_component *scomp) } } - /* - * then update all widget IPC structures. If any of the ipc_setup callbacks fail, the - * topology will be removed and all widgets will be unloaded resulting in freeing all - * associated memories. - */ - list_for_each_entry(swidget, &sdev->widget_list, list) { - if (widget_ops && widget_ops[swidget->id].ipc_setup) { - ret = widget_ops[swidget->id].ipc_setup(swidget); + /* set up the IPC structures for the pipeline widgets */ + list_for_each_entry(spipe, &sdev->pipeline_list, list) { + struct snd_sof_widget *pipe_widget = spipe->pipe_widget; + struct snd_sof_widget *swidget; + + /* Update the scheduler widget's IPC structure */ + if (widget_ops && widget_ops[pipe_widget->id].ipc_setup) { + ret = widget_ops[pipe_widget->id].ipc_setup(pipe_widget); if (ret < 0) { dev_err(sdev->dev, "failed updating IPC struct for %s\n", - swidget->widget->name); + pipe_widget->widget->name); return ret; } } - } - - /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ - list_for_each_entry(spipe, &sdev->pipeline_list, list) { - struct snd_sof_widget *pipe_widget = spipe->pipe_widget; - /* - * Apply the dynamic_pipeline_widget flag and set the pipe_widget field - * for all widgets that have the same pipeline ID as the scheduler widget. - * Skip the scheduler widgets as they have their pipeline set during widget_ready - */ - list_for_each_entry(comp_swidget, &sdev->widget_list, list) - if (comp_swidget->widget->id != snd_soc_dapm_scheduler && - comp_swidget->pipeline_id == pipe_widget->pipeline_id) { - ret = sof_set_widget_pipeline(sdev, spipe, comp_swidget); + /* set the pipeline and update the IPC structure for the non scheduler widgets */ + list_for_each_entry(swidget, &sdev->widget_list, list) + if (swidget->widget->id != snd_soc_dapm_scheduler && + swidget->pipeline_id == pipe_widget->pipeline_id) { + ret = sof_set_widget_pipeline(sdev, spipe, swidget); if (ret < 0) return ret; + + if (widget_ops && widget_ops[swidget->id].ipc_setup) { + ret = widget_ops[swidget->id].ipc_setup(swidget); + if (ret < 0) { + dev_err(sdev->dev, + "failed updating IPC struct for %s\n", + swidget->widget->name); + return ret; + } + } } } @@ -2264,6 +2274,126 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { .bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops), }; +static int snd_sof_dspless_kcontrol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static const struct snd_soc_tplg_kcontrol_ops sof_dspless_io_ops[] = { + {SOF_TPLG_KCTL_VOL_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol}, + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol}, + {SOF_TPLG_KCTL_ENUM_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol}, + {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol}, +}; + +static int snd_sof_dspless_bytes_ext_get(struct snd_kcontrol *kcontrol, + unsigned int __user *binary_data, + unsigned int size) +{ + return 0; +} + +static int snd_sof_dspless_bytes_ext_put(struct snd_kcontrol *kcontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + return 0; +} + +static const struct snd_soc_tplg_bytes_ext_ops sof_dspless_bytes_ext_ops[] = { + {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_bytes_ext_get, snd_sof_dspless_bytes_ext_put}, + {SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_dspless_bytes_ext_get}, +}; + +/* external widget init - used for any driver specific init */ +static int sof_dspless_widget_ready(struct snd_soc_component *scomp, int index, + struct snd_soc_dapm_widget *w, + struct snd_soc_tplg_dapm_widget *tw) +{ + if (WIDGET_IS_DAI(w->id)) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_widget *swidget; + struct snd_sof_dai dai; + int ret; + + swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); + if (!swidget) + return -ENOMEM; + + memset(&dai, 0, sizeof(dai)); + + ret = sof_connect_dai_widget(scomp, w, tw, &dai); + if (ret) { + kfree(swidget); + return ret; + } + + swidget->scomp = scomp; + swidget->widget = w; + mutex_init(&swidget->setup_mutex); + w->dobj.private = swidget; + list_add(&swidget->list, &sdev->widget_list); + } + + return 0; +} + +static int sof_dspless_widget_unload(struct snd_soc_component *scomp, + struct snd_soc_dobj *dobj) +{ + struct snd_soc_dapm_widget *w = container_of(dobj, struct snd_soc_dapm_widget, dobj); + + if (WIDGET_IS_DAI(w->id)) { + struct snd_sof_widget *swidget = dobj->private; + + sof_disconnect_dai_widget(scomp, w); + + if (!swidget) + return 0; + + /* remove and free swidget object */ + list_del(&swidget->list); + kfree(swidget); + } + + return 0; +} + +static int sof_dspless_link_load(struct snd_soc_component *scomp, int index, + struct snd_soc_dai_link *link, + struct snd_soc_tplg_link_config *cfg) +{ + link->platforms->name = dev_name(scomp->dev); + + /* Set nonatomic property for FE dai links for FE-BE compatibility */ + if (!link->no_pcm) + link->nonatomic = true; + + return 0; +} + +static struct snd_soc_tplg_ops sof_dspless_tplg_ops = { + /* external widget init - used for any driver specific init */ + .widget_ready = sof_dspless_widget_ready, + .widget_unload = sof_dspless_widget_unload, + + /* FE DAI - used for any driver specific init */ + .dai_load = sof_dai_load, + .dai_unload = sof_dai_unload, + + /* DAI link - used for any driver specific init */ + .link_load = sof_dspless_link_load, + + /* vendor specific kcontrol handlers available for binding */ + .io_ops = sof_dspless_io_ops, + .io_ops_count = ARRAY_SIZE(sof_dspless_io_ops), + + /* vendor specific bytes ext handlers available for binding */ + .bytes_ext_ops = sof_dspless_bytes_ext_ops, + .bytes_ext_ops_count = ARRAY_SIZE(sof_dspless_bytes_ext_ops), +}; + int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); @@ -2281,7 +2411,11 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) return ret; } - ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + if (sdev->dspless_mode_selected) + ret = snd_soc_tplg_component_load(scomp, &sof_dspless_tplg_ops, fw); + else + ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw); + if (ret < 0) { dev_err(scomp->dev, "error: tplg component load failed %d\n", ret); |