diff --git a/src/audio/component.c b/src/audio/component.c index de20e3733b2e..0919ad578174 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -56,7 +56,6 @@ void comp_unregister(struct comp_driver_info *drv) int comp_set_state(struct comp_dev *dev, int cmd) { int requested_state = comp_get_requested_state(cmd); - int ret = 0; if (dev->state == requested_state) { comp_info(dev, "comp_set_state(), state already set to %u", @@ -66,41 +65,33 @@ int comp_set_state(struct comp_dev *dev, int cmd) switch (cmd) { case COMP_TRIGGER_START: - if (dev->state == COMP_STATE_PREPARE) { - dev->state = COMP_STATE_ACTIVE; - } else { + if (dev->state != COMP_STATE_PRE_ACTIVE) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_START", dev->state); - ret = -EINVAL; + return -EINVAL; } break; case COMP_TRIGGER_RELEASE: - if (dev->state == COMP_STATE_PAUSED) { - dev->state = COMP_STATE_ACTIVE; - } else { + if (dev->state != COMP_STATE_PRE_ACTIVE) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_RELEASE", dev->state); - ret = -EINVAL; + return -EINVAL; } break; case COMP_TRIGGER_STOP: - if (dev->state == COMP_STATE_ACTIVE || - dev->state == COMP_STATE_PAUSED) { - dev->state = COMP_STATE_PREPARE; - } else { + if (dev->state != COMP_STATE_ACTIVE && + dev->state != COMP_STATE_PAUSED) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_STOP", dev->state); - ret = -EINVAL; + return -EINVAL; } break; case COMP_TRIGGER_PAUSE: /* only support pausing for running */ - if (dev->state == COMP_STATE_ACTIVE) { - dev->state = COMP_STATE_PAUSED; - } else { + if (dev->state != COMP_STATE_ACTIVE) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_PAUSE", dev->state); - ret = -EINVAL; + return -EINVAL; } break; case COMP_TRIGGER_RESET: @@ -109,26 +100,40 @@ int comp_set_state(struct comp_dev *dev, int cmd) dev->state == COMP_STATE_PAUSED) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_RESET", dev->state); - ret = 0; } - dev->state = COMP_STATE_READY; break; case COMP_TRIGGER_PREPARE: - if (dev->state == COMP_STATE_READY) { - dev->state = COMP_STATE_PREPARE; - } else { + if (dev->state != COMP_STATE_READY) { comp_err(dev, "comp_set_state(): wrong state = %u, COMP_TRIGGER_PREPARE", dev->state); - ret = -EINVAL; + return -EINVAL; } break; - default: + case COMP_TRIGGER_PRE_START: + if (dev->state != COMP_STATE_PREPARE) { + comp_err(dev, + "comp_set_state(): wrong state = %u, COMP_TRIGGER_PRE_START", + dev->state); + return -EINVAL; + } + break; + case COMP_TRIGGER_PRE_RELEASE: + if (dev->state != COMP_STATE_PAUSED) { + comp_err(dev, + "comp_set_state(): wrong state = %u, COMP_TRIGGER_PRE_RELEASE", + dev->state); + return -EINVAL; + } break; + default: + return 0; } + dev->state = requested_state; + comp_writeback(dev); - return ret; + return 0; } void sys_comp_init(struct sof *sof) diff --git a/src/audio/dai.c b/src/audio/dai.c index de086eafe723..bc437939b36e 100644 --- a/src/audio/dai.c +++ b/src/audio/dai.c @@ -594,12 +594,12 @@ static int dai_config_prepare(struct comp_dev *dev) /* allocate DMA channel */ dd->chan = dma_channel_get(dd->dma, channel); if (!dd->chan) { - comp_err(dev, "dai_config(): dma_channel_get() failed"); + comp_err(dev, "dai_config_prepare(): dma_channel_get() failed"); dd->chan = NULL; return -EIO; } - comp_info(dev, "dai_config(): new configured dma channel index %d", + comp_info(dev, "dai_config_prepare(): new configured dma channel index %d", dd->chan->index); /* setup callback */ @@ -804,7 +804,13 @@ static int dai_comp_trigger_internal(struct comp_dev *dev, int cmd) ret = dma_pause(dd->chan); dai_trigger(dd->dai, cmd, dev->direction); break; - default: + case COMP_TRIGGER_PRE_START: + case COMP_TRIGGER_PRE_RELEASE: + /* only start the DAI if we are not XRUN handling */ + if (dd->xrun) + dd->xrun = 0; + else + dai_trigger(dd->dai, cmd, dev->direction); break; } diff --git a/src/audio/mixer.c b/src/audio/mixer.c index 8abbddf81d4f..756076d27b2f 100644 --- a/src/audio/mixer.c +++ b/src/audio/mixer.c @@ -224,6 +224,30 @@ static int mixer_trigger(struct comp_dev *dev, int cmd) comp_dbg(dev, "mixer_trigger()"); + /* + * This works around an unclear and apparently needlessly complicated + * mixer state machine. + */ + if (dir == SOF_IPC_STREAM_PLAYBACK) { + switch (cmd) { + case COMP_TRIGGER_PRE_RELEASE: + /* Mixer and everything downstream is active */ + dev->state = COMP_STATE_PRE_ACTIVE; + break; + case COMP_TRIGGER_RELEASE: + /* Mixer and everything downstream is active */ + dev->state = COMP_STATE_ACTIVE; + break; + case COMP_TRIGGER_PRE_START: + /* Mixer and downstream components might or might not be active */ + if (mixer_source_status_count(dev, COMP_STATE_ACTIVE) || + mixer_source_status_count(dev, COMP_STATE_PAUSED)) + return PPL_STATUS_PATH_STOP; + } + + comp_writeback(dev); + } + ret = comp_set_state(dev, cmd); if (ret < 0) return ret; @@ -241,6 +265,7 @@ static int mixer_trigger(struct comp_dev *dev, int cmd) (mixer_source_status_count(dev, COMP_STATE_ACTIVE) || mixer_source_status_count(dev, COMP_STATE_PAUSED)))) { dev->state = COMP_STATE_ACTIVE; + comp_writeback(dev); ret = PPL_STATUS_PATH_STOP; } diff --git a/src/audio/mux/mux.c b/src/audio/mux/mux.c index a8d538c689c4..ba830ddf2461 100644 --- a/src/audio/mux/mux.c +++ b/src/audio/mux/mux.c @@ -702,6 +702,13 @@ static int mux_trigger(struct comp_dev *dev, int cmd) comp_info(dev, "mux_trigger(), command = %u", cmd); + switch (cmd) { + case COMP_TRIGGER_PRE_START: + if (mux_source_status_count(dev, COMP_STATE_ACTIVE) || + mux_source_status_count(dev, COMP_STATE_PAUSED)) + return PPL_STATUS_PATH_STOP; + } + ret = comp_set_state(dev, cmd); if (ret < 0) return ret; diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index f4e907c271cc..d013fd2a59fd 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -127,6 +127,7 @@ struct pipeline *pipeline_new(uint32_t pipeline_id, uint32_t priority, uint32_t p->priority = priority; p->pipeline_id = pipeline_id; p->status = COMP_STATE_INIT; + p->trigger.cmd = -EINVAL; ret = memcpy_s(&p->tctx, sizeof(struct tr_ctx), &pipe_tr, sizeof(struct tr_ctx)); assert(!ret); diff --git a/src/audio/pipeline/pipeline-schedule.c b/src/audio/pipeline/pipeline-schedule.c index ceb146ae0b66..d374effc736e 100644 --- a/src/audio/pipeline/pipeline-schedule.c +++ b/src/audio/pipeline/pipeline-schedule.c @@ -55,12 +55,55 @@ static enum task_state pipeline_task(void *arg) return SOF_TASK_STATE_COMPLETED; } + if (p->trigger.delay) { + p->trigger.delay--; + return SOF_TASK_STATE_RESCHEDULE; + } + + if (p->trigger.cmd >= 0) { + /* First pipeline task run for either START or RELEASE: PRE stage */ + struct sof_ipc_reply reply = { + .hdr.cmd = SOF_IPC_GLB_REPLY, + .hdr.size = sizeof(reply), + }; + int cmd = p->trigger.cmd; + + err = pipeline_trigger_run(p, p->trigger.host, cmd); + if (err < 0) { + pipe_err(p, "pipeline_task(): failed to trigger components: %d", err); + reply.error = err; + err = SOF_TASK_STATE_COMPLETED; + } else if (err == PPL_STATUS_PATH_STOP) { + pipe_warn(p, "pipeline_task(): stopping for xrun"); + err = SOF_TASK_STATE_COMPLETED; + } else if (p->trigger.cmd != cmd) { + /* PRE stage completed */ + if (p->trigger.delay) + return SOF_TASK_STATE_RESCHEDULE; + /* No delay: the final stage has already run too */ + } else { + p->status = COMP_STATE_ACTIVE; + err = SOF_TASK_STATE_RESCHEDULE; + } + + p->trigger.cmd = -EINVAL; + + ipc_msg_reply(&reply); + + return err; + } + + /* + * The first execution of the pipeline task above has triggered all + * pipeline components. Subsequent iterations actually perform data + * copying below. + */ err = pipeline_copy(p); if (err < 0) { /* try to recover */ err = pipeline_xrun_recover(p); if (err < 0) { - pipe_err(p, "pipeline_task(): xrun recover failed! pipeline will be stopped!"); + pipe_err(p, "pipeline_task(): xrun recovery failed! pipeline is stopped."); /* failed - host will stop this pipeline */ return SOF_TASK_STATE_COMPLETED; } @@ -107,9 +150,11 @@ int pipeline_schedule_config(struct pipeline *p, uint32_t sched_id, return 0; } +/* trigger connected pipelines: either immediately or schedule them */ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, int cmd) { + struct pipeline_data *ppl_data = ctx->comp_data; struct list_item *tlist; struct pipeline *p; uint32_t flags; @@ -121,25 +166,31 @@ void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, */ irq_local_disable(flags); - list_for_item(tlist, &ctx->pipelines) { - p = container_of(tlist, struct pipeline, list); - - switch (cmd) { - case COMP_TRIGGER_PAUSE: - case COMP_TRIGGER_STOP: + switch (cmd) { + case COMP_TRIGGER_PAUSE: + case COMP_TRIGGER_STOP: + list_for_item(tlist, &ctx->pipelines) { + p = container_of(tlist, struct pipeline, list); pipeline_schedule_cancel(p); p->status = COMP_STATE_PAUSED; - break; - case COMP_TRIGGER_RELEASE: - case COMP_TRIGGER_START: + } + break; + case COMP_TRIGGER_PRE_RELEASE: + case COMP_TRIGGER_PRE_START: + list_for_item(tlist, &ctx->pipelines) { + p = container_of(tlist, struct pipeline, list); + if (pipeline_is_timer_driven(p)) { + /* Use the first of connected pipelines to trigger */ + if (cmd >= 0) { + p->trigger.cmd = cmd; + p->trigger.host = ppl_data->start; + cmd = -EINVAL; + } + } else { + p->xrun_bytes = 0; + p->status = COMP_STATE_ACTIVE; + } pipeline_schedule_copy(p, 0); - p->xrun_bytes = 0; - p->status = COMP_STATE_ACTIVE; - break; - case COMP_TRIGGER_SUSPEND: - case COMP_TRIGGER_RESUME: - default: - break; } } diff --git a/src/audio/pipeline/pipeline-stream.c b/src/audio/pipeline/pipeline-stream.c index 0277bf751ea3..18081bd7a26c 100644 --- a/src/audio/pipeline/pipeline-stream.c +++ b/src/audio/pipeline/pipeline-stream.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -59,21 +61,49 @@ pipeline_should_report_enodata_on_trigger(struct comp_dev *rsrc, return false; } +/* Runs in IPC or in pipeline task context */ static int pipeline_comp_trigger(struct comp_dev *current, struct comp_buffer *calling_buf, struct pipeline_walk_context *ctx, int dir) { struct pipeline_data *ppl_data = ctx->comp_data; bool is_single_ppl = comp_is_single_pipeline(current, ppl_data->start); - bool is_same_sched = - pipeline_is_same_sched_comp(current->pipeline, - ppl_data->start->pipeline); + bool is_same_sched, async; int err; pipe_dbg(current->pipeline, "pipeline_comp_trigger(), current->comp.id = %u, dir = %u", dev_comp_id(current), dir); + switch (ppl_data->cmd) { + case COMP_TRIGGER_PAUSE: + case COMP_TRIGGER_STOP: + /* + * PAUSE and STOP are triggered in IPC context, not from the + * pipeline task + */ + async = true; + break; + case COMP_TRIGGER_PRE_RELEASE: + case COMP_TRIGGER_PRE_START: + if (comp_get_endpoint_type(current) == COMP_ENDPOINT_DAI) { + struct dai_data *dd = comp_get_drvdata(current); + + ppl_data->delay_ms = dai_get_init_delay_ms(dd->dai); + } + + COMPILER_FALLTHROUGH; + case COMP_TRIGGER_RELEASE: + case COMP_TRIGGER_START: + async = !pipeline_is_timer_driven(current->pipeline); + break; + default: + return -EINVAL; + } + + is_same_sched = pipeline_is_same_sched_comp(current->pipeline, + ppl_data->start->pipeline); + /* trigger should propagate to the connected pipelines, * which need to be scheduled together */ @@ -92,7 +122,12 @@ static int pipeline_comp_trigger(struct comp_dev *current, if (err < 0 || err == PPL_STATUS_PATH_STOP) return err; - pipeline_comp_trigger_sched_comp(current->pipeline, current, ctx); + /* + * Add scheduling components to the list. This is only needed for the + * stopping flow. + */ + if (async) + pipeline_comp_trigger_sched_comp(current->pipeline, current, ctx); return pipeline_for_each_comp(current, ctx, dir); } @@ -172,10 +207,87 @@ int pipeline_copy(struct pipeline *p) return ret; } -/* trigger pipeline */ +/* only collect scheduling components */ +static int pipeline_comp_list(struct comp_dev *current, + struct comp_buffer *calling_buf, + struct pipeline_walk_context *ctx, int dir) +{ + struct pipeline_data *ppl_data = ctx->comp_data; + bool is_single_ppl = comp_is_single_pipeline(current, ppl_data->start); + bool is_same_sched = pipeline_is_same_sched_comp(current->pipeline, + ppl_data->start->pipeline); + + if (!is_single_ppl && !is_same_sched) { + pipe_dbg(current->pipeline, + "pipeline_comp_list(), current is from another pipeline"); + return 0; + } + + /* Add scheduling components to the list */ + pipeline_comp_trigger_sched_comp(current->pipeline, current, ctx); + + return pipeline_for_each_comp(current, ctx, dir); +} + +/* build a list of connected pipelines' scheduling components and trigger them */ +static int pipeline_trigger_list(struct pipeline *p, struct comp_dev *host, int cmd) +{ + struct pipeline_data data = { + .start = host, + .cmd = cmd, + }; + struct pipeline_walk_context walk_ctx = { + .comp_func = pipeline_comp_list, + .comp_data = &data, + .skip_incomplete = true, + }; + int ret; + + list_init(&walk_ctx.pipelines); + + ret = walk_ctx.comp_func(host, NULL, &walk_ctx, host->direction); + if (ret < 0) + pipe_err(p, "pipeline_trigger_list(): ret = %d, host->comp.id = %u, cmd = %d", + ret, dev_comp_id(host), cmd); + else + pipeline_schedule_triggered(&walk_ctx, cmd); + + return ret; +} + +/* trigger pipeline in IPC context */ int pipeline_trigger(struct pipeline *p, struct comp_dev *host, int cmd) { - struct pipeline_data data; + int ret; + + pipe_info(p, "pipe trigger cmd %d", cmd); + + switch (cmd) { + case COMP_TRIGGER_PAUSE: + case COMP_TRIGGER_STOP: + /* Execute immediately */ + ret = pipeline_trigger_run(p, host, cmd); + return ret == PPL_STATUS_PATH_STOP ? 0 : ret; + case COMP_TRIGGER_PRE_RELEASE: + case COMP_TRIGGER_PRE_START: + /* Add all connected pipelines to the list and schedule them all */ + ret = pipeline_trigger_list(p, host, cmd); + if (ret < 0) + return ret; + /* IPC response will be sent from the task */ + return 1; + } + + return 0; +} + +/* actually execute pipeline trigger, including components: either in IPC or in task context */ +int pipeline_trigger_run(struct pipeline *p, struct comp_dev *host, int cmd) +{ + struct pipeline_data data = { + .start = host, + .cmd = cmd, + }; struct pipeline_walk_context walk_ctx = { .comp_func = pipeline_comp_trigger, .comp_data = &data, @@ -183,7 +295,7 @@ int pipeline_trigger(struct pipeline *p, struct comp_dev *host, int cmd) }; int ret; - pipe_info(p, "pipe trigger cmd %d", cmd); + pipe_dbg(p, "execute trigger cmd %d on pipe %u", cmd, p->pipeline_id); list_init(&walk_ctx.pipelines); @@ -193,20 +305,58 @@ int pipeline_trigger(struct pipeline *p, struct comp_dev *host, int cmd) if (ret < 0) { pipe_err(p, "xrun handle: ret = %d", ret); return ret; - } else if (ret == PPL_STATUS_PATH_STOP) + } + + if (ret == PPL_STATUS_PATH_STOP) /* no further action needed*/ - return 0; + return pipeline_is_timer_driven(p); } - data.start = host; - data.cmd = cmd; - ret = walk_ctx.comp_func(host, NULL, &walk_ctx, host->direction); if (ret < 0) { - pipe_err(p, "pipeline_trigger(): ret = %d, host->comp.id = %u, cmd = %d", + pipe_err(p, "pipeline_trigger_run(): ret = %d, host->comp.id = %u, cmd = %d", ret, dev_comp_id(host), cmd); + goto out; + } + + switch (cmd) { + case COMP_TRIGGER_PRE_START: + data.cmd = COMP_TRIGGER_START; + break; + case COMP_TRIGGER_PRE_RELEASE: + data.cmd = COMP_TRIGGER_RELEASE; + } + + if (data.cmd != cmd) { + if (data.delay_ms && pipeline_is_timer_driven(p)) { + /* The task will skip .delay periods before processing the next command */ + p->trigger.delay = (data.delay_ms * 1000 + p->period - 1) / p->period; + p->trigger.cmd = data.cmd; + + return ret; + } + + list_init(&walk_ctx.pipelines); + + if (data.delay_ms) + wait_delay(clock_ms_to_ticks(PLATFORM_DEFAULT_CLOCK, + data.delay_ms)); + + ret = walk_ctx.comp_func(host, NULL, &walk_ctx, host->direction); + if (ret < 0) + pipe_err(p, "pipeline_trigger(): ret = %d, host->comp.id = %u, cmd = %d", + ret, dev_comp_id(host), cmd); + + if (pipeline_is_timer_driven(p)) + return ret; } +out: + /* + * When called from the pipeline task, pipeline_comp_trigger() will not + * add pipelines to the list, so pipeline_schedule_triggered() will have + * no effect. + */ pipeline_schedule_triggered(&walk_ctx, cmd); return ret; diff --git a/src/audio/pipeline/pipeline-xrun.c b/src/audio/pipeline/pipeline-xrun.c index 1f22fbb93b51..04b85f3abfab 100644 --- a/src/audio/pipeline/pipeline-xrun.c +++ b/src/audio/pipeline/pipeline-xrun.c @@ -153,7 +153,7 @@ void pipeline_xrun(struct pipeline *p, struct comp_dev *dev, return; /* notify all pipeline comps we are in XRUN, and stop copying */ - ret = pipeline_trigger(p, p->source_comp, COMP_TRIGGER_XRUN); + ret = pipeline_trigger_run(p, p->source_comp, COMP_TRIGGER_XRUN); if (ret < 0) pipe_err(p, "pipeline_xrun(): Pipelines notification about XRUN failed, ret = %d", ret); diff --git a/src/drivers/imx/sai.c b/src/drivers/imx/sai.c index 5878443eae22..2c013b5b5d3d 100644 --- a/src/drivers/imx/sai.c +++ b/src/drivers/imx/sai.c @@ -348,10 +348,10 @@ static int sai_trigger(struct dai *dai, int cmd, int direction) sai_stop(dai, direction); break; case COMP_TRIGGER_RELEASE: - break; case COMP_TRIGGER_SUSPEND: - break; case COMP_TRIGGER_RESUME: + case COMP_TRIGGER_PRE_START: + case COMP_TRIGGER_PRE_RELEASE: break; default: dai_err(dai, "SAI: invalid trigger cmd %d", cmd); diff --git a/src/drivers/intel/cavs/ipc.c b/src/drivers/intel/cavs/ipc.c index 67528d6748b4..a272ebb8e46b 100644 --- a/src/drivers/intel/cavs/ipc.c +++ b/src/drivers/intel/cavs/ipc.c @@ -201,10 +201,13 @@ enum task_state ipc_platform_do_cmd(void *data) void ipc_platform_complete_cmd(void *data) { struct ipc *ipc = data; + uint32_t flags; - if (!cpu_is_me(ipc->core)) + if (!cpu_is_me(ipc->core) || ipc->delayed_response) return; + spin_lock_irq(&ipc->lock, flags); + /* write 1 to clear busy, and trigger interrupt to host*/ #if CAVS_VERSION == CAVS_VERSION_1_5 ipc_write(IPC_DIPCT, ipc_read(IPC_DIPCT) | IPC_DIPCT_BUSY); @@ -229,6 +232,8 @@ void ipc_platform_complete_cmd(void *data) } #endif + + spin_unlock_irq(&ipc->lock, flags); } int ipc_platform_send_msg(struct ipc_msg *msg) diff --git a/src/drivers/intel/ssp/ssp.c b/src/drivers/intel/ssp/ssp.c index 26b0810102ef..1efc9cfe0b9e 100644 --- a/src/drivers/intel/ssp/ssp.c +++ b/src/drivers/intel/ssp/ssp.c @@ -949,8 +949,7 @@ static int ssp_get_hw_params(struct dai *dai, return 0; } -/* start the SSP for either playback or capture */ -static void ssp_start(struct dai *dai, int direction) +static void ssp_early_start(struct dai *dai, int direction) { struct ssp_pdata *ssp = dai_get_drvdata(dai); @@ -967,19 +966,21 @@ static void ssp_start(struct dai *dai, int direction) /* enable port */ ssp_update_bits(dai, SSCR0, SSCR0_SSE, SSCR0_SSE); - dai_info(dai, "ssp_start(): SSE set for SSP%d", dai->index); + dai_info(dai, "ssp_early_start(): SSE set for SSP%d", dai->index); } - ssp->state[direction] = COMP_STATE_ACTIVE; - dai_info(dai, "ssp_start()"); - if (ssp->params.bclk_delay) { - /* drive BCLK early for guaranteed time, - * before first FSYNC, it is required by some codecs - */ - wait_delay(clock_ms_to_ticks(PLATFORM_DEFAULT_CLOCK, - ssp->params.bclk_delay)); - } + spin_unlock(&dai->lock); +} + +/* start the SSP for either playback or capture */ +static void ssp_start(struct dai *dai, int direction) +{ + struct ssp_pdata *ssp = dai_get_drvdata(dai); + + spin_lock(&dai->lock); + + dai_info(dai, "ssp_start()"); /* enable DMA */ if (direction == DAI_DIR_PLAYBACK) @@ -987,6 +988,8 @@ static void ssp_start(struct dai *dai, int direction) else ssp_update_bits(dai, SSRSA, SSRSA_RXEN, SSRSA_RXEN); + ssp->state[direction] = COMP_STATE_ACTIVE; + /* * Wait to get valid fifo status in clock consumer mode. TODO it's * uncertain which SSP clock consumer modes need the delay atm, but @@ -1103,7 +1106,9 @@ static int ssp_trigger(struct dai *dai, int cmd, int direction) case COMP_TRIGGER_SUSPEND: ssp_context_store(dai); break; - default: + case COMP_TRIGGER_PRE_START: + case COMP_TRIGGER_PRE_RELEASE: + ssp_early_start(dai, direction); break; } @@ -1170,6 +1175,19 @@ static int ssp_get_fifo(struct dai *dai, int direction, int stream_id) return dai->plat_data.fifo[direction].offset; } +static uint32_t ssp_get_init_delay_ms(struct dai *dai) +{ + struct ssp_pdata *ssp = dai_get_drvdata(dai); + + if (ssp->clk_active & SSP_CLK_BCLK_ACTIVE) + return 0; + + /* drive BCLK early for guaranteed time, + * before first FSYNC, it is required by some codecs + */ + return ssp->params.bclk_delay; +} + const struct dai_driver ssp_driver = { .type = SOF_DAI_INTEL_SSP, .uid = SOF_UUID(ssp_uuid), @@ -1184,6 +1202,7 @@ const struct dai_driver ssp_driver = { .get_hw_params = ssp_get_hw_params, .get_handshake = ssp_get_handshake, .get_fifo = ssp_get_fifo, + .get_init_delay_ms = ssp_get_init_delay_ms, .probe = ssp_probe, .remove = ssp_remove, }, diff --git a/src/include/sof/audio/component.h b/src/include/sof/audio/component.h index acd40569a331..53ad2f921b01 100644 --- a/src/include/sof/audio/component.h +++ b/src/include/sof/audio/component.h @@ -64,21 +64,33 @@ struct timestamp_data; #define COMP_STATE_PREPARE 3 /**< Component prepared */ #define COMP_STATE_PAUSED 4 /**< Component paused */ #define COMP_STATE_ACTIVE 5 /**< Component active */ +#define COMP_STATE_PRE_ACTIVE 6 /**< Component after early initialisation */ /** @}*/ /** \name Standard Component Stream Commands * TODO: use IPC versions after 1.1 + * + * Most component stream commands match one-to-one IPC stream trigger commands. + * However we add two PRE_ and two POST_ commands to the set. They are issued + * internally without matching IPC commands. A single START IPC command is + * translated into a sequence of PRE_START and START component commands, etc. + * POST_* commands aren't used so far. + * * @{ */ -#define COMP_TRIGGER_STOP 0 /**< Stop component stream */ -#define COMP_TRIGGER_START 1 /**< Start component stream */ -#define COMP_TRIGGER_PAUSE 2 /**< Pause the component stream */ -#define COMP_TRIGGER_RELEASE 3 /**< Release paused component stream */ -#define COMP_TRIGGER_SUSPEND 4 /**< Suspend component */ -#define COMP_TRIGGER_RESUME 5 /**< Resume component */ -#define COMP_TRIGGER_RESET 6 /**< Reset component */ -#define COMP_TRIGGER_PREPARE 7 /**< Prepare component */ -#define COMP_TRIGGER_XRUN 8 /**< XRUN component */ +#define COMP_TRIGGER_STOP 0 /**< Stop component stream */ +#define COMP_TRIGGER_START 1 /**< Start component stream */ +#define COMP_TRIGGER_PAUSE 2 /**< Pause the component stream */ +#define COMP_TRIGGER_RELEASE 3 /**< Release paused component stream */ +#define COMP_TRIGGER_SUSPEND 4 /**< Suspend component */ +#define COMP_TRIGGER_RESUME 5 /**< Resume component */ +#define COMP_TRIGGER_RESET 6 /**< Reset component */ +#define COMP_TRIGGER_PREPARE 7 /**< Prepare component */ +#define COMP_TRIGGER_XRUN 8 /**< XRUN component */ +#define COMP_TRIGGER_PRE_START 9 /**< Prepare to start component stream */ +#define COMP_TRIGGER_PRE_RELEASE 10 /**< Prepare to release paused component stream */ +#define COMP_TRIGGER_POST_STOP 11 /**< Finalize stop component stream */ +#define COMP_TRIGGER_POST_PAUSE 12 /**< Finalize pause component stream */ /** @}*/ /** \name Standard Component Control Commands diff --git a/src/include/sof/audio/component_ext.h b/src/include/sof/audio/component_ext.h index f5eb39c25b23..5c646908afb9 100644 --- a/src/include/sof/audio/component_ext.h +++ b/src/include/sof/audio/component_ext.h @@ -345,7 +345,9 @@ static inline int comp_get_requested_state(int cmd) case COMP_TRIGGER_RESET: state = COMP_STATE_READY; break; - default: + case COMP_TRIGGER_PRE_START: + case COMP_TRIGGER_PRE_RELEASE: + state = COMP_STATE_PRE_ACTIVE; break; } diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index bc3f5fc0e411..00db90b5320c 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -73,6 +73,11 @@ struct pipeline { /* position update */ uint32_t posn_offset; /* position update array offset*/ struct ipc_msg *msg; + struct { + int cmd; + struct comp_dev *host; + unsigned int delay; /* period count */ + } trigger; }; struct pipeline_walk_context { @@ -99,6 +104,7 @@ struct pipeline_data { struct sof_ipc_stream_posn *posn; struct pipeline *p; int cmd; + uint32_t delay_ms; /* between PRE_{START,RELEASE} and {START,RELEASE} */ }; /* @@ -218,14 +224,22 @@ int pipeline_prepare(struct pipeline *p, struct comp_dev *cd); */ /** - * \brief Trigger pipeline - atomic + * \brief Trigger pipeline - IPC context * \param[in] p pipeline. - * \param[in] host_cd Host DMA component. + * \param[in] host Host DMA component. * \param[in] cmd Pipeline trigger command. * \return 0 on success. */ -/* */ -int pipeline_trigger(struct pipeline *p, struct comp_dev *host_cd, int cmd); +int pipeline_trigger(struct pipeline *p, struct comp_dev *host, int cmd); + +/** + * \brief Trigger pipeline - either IPC or pipeline task context + * \param[in] p pipeline. + * \param[in] host Host DMA component. + * \param[in] cmd Pipeline trigger command. + * \return 0 on success. + */ +int pipeline_trigger_run(struct pipeline *p, struct comp_dev *host, int cmd); /** * \brief Copy data along a pipeline. diff --git a/src/include/sof/ipc/common.h b/src/include/sof/ipc/common.h index a53010a02f69..79cf8de4c8e5 100644 --- a/src/include/sof/ipc/common.h +++ b/src/include/sof/ipc/common.h @@ -68,6 +68,7 @@ struct ipc { struct list_item msg_list; /* queue of messages to be sent */ bool is_notification_pending; /* notification is being sent to host */ + bool delayed_response; /* response will be sent from a different context */ unsigned int core; /* core, processing the IPC */ struct list_item comp_list; /* list of component devices */ @@ -196,4 +197,10 @@ void ipc_cmd(ipc_cmd_hdr *hdr); */ int ipc_process_on_core(uint32_t core, bool blocking); +/** + * \brief reply to an IPC message. + * @param[in] reply pointer to the reply structure. + */ +void ipc_msg_reply(struct sof_ipc_reply *reply); + #endif /* __SOF_DRIVERS_IPC_H__ */ diff --git a/src/include/sof/lib/dai.h b/src/include/sof/lib/dai.h index 67765b4153d9..5e1c72b019fb 100644 --- a/src/include/sof/lib/dai.h +++ b/src/include/sof/lib/dai.h @@ -86,6 +86,7 @@ struct dai_ops { int (*get_fifo)(struct dai *dai, int direction, int stream_id); int (*probe)(struct dai *dai); int (*remove)(struct dai *dai); + uint32_t (*get_init_delay_ms)(struct dai *dai); }; struct timestamp_cfg { @@ -465,6 +466,17 @@ static inline int dai_remove(struct dai *dai) return ret; } +/** + * \brief Get DAI initial delay in milliseconds + */ +static inline uint32_t dai_get_init_delay_ms(struct dai *dai) +{ + if (dai && dai->drv->ops.get_init_delay_ms) + return dai->drv->ops.get_init_delay_ms(dai); + + return 0; +} + /** * \brief Get driver specific DAI information */ diff --git a/src/include/sof/lib/wait.h b/src/include/sof/lib/wait.h index 3043ae590a25..6a824cea3f86 100644 --- a/src/include/sof/lib/wait.h +++ b/src/include/sof/lib/wait.h @@ -42,7 +42,11 @@ static inline void wait_for_interrupt(int level) * \brief Waits at least passed number of clocks. * \param[in] number_of_clks Minimum number of clocks to wait. */ +#if !CONFIG_LIBRARY void wait_delay(uint64_t number_of_clks); +#else +static inline void wait_delay(uint64_t number_of_clks) {} +#endif int poll_for_register_delay(uint32_t reg, uint32_t mask, uint32_t val, uint64_t us); diff --git a/src/ipc/ipc-common.c b/src/ipc/ipc-common.c index f88412558a37..9c341636de90 100644 --- a/src/ipc/ipc-common.c +++ b/src/ipc/ipc-common.c @@ -220,6 +220,15 @@ void ipc_msg_send(struct ipc_msg *msg, void *data, bool high_priority) spin_unlock_irq(&ipc->lock, flags); } +void ipc_msg_reply(struct sof_ipc_reply *reply) +{ + struct ipc *ipc = ipc_get(); + + ipc->delayed_response = false; + mailbox_hostbox_write(0, reply, reply->hdr.size); + ipc_platform_complete_cmd(ipc); +} + void ipc_schedule_process(struct ipc *ipc) { schedule_task(&ipc->ipc_task, 0, IPC_PERIOD_USEC); diff --git a/src/ipc/ipc3/handler.c b/src/ipc/ipc3/handler.c index 43d897651b14..b049bd398c3a 100644 --- a/src/ipc/ipc3/handler.c +++ b/src/ipc/ipc3/handler.c @@ -412,7 +412,7 @@ static int ipc_stream_trigger(uint32_t header) switch (ipc_command) { case SOF_IPC_STREAM_TRIG_START: - cmd = COMP_TRIGGER_START; + cmd = COMP_TRIGGER_PRE_START; break; case SOF_IPC_STREAM_TRIG_STOP: cmd = COMP_TRIGGER_STOP; @@ -421,7 +421,7 @@ static int ipc_stream_trigger(uint32_t header) cmd = COMP_TRIGGER_PAUSE; break; case SOF_IPC_STREAM_TRIG_RELEASE: - cmd = COMP_TRIGGER_RELEASE; + cmd = COMP_TRIGGER_PRE_RELEASE; break; /* XRUN is special case- TODO */ case SOF_IPC_STREAM_TRIG_XRUN: @@ -437,12 +437,24 @@ static int ipc_stream_trigger(uint32_t header) return -EINVAL; } - /* trigger the component */ - ret = pipeline_trigger(pcm_dev->cd->pipeline, pcm_dev->cd, cmd); - if (ret < 0) { + /* + * Trigger the component: timer domain pipelines offload some trigger + * operations in their pipeline tasks, in which case IPC response to + * the host can be delayed. DMA domain pipelines always trigger + * synchronously. + */ + if (pipeline_is_timer_driven(pcm_dev->cd->pipeline)) { + ret = pipeline_trigger(pcm_dev->cd->pipeline, pcm_dev->cd, cmd); + if (ret > 0) + ipc->delayed_response = true; + } else { + ret = pipeline_trigger_run(pcm_dev->cd->pipeline, pcm_dev->cd, cmd); + } + + if (ret < 0) tr_err(&ipc_tr, "ipc: comp %d trigger 0x%x failed %d", stream.comp_id, ipc_command, ret); - } + return ret; } @@ -1515,6 +1527,8 @@ void ipc_cmd(ipc_cmd_hdr *_hdr) /* A new IPC from the host, delivered to the primary core */ ipc->core = PLATFORM_PRIMARY_CORE_ID; + ipc->delayed_response = false; + type = iGS(hdr->cmd); switch (type) { diff --git a/test/cmocka/src/audio/buffer/CMakeLists.txt b/test/cmocka/src/audio/buffer/CMakeLists.txt index 62032f72d177..d6419f3cb2fc 100644 --- a/test/cmocka/src/audio/buffer/CMakeLists.txt +++ b/test/cmocka/src/audio/buffer/CMakeLists.txt @@ -16,6 +16,7 @@ cmocka_test(buffer_copy cmocka_test(buffer_new buffer_new.c + ${PROJECT_SOURCE_DIR}/test/cmocka/src/common_mocks.c ${PROJECT_SOURCE_DIR}/test/cmocka/src/notifier_mocks.c ${PROJECT_SOURCE_DIR}/src/audio/buffer.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc3/helper.c @@ -30,6 +31,7 @@ cmocka_test(buffer_new cmocka_test(buffer_wrap buffer_wrap.c + ${PROJECT_SOURCE_DIR}/test/cmocka/src/common_mocks.c ${PROJECT_SOURCE_DIR}/test/cmocka/src/notifier_mocks.c ${PROJECT_SOURCE_DIR}/src/audio/buffer.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc3/helper.c @@ -44,6 +46,7 @@ cmocka_test(buffer_wrap cmocka_test(buffer_write buffer_write.c + ${PROJECT_SOURCE_DIR}/test/cmocka/src/common_mocks.c ${PROJECT_SOURCE_DIR}/test/cmocka/src/notifier_mocks.c ${PROJECT_SOURCE_DIR}/src/audio/buffer.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc3/helper.c diff --git a/test/cmocka/src/audio/component/comp_set_state.c b/test/cmocka/src/audio/component/comp_set_state.c index 00d9fb689a10..b70b4e578d87 100644 --- a/test/cmocka/src/audio/component/comp_set_state.c +++ b/test/cmocka/src/audio/component/comp_set_state.c @@ -46,8 +46,12 @@ struct test_case test_cases[] = { /*succeed set state*/ TEST_CASE(SUCCEED, COMP_STATE_PREPARE, + COMP_TRIGGER_PRE_START, NULL_STATE), + TEST_CASE(SUCCEED, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_START, NULL_STATE), TEST_CASE(SUCCEED, COMP_STATE_PAUSED, + COMP_TRIGGER_PRE_RELEASE, NULL_STATE), + TEST_CASE(SUCCEED, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_RELEASE, NULL_STATE), TEST_CASE(SUCCEED, COMP_STATE_ACTIVE, COMP_TRIGGER_STOP, NULL_STATE), @@ -77,6 +81,8 @@ struct test_case test_cases[] = { NULL_STATE), TEST_CASE(FAIL, COMP_STATE_PAUSED, COMP_TRIGGER_START, NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PREPARE, COMP_TRIGGER_START, + NULL_STATE), TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_RELEASE, NULL_STATE), TEST_CASE(FAIL, COMP_STATE_READY, COMP_TRIGGER_RELEASE, @@ -85,12 +91,16 @@ struct test_case test_cases[] = { NULL_STATE), TEST_CASE(FAIL, COMP_STATE_PREPARE, COMP_TRIGGER_RELEASE, NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PAUSED, COMP_TRIGGER_RELEASE, + NULL_STATE), TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_STOP, NULL_STATE), TEST_CASE(FAIL, COMP_STATE_READY, COMP_TRIGGER_STOP, NULL_STATE), TEST_CASE(FAIL, COMP_STATE_SUSPEND, COMP_TRIGGER_STOP, NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_STOP, + NULL_STATE), TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_PAUSE, NULL_STATE), TEST_CASE(FAIL, COMP_STATE_READY, COMP_TRIGGER_PAUSE, @@ -99,6 +109,8 @@ struct test_case test_cases[] = { NULL_STATE), TEST_CASE(FAIL, COMP_STATE_PREPARE, COMP_TRIGGER_PAUSE, NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_PAUSE, + NULL_STATE), TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_PREPARE, NULL_STATE), TEST_CASE(FAIL, COMP_STATE_SUSPEND, COMP_TRIGGER_PREPARE, @@ -107,11 +119,37 @@ struct test_case test_cases[] = { NULL_STATE), TEST_CASE(FAIL, COMP_STATE_ACTIVE, COMP_TRIGGER_PREPARE, NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_PREPARE, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_PRE_START, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_READY, COMP_TRIGGER_PRE_START, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_SUSPEND, COMP_TRIGGER_PRE_START, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PAUSED, COMP_TRIGGER_PRE_START, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_ACTIVE, COMP_TRIGGER_PRE_START, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_INIT, COMP_TRIGGER_PRE_RELEASE, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_READY, COMP_TRIGGER_PRE_RELEASE, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_SUSPEND, COMP_TRIGGER_PRE_RELEASE, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_ACTIVE, COMP_TRIGGER_PRE_RELEASE, + NULL_STATE), + TEST_CASE(FAIL, COMP_STATE_PREPARE, COMP_TRIGGER_PRE_RELEASE, + NULL_STATE), /*correct output state*/ TEST_CASE(CORRECT_OUTPUT_STATE, COMP_STATE_PREPARE, + COMP_TRIGGER_PRE_START, COMP_STATE_PRE_ACTIVE), + TEST_CASE(CORRECT_OUTPUT_STATE, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_START, COMP_STATE_ACTIVE), TEST_CASE(CORRECT_OUTPUT_STATE, COMP_STATE_PAUSED, + COMP_TRIGGER_PRE_RELEASE, COMP_STATE_PRE_ACTIVE), + TEST_CASE(CORRECT_OUTPUT_STATE, COMP_STATE_PRE_ACTIVE, COMP_TRIGGER_RELEASE, COMP_STATE_ACTIVE), TEST_CASE(CORRECT_OUTPUT_STATE, COMP_STATE_ACTIVE, COMP_TRIGGER_STOP, COMP_STATE_PREPARE), diff --git a/test/cmocka/src/common_mocks.c b/test/cmocka/src/common_mocks.c index e362f4b953bd..45a7190b4148 100644 --- a/test/cmocka/src/common_mocks.c +++ b/test/cmocka/src/common_mocks.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -200,6 +201,11 @@ int WEAK ipc_platform_send_msg(struct ipc_msg *msg) { return 0; } + +void WEAK wait_delay(uint64_t number_of_clks) +{ +} + void WEAK xthal_icache_region_invalidate(void *addr, unsigned size) { } diff --git a/tools/testbench/common_test.c b/tools/testbench/common_test.c index c6e27902316e..2e813dd54be3 100644 --- a/tools/testbench/common_test.c +++ b/tools/testbench/common_test.c @@ -118,8 +118,10 @@ int tb_pipeline_start(struct ipc *ipc, struct pipeline *p, if (ret < 0) printf("Warning: Failed prepare pipeline command.\n"); - /* Start the pipeline */ - ret = pipeline_trigger(p, cd, COMP_TRIGGER_START); + /* Prepare and start the pipeline */ + ret = pipeline_trigger(p, cd, COMP_TRIGGER_PRE_START); + if (ret >= 0) + ret = pipeline_copy(p); if (ret < 0) printf("Warning: Failed start pipeline command.\n");