2022-06-08 20:26:31 -07:00
// 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"
2023-02-02 15:29:54 +02:00
# include "ops.h"
2022-06-08 20:26:31 -07:00
# include "ipc4-priv.h"
# include "ipc4-topology.h"
2023-02-02 15:29:49 +02:00
# include "ipc4-fw-reg.h"
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:24 +02:00
static int sof_ipc4_set_multi_pipeline_state ( struct snd_sof_dev * sdev , u32 state ,
2023-01-27 14:00:26 +02:00
struct ipc4_pipeline_set_state_data * trigger_list )
2023-01-27 14:00:24 +02:00
{
struct sof_ipc4_msg msg = { { 0 } } ;
u32 primary , ipc_size ;
/* trigger a single pipeline */
2023-01-27 14:00:26 +02:00
if ( trigger_list - > count = = 1 )
return sof_ipc4_set_pipeline_state ( sdev , trigger_list - > pipeline_ids [ 0 ] , state ) ;
2023-01-27 14:00:24 +02:00
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 */
2023-01-27 14:00:26 +02:00
ipc_size = sizeof ( u32 ) * ( trigger_list - > count + 1 ) ;
2023-01-27 14:00:24 +02:00
msg . data_size = ipc_size ;
2023-01-27 14:00:26 +02:00
msg . data_ptr = trigger_list ;
2023-01-27 14:00:24 +02:00
2023-04-19 14:40:57 -05:00
return sof_ipc_tx_message_no_reply ( sdev - > ipc , & msg , ipc_size ) ;
2023-01-27 14:00:24 +02:00
}
2022-06-08 20:26:35 -07:00
int sof_ipc4_set_pipeline_state ( struct snd_sof_dev * sdev , u32 id , u32 state )
2022-06-08 20:26:31 -07:00
{
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 ;
2023-04-19 14:40:57 -05:00
return sof_ipc_tx_message_no_reply ( sdev - > ipc , & msg , 0 ) ;
2022-06-08 20:26:31 -07:00
}
2022-06-08 20:26:35 -07:00
EXPORT_SYMBOL ( sof_ipc4_set_pipeline_state ) ;
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:27 +02:00
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 .
*/
2023-03-21 11:26:54 +02:00
/*
* 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 ;
2023-04-19 14:40:57 -05:00
return sof_ipc_tx_message_no_reply ( sdev - > ipc , & msg , 0 ) ;
2023-03-21 11:26:54 +02:00
}
2022-06-08 20:26:31 -07:00
static int sof_ipc4_trigger_pipelines ( struct snd_soc_component * component ,
2023-01-27 14:00:27 +02:00
struct snd_pcm_substream * substream , int state , int cmd )
2022-06-08 20:26:31 -07:00
{
struct snd_sof_dev * sdev = snd_soc_component_get_drvdata ( component ) ;
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2023-01-27 14:00:24 +02:00
struct snd_sof_pcm_stream_pipeline_list * pipeline_list ;
2023-01-27 14:00:27 +02:00
struct sof_ipc4_fw_data * ipc4_data = sdev - > private ;
2023-01-27 14:00:26 +02:00
struct ipc4_pipeline_set_state_data * trigger_list ;
2023-03-21 11:26:54 +02:00
struct snd_sof_widget * pipe_widget ;
struct sof_ipc4_pipeline * pipeline ;
2023-01-27 14:00:25 +02:00
struct snd_sof_pipeline * spipe ;
2022-06-08 20:26:31 -07:00
struct snd_sof_pcm * spcm ;
2023-01-27 14:00:24 +02:00
int ret ;
2023-01-27 14:00:27 +02:00
int i ;
dev_dbg ( sdev - > dev , " trigger cmd: %d state: %d \n " , cmd , state ) ;
2022-06-08 20:26:31 -07:00
spcm = snd_sof_find_spcm_dai ( component , rtd ) ;
if ( ! spcm )
return - EINVAL ;
2023-01-27 14:00:24 +02:00
pipeline_list = & spcm - > stream [ substream - > stream ] . pipeline_list ;
/* nothing to trigger if the list is empty */
2023-01-27 14:00:31 +02:00
if ( ! pipeline_list - > pipelines | | ! pipeline_list - > count )
2023-01-27 14:00:24 +02:00
return 0 ;
2023-03-21 11:26:54 +02:00
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 ) ;
2023-01-27 14:00:24 +02:00
/* allocate memory for the pipeline data */
2023-01-27 14:00:26 +02:00
trigger_list = kzalloc ( struct_size ( trigger_list , pipeline_ids , pipeline_list - > count ) ,
GFP_KERNEL ) ;
if ( ! trigger_list )
2023-01-27 14:00:24 +02:00
return - ENOMEM ;
2023-01-27 14:00:28 +02:00
mutex_lock ( & ipc4_data - > pipeline_state_mutex ) ;
2023-01-27 14:00:27 +02:00
2023-01-27 14:00:24 +02:00
/*
* IPC4 requires pipelines to be triggered in order starting at the sink and
2023-01-27 14:00:27 +02:00
* 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 .
2023-01-27 14:00:24 +02:00
*/
2023-01-27 14:00:27 +02:00
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 ) ;
}
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:24 +02:00
/* return if all pipelines are in the requested state already */
2023-01-27 14:00:26 +02:00
if ( ! trigger_list - > count ) {
2023-01-27 14:00:27 +02:00
ret = 0 ;
goto free ;
2023-01-27 14:00:24 +02:00
}
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:27 +02:00
/* 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 ;
2023-01-27 14:00:24 +02:00
/*
2023-01-27 14:00:27 +02:00
* 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 .
2023-01-27 14:00:24 +02:00
*/
2023-01-27 14:00:26 +02:00
ret = sof_ipc4_set_multi_pipeline_state ( sdev , SOF_IPC4_PIPE_PAUSED , trigger_list ) ;
2023-01-27 14:00:24 +02:00
if ( ret < 0 ) {
dev_err ( sdev - > dev , " failed to pause all pipelines \n " ) ;
goto free ;
}
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:27 +02:00
/* 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 ) ;
2023-01-27 14:00:24 +02:00
}
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:24 +02:00
/* return if this is the final state */
if ( state = = SOF_IPC4_PIPE_PAUSED )
goto free ;
2023-01-27 14:00:27 +02:00
skip_pause_transition :
/* else set the RUNNING/RESET state in the DSP */
2023-01-27 14:00:26 +02:00
ret = sof_ipc4_set_multi_pipeline_state ( sdev , state , trigger_list ) ;
2023-01-27 14:00:24 +02:00
if ( ret < 0 ) {
dev_err ( sdev - > dev , " failed to set final state %d for all pipelines \n " , state ) ;
goto free ;
}
2022-06-08 20:26:31 -07:00
2023-01-27 14:00:27 +02:00
/* 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 ) ;
2022-06-08 20:26:31 -07:00
}
2023-01-27 14:00:24 +02:00
free :
2023-01-27 14:00:28 +02:00
mutex_unlock ( & ipc4_data - > pipeline_state_mutex ) ;
2023-01-27 14:00:26 +02:00
kfree ( trigger_list ) ;
2022-06-08 20:26:31 -07:00
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 */
2023-01-27 14:00:27 +02:00
return sof_ipc4_trigger_pipelines ( component , substream , state , cmd ) ;
2022-06-08 20:26:31 -07:00
}
static int sof_ipc4_pcm_hw_free ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
{
2023-01-27 14:00:27 +02:00
/* command is not relevant with RESET, so just pass 0 */
return sof_ipc4_trigger_pipelines ( component , substream , SOF_IPC4_PIPE_RESET , 0 ) ;
2022-06-08 20:26:31 -07:00
}
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 ;
}
}
}
2023-03-16 16:14:57 +02:00
/*
* 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 ;
}
2022-06-08 20:26:31 -07:00
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 ) ;
2023-03-21 11:26:54 +02:00
struct snd_soc_dai * cpu_dai = asoc_rtd_to_cpu ( rtd , 0 ) ;
2022-06-08 20:26:31 -07:00
struct sof_ipc4_copier * ipc4_copier ;
2023-03-21 11:26:54 +02:00
bool use_chain_dma = false ;
int dir ;
2022-06-08 20:26:31 -07:00
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 ;
}
2023-03-21 11:26:54 +02:00
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 ;
}
2022-07-15 09:52:08 -05:00
2022-06-08 20:26:31 -07:00
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 ;
}
2023-01-27 14:00:21 +02:00
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 ;
2023-01-27 14:00:25 +02:00
kfree ( pipeline_list - > pipelines ) ;
pipeline_list - > pipelines = NULL ;
2023-02-02 15:29:49 +02:00
kfree ( spcm - > stream [ stream ] . private ) ;
spcm - > stream [ stream ] . private = NULL ;
2023-01-27 14:00:21 +02:00
}
}
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 ;
2023-02-02 15:29:49 +02:00
struct sof_ipc4_timestamp_info * stream_info ;
bool support_info = true ;
u32 abi_version ;
u32 abi_offset ;
2023-01-27 14:00:21 +02:00
int stream ;
2023-02-02 15:29:49 +02:00
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 ;
2023-01-27 14:00:21 +02:00
for_each_pcm_streams ( stream ) {
pipeline_list = & spcm - > stream [ stream ] . pipeline_list ;
/* allocate memory for max number of pipeline IDs */
2023-01-27 14:00:25 +02:00
pipeline_list - > pipelines = kcalloc ( ipc4_data - > max_num_pipelines ,
sizeof ( struct snd_sof_widget * ) , GFP_KERNEL ) ;
if ( ! pipeline_list - > pipelines ) {
2023-01-27 14:00:21 +02:00
sof_ipc4_pcm_free ( sdev , spcm ) ;
return - ENOMEM ;
}
2023-02-02 15:29:49 +02:00
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 ;
2023-01-27 14:00:21 +02:00
}
return 0 ;
}
2023-02-02 15:29:50 +02:00
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 ;
}
2023-02-02 15:29:54 +02:00
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 ;
}
2022-06-08 20:26:31 -07:00
const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
2023-02-02 15:29:50 +02:00
. hw_params = sof_ipc4_pcm_hw_params ,
2022-06-08 20:26:31 -07:00
. trigger = sof_ipc4_pcm_trigger ,
. hw_free = sof_ipc4_pcm_hw_free ,
. dai_link_fixup = sof_ipc4_pcm_dai_link_fixup ,
2023-01-27 14:00:21 +02:00
. pcm_setup = sof_ipc4_pcm_setup ,
. pcm_free = sof_ipc4_pcm_free ,
2023-03-22 11:43:46 +02:00
. delay = sof_ipc4_pcm_delay ,
2023-04-20 14:41:37 +03:00
. ipc_first_on_start = true ,
. platform_stop_during_hw_free = true ,
2022-06-08 20:26:31 -07:00
} ;