// 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) 2021 Intel Corporation // //
/** * sof_comp_alloc - allocate and initialize buffer for a new component * @swidget: pointer to struct snd_sof_widget containing extended data * @ipc_size: IPC payload size that will be updated depending on valid * extended data. * @index: ID of the pipeline the component belongs to * * Return: The pointer to the new allocated component, NULL if failed.
*/ staticvoid *sof_comp_alloc(struct snd_sof_widget *swidget, size_t *ipc_size, int index)
{ struct sof_ipc_comp *comp;
size_t total_size = *ipc_size;
size_t ext_size = sizeof(swidget->uuid);
/* only non-zero UUID is valid */ if (!guid_is_null(&swidget->uuid))
total_size += ext_size;
comp = kzalloc(total_size, GFP_KERNEL); if (!comp) return NULL;
/* handle the extended data if needed */ if (total_size > *ipc_size) { /* append extended data to the end of the component */
memcpy((u8 *)comp + *ipc_size, &swidget->uuid, ext_size);
comp->ext_data_length = ext_size;
}
/* parse one set of pcm_tokens */
ret = sof_update_ipc_object(scomp, host, SOF_PCM_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*host), 1); if (ret < 0) goto err;
/* parse one set of comp_tokens */
ret = sof_update_ipc_object(scomp, &host->config, SOF_COMP_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(host->config), 1); if (ret < 0) goto err;
/* component at start of pipeline is our stream id */
comp_swidget = snd_sof_find_swidget(scomp, swidget->widget->sname); if (!comp_swidget) {
dev_err(scomp->dev, "scheduler %s refers to non existent widget %s\n",
swidget->widget->name, swidget->widget->sname);
ret = -EINVAL; goto err;
}
pipeline->sched_id = comp_swidget->comp_id;
/* parse one set of scheduler tokens */
ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*pipeline), 1); if (ret < 0) goto err;
/* parse one set of pipeline tokens */
ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*swidget), 1); if (ret < 0) goto err;
if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE))
pipeline->core = SOF_DSP_PRIMARY_CORE;
if (sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE))
swidget->dynamic_pipeline_widget =
sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_ENABLE);
/* parse one set of src tokens */
ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*src), 1); if (ret < 0) goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &src->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(src->config), 1); if (ret < 0) goto err;
/* parse one set of asrc tokens */
ret = sof_update_ipc_object(scomp, asrc, SOF_ASRC_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*asrc), 1); if (ret < 0) goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &asrc->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(asrc->config), 1); if (ret < 0) goto err;
/* parse one set of volume tokens */
ret = sof_update_ipc_object(scomp, volume, SOF_VOLUME_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*volume), 1); if (ret < 0) goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &volume->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(volume->config), 1); if (ret < 0) goto err;
for (i = 0; i < widget->num_kcontrols; i++) {
kc = &widget->kcontrol_news[i];
switch (widget->dobj.widget.kcontrol_type[i]) { case SND_SOC_TPLG_TYPE_MIXER:
sm = (struct soc_mixer_control *)kc->private_value;
wdata[i].control = sm->dobj.private; break; case SND_SOC_TPLG_TYPE_BYTES:
sbe = (struct soc_bytes_ext *)kc->private_value;
wdata[i].control = sbe->dobj.private; break; case SND_SOC_TPLG_TYPE_ENUM:
se = (struct soc_enum *)kc->private_value;
wdata[i].control = se->dobj.private; break; default:
dev_err(scomp->dev, "Unknown kcontrol type %u in widget %s\n",
widget->dobj.widget.kcontrol_type[i], widget->name); return -EINVAL;
}
if (!wdata[i].control) {
dev_err(scomp->dev, "No scontrol for widget %s\n", widget->name); return -EINVAL;
}
cdata = wdata[i].control->ipc_control_data;
if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES) { /* make sure data is valid - data can be updated at runtime */ if (cdata->data->magic != SOF_ABI_MAGIC) return -EINVAL;
wdata[i].pdata = cdata->data->data;
wdata[i].pdata_size = cdata->data->size;
} else { /* points to the control data union */
wdata[i].pdata = cdata->chanv; /* * wdata[i].control->size is calculated with struct_size * and includes the size of struct sof_ipc_ctrl_data
*/
wdata[i].pdata_size = wdata[i].control->size - sizeof(struct sof_ipc_ctrl_data);
}
*size += wdata[i].pdata_size;
/* get data type */ switch (cdata->cmd) { case SOF_CTRL_CMD_VOLUME: case SOF_CTRL_CMD_ENUM: case SOF_CTRL_CMD_SWITCH:
wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE;
wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; break; case SOF_CTRL_CMD_BINARY:
wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA;
wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET; break; default: break;
}
}
return 0;
}
staticint sof_process_load(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, int type)
{ struct snd_soc_dapm_widget *widget = swidget->widget; struct sof_ipc_comp_process *process; struct sof_widget_data *wdata = NULL;
size_t ipc_data_size = 0;
size_t ipc_size; int offset = 0; int ret; int i;
/* allocate struct for widget control data sizes and types */ if (widget->num_kcontrols) {
wdata = kcalloc(widget->num_kcontrols, sizeof(*wdata), GFP_KERNEL); if (!wdata) return -ENOMEM;
/* get possible component controls and get size of all pdata */
ret = sof_get_control_data(scomp, widget, wdata, &ipc_data_size); if (ret < 0) goto out;
}
/* we are exceeding max ipc size, config needs to be sent separately */ if (ipc_size > SOF_IPC_MSG_MAX_SIZE) {
ipc_size -= ipc_data_size;
ipc_data_size = 0;
}
process = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); if (!process) {
ret = -ENOMEM; goto out;
}
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &process->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(process->config), 1); if (ret < 0) goto err;
dev_dbg(scomp->dev, "loaded process %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &process->config);
/* * found private data in control, so copy it. * get possible component controls - get size of all pdata, * then memcpy with headers
*/ if (ipc_data_size) { for (i = 0; i < widget->num_kcontrols; i++) { if (!wdata[i].pdata_size) continue;
/* parse one set of process tokens */
ret = sof_update_ipc_object(scomp, &config, SOF_PROCESS_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(config), 1); if (ret < 0) return ret;
/* now load process specific data and send IPC */ return sof_process_load(scomp, swidget, find_process_comp_type(config.type));
}
/* parse one set of HDA tokens */
ret = sof_update_ipc_object(scomp, &config->hda, SOF_HDA_TOKENS, slink->tuples,
slink->num_tuples, size, 1); if (ret < 0) return ret;
/* parse one set of SAI tokens */
ret = sof_update_ipc_object(scomp, &config->sai, SOF_SAI_TOKENS, slink->tuples,
slink->num_tuples, size, 1); if (ret < 0) return ret;
/* parse one set of ESAI tokens */
ret = sof_update_ipc_object(scomp, &config->esai, SOF_ESAI_TOKENS, slink->tuples,
slink->num_tuples, size, 1); if (ret < 0) return ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
config->hdr.size = size;
/* parse the required set of MICFIL PDM tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->micfil, SOF_MICFIL_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
config->hdr.size = size;
/* parse the required set of ACPDMIC tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->acpdmic, SOF_ACPDMIC_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* parse the required set of ACP_SDW tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->acp_sdw, SOF_ACP_SDW_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* set config for all DAI's with name matching the link name */
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL); if (!private->dai_config) return -ENOMEM;
/* parse the required set of AFE tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->afe, SOF_AFE_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* * Parse common data, we should have 1 common data per hw_config.
*/
ret = sof_update_ipc_object(scomp, &config->ssp, SOF_SSP_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* process all possible hw configs */ for (i = 0; i < slink->num_hw_configs; i++) { if (le32_to_cpu(hw_config[i].id) == slink->default_hw_cfg_id)
current_config = i;
/* handle master/slave and inverted clocks */
sof_dai_set_format(&hw_config[i], &config[i]);
config[i].hdr.size = size;
if (sdev->mclk_id_override) {
dev_dbg(scomp->dev, "tplg: overriding topology mclk_id %d by quirk %d\n",
config[i].ssp.mclk_id, sdev->mclk_id_quirk);
config[i].ssp.mclk_id = sdev->mclk_id_quirk;
}
/* Ensure the entire DMIC config struct is zeros */
memset(&config->dmic, 0, sizeof(config->dmic));
/* parse the required set of DMIC tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->dmic, SOF_DMIC_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* parse the required set of DMIC PDM tokens based on number of active PDM's */
ret = sof_update_ipc_object(scomp, &config->dmic.pdm[0], SOF_DMIC_PDM_TOKENS,
slink->tuples, slink->num_tuples, sizeof(struct sof_ipc_dai_dmic_pdm_ctrl),
config->dmic.num_pdm_active); if (ret < 0) return ret;
/* set IPC header size */
config->hdr.size = size;
for (i = 0; i < config->dmic.num_pdm_active; i++) {
dev_dbg(scomp->dev, "pdm %d mic a %d mic b %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].enable_mic_a,
config->dmic.pdm[i].enable_mic_b);
dev_dbg(scomp->dev, "pdm %d polarity a %d polarity b %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].polarity_mic_a,
config->dmic.pdm[i].polarity_mic_b);
dev_dbg(scomp->dev, "pdm %d clk_edge %d skew %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].clk_edge,
config->dmic.pdm[i].skew);
}
/* * this takes care of backwards compatible handling of fifo_bits_b. * It is deprecated since firmware ABI version 3.0.1.
*/ if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1))
config->dmic.fifo_bits_b = config->dmic.fifo_bits;
/* parse the required set of ALH tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->alh, SOF_ALH_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs); if (ret < 0) return ret;
/* init IPC */
config->hdr.size = size;
/* set config for all DAI's with name matching the link name */
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL); if (!private->dai_config) return -ENOMEM;
/* parse one set of DAI tokens */
ret = sof_update_ipc_object(scomp, comp_dai, SOF_DAI_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*comp_dai), 1); if (ret < 0) goto free_comp;
/* update comp_tokens */
ret = sof_update_ipc_object(scomp, &comp_dai->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(comp_dai->config), 1); if (ret < 0) goto free_comp;
/* Subtract the base to match the FW dai index. */ if (comp_dai->type == SOF_DAI_INTEL_ALH) { if (comp_dai->dai_index < INTEL_ALH_DAI_INDEX_BASE) {
dev_err(sdev->dev, "Invalid ALH dai index %d, only Pin numbers >= %d can be used\n",
comp_dai->dai_index, INTEL_ALH_DAI_INDEX_BASE);
ret = -EINVAL; goto free_comp;
}
comp_dai->dai_index -= INTEL_ALH_DAI_INDEX_BASE;
}
dev_dbg(scomp->dev, "dai %s: type %d index %d\n",
swidget->widget->name, comp_dai->type, comp_dai->dai_index);
sof_dbg_comp_config(scomp, &comp_dai->config);
/* now update DAI config */
list_for_each_entry(slink, &sdev->dai_link_list, list) { struct sof_ipc_dai_config common_config; int i;
if (strcmp(slink->link->name, dai->name)) continue;
/* Reserve memory for all hw configs, eventually freed by widget */
config = kcalloc(slink->num_hw_configs, sizeof(*config), GFP_KERNEL); if (!config) {
ret = -ENOMEM; goto free_comp;
}
/* parse one set of DAI link tokens */
ret = sof_update_ipc_object(scomp, &common_config, SOF_DAI_LINK_TOKENS,
slink->tuples, slink->num_tuples, sizeof(common_config), 1); if (ret < 0) goto free_config;
for (i = 0; i < slink->num_hw_configs; i++) {
config[i].hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
config[i].format = le32_to_cpu(slink->hw_configs[i].fmt);
config[i].type = common_config.type;
config[i].dai_index = comp_dai->dai_index;
}
switch (common_config.type) { case SOF_DAI_INTEL_SSP:
ret = sof_link_ssp_load(scomp, slink, config, dai); break; case SOF_DAI_INTEL_DMIC:
ret = sof_link_dmic_load(scomp, slink, config, dai); break; case SOF_DAI_INTEL_HDA:
ret = sof_link_hda_load(scomp, slink, config, dai); break; case SOF_DAI_INTEL_ALH:
ret = sof_link_alh_load(scomp, slink, config, dai); break; case SOF_DAI_IMX_SAI:
ret = sof_link_sai_load(scomp, slink, config, dai); break; case SOF_DAI_IMX_ESAI:
ret = sof_link_esai_load(scomp, slink, config, dai); break; case SOF_DAI_IMX_MICFIL:
ret = sof_link_micfil_load(scomp, slink, config, dai); break; case SOF_DAI_AMD_BT:
ret = sof_link_acp_bt_load(scomp, slink, config, dai); break; case SOF_DAI_AMD_SP: case SOF_DAI_AMD_SP_VIRTUAL:
ret = sof_link_acp_sp_load(scomp, slink, config, dai); break; case SOF_DAI_AMD_HS: case SOF_DAI_AMD_HS_VIRTUAL:
ret = sof_link_acp_hs_load(scomp, slink, config, dai); break; case SOF_DAI_AMD_DMIC:
ret = sof_link_acp_dmic_load(scomp, slink, config, dai); break; case SOF_DAI_MEDIATEK_AFE:
ret = sof_link_afe_load(scomp, slink, config, dai); break; case SOF_DAI_AMD_SDW:
ret = sof_link_acp_sdw_load(scomp, slink, config, dai); break; default: break;
} if (ret < 0) {
dev_err(scomp->dev, "failed to load config for dai %s\n", dai->name); goto free_config;
}
if (cdata->data->magic != SOF_ABI_MAGIC) {
dev_err(sdev->dev, "Wrong ABI magic 0x%08x.\n", cdata->data->magic);
ret = -EINVAL; goto err;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
dev_err(sdev->dev, "Incompatible ABI version 0x%08x.\n",
cdata->data->abi);
ret = -EINVAL; goto err;
}
priv_size_check = cdata->data->size + sizeof(struct sof_abi_hdr); if (priv_size_check != scontrol->priv_size) {
dev_err(sdev->dev, "Conflict in bytes (%zu) vs. priv size (%zu).\n",
priv_size_check, scontrol->priv_size);
ret = -EINVAL; goto err;
}
}
/* set cmd for mixer control */ if (scontrol->max == 1) {
cdata->cmd = SOF_CTRL_CMD_SWITCH; return 0;
}
cdata->cmd = SOF_CTRL_CMD_VOLUME;
/* set default volume values to 0dB in control */ for (i = 0; i < scontrol->num_channels; i++) {
cdata->chanv[i].channel = i;
cdata->chanv[i].value = VOL_ZERO_DB;
}
/* set format */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16:
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; break; case SNDRV_PCM_FORMAT_S24:
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; break; case SNDRV_PCM_FORMAT_S32:
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; break; default: return -EINVAL;
}
/* send IPC to the DSP */
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);
/* send IPC to the DSP */
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);
return ret;
}
staticint sof_ipc3_keyword_dapm_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event)
{ struct snd_sof_widget *swidget = w->dobj.private; struct snd_soc_component *scomp; int stream = SNDRV_PCM_STREAM_CAPTURE; struct snd_sof_pcm *spcm; int ret = 0;
if (!swidget) return 0;
scomp = swidget->scomp;
dev_dbg(scomp->dev, "received event %d for widget %s\n",
event, w->name);
/* get runtime PCM params using widget's stream name */
spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); if (!spcm) {
dev_err(scomp->dev, "%s: Cannot find PCM for %s\n", __func__,
swidget->widget->name); return -EINVAL;
}
/* process events */ switch (event) { case SND_SOC_DAPM_PRE_PMU: if (spcm->stream[stream].suspend_ignored) {
dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n"); return 0;
}
/* set pcm params */
ret = sof_ipc3_keyword_detect_pcm_params(swidget, stream); if (ret < 0) {
dev_err(scomp->dev, "%s: Failed to set pcm params for widget %s\n",
__func__, swidget->widget->name); break;
}
/* start trigger */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_START); if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
swidget->widget->name); break; case SND_SOC_DAPM_POST_PMD: if (spcm->stream[stream].suspend_ignored) {
dev_dbg(scomp->dev, "POST_PMD event ignored, KWD pipeline will remain RUNNING\n"); return 0;
}
/* stop trigger */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP); if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
swidget->widget->name);
/* pcm free */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_PCM_FREE); if (ret < 0)
dev_err(scomp->dev, "%s: Failed to free PCM for widget %s\n", __func__,
swidget->widget->name); break; default: break;
}
/* validate widget event type */ switch (event_type) { case SOF_KEYWORD_DETECT_DAPM_EVENT: /* only KEYWORD_DETECT comps should handle this */ if (swidget->id != snd_soc_dapm_effect) break;
ipc_comp = swidget->private; if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT) break;
if (!dai || !dai->private) {
dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name); return -EINVAL;
}
private = dai->private; if (!private->dai_config) {
dev_err(sdev->dev, "No config for DAI %s\n", dai->name); return -EINVAL;
}
config = &private->dai_config[dai->current_config]; if (!config) {
dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name); return -EINVAL;
}
switch (config->type) { case SOF_DAI_INTEL_SSP: /* * DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older * firmware
*/ if (v->abi_version < SOF_ABI_VER(3, 18, 0) &&
((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) ||
(flags & SOF_DAI_CONFIG_FLAGS_HW_FREE))) return 0; break; case SOF_DAI_INTEL_HDA: if (data)
config->hda.link_dma_ch = data->dai_data; break; case SOF_DAI_INTEL_ALH: if (data) { /* save the dai_index during hw_params and reuse it for hw_free */ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) { /* Subtract the base to match the FW dai index. */ if (data->dai_index < INTEL_ALH_DAI_INDEX_BASE) {
dev_err(sdev->dev, "Invalid ALH dai index %d, only Pin numbers >= %d can be used\n",
config->dai_index, INTEL_ALH_DAI_INDEX_BASE); return -EINVAL;
}
config->dai_index = data->dai_index - INTEL_ALH_DAI_INDEX_BASE;
}
config->alh.stream_id = data->dai_data;
} break; default: break;
}
/* * The dai_config op is invoked several times and the flags argument varies as below: * BE DAI hw_params: When the op is invoked during the BE DAI hw_params, flags contains * SOF_DAI_CONFIG_FLAGS_HW_PARAMS along with quirks * FE DAI hw_params: When invoked during FE DAI hw_params after the DAI widget has * just been set up in the DSP, flags is set to SOF_DAI_CONFIG_FLAGS_HW_PARAMS with no * quirks * BE DAI trigger: When invoked during the BE DAI trigger, flags is set to * SOF_DAI_CONFIG_FLAGS_PAUSE and contains no quirks * BE DAI hw_free: When invoked during the BE DAI hw_free, flags is set to * SOF_DAI_CONFIG_FLAGS_HW_FREE and contains no quirks * FE DAI hw_free: When invoked during the FE DAI hw_free, flags is set to * SOF_DAI_CONFIG_FLAGS_HW_FREE and contains no quirks * * The DAI_CONFIG IPC is sent to the DSP, only after the widget is set up during the FE * DAI hw_params. But since the BE DAI hw_params precedes the FE DAI hw_params, the quirks * need to be preserved when assigning the flags before sending the IPC. * For the case of PAUSE/HW_FREE, since there are no quirks, flags can be used as is.
*/
/* only send the IPC if the widget is set up in the DSP */ if (swidget->use_count > 0) {
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);
/* clear the flags once the IPC has been sent even if it fails */
config->flags = SOF_DAI_CONFIG_FLAGS_NONE;
}
return ret;
}
staticint sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{ int ret;
/* restore pipeline components */
list_for_each_entry(swidget, &sdev->widget_list, list) { /* only set up the widgets belonging to static pipelines */ if (!verify && swidget->dynamic_pipeline_widget) continue;
/* * For older firmware, skip scheduler widgets in this loop, * sof_widget_setup() will be called in the 'complete pipeline' loop
*/ if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
swidget->id == snd_soc_dapm_scheduler) continue;
/* update DAI config. The IPC will be sent in sof_widget_setup() */ if (WIDGET_IS_DAI(swidget->id)) { struct snd_sof_dai *dai = swidget->private; struct sof_dai_private_data *private; struct sof_ipc_dai_config *config;
if (!dai || !dai->private) continue; private = dai->private; if (!private->dai_config) continue;
config = private->dai_config; /* * The link DMA channel would be invalidated for running * streams but not for streams that were in the PAUSED * state during suspend. So invalidate it here before setting * the dai config in the DSP.
*/ if (config->type == SOF_DAI_INTEL_HDA)
config->hda.link_dma_ch = DMA_CHAN_INVALID;
}
ret = sof_widget_setup(sdev, swidget); if (ret < 0) return ret;
}
/* restore pipeline connections */
list_for_each_entry(sroute, &sdev->route_list, list) { /* only set up routes belonging to static pipelines */ if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
sroute->sink_widget->dynamic_pipeline_widget)) continue;
/* * For virtual routes, both sink and source are not buffer. IPC3 only supports * connections between a buffer and a component. Ignore the rest.
*/ if (sroute->src_widget->id != snd_soc_dapm_buffer &&
sroute->sink_widget->id != snd_soc_dapm_buffer) continue;
ret = sof_route_setup(sdev, sroute->src_widget->widget,
sroute->sink_widget->widget); if (ret < 0) {
dev_err(sdev->dev, "%s: route set up failed\n", __func__); return ret;
}
}
/* complete pipeline */
list_for_each_entry(swidget, &sdev->widget_list, list) { switch (swidget->id) { case snd_soc_dapm_scheduler: /* only complete static pipelines */ if (!verify && swidget->dynamic_pipeline_widget) continue;
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
ret = sof_widget_setup(sdev, swidget); if (ret < 0) return ret;
}
/* * Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that * did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
*/ staticint sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
{ struct snd_sof_widget *swidget; int ret;
/* * free all PCMs and their associated DAPM widgets if their connected DAPM widget * list is not NULL. This should only be true for paused streams at this point. * This is equivalent to the handling of FE DAI suspend trigger for running streams.
*/
ret = sof_pcm_free_all_streams(sdev); if (ret) return ret;
/* * free any left over DAI widgets. This is equivalent to the handling of suspend trigger * for the BE DAI for running streams.
*/
list_for_each_entry(swidget, &sdev->widget_list, list) if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
ret = sof_widget_free(sdev, swidget); if (ret < 0) return ret;
}
/* Do not free widgets for static pipelines with FW older than SOF2.2 */ if (!verify && !swidget->dynamic_pipeline_widget &&
SOF_FW_VER(v->major, v->minor, v->micro) < SOF_FW_VER(2, 2, 0)) {
mutex_lock(&swidget->setup_mutex);
swidget->use_count = 0;
mutex_unlock(&swidget->setup_mutex); if (swidget->spipe)
swidget->spipe->complete = 0; continue;
}
if (include_scheduler && swidget->id != snd_soc_dapm_scheduler) continue;
if (!include_scheduler && swidget->id == snd_soc_dapm_scheduler) continue;
ret = sof_widget_free(sdev, swidget); if (ret < 0) return ret;
}
return 0;
}
/* * For older firmware, this function doesn't free widgets for static pipelines during suspend. * It only resets use_count for all widgets.
*/ staticint sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{ struct sof_ipc_fw_version *v = &sdev->fw_ready.version; struct snd_sof_widget *swidget; struct snd_sof_route *sroute; bool dyn_widgets = false; int ret;
/* * This function is called during suspend and for one-time topology verification during * first boot. In both cases, there is no need to protect swidget->use_count and * sroute->setup because during suspend all running streams are suspended and during * topology loading the sound card unavailable to open PCMs. Do not free the scheduler * widgets yet so that the secondary cores do not get powered down before all the widgets * associated with the scheduler are freed.
*/
ret = sof_ipc3_free_widgets_in_list(sdev, false, &dyn_widgets, verify); if (ret < 0) return ret;
/* * Tear down all pipelines associated with PCMs that did not get suspended * and unset the prepare flag so that they can be set up again during resume. * Skip this step for older firmware unless topology has any * dynamic pipeline (in which case the step is mandatory).
*/ if (!verify && (dyn_widgets || SOF_FW_VER(v->major, v->minor, v->micro) >=
SOF_FW_VER(2, 2, 0))) {
ret = sof_tear_down_left_over_pipelines(sdev); if (ret < 0) {
dev_err(sdev->dev, "failed to tear down paused pipelines\n"); return ret;
}
}
/* free all the scheduler widgets now. This will also power down the secondary cores */
ret = sof_ipc3_free_widgets_in_list(sdev, true, &dyn_widgets, verify); if (ret < 0) return ret;
/* * before suspending, make sure the refcounts are all zeroed out. There's no way * to recover at this point but this will help root cause bad sequences leading to * more issues on resume
*/
list_for_each_entry(swidget, &sdev->widget_list, list) { if (swidget->use_count != 0) {
dev_err(sdev->dev, "%s: widget %s is still in use: count %d\n",
__func__, swidget->widget->name, swidget->use_count);
}
}
switch (private->dai_config->type) { case SOF_DAI_INTEL_SSP: switch (param_type) { case SOF_DAI_PARAM_INTEL_SSP_MCLK: return private->dai_config->ssp.mclk_rate; case SOF_DAI_PARAM_INTEL_SSP_BCLK: return private->dai_config->ssp.bclk_rate; case SOF_DAI_PARAM_INTEL_SSP_TDM_SLOTS: return private->dai_config->ssp.tdm_slots; default:
dev_err(sdev->dev, "invalid SSP param %d\n", param_type); break;
} break; default: /* not yet implemented for platforms other than the above */
dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type); break;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
dev_err(scomp->dev, "%s: Incompatible topology ABI version\n", __func__); return -EINVAL;
}
if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) {
dev_err(scomp->dev, "%s: Topology ABI is more recent than kernel\n", __func__); return -EINVAL;
}
/* * set default trigger order for all links. Exceptions to * the rule will be handled in sof_pcm_dai_link_fixup() * For playback, the sequence is the following: start FE, * start BE, stop BE, stop FE; for Capture the sequence is * inverted start BE, start FE, stop FE, stop BE
*/
link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_PRE;
link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_POST;
return 0;
}
/* token list for each topology object */ staticenum sof_tokens host_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_PCM_TOKENS,
SOF_COMP_TOKENS,
};
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.49Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.