linux-stable/sound/soc/sof/ipc4-pcm.c
Ranjani Sridharan 225f37b578
ASoC: SOF: ipc4-pcm: reset all pipelines during FE DAI hw_free
Do not reset pipelines during the stop/suspend triggers in the BE DAI
ops as the BE DAI pipeline needs to be left in the PAUSED state. It should
only be reset during hw_free. This simplification is already done for
the FE pipelines and the DAI trigger only toggles the states between
PAUSED and RUNNING.

But because the FE DAI hw_free is invoked first and all the pipelines are
freed during this op, we need to make sure that the BE DAI pipeline also
gets reset before it is freed. So do not skip the pipelines that have the
skip_during_fe_trigger flag set when resetting pipelines.

Also, because the pipeline state changes are split between the FE and BE
DAI ops now, protect the BE DAI pipeline state changes with the
pipeline_state_mutex as well.

Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@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/20230515112022.30297-3-peter.ujfalusi@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org
2023-05-15 20:25:37 +09:00

841 lines
25 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_no_reply(sdev->ipc, &msg, ipc_size);
}
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_no_reply(sdev->ipc, &msg, 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 && state != SOF_IPC4_PIPE_RESET)
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 && state != SOF_IPC4_PIPE_RESET)
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.
*/
/*
* Chained DMA is a special case where there is no processing on
* DSP. The samples are just moved over by host side DMA to a single
* buffer on DSP and directly from there to link DMA. However, the
* model on SOF driver has two notional pipelines, one at host DAI,
* and another at link DAI. They both shall have the use_chain_dma
* attribute.
*/
static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
struct snd_sof_pcm_stream_pipeline_list *pipeline_list,
int state, int cmd)
{
bool allocate, enable, set_fifo_size;
struct sof_ipc4_msg msg = {{ 0 }};
int i;
switch (state) {
case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */
allocate = true;
enable = true;
/*
* SOF assumes creation of a new stream from the presence of fifo_size
* in the message, so we must leave it out in pause release case.
*/
if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
set_fifo_size = false;
else
set_fifo_size = true;
break;
case SOF_IPC4_PIPE_PAUSED: /* Disable chained DMA. */
allocate = true;
enable = false;
set_fifo_size = false;
break;
case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */
allocate = false;
enable = false;
set_fifo_size = false;
break;
default:
dev_err(sdev->dev, "Unexpected state %d", state);
return -EINVAL;
}
msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
/*
* To set-up the DMA chain, the host DMA ID and SCS setting
* are retrieved from the host pipeline configuration. Likewise
* the link DMA ID and fifo_size are retrieved from the link
* pipeline configuration.
*/
for (i = 0; i < pipeline_list->count; i++) {
struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i];
struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
if (!pipeline->use_chain_dma) {
dev_err(sdev->dev,
"All pipelines in chained DMA stream should have use_chain_dma attribute set.");
return -EINVAL;
}
msg.primary |= pipeline->msg.primary;
/* Add fifo_size (actually DMA buffer size) field to the message */
if (set_fifo_size)
msg.extension |= pipeline->msg.extension;
}
if (allocate)
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
if (enable)
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
}
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_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
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;
spipe = pipeline_list->pipelines[0];
pipe_widget = spipe->pipe_widget;
pipeline = pipe_widget->private;
/*
* 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, pipeline_list, state, cmd);
/* 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;
}
}
}
/*
* Fixup DAI link parameters for sampling rate based on
* DAI copier configuration.
*/
static int sof_ipc4_pcm_dai_link_fixup_rate(struct snd_sof_dev *sdev,
struct snd_pcm_hw_params *params,
struct sof_ipc4_copier *ipc4_copier)
{
struct sof_ipc4_pin_format *pin_fmts = ipc4_copier->available_fmt.input_pin_fmts;
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
int num_input_formats = ipc4_copier->available_fmt.num_input_formats;
unsigned int fe_rate = params_rate(params);
bool fe_be_rate_match = false;
bool single_be_rate = true;
unsigned int be_rate;
int i;
/*
* Copier does not change sampling rate, so we
* need to only consider the input pin information.
*/
for (i = 0; i < num_input_formats; i++) {
unsigned int val = pin_fmts[i].audio_fmt.sampling_frequency;
if (i == 0)
be_rate = val;
else if (val != be_rate)
single_be_rate = false;
if (val == fe_rate) {
fe_be_rate_match = true;
break;
}
}
/*
* If rate is different than FE rate, topology must
* contain an SRC. But we do require topology to
* define a single rate in the DAI copier config in
* this case (FE rate may be variable).
*/
if (!fe_be_rate_match) {
if (!single_be_rate) {
dev_err(sdev->dev, "Unable to select sampling rate for DAI link\n");
return -EINVAL;
}
rate->min = be_rate;
rate->max = rate->min;
}
return 0;
}
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_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
struct sof_ipc4_copier *ipc4_copier;
bool use_chain_dma = false;
int dir;
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;
}
for_each_pcm_streams(dir) {
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, dir);
if (w) {
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
if (pipeline->use_chain_dma)
use_chain_dma = true;
}
}
/* Chain DMA does not use copiers, so no fixup needed */
if (!use_chain_dma) {
int ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier);
if (ret)
return ret;
}
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,
.ipc_first_on_start = true,
.platform_stop_during_hw_free = true,
};