diff options
-rw-r--r-- | sound/soc/sof/ipc4-pcm.c | 228 | ||||
-rw-r--r-- | sound/soc/sof/ipc4-priv.h | 2 | ||||
-rw-r--r-- | sound/soc/sof/ipc4.c | 2 |
3 files changed, 182 insertions, 50 deletions
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 284e402709be..ababa29d6eac 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -59,19 +59,152 @@ int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) } 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) + 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, j; + int i; + + dev_dbg(sdev->dev, "trigger cmd: %d state: %d\n", cmd, state); spcm = snd_sof_find_spcm_dai(component, rtd); if (!spcm) @@ -89,33 +222,41 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, if (!trigger_list) return -ENOMEM; + mutex_lock(&ipc4_data->trigger_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 reverse order. - * Skip the pipelines that have their skip_during_fe_trigger flag set or if they're already - * in the requested state. 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. + * 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. */ - for (i = pipeline_list->count - 1; i >= 0; i--) { - spipe = pipeline_list->pipelines[i]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - if (pipeline->state != state && !pipeline->skip_during_fe_trigger) - trigger_list->pipeline_ids[trigger_list->count++] = - pipe_widget->instance_id; - } + 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) { - kfree(trigger_list); - return 0; + 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; + /* - * Pause all pipelines. This could result in an extra IPC to pause all pipelines even if - * they are already paused. But it helps keep the logic simpler and the firmware handles - * the repeated pause gracefully. This can be optimized in the future if needed. + * 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) { @@ -123,46 +264,32 @@ static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, goto free; } - /* update PAUSED state for all pipelines that were just triggered */ - for (i = 0; i < trigger_list->count; i++) { - for (j = 0; j < pipeline_list->count; j++) { - spipe = pipeline_list->pipelines[j]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - - if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { - pipeline->state = SOF_IPC4_PIPE_PAUSED; - break; - } - } + /* 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; - - /* else set the final state in the DSP */ +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 final state for all pipelines that were just triggered */ - for (i = 0; i < trigger_list->count; i++) { - for (j = 0; j < pipeline_list->count; j++) { - spipe = pipeline_list->pipelines[j]; - pipe_widget = spipe->pipe_widget; - pipeline = pipe_widget->private; - - if (trigger_list->pipeline_ids[i] == pipe_widget->instance_id) { - pipeline->state = state; - break; - } - } + /* 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->trigger_mutex); kfree(trigger_list); return ret; } @@ -192,13 +319,14 @@ static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, } /* set the pipeline state */ - return sof_ipc4_trigger_pipelines(component, substream, 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) { - return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET); + /* 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, diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index fc9efdce67e0..0c0d48376045 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -70,6 +70,7 @@ struct sof_ipc4_fw_library { * base firmware * * @load_library: Callback function for platform dependent library loading + * @trigger_mutex: Mutex to protect pipeline triggers, ref counts and states */ struct sof_ipc4_fw_data { u32 manifest_fw_hdr_offset; @@ -82,6 +83,7 @@ struct sof_ipc4_fw_data { int (*load_library)(struct snd_sof_dev *sdev, struct sof_ipc4_fw_library *fw_lib, bool reload); + struct mutex trigger_mutex; /* protect pipeline triggers, ref counts and states */ }; extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index 74cd7e956019..fb4760ae593f 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -662,6 +662,8 @@ static int sof_ipc4_init(struct snd_sof_dev *sdev) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; + mutex_init(&ipc4_data->trigger_mutex); + xa_init_flags(&ipc4_data->fw_lib_xa, XA_FLAGS_ALLOC); return 0; |