mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 01:49:36 +00:00
![Rander Wang](/assets/img/avatar_default.png)
The delay function is used to calculate the difference between hw_ptr and dai dma position. I2S, DMIC and SDW will use dai dma position in shared SRAM window to calculate the delay. HDaudio will retrieve dai dma position from host mmio memory space since it doesn't support LLP counter reported by firmware. In two cases dai dma position is inaccurate for delay calculation (1) dai pipeline is started before host pipeline (2) multiple streams mixed into one. Each stream has the same dai dma position Firmware calculates correct stream_start_offset for all cases including above two. Driver subtracts stream_start_offset from dai dma position to get accurate one. Signed-off-by: Rander Wang <rander.wang@intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com> Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20230202132954.26773-10-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
675 lines
21 KiB
C
675 lines
21 KiB
C
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
|
//
|
|
// This file is provided under a dual BSD/GPLv2 license. When using or
|
|
// redistributing this file, you may do so under either license.
|
|
//
|
|
// Copyright(c) 2022 Intel Corporation. All rights reserved.
|
|
//
|
|
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/sof/ipc4/header.h>
|
|
#include "sof-audio.h"
|
|
#include "sof-priv.h"
|
|
#include "ops.h"
|
|
#include "ipc4-priv.h"
|
|
#include "ipc4-topology.h"
|
|
#include "ipc4-fw-reg.h"
|
|
|
|
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;
|
|
|
|
/* trigger a single pipeline */
|
|
if (trigger_list->count == 1)
|
|
return sof_ipc4_set_pipeline_state(sdev, trigger_list->pipeline_ids[0], state);
|
|
|
|
primary = state;
|
|
primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE);
|
|
primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
|
|
primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
|
|
msg.primary = primary;
|
|
|
|
/* trigger multiple pipelines with a single IPC */
|
|
msg.extension = SOF_IPC4_GLB_PIPE_STATE_EXT_MULTI;
|
|
|
|
/* ipc_size includes the count and the pipeline IDs for the number of pipelines */
|
|
ipc_size = sizeof(u32) * (trigger_list->count + 1);
|
|
msg.data_size = ipc_size;
|
|
msg.data_ptr = trigger_list;
|
|
|
|
return sof_ipc_tx_message(sdev->ipc, &msg, ipc_size, NULL, 0);
|
|
}
|
|
|
|
int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state)
|
|
{
|
|
struct sof_ipc4_msg msg = {{ 0 }};
|
|
u32 primary;
|
|
|
|
dev_dbg(sdev->dev, "ipc4 set pipeline %d state %d", id, state);
|
|
|
|
primary = state;
|
|
primary |= SOF_IPC4_GLB_PIPE_STATE_ID(id);
|
|
primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE);
|
|
primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
|
|
primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
|
|
|
|
msg.primary = primary;
|
|
|
|
return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0);
|
|
}
|
|
EXPORT_SYMBOL(sof_ipc4_set_pipeline_state);
|
|
|
|
static void
|
|
sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state,
|
|
struct snd_sof_pipeline *spipe,
|
|
struct ipc4_pipeline_set_state_data *trigger_list)
|
|
{
|
|
struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
|
|
struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
|
|
|
|
if (pipeline->skip_during_fe_trigger)
|
|
return;
|
|
|
|
switch (state) {
|
|
case SOF_IPC4_PIPE_RUNNING:
|
|
/*
|
|
* Trigger pipeline if all PCMs containing it are paused or if it is RUNNING
|
|
* for the first time
|
|
*/
|
|
if (spipe->started_count == spipe->paused_count)
|
|
trigger_list->pipeline_ids[trigger_list->count++] =
|
|
pipe_widget->instance_id;
|
|
break;
|
|
case SOF_IPC4_PIPE_RESET:
|
|
/* RESET if the pipeline is neither running nor paused */
|
|
if (!spipe->started_count && !spipe->paused_count)
|
|
trigger_list->pipeline_ids[trigger_list->count++] =
|
|
pipe_widget->instance_id;
|
|
break;
|
|
case SOF_IPC4_PIPE_PAUSED:
|
|
/* Pause the pipeline only when its started_count is 1 more than paused_count */
|
|
if (spipe->paused_count == (spipe->started_count - 1))
|
|
trigger_list->pipeline_ids[trigger_list->count++] =
|
|
pipe_widget->instance_id;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd,
|
|
struct snd_sof_pipeline *spipe,
|
|
struct ipc4_pipeline_set_state_data *trigger_list)
|
|
{
|
|
struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
|
|
struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
|
|
int i;
|
|
|
|
if (pipeline->skip_during_fe_trigger)
|
|
return;
|
|
|
|
/* set state for pipeline if it was just triggered */
|
|
for (i = 0; i < trigger_list->count; i++) {
|
|
if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) {
|
|
pipeline->state = state;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
case SOF_IPC4_PIPE_PAUSED:
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
/*
|
|
* increment paused_count if the PAUSED is the final state during
|
|
* the PAUSE trigger
|
|
*/
|
|
spipe->paused_count++;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
/*
|
|
* decrement started_count if PAUSED is the final state during the
|
|
* STOP trigger
|
|
*/
|
|
spipe->started_count--;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SOF_IPC4_PIPE_RUNNING:
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
/* decrement paused_count for RELEASE */
|
|
spipe->paused_count--;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
/* increment started_count for START/RESUME */
|
|
spipe->started_count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The picture below represents the pipeline state machine wrt PCM actions corresponding to the
|
|
* triggers and ioctls
|
|
* +---------------+
|
|
* | |
|
|
* | INIT |
|
|
* | |
|
|
* +-------+-------+
|
|
* |
|
|
* |
|
|
* | START
|
|
* |
|
|
* |
|
|
* +----------------+ +------v-------+ +-------------+
|
|
* | | START | | HW_FREE | |
|
|
* | RUNNING <-------------+ PAUSED +--------------> + RESET |
|
|
* | | PAUSE | | | |
|
|
* +------+---------+ RELEASE +---------+----+ +-------------+
|
|
* | ^
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* | PAUSE |
|
|
* +---------------------------------+
|
|
* STOP/SUSPEND
|
|
*
|
|
* Note that during system suspend, the suspend trigger is followed by a hw_free in
|
|
* sof_pcm_trigger(). So, the final state during suspend would be RESET.
|
|
* Also, since the SOF driver doesn't support full resume, streams would be restarted with the
|
|
* prepare ioctl before the START trigger.
|
|
*/
|
|
|
|
static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int state, int cmd)
|
|
{
|
|
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
|
|
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
|
|
struct ipc4_pipeline_set_state_data *trigger_list;
|
|
struct snd_sof_pipeline *spipe;
|
|
struct snd_sof_pcm *spcm;
|
|
int ret;
|
|
int i;
|
|
|
|
dev_dbg(sdev->dev, "trigger cmd: %d state: %d\n", cmd, state);
|
|
|
|
spcm = snd_sof_find_spcm_dai(component, rtd);
|
|
if (!spcm)
|
|
return -EINVAL;
|
|
|
|
pipeline_list = &spcm->stream[substream->stream].pipeline_list;
|
|
|
|
/* nothing to trigger if the list is empty */
|
|
if (!pipeline_list->pipelines || !pipeline_list->count)
|
|
return 0;
|
|
|
|
/* allocate memory for the pipeline data */
|
|
trigger_list = kzalloc(struct_size(trigger_list, pipeline_ids, pipeline_list->count),
|
|
GFP_KERNEL);
|
|
if (!trigger_list)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&ipc4_data->pipeline_state_mutex);
|
|
|
|
/*
|
|
* IPC4 requires pipelines to be triggered in order starting at the sink and
|
|
* walking all the way to the source. So traverse the pipeline_list in the order
|
|
* sink->source when starting PCM's and in the reverse order to pause/stop PCM's.
|
|
* Skip the pipelines that have their skip_during_fe_trigger flag set. If there is a fork
|
|
* in the pipeline, the order of triggering between the left/right paths will be
|
|
* indeterministic. But the sink->source trigger order sink->source would still be
|
|
* guaranteed for each fork independently.
|
|
*/
|
|
if (state == SOF_IPC4_PIPE_RUNNING || state == SOF_IPC4_PIPE_RESET)
|
|
for (i = pipeline_list->count - 1; i >= 0; i--) {
|
|
spipe = pipeline_list->pipelines[i];
|
|
sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list);
|
|
}
|
|
else
|
|
for (i = 0; i < pipeline_list->count; i++) {
|
|
spipe = pipeline_list->pipelines[i];
|
|
sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list);
|
|
}
|
|
|
|
/* return if all pipelines are in the requested state already */
|
|
if (!trigger_list->count) {
|
|
ret = 0;
|
|
goto free;
|
|
}
|
|
|
|
/* no need to pause before reset or before pause release */
|
|
if (state == SOF_IPC4_PIPE_RESET || cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
|
|
goto skip_pause_transition;
|
|
|
|
/*
|
|
* set paused state for pipelines if the final state is PAUSED or when the pipeline
|
|
* is set to RUNNING for the first time after the PCM is started.
|
|
*/
|
|
ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "failed to pause all pipelines\n");
|
|
goto free;
|
|
}
|
|
|
|
/* update PAUSED state for all pipelines just triggered */
|
|
for (i = 0; i < pipeline_list->count ; i++) {
|
|
spipe = pipeline_list->pipelines[i];
|
|
sof_ipc4_update_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, cmd, spipe,
|
|
trigger_list);
|
|
}
|
|
|
|
/* return if this is the final state */
|
|
if (state == SOF_IPC4_PIPE_PAUSED)
|
|
goto free;
|
|
skip_pause_transition:
|
|
/* else set the RUNNING/RESET state in the DSP */
|
|
ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "failed to set final state %d for all pipelines\n", state);
|
|
goto free;
|
|
}
|
|
|
|
/* update RUNNING/RESET state for all pipelines that were just triggered */
|
|
for (i = 0; i < pipeline_list->count; i++) {
|
|
spipe = pipeline_list->pipelines[i];
|
|
sof_ipc4_update_pipeline_state(sdev, state, cmd, spipe, trigger_list);
|
|
}
|
|
|
|
free:
|
|
mutex_unlock(&ipc4_data->pipeline_state_mutex);
|
|
kfree(trigger_list);
|
|
return ret;
|
|
}
|
|
|
|
static int sof_ipc4_pcm_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
int state;
|
|
|
|
/* determine the pipeline state */
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
state = SOF_IPC4_PIPE_PAUSED;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
state = SOF_IPC4_PIPE_RUNNING;
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
state = SOF_IPC4_PIPE_PAUSED;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* set the pipeline state */
|
|
return sof_ipc4_trigger_pipelines(component, substream, state, cmd);
|
|
}
|
|
|
|
static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
/* command is not relevant with RESET, so just pass 0 */
|
|
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)
|
|
{
|
|
struct snd_sof_dai_link *slink;
|
|
struct snd_sof_dai *dai;
|
|
bool dai_link_found = false;
|
|
int i;
|
|
|
|
list_for_each_entry(slink, &sdev->dai_link_list, list) {
|
|
if (!strcmp(slink->link->name, link_name)) {
|
|
dai_link_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dai_link_found)
|
|
return;
|
|
|
|
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);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
|
|
struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name);
|
|
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
|
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
|
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
|
struct sof_ipc4_copier *ipc4_copier;
|
|
|
|
if (!dai) {
|
|
dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
|
|
rtd->dai_link->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ipc4_copier = dai->private;
|
|
if (!ipc4_copier) {
|
|
dev_err(component->dev, "%s: No private data found for DAI %s\n",
|
|
__func__, rtd->dai_link->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* always set BE format to 32-bits for both playback and capture */
|
|
snd_mask_none(fmt);
|
|
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
|
|
|
|
rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency;
|
|
rate->max = rate->min;
|
|
|
|
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;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm)
|
|
{
|
|
struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
|
|
int stream;
|
|
|
|
for_each_pcm_streams(stream) {
|
|
pipeline_list = &spcm->stream[stream].pipeline_list;
|
|
kfree(pipeline_list->pipelines);
|
|
pipeline_list->pipelines = NULL;
|
|
kfree(spcm->stream[stream].private);
|
|
spcm->stream[stream].private = NULL;
|
|
}
|
|
}
|
|
|
|
static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm)
|
|
{
|
|
struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
|
|
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
|
|
struct sof_ipc4_timestamp_info *stream_info;
|
|
bool support_info = true;
|
|
u32 abi_version;
|
|
u32 abi_offset;
|
|
int stream;
|
|
|
|
abi_offset = offsetof(struct sof_ipc4_fw_registers, abi_ver);
|
|
sof_mailbox_read(sdev, sdev->fw_info_box.offset + abi_offset, &abi_version,
|
|
sizeof(abi_version));
|
|
|
|
if (abi_version < SOF_IPC4_FW_REGS_ABI_VER)
|
|
support_info = false;
|
|
|
|
for_each_pcm_streams(stream) {
|
|
pipeline_list = &spcm->stream[stream].pipeline_list;
|
|
|
|
/* allocate memory for max number of pipeline IDs */
|
|
pipeline_list->pipelines = kcalloc(ipc4_data->max_num_pipelines,
|
|
sizeof(struct snd_sof_widget *), GFP_KERNEL);
|
|
if (!pipeline_list->pipelines) {
|
|
sof_ipc4_pcm_free(sdev, spcm);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!support_info)
|
|
continue;
|
|
|
|
stream_info = kzalloc(sizeof(*stream_info), GFP_KERNEL);
|
|
if (!stream_info) {
|
|
sof_ipc4_pcm_free(sdev, spcm);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spcm->stream[stream].private = stream_info;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *spcm)
|
|
{
|
|
struct sof_ipc4_copier *host_copier = NULL;
|
|
struct sof_ipc4_copier *dai_copier = NULL;
|
|
struct sof_ipc4_llp_reading_slot llp_slot;
|
|
struct sof_ipc4_timestamp_info *info;
|
|
struct snd_soc_dapm_widget *widget;
|
|
struct snd_sof_dai *dai;
|
|
int i;
|
|
|
|
/* find host & dai to locate info in memory window */
|
|
for_each_dapm_widgets(spcm->list, i, widget) {
|
|
struct snd_sof_widget *swidget = widget->dobj.private;
|
|
|
|
if (!swidget)
|
|
continue;
|
|
|
|
if (WIDGET_IS_AIF(swidget->widget->id)) {
|
|
host_copier = swidget->private;
|
|
} else if (WIDGET_IS_DAI(swidget->widget->id)) {
|
|
dai = swidget->private;
|
|
dai_copier = dai->private;
|
|
}
|
|
}
|
|
|
|
/* both host and dai copier must be valid for time_info */
|
|
if (!host_copier || !dai_copier) {
|
|
dev_err(sdev->dev, "host or dai copier are not found\n");
|
|
return;
|
|
}
|
|
|
|
info = spcm->private;
|
|
info->host_copier = host_copier;
|
|
info->dai_copier = dai_copier;
|
|
info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_gpdma_reading_slots) +
|
|
sdev->fw_info_box.offset;
|
|
|
|
/* find llp slot used by current dai */
|
|
for (i = 0; i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS; i++) {
|
|
sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
|
|
if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
|
|
break;
|
|
|
|
info->llp_offset += sizeof(llp_slot);
|
|
}
|
|
|
|
if (i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS)
|
|
return;
|
|
|
|
/* if no llp gpdma slot is used, check aggregated sdw slot */
|
|
info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_sndw_reading_slots) +
|
|
sdev->fw_info_box.offset;
|
|
for (i = 0; i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS; i++) {
|
|
sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
|
|
if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
|
|
break;
|
|
|
|
info->llp_offset += sizeof(llp_slot);
|
|
}
|
|
|
|
if (i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS)
|
|
return;
|
|
|
|
/* check EVAD slot */
|
|
info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_evad_reading_slot) +
|
|
sdev->fw_info_box.offset;
|
|
sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
|
|
if (llp_slot.node_id != dai_copier->data.gtw_cfg.node_id) {
|
|
dev_info(sdev->dev, "no llp found, fall back to default HDA path");
|
|
info->llp_offset = 0;
|
|
}
|
|
}
|
|
|
|
static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_sof_platform_stream_params *platform_params)
|
|
{
|
|
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct sof_ipc4_timestamp_info *time_info;
|
|
struct snd_sof_pcm *spcm;
|
|
|
|
spcm = snd_sof_find_spcm_dai(component, rtd);
|
|
time_info = spcm->stream[substream->stream].private;
|
|
/* delay calculation is not supported by current fw_reg ABI */
|
|
if (!time_info)
|
|
return 0;
|
|
|
|
time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION;
|
|
time_info->llp_offset = 0;
|
|
|
|
sof_ipc4_build_time_info(sdev, &spcm->stream[substream->stream]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_sof_pcm_stream *stream,
|
|
struct sof_ipc4_timestamp_info *time_info)
|
|
{
|
|
struct sof_ipc4_copier *host_copier = time_info->host_copier;
|
|
struct sof_ipc4_copier *dai_copier = time_info->dai_copier;
|
|
struct sof_ipc4_pipeline_registers ppl_reg;
|
|
u64 stream_start_position;
|
|
u32 dai_sample_size;
|
|
u32 ch, node_index;
|
|
u32 offset;
|
|
|
|
if (!host_copier || !dai_copier)
|
|
return -EINVAL;
|
|
|
|
if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID)
|
|
return -EINVAL;
|
|
|
|
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);
|
|
sof_mailbox_read(sdev, sdev->fw_info_box.offset + offset, &ppl_reg, sizeof(ppl_reg));
|
|
if (ppl_reg.stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION)
|
|
return -EINVAL;
|
|
|
|
stream_start_position = ppl_reg.stream_start_offset;
|
|
ch = dai_copier->data.out_format.fmt_cfg;
|
|
ch = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(ch);
|
|
dai_sample_size = (dai_copier->data.out_format.bit_depth >> 3) * ch;
|
|
/* convert offset to sample count */
|
|
do_div(stream_start_position, dai_sample_size);
|
|
time_info->stream_start_offset = stream_start_position;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct sof_ipc4_timestamp_info *time_info;
|
|
struct sof_ipc4_llp_reading_slot llp;
|
|
snd_pcm_uframes_t head_ptr, tail_ptr;
|
|
struct snd_sof_pcm_stream *stream;
|
|
struct snd_sof_pcm *spcm;
|
|
u64 tmp_ptr;
|
|
int ret;
|
|
|
|
spcm = snd_sof_find_spcm_dai(component, rtd);
|
|
if (!spcm)
|
|
return 0;
|
|
|
|
stream = &spcm->stream[substream->stream];
|
|
time_info = stream->private;
|
|
if (!time_info)
|
|
return 0;
|
|
|
|
/*
|
|
* stream_start_offset is updated to memory window by FW based on
|
|
* pipeline statistics and it may be invalid if host query happens before
|
|
* the statistics is complete. And it will not change after the first initiailization.
|
|
*/
|
|
if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) {
|
|
ret = sof_ipc4_get_stream_start_offset(sdev, substream, stream, time_info);
|
|
if (ret < 0)
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* HDaudio links don't support the LLP counter reported by firmware
|
|
* the link position is read directly from hardware registers.
|
|
*/
|
|
if (!time_info->llp_offset) {
|
|
tmp_ptr = snd_sof_pcm_get_stream_position(sdev, component, substream);
|
|
if (!tmp_ptr)
|
|
return 0;
|
|
} else {
|
|
sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp));
|
|
tmp_ptr = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l;
|
|
}
|
|
|
|
/* In two cases dai dma position is not accurate
|
|
* (1) dai pipeline is started before host pipeline
|
|
* (2) multiple streams mixed into one. Each stream has the same dai dma position
|
|
*
|
|
* Firmware calculates correct stream_start_offset for all cases including above two.
|
|
* Driver subtracts stream_start_offset from dai dma position to get accurate one
|
|
*/
|
|
tmp_ptr -= time_info->stream_start_offset;
|
|
|
|
/* Calculate the delay taking into account that both pointer can wrap */
|
|
div64_u64_rem(tmp_ptr, substream->runtime->boundary, &tmp_ptr);
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
head_ptr = substream->runtime->status->hw_ptr;
|
|
tail_ptr = tmp_ptr;
|
|
} else {
|
|
head_ptr = tmp_ptr;
|
|
tail_ptr = substream->runtime->status->hw_ptr;
|
|
}
|
|
|
|
if (head_ptr < tail_ptr)
|
|
return substream->runtime->boundary - tail_ptr + head_ptr;
|
|
|
|
return head_ptr - tail_ptr;
|
|
}
|
|
|
|
const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
|
|
.hw_params = sof_ipc4_pcm_hw_params,
|
|
.trigger = sof_ipc4_pcm_trigger,
|
|
.hw_free = sof_ipc4_pcm_hw_free,
|
|
.dai_link_fixup = sof_ipc4_pcm_dai_link_fixup,
|
|
.pcm_setup = sof_ipc4_pcm_setup,
|
|
.pcm_free = sof_ipc4_pcm_free,
|
|
.delay = sof_ipc4_pcm_delay
|
|
};
|