diff options
Diffstat (limited to 'sound/soc/sof/ipc4-pcm.c')
-rw-r--r-- | sound/soc/sof/ipc4-pcm.c | 137 |
1 files changed, 117 insertions, 20 deletions
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 8eee3e1aadf9..374dc10d10fd 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -56,17 +56,41 @@ sof_ipc4_sps_to_time_info(struct snd_sof_pcm_stream *sps) return stream_priv->time_info; } +static +char *sof_ipc4_set_multi_pipeline_state_debug(struct snd_sof_dev *sdev, char *buf, size_t size, + struct ipc4_pipeline_set_state_data *trigger_list) +{ + int i, offset = 0; + + for (i = 0; i < trigger_list->count; i++) { + offset += snprintf(buf + offset, size - offset, " %d", + trigger_list->pipeline_instance_ids[i]); + + if (offset >= size - 1) { + buf[size - 1] = '\0'; + break; + } + } + return buf; +} + static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state, struct ipc4_pipeline_set_state_data *trigger_list) { struct sof_ipc4_msg msg = {{ 0 }}; u32 primary, ipc_size; + char debug_buf[32]; /* trigger a single pipeline */ if (trigger_list->count == 1) return sof_ipc4_set_pipeline_state(sdev, trigger_list->pipeline_instance_ids[0], state); + dev_dbg(sdev->dev, "Set pipelines %s to state %d%s", + sof_ipc4_set_multi_pipeline_state_debug(sdev, debug_buf, sizeof(debug_buf), + trigger_list), + state, sof_ipc4_pipeline_state_str(state)); + primary = state; primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE); primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); @@ -89,7 +113,8 @@ int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 instance_id, u32 s struct sof_ipc4_msg msg = {{ 0 }}; u32 primary; - dev_dbg(sdev->dev, "ipc4 set pipeline instance %d state %d", instance_id, state); + dev_dbg(sdev->dev, "Set pipeline %d to state %d%s", instance_id, state, + sof_ipc4_pipeline_state_str(state)); primary = state; primary |= SOF_IPC4_GLB_PIPE_STATE_ID(instance_id); @@ -409,9 +434,33 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, * 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, spcm, substream->stream, - pipeline_list, state, cmd); + if (pipeline->use_chain_dma) { + struct sof_ipc4_timestamp_info *time_info; + + time_info = sof_ipc4_sps_to_time_info(&spcm->stream[substream->stream]); + + ret = sof_ipc4_chain_dma_trigger(sdev, spcm, substream->stream, + pipeline_list, state, cmd); + if (ret || !time_info) + return ret; + + if (state == SOF_IPC4_PIPE_PAUSED) { + /* + * Record the DAI position for delay reporting + * To handle multiple pause/resume/xrun we need to add + * the positions to simulate how the firmware behaves + */ + u64 pos = snd_sof_pcm_get_dai_frame_counter(sdev, component, + substream); + + time_info->stream_end_offset += pos; + } else if (state == SOF_IPC4_PIPE_RESET) { + /* Reset the end offset as the stream is stopped */ + time_info->stream_end_offset = 0; + } + + return 0; + } /* allocate memory for the pipeline data */ trigger_list = kzalloc(struct_size(trigger_list, pipeline_instance_ids, @@ -557,12 +606,15 @@ static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0); } -static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, - struct snd_pcm_hw_params *params) +static int ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, + const char *link_name, + struct snd_pcm_hw_params *params) { struct snd_sof_dai_link *slink; struct snd_sof_dai *dai; bool dai_link_found = false; + int current_config = -1; + bool partial_match; int i; list_for_each_entry(slink, &sdev->dai_link_list, list) { @@ -573,19 +625,50 @@ static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const } if (!dai_link_found) - return; + return 0; + /* + * Find the first best matching hardware config: + * rate + format + channels are matching + * rate + channel are matching + * + * The copier cannot do rate and/or channel conversion. + */ for (i = 0; i < slink->num_hw_configs; i++) { struct snd_soc_tplg_hw_config *hw_config = &slink->hw_configs[i]; - if (params_rate(params) == le32_to_cpu(hw_config->fsync_rate)) { - /* set current config for all DAI's with matching name */ - list_for_each_entry(dai, &sdev->dai_list, list) - if (!strcmp(slink->link->name, dai->name)) - dai->current_config = le32_to_cpu(hw_config->id); + if (params_rate(params) == le32_to_cpu(hw_config->fsync_rate) && + params_width(params) == le32_to_cpu(hw_config->tdm_slot_width) && + params_channels(params) == le32_to_cpu(hw_config->tdm_slots)) { + current_config = le32_to_cpu(hw_config->id); + partial_match = false; + /* best match found */ break; + } else if (current_config < 0 && + params_rate(params) == le32_to_cpu(hw_config->fsync_rate) && + params_channels(params) == le32_to_cpu(hw_config->tdm_slots)) { + current_config = le32_to_cpu(hw_config->id); + partial_match = true; + /* keep looking for better match */ } } + + if (current_config < 0) { + dev_err(sdev->dev, + "%s: No suitable hw_config found for %s (num_hw_configs: %d)\n", + __func__, slink->link->name, slink->num_hw_configs); + return -EINVAL; + } + + dev_dbg(sdev->dev, + "hw_config for %s: %d (num_hw_configs: %d) with %s match\n", + slink->link->name, current_config, slink->num_hw_configs, + partial_match ? "partial" : "full"); + list_for_each_entry(dai, &sdev->dai_list, list) + if (!strcmp(slink->link->name, dai->name)) + dai->current_config = current_config; + + return 0; } /* @@ -728,13 +811,10 @@ static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, break; } - switch (ipc4_copier->dai_type) { - case SOF_DAI_INTEL_SSP: - ipc4_ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params); - break; - default: - break; - } + if (ipc4_copier->dai_type == SOF_DAI_INTEL_SSP) + return ipc4_ssp_dai_config_pcm_params_match(sdev, + (char *)rtd->dai_link->name, + params); return 0; } @@ -928,8 +1008,24 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, if (!host_copier || !dai_copier) return -EINVAL; - if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID) + if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID) { return -EINVAL; + } else if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_CHAIN_DMA_NODE_ID) { + /* + * While the firmware does not supports time_info reporting for + * streams using ChainDMA, it is granted that ChainDMA can only + * be used on Host+Link pairs where the link position is + * accessible from the host side. + * + * Enable delay calculation in case of ChainDMA via host + * accessible registers. + * + * The ChainDMA uses 2x 1ms ping-pong buffer, dai side starts + * when 1ms data is available + */ + time_info->stream_start_offset = substream->runtime->rate / MSEC_PER_SEC; + goto out; + } node_index = SOF_IPC4_NODE_INDEX(host_copier->data.gtw_cfg.node_id); offset = offsetof(struct sof_ipc4_fw_registers, pipeline_regs) + node_index * sizeof(ppl_reg); @@ -947,6 +1043,7 @@ static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev, time_info->stream_end_offset = ppl_reg.stream_end_offset; do_div(time_info->stream_end_offset, dai_sample_size); +out: /* * Calculate the wrap boundary need to be used for delay calculation * The host counter is in bytes, it will wrap earlier than the frames |