// 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 // // #include <linux/bitfield.h> #include <linux/cleanup.h> #include <uapi/sound/sof/tokens.h> #include <sound/pcm_params.h> #include <sound/sof/ext_manifest4.h> #include <sound/intel-nhlt.h> #include"sof-priv.h" #include"sof-audio.h" #include"ipc4-priv.h" #include"ipc4-topology.h" #include"ops.h"
/* * The ignore_cpc flag can be used to ignore the CPC value for all modules by * using 0 instead. * The CPC is sent to the firmware along with the SOF_IPC4_MOD_INIT_INSTANCE * message and it is used for clock scaling. * 0 as CPC value will instruct the firmware to use maximum frequency, thus * deactivating the clock scaling.
*/ staticbool ignore_cpc;
module_param_named(ipc4_ignore_cpc, ignore_cpc, bool, 0444);
MODULE_PARM_DESC(ipc4_ignore_cpc, "Ignore CPC values. This option will disable clock scaling in firmware.");
if (!available_fmt->num_input_formats &&
!available_fmt->num_output_formats) return;
/* Only input or output is supported by the module */ if (!available_fmt->num_input_formats) { if (available_fmt->num_output_formats == 1)
dev_dbg(dev, "Output audio format for %s:\n",
swidget->widget->name); else
dev_dbg(dev, "Output audio format (format index: %d) for %s:\n",
out_fmt_index, swidget->widget->name);
if (!(in_valid_bits != out_valid_bits || in_rate != out_rate ||
in_channels != out_channels)) { /* There is no change in format */ if (available_fmt->num_input_formats == 1 &&
available_fmt->num_output_formats == 1)
dev_dbg(dev, "Audio format for %s:\n",
swidget->widget->name); else
dev_dbg(dev, "Audio format (in/out format index: %d/%d) for %s:\n",
in_fmt_index, out_fmt_index, swidget->widget->name);
/* The format is changed by the module */ if (available_fmt->num_input_formats == 1)
dev_dbg(dev, "Input audio format for %s:\n",
swidget->widget->name); else
dev_dbg(dev, "Input audio format (format index: %d) for %s:\n",
in_fmt_index, swidget->widget->name);
if (available_fmt->num_output_formats == 1)
dev_dbg(dev, "Output audio format for %s:\n",
swidget->widget->name); else
dev_dbg(dev, "Output audio format (format index: %d) for %s:\n",
out_fmt_index, swidget->widget->name);
staticconststruct 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;
/* * For process modules without base config extension, base module config * format is used for all input pins
*/ if (process->init_config != SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) return &process->base_config.audio_fmt;
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 * @module_base_cfg: Pointer to the base_config in the module init IPC payload * * Return: 0 if successful
*/ staticint sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt, struct sof_ipc4_base_module_cfg *module_base_cfg)
{ struct sof_ipc4_pin_format *in_format = NULL; struct sof_ipc4_pin_format *out_format; int ret;
ret = sof_update_ipc_object(scomp, available_fmt,
SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*available_fmt), 1); if (ret) {
dev_err(scomp->dev, "Failed to parse audio format token count\n"); return ret;
}
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;
}
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);
/* set 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 comp tokens for %s failed, error: %d\n",
swidget->widget->name, ret); return ret;
}
/* 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;
/* * Update the card's components list with iec61937-pcm and a list of PCM * ids where ChainDMA is enabled. * These PCMs can be used for bytestream passthrough.
*/ if (!pipeline->use_chain_dma) return 0;
if (card->components) { constchar *tmp = card->components;
dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt,
&ipc4_copier->data.base_config); if (ret) goto free_copier;
/* * This callback is used by host copier and module-to-module copier, * and only host copier needs to set gtw_cfg.
*/ if (!WIDGET_IS_AIF(swidget->id)) goto skip_gtw_cfg;
ret = sof_update_ipc_object(scomp, &node_type,
SOF_COPIER_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(node_type), 1);
spcm = snd_sof_find_spcm_comp(scomp, swidget->comp_id, &dir); if (!spcm) goto skip_gtw_cfg;
ret = sof_ipc4_update_card_components_string(swidget, spcm, dir); if (ret) goto free_available_fmt;
if (dir == SNDRV_PCM_STREAM_PLAYBACK) { struct snd_sof_pcm_stream *sps = &spcm->stream[dir];
sof_update_ipc_object(scomp, &sps->dsp_max_burst_size_in_ms,
SOF_COPIER_DEEP_BUFFER_TOKENS,
swidget->tuples,
swidget->num_tuples, sizeof(u32), 1); /* Set default DMA buffer size if it is not specified in topology */ if (!sps->dsp_max_burst_size_in_ms) { struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
sps->dsp_max_burst_size_in_ms = pipeline->use_chain_dma ?
SOF_IPC4_CHAIN_DMA_BUFFER_SIZE : SOF_IPC4_MIN_DMA_BUFFER_SIZE;
}
} else { /* Capture data is copied from DSP to host in 1ms bursts */
spcm->stream[dir].dsp_max_burst_size_in_ms = 1;
}
skip_gtw_cfg:
ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) {
ret = -ENOMEM; goto free_available_fmt;
}
if (pipeline->use_chain_dma &&
!snd_sof_is_chain_dma_supported(sdev, ipc4_copier->dai_type)) {
dev_err(scomp->dev, "Bad DAI type '%d', Chain DMA is not supported\n",
ipc4_copier->dai_type);
ret = -ENODEV; goto free_available_fmt;
}
switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_ALH:
{ struct sof_ipc4_alh_configuration_blob *blob; struct snd_soc_dapm_path *p; struct snd_sof_widget *w; int src_num = 0;
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.
*/ break;
}
blob = kzalloc(sizeof(*blob), GFP_KERNEL); if (!blob) {
ret = -ENOMEM; goto free_available_fmt;
}
if (!WIDGET_IS_DAI(w->id) || !w->widget->sname ||
strcmp(w->widget->sname, swidget->widget->sname)) continue;
alh_dai = w->private; if (alh_dai->type != SOF_DAI_INTEL_ALH) continue;
blob->alh_cfg.device_count++;
}
ipc4_copier->copier_config = (uint32_t *)blob; /* set data.gtw_cfg.config_length based on device_count */
ipc4_copier->data.gtw_cfg.config_length = (sizeof(blob->gw_attr) + sizeof(blob->alh_cfg.device_count) + sizeof(*blob->alh_cfg.mapping) *
blob->alh_cfg.device_count) >> 2; break;
} case SOF_DAI_INTEL_SSP: /* set SSP DAI index as the node_id */
ipc4_copier->data.gtw_cfg.node_id |=
SOF_IPC4_NODE_INDEX_INTEL_SSP(ipc4_copier->dai_index); break; case SOF_DAI_INTEL_DMIC: /* set DMIC DAI index as the node_id */
ipc4_copier->data.gtw_cfg.node_id |=
SOF_IPC4_NODE_INDEX_INTEL_DMIC(ipc4_copier->dai_index); break; default:
ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) {
ret = -ENOMEM; goto free_available_fmt;
}
/* * Add the process modules support. The process modules are defined as snd_soc_dapm_effect modules.
*/ staticint 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 snd_sof_pipeline *spipe = swidget->spipe; 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);
/* pick the first format if there's only one available or if all formats are the same */ if (single_format) goto out_fmt;
/* * if there are multiple output formats, then choose the output format that matches * the reference params
*/ for (i = 0; i < pin_fmts_size; i++) { struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt;
if (!pin_fmts_size) {
dev_err(sdev->dev, "no input formats for %s\n", swidget->widget->name); return -EINVAL;
}
single_format = sof_ipc4_is_single_format(sdev, pin_fmts, pin_fmts_size); if (single_format) goto in_fmt;
sample_valid_bits = sof_ipc4_get_valid_bits(sdev, params); if (sample_valid_bits < 0) return sample_valid_bits;
/* * Search supported input audio formats with pin index 0 to match rate, channels and * sample_valid_bits from reference params
*/ for (i = 0; i < pin_fmts_size; i++) { struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt;
if (pipeline->use_chain_dma) { /* * Preserve the DMA Link ID and clear other bits since * the DMA Link ID is only configured once during * dai_config, other fields are expected to be 0 for * re-configuration
*/
pipeline->msg.primary &= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK;
pipeline->msg.extension = 0;
}
if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_alh_configuration_blob *blob; unsignedint group_id;
#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_INTEL_NHLT) staticint snd_sof_get_hw_config_params(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int *sample_rate, int *channel_count, int *bit_depth)
{ struct snd_soc_tplg_hw_config *hw_config; struct snd_sof_dai_link *slink; bool dai_link_found = false; bool hw_cfg_found = false; int i;
/* get current hw_config from link */
list_for_each_entry(slink, &sdev->dai_link_list, list) { if (!strcmp(slink->link->name, dai->name)) {
dai_link_found = true; break;
}
}
if (!dai_link_found) {
dev_err(sdev->dev, "%s: no DAI link found for DAI %s\n", __func__, dai->name); return -EINVAL;
}
for (i = 0; i < slink->num_hw_configs; i++) {
hw_config = &slink->hw_configs[i]; if (dai->current_config == le32_to_cpu(hw_config->id)) {
hw_cfg_found = true; break;
}
}
if (!hw_cfg_found) {
dev_err(sdev->dev, "%s: no matching hw_config found for DAI %s\n", __func__,
dai->name); return -EINVAL;
}
/* convert to NHLT type */ switch (linktype) { case SOF_DAI_INTEL_DMIC:
nhlt_type = NHLT_LINK_DMIC;
channel_count = params_channels(params);
sample_rate = params_rate(params);
bit_depth = params_width(params); /* * Look for 32-bit blob first instead of 16-bit if copier * supports multiple formats
*/ if (bit_depth == 16 && !single_bitdepth) {
dev_dbg(sdev->dev, "Looking for 32-bit blob first for DMIC\n");
format_change = true;
bit_depth = 32;
} break; case SOF_DAI_INTEL_SSP:
nhlt_type = NHLT_LINK_SSP;
ret = snd_sof_get_hw_config_params(sdev, dai, &sample_rate, &channel_count,
&bit_depth); if (ret < 0) return ret;
/* * We need to know the type of the external device attached to a SSP * port to retrieve the blob from NHLT. However, device type is not * specified in topology. * Query the type for the port and then pass that information back * to the blob lookup function.
*/
dev_type = intel_nhlt_ssp_device_type(sdev->dev, ipc4_data->nhlt,
dai_index); if (dev_type < 0) return dev_type; break; default: return 0;
}
dev_dbg(sdev->dev, "dai index %d nhlt type %d direction %d dev type %d\n",
dai_index, nhlt_type, dir, dev_type);
if (format_change) { /* * The 32-bit blob was not found in NHLT table, try to * look for one based on the params
*/
bit_depth = params_width(params);
format_change = false;
get_new_blob = true;
} elseif (linktype == SOF_DAI_INTEL_DMIC && !single_bitdepth) { /* * The requested 32-bit blob (no format change for the * blob request) was not found in NHLT table, try to * look for 16-bit blob if the copier supports multiple * formats
*/
bit_depth = 16;
format_change = true;
get_new_blob = true;
}
if (get_new_blob) {
cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt,
dai_index, nhlt_type,
bit_depth, bit_depth,
channel_count, sample_rate,
dir, dev_type); if (cfg) goto out;
}
dev_err(sdev->dev, "no matching blob for sample rate: %d sample width: %d channels: %d\n",
sample_rate, bit_depth, channel_count); return -EINVAL;
}
out: /* config length should be in dwords */
*len = cfg->size >> 2;
*dst = (u32 *)cfg->caps;
if (format_change) { /* * Update the params to reflect that different blob was loaded * instead of the requested bit depth (16 -> 32 or 32 -> 16). * This information is going to be used by the caller to find * matching copier format on the dai side.
*/ struct snd_mask *m;
m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
snd_mask_none(m); if (bit_depth == 16)
snd_mask_set_format(m, SNDRV_PCM_FORMAT_S16_LE); else
snd_mask_set_format(m, SNDRV_PCM_FORMAT_S32_LE);
/* * Fixup the params based on the format parameters of the DAI. If any * of the RATE, CHANNELS, bit depth is static among the formats then * narrow the params to only allow that specific parameter value.
*/ if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
pin_fmts = available_fmt->output_pin_fmts;
num_pin_fmts = available_fmt->num_output_formats;
} else {
pin_fmts = available_fmt->input_pin_fmts;
num_pin_fmts = available_fmt->num_input_formats;
}
ret = sof_ipc4_adjust_params_to_dai_format(sdev, &dai_params, pin_fmts,
num_pin_fmts); if (ret) return ret;
single_bitdepth = sof_ipc4_copier_is_single_bitdepth(sdev, pin_fmts,
num_pin_fmts);
ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, single_bitdepth,
&dai_params,
ipc4_copier->dai_index,
ipc4_copier->dai_type, dir,
&ipc4_copier->copier_config,
&copier_data->gtw_cfg.config_length); /* Update the params to reflect the changes made in this function */ if (!ret)
*params = dai_params;
/* 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;
}
/* 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, but it * can use the host side registers for delay calculation.
*/
copier_data->gtw_cfg.node_id = SOF_IPC4_CHAIN_DMA_NODE_ID;
return 0;
}
/* * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts * for capture.
*/ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
ref_params = kmemdup(fe_params, sizeof(*ref_params), GFP_KERNEL); else
ref_params = kmemdup(pipeline_params, sizeof(*ref_params), GFP_KERNEL); if (!ref_params) return -ENOMEM;
/* * Use the fe_params as a base for the copier configuration. * The ref_params might get updated to reflect what format is * supported by the copier on the DAI side. * * In case of capture the ref_params returned will be used to * find the input configuration of the copier.
*/
ref_params = kmemdup(fe_params, sizeof(*ref_params), GFP_KERNEL); if (!ref_params) return -ENOMEM;
ret = sof_ipc4_prepare_dai_copier(sdev, dai, ref_params, dir); if (ret < 0) return ret;
/* * For playback the pipeline_params needs to be used to find the * input configuration of the copier.
*/ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
memcpy(ref_params, pipeline_params, sizeof(*ref_params));
break;
} case snd_soc_dapm_buffer:
{
dev_dbg(sdev->dev, "Module copier %s, type %d\n",
swidget->widget->name, swidget->id);
ref_params = kmemdup(pipeline_params, sizeof(*ref_params), GFP_KERNEL); if (!ref_params) return -ENOMEM;
break;
} default:
dev_err(sdev->dev, "unsupported type %d for copier %s",
swidget->id, swidget->widget->name); return -EINVAL;
}
/* set input and output audio formats */
input_fmt_index = sof_ipc4_init_input_audio_fmt(sdev, swidget,
&copier_data->base_config,
ref_params, available_fmt); if (input_fmt_index < 0) return input_fmt_index;
/* set the reference params for output format selection */
single_output_bitdepth = sof_ipc4_copier_is_single_bitdepth(sdev,
available_fmt->output_pin_fmts,
available_fmt->num_output_formats); switch (swidget->id) { case snd_soc_dapm_aif_in: case snd_soc_dapm_dai_out: case snd_soc_dapm_buffer:
{ struct sof_ipc4_audio_format *in_fmt;
if (!single_output_bitdepth)
out_ref_valid_bits =
SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); break;
} case snd_soc_dapm_aif_out: case snd_soc_dapm_dai_in:
out_ref_rate = params_rate(fe_params);
out_ref_channels = params_channels(fe_params); if (!single_output_bitdepth) {
out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); if (out_ref_valid_bits < 0) return out_ref_valid_bits;
} break; default: /* * Unsupported type should be caught by the former switch default * case, this should never happen in reality.
*/ return -EINVAL;
}
/* * if the output format is the same across all available output formats, choose * that as the reference.
*/ if (single_output_bitdepth) { struct sof_ipc4_audio_format *out_fmt;
/* * 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[output_fmt_index].audio_fmt, sizeof(struct sof_ipc4_audio_format));
switch (swidget->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out:
{ /* * Only SOF_DAI_INTEL_ALH needs copier_data to set blob. * That's why only ALH dai's blob is set after sof_ipc4_init_input_audio_fmt
*/ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_alh_configuration_blob *blob; struct sof_ipc4_dma_config *dma_config; struct sof_ipc4_copier_data *alh_data; struct sof_ipc4_copier *alh_copier; struct snd_sof_widget *w;
u32 ch_count = 0;
u32 ch_mask = 0;
u32 ch_map;
u32 step;
u32 mask;
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.