// 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;
/* Get channel_mask from ch_map */
ch_map = copier_data->base_config.audio_fmt.ch_map; for (i = 0; ch_map; i++) { if ((ch_map & 0xf) != 0xf) {
ch_mask |= BIT(i);
ch_count++;
}
ch_map >>= 4;
}
step = ch_count / blob->alh_cfg.device_count;
mask = GENMASK(step - 1, 0); /* * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[] * for all widgets with the same stream name
*/
i = 0;
list_for_each_entry(w, &sdev->widget_list, list) {
u32 node_type;
if (!WIDGET_IS_DAI(w->id) || !w->widget->sname ||
strcmp(w->widget->sname, swidget->widget->sname)) continue;
/* * The mapping[i] device in ALH blob should be the same as the * dma_config_tlv[i] mapping device if a dma_config_tlv is present. * The device id will be used for DMA tlv mapping purposes.
*/ if (ipc4_copier->dma_config_tlv[i].length) {
dma_config = &ipc4_copier->dma_config_tlv[i].dma_config;
blob->alh_cfg.mapping[i].device =
dma_config->dma_stream_channel_map.mapping[0].device;
}
/* * Set the same channel mask for playback as the audio data is * duplicated for all speakers. For capture, split the channels * among the aggregated DAIs. For example, with 4 channels on 2 * aggregated DAIs, the channel_mask should be 0x3 and 0xc for the * two DAI's. * The channel masks used depend on the cpu_dais used in the * dailink at the machine driver level, which actually comes from * the tables in soc_acpi files depending on the _ADR and devID * registers for each codec.
*/ if (w->id == snd_soc_dapm_dai_in)
blob->alh_cfg.mapping[i].channel_mask = ch_mask; else
blob->alh_cfg.mapping[i].channel_mask = mask << (step * i);
i++;
} if (blob->alh_cfg.device_count > 1) { int group_id;
/* modify the input params for the next widget */
ret = sof_ipc4_update_hw_params(sdev, pipeline_params,
&copier_data->out_format,
BIT(SNDRV_PCM_HW_PARAM_FORMAT) |
BIT(SNDRV_PCM_HW_PARAM_CHANNELS) |
BIT(SNDRV_PCM_HW_PARAM_RATE)); 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 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;
dev_dbg(sdev->dev, "copier %s, dma buffer%s: %u ms (%u bytes)",
swidget->widget->name,
deep_buffer_dma_ms ? " (using Deep Buffer)" : "",
max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms),
copier_data->gtw_cfg.dma_buffer_size); break; 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: break;
}
data = &ipc4_copier->copier_config;
ipc_config_size = &ipc4_copier->ipc_config_size;
ipc_config_data = &ipc4_copier->ipc_config_data;
/* config_length is DWORD based */
gtw_cfg_config_length = copier_data->gtw_cfg.config_length * 4;
ipc_size = sizeof(*copier_data) + gtw_cfg_config_length;
dma_config_tlv_size = 0; for (i = 0; i < SOF_IPC4_DMA_DEVICE_MAX_COUNT; i++) { if (ipc4_copier->dma_config_tlv[i].type != SOF_IPC4_GTW_DMA_CONFIG_ID) continue;
dma_config_tlv_size += ipc4_copier->dma_config_tlv[i].length;
dma_config_tlv_size +=
ipc4_copier->dma_config_tlv[i].dma_config.dma_priv_config_size;
dma_config_tlv_size += (sizeof(ipc4_copier->dma_config_tlv[i]) - sizeof(ipc4_copier->dma_config_tlv[i].dma_config));
}
if (dma_config_tlv_size) {
ipc_size += dma_config_tlv_size;
/* we also need to increase the size at the gtw level */
copier_data->gtw_cfg.config_length += dma_config_tlv_size / 4;
}
dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size);
*ipc_config_data = kzalloc(ipc_size, GFP_KERNEL); if (!*ipc_config_data) return -ENOMEM;
/* copy IPC data */
memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data)); if (gtw_cfg_config_length)
memcpy(*ipc_config_data + sizeof(*copier_data),
*data, gtw_cfg_config_length);
/* add DMA Config TLV, if configured */ if (dma_config_tlv_size)
memcpy(*ipc_config_data + sizeof(*copier_data) +
gtw_cfg_config_length,
&ipc4_copier->dma_config_tlv, dma_config_tlv_size);
/* * Restore gateway config length now that IPC payload is prepared. This avoids * counting the DMA CONFIG TLV multiple times
*/
copier_data->gtw_cfg.config_length = gtw_cfg_config_length / 4;
/* * For playback, the SRC sink rate will be configured based on the requested output * format, which is restricted to only deal with DAI's with a single format for now.
*/ if (dir == SNDRV_PCM_STREAM_PLAYBACK && available_fmt->num_output_formats > 1) {
dev_err(sdev->dev, "Invalid number of output formats: %d for SRC %s\n",
available_fmt->num_output_formats, swidget->widget->name); return -EINVAL;
}
/* * SRC does not perform format conversion, so the output channels and valid bit depth must * be the same as that of the input.
*/
in_audio_fmt = &available_fmt->input_pin_fmts[input_fmt_index].audio_fmt;
out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_audio_fmt->fmt_cfg);
out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_audio_fmt->fmt_cfg);
/* * For capture, the SRC module should convert the rate to match the rate requested by the * PCM hw_params. Set the reference params based on the fe_params unconditionally as it * will be ignored for playback anyway.
*/
out_ref_rate = params_rate(fe_params);
staticint
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;
}
staticint 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;
}
/* Configure output audio format only if the module supports output */ if (available_fmt->num_output_formats) { struct sof_ipc4_audio_format *in_fmt; struct sof_ipc4_pin_format *pin_fmt;
u32 out_ref_rate, out_ref_channels; int out_ref_valid_bits;
/* copy Pin output format for Pin 0 only */ if (pin_fmt->pin_index == 0) {
memcpy(&process->output_format, &pin_fmt->audio_fmt, sizeof(struct sof_ipc4_audio_format));
/* modify the pipeline params with the output format */
ret = sof_ipc4_update_hw_params(sdev, pipeline_params,
&process->output_format,
BIT(SNDRV_PCM_HW_PARAM_FORMAT) |
BIT(SNDRV_PCM_HW_PARAM_CHANNELS) |
BIT(SNDRV_PCM_HW_PARAM_RATE)); if (ret) return ret;
}
}
/* 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;
/* scontrol->ipc_control_data will be freed in sof_control_unload */
scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); if (!scontrol->ipc_control_data) return -ENOMEM;
/* volume controls with range 0-1 (off/on) are switch controls */ if (scontrol->max == 1)
msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_SWITCH_CONTROL_PARAM_ID); else
msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID);
for (i = 0; i < scontrol->num_channels; i++) {
control_data->chanv[i].channel = i; /* * Default, initial values: * - 0dB for volume controls * - off (0) for switch controls - value already zero after * memory allocation
*/ if (scontrol->max > 1)
control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB;
}
/* scontrol->ipc_control_data will be freed in sof_control_unload */
scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); if (!scontrol->ipc_control_data) return -ENOMEM;
/* Default, initial value for enums: first enum entry is selected (0) */ for (i = 0; i < scontrol->num_channels; i++)
control_data->chanv[i].channel = i;
msg = &process->msg; break;
} default:
dev_err(sdev->dev, "widget type %d not supported", swidget->id); return -EINVAL;
}
if (swidget->id != snd_soc_dapm_scheduler) { int module_id = msg->primary & SOF_IPC4_MOD_ID_MASK;
ret = sof_ipc4_widget_assign_instance_id(sdev, swidget); if (ret < 0) {
dev_err(sdev->dev, "failed to assign instance id for %s\n",
swidget->widget->name); return ret;
}
/* freeing a pipeline frees all the widgets associated with it */ if (swidget->id == snd_soc_dapm_scheduler) { struct sof_ipc4_pipeline *pipeline = swidget->private; 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;
}
if (num_pins < 1) {
dev_err(scomp->dev, "invalid %s num_pins: %d for queue allocation for %s\n",
(pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"),
num_pins, current_swidget->widget->name); return -EINVAL;
}
/* If there is only one input/output pin, queue id must be 0 */ if (num_pins == 1) return 0;
/* Allocate queue ID from pin binding array if it is defined in topology. */ if (pin_binding) { for (i = 0; i < num_pins; i++) { if (!strcmp(pin_binding[i], buddy_name)) return i;
} /* * Fail if no queue ID found from pin binding array, so that we don't * 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_OUTPUT ? "output" : "input"),
current_swidget->widget->name); return -EINVAL;
}
/* If no pin binding array specified in topology, use ida to allocate one */ return ida_alloc_max(queue_ida, num_pins, GFP_KERNEL);
}
staticvoid sof_ipc4_put_queue_id(struct snd_sof_widget *swidget, int queue_id, bool pin_type)
{ struct ida *queue_ida; char **pin_binding; int num_pins;
pin_fmt = sof_ipc4_get_input_pin_audio_fmt(sink_widget, sroute->dst_queue_id); if (!pin_fmt) {
dev_err(sdev->dev, "Failed to get input audio format of %s:%d for output of %s:%d\n",
sink_widget->widget->name, sroute->dst_queue_id,
src_widget->widget->name, sroute->src_queue_id); return -EINVAL;
}
/* 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;
}
sroute->src_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
SOF_PIN_TYPE_OUTPUT); if (sroute->src_queue_id < 0) {
dev_err(sdev->dev, "failed to get src_queue_id ID from source widget %s\n",
src_widget->widget->name); return sroute->src_queue_id;
}
sroute->dst_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
SOF_PIN_TYPE_INPUT); if (sroute->dst_queue_id < 0) {
dev_err(sdev->dev, "failed to get dst_queue_id ID from sink widget %s\n",
sink_widget->widget->name);
sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id,
SOF_PIN_TYPE_OUTPUT); return sroute->dst_queue_id;
}
/* Pin 0 format is already set during copier module init */ if (sroute->src_queue_id > 0 && WIDGET_IS_COPIER(src_widget->id)) {
ret = sof_ipc4_set_copier_sink_format(sdev, src_widget,
sink_widget, sroute); if (ret < 0) {
dev_err(sdev->dev, "failed to set sink format for source %s:%d\n",
src_widget->widget->name, sroute->src_queue_id); goto out;
}
}
/* * routes belonging to the same pipeline will be disconnected by the FW when the pipeline * is freed. So avoid sending this IPC which will be ignored by the FW anyway.
*/ if (src_widget->spipe->pipe_widget == sink_widget->spipe->pipe_widget) goto out;
if (pipeline->use_chain_dma) { /* * Only configure the DMA Link ID for ChainDMA when this op is * invoked with SOF_DAI_CONFIG_FLAGS_HW_PARAMS
*/ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) {
pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK;
pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(data->dai_data);
} return 0;
}
switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_HDA:
gtw_attr = ipc4_copier->gtw_attr;
gtw_attr->lp_buffer_alloc = pipeline->lp_mode; if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) {
copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_data);
} break; case SOF_DAI_INTEL_ALH: /* * Do not clear the node ID when this op is invoked with * SOF_DAI_CONFIG_FLAGS_HW_FREE. It is needed to free the group_ida during * unprepare. The node_id for multi-gateway DAI's will be overwritten with the * group_id during copier's ipc_prepare op.
*/ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) { struct sof_ipc4_alh_configuration_blob *blob;
/* * no need to set the node_id for aggregated DAI's. These will be assigned * a group_id during widget ipc_prepare
*/ if (blob->alh_cfg.device_count == 1) {
copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
copier_data->gtw_cfg.node_id |=
SOF_IPC4_NODE_INDEX(data->dai_node_id);
}
}
break; case SOF_DAI_INTEL_DMIC: case SOF_DAI_INTEL_SSP: /* nothing to do for SSP/DMIC */ break; default:
dev_err(sdev->dev, "%s: unsupported dai type %d\n", __func__,
ipc4_copier->dai_type); return -EINVAL;
}
if (!size || size < SOF_IPC4_TPLG_ABI_SIZE) {
dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
__func__, size); return -EINVAL;
}
manifest = (struct sof_manifest *)man_ptr;
dev_info(scomp->dev, "Topology: ABI %d:%d:%d Kernel ABI %u:%u:%u\n",
le16_to_cpu(manifest->abi_major), le16_to_cpu(manifest->abi_minor),
le16_to_cpu(manifest->abi_patch),
SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
/* TODO: Add ABI compatibility check */
/* no more data after the ABI version */ if (size <= SOF_IPC4_TPLG_ABI_SIZE) return 0;
manifest_tlv = manifest->items;
len_check = sizeof(struct sof_manifest); for (i = 0; i < le16_to_cpu(manifest->count); i++) {
len_check += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size); if (len_check > size) return -EINVAL;
switch (le32_to_cpu(manifest_tlv->type)) { case SOF_MANIFEST_DATA_TYPE_NHLT: /* no NHLT in BIOS, so use the one from topology manifest */ if (ipc4_data->nhlt) break;
ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data,
le32_to_cpu(manifest_tlv->size), GFP_KERNEL); if (!ipc4_data->nhlt) return -ENOMEM; break; default:
dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n",
manifest_tlv->type); break;
}
man_ptr += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size);
manifest_tlv = (struct sof_manifest_tlv *)man_ptr;
}
if (!dai_link_found) {
dev_err(sdev->dev, "no DAI link found for DAI %s\n", 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, "no matching hw_config found for DAI %s\n", dai->name); return -EINVAL;
}
switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_SSP: switch (param_type) { case SOF_DAI_PARAM_INTEL_SSP_MCLK: return le32_to_cpu(hw_config->mclk_rate); case SOF_DAI_PARAM_INTEL_SSP_BCLK: return le32_to_cpu(hw_config->bclk_rate); case SOF_DAI_PARAM_INTEL_SSP_TDM_SLOTS: return le32_to_cpu(hw_config->tdm_slots); default:
dev_err(sdev->dev, "invalid SSP param %d\n", param_type); break;
} break; default:
dev_err(sdev->dev, "DAI type %d not supported yet!\n", ipc4_copier->dai_type); break;
}
return -EINVAL;
}
staticint sof_ipc4_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{ /* * This function is called during system suspend, we need to make sure * that all streams have been freed up. * Freeing might have been skipped when xrun happened just at the start * of the suspend and it sent a SNDRV_PCM_TRIGGER_STOP to the active * stream. This will call sof_pcm_stream_free() with * free_widget_list = false which will leave the kernel and firmware out * of sync during suspend/resume. * * This will also make sure that paused streams handled correctly.
*/
/* * 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 BE, * start FE, stop FE, stop BE; for Capture the sequence is * inverted start FE, start BE, stop BE, stop FE
*/
link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_POST;
link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE;
return 0;
}
/* Tokens needed for different copier variants (aif, dai and buffer) */ staticenum sof_tokens copier_token_list[] = {
SOF_COMP_TOKENS,
SOF_COPIER_TOKENS,
SOF_AUDIO_FMT_NUM_TOKENS,
SOF_IN_AUDIO_FORMAT_TOKENS,
SOF_OUT_AUDIO_FORMAT_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_COPIER_DEEP_BUFFER_TOKENS, /* for AIF copier */
SOF_DAI_TOKENS, /* for DAI copier */
};
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.