/* is the current PCM operation for this FE ? */ #if 0 staticint snd_soc_dpcm_can_fe_update(struct snd_soc_pcm_runtime *fe, int stream)
{ if (fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) return 1; return 0;
} #endif
/* is the current PCM operation for this BE ? */ staticint snd_soc_dpcm_can_be_update(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ if ((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) ||
((fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_BE) &&
be->dpcm[stream].runtime_update)) return 1; return 0;
}
staticint snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream, constenum snd_soc_dpcm_state *states, int num_states)
{ struct snd_soc_dpcm *dpcm; int state; int ret = 1; int i;
for_each_dpcm_fe(be, stream, dpcm) {
if (dpcm->fe == fe) continue;
state = dpcm->fe->dpcm[stream].state; for (i = 0; i < num_states; i++) { if (state == states[i]) {
ret = 0; break;
}
}
}
/* it's safe to do this BE DAI */ return ret;
}
/* * We can only hw_free, stop, pause or suspend a BE DAI if any of it's FE * are not running, paused or suspended for the specified stream direction.
*/ staticint snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ constenum snd_soc_dpcm_state state[] = {
SND_SOC_DPCM_STATE_START,
SND_SOC_DPCM_STATE_PAUSED,
SND_SOC_DPCM_STATE_SUSPEND,
};
return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state));
}
/* * We can only change hw params a BE DAI if any of it's FE are not prepared, * running, paused or suspended for the specified stream direction.
*/ staticint snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ constenum snd_soc_dpcm_state state[] = {
SND_SOC_DPCM_STATE_START,
SND_SOC_DPCM_STATE_PAUSED,
SND_SOC_DPCM_STATE_SUSPEND,
SND_SOC_DPCM_STATE_PREPARE,
};
return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state));
}
/* * We can only prepare a BE DAI if any of it's FE are not prepared, * running or paused for the specified stream direction.
*/ staticint snd_soc_dpcm_can_be_prepared(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ constenum snd_soc_dpcm_state state[] = {
SND_SOC_DPCM_STATE_START,
SND_SOC_DPCM_STATE_PAUSED,
SND_SOC_DPCM_STATE_PREPARE,
};
return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state));
}
staticconstchar *dpcm_state_string(enum snd_soc_dpcm_state state)
{ switch (state) { case SND_SOC_DPCM_STATE_NEW: return"new"; case SND_SOC_DPCM_STATE_OPEN: return"open"; case SND_SOC_DPCM_STATE_HW_PARAMS: return"hw_params"; case SND_SOC_DPCM_STATE_PREPARE: return"prepare"; case SND_SOC_DPCM_STATE_START: return"start"; case SND_SOC_DPCM_STATE_STOP: return"stop"; case SND_SOC_DPCM_STATE_SUSPEND: return"suspend"; case SND_SOC_DPCM_STATE_PAUSED: return"paused"; case SND_SOC_DPCM_STATE_HW_FREE: return"hw_free"; case SND_SOC_DPCM_STATE_CLOSE: return"close";
}
/* Set FE's runtime_update state; the state is protected via PCM stream lock * for avoiding the race with trigger callback. * If the state is unset and a trigger is pending while the previous operation, * process the pending trigger action here.
*/ staticint dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); staticvoid dpcm_set_fe_update_state(struct snd_soc_pcm_runtime *fe, int stream, enum snd_soc_dpcm_update state)
{ struct snd_pcm_substream *substream =
snd_soc_dpcm_get_substream(fe, stream);
/** * snd_soc_runtime_action() - Increment/Decrement active count for * PCM runtime components * @rtd: ASoC PCM runtime that is activated * @stream: Direction of the PCM stream * @action: Activate stream if 1. Deactivate if -1. * * Increments/Decrements the active count for all the DAIs and components * attached to a PCM runtime. * Should typically be called when a stream is opened. * * Must be called with the rtd->card->pcm_mutex being held
*/ void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, int stream, int action)
{ struct snd_soc_component *component; struct snd_soc_dai *dai; int i;
snd_soc_dpcm_mutex_assert_held(rtd);
for_each_rtd_dais(rtd, i, dai)
snd_soc_dai_action(dai, stream, action);
/* Increments/Decrements the active count for components without DAIs */
for_each_rtd_components(rtd, i, component) { if (component->num_dai) continue;
component->active += action;
}
}
EXPORT_SYMBOL_GPL(snd_soc_runtime_action);
/** * snd_soc_runtime_ignore_pmdown_time() - Check whether to ignore the power down delay * @rtd: The ASoC PCM runtime that should be checked. * * This function checks whether the power down delay should be ignored for a * specific PCM runtime. Returns true if the delay is 0, if the DAI link has * been configured to ignore the delay, or if none of the components benefits * from having the delay.
*/ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd)
{ struct snd_soc_component *component; int i;
if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) returntrue;
for_each_rtd_components(rtd, i, component) if (component->driver->use_pmdown_time) /* No need to go through all components */ returnfalse;
returntrue;
}
/* DPCM stream event, send event to FE and all active BEs. */ void dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, int event)
{ struct snd_soc_dpcm *dpcm;
snd_soc_dpcm_mutex_assert_held(fe);
for_each_dpcm_be(fe, dir, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be;
dev_dbg(be->dev, "ASoC: BE %s event %d dir %d\n",
be->dai_link->name, event, dir);
if ((event == SND_SOC_DAPM_STREAM_STOP) &&
(be->dpcm[dir].users >= 1)) continue;
/** * snd_soc_runtime_calc_hw() - Calculate hw limits for a PCM stream * @rtd: ASoC PCM runtime * @hw: PCM hardware parameters (output) * @stream: Direction of the PCM stream * * Calculates the subset of stream parameters supported by all DAIs * associated with the PCM stream.
*/ int snd_soc_runtime_calc_hw(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hardware *hw, int stream)
{ struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai; conststruct snd_soc_pcm_stream *codec_stream; conststruct snd_soc_pcm_stream *cpu_stream; unsignedint cpu_chan_min = 0, cpu_chan_max = UINT_MAX; int i;
soc_pcm_hw_init(hw);
/* first calculate min/max only for CPUs in the DAI link */
for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
/* * Skip CPUs which don't support the current stream type. * Otherwise, since the rate, channel, and format values will * zero in that case, we would have no usable settings left, * causing the resulting setup to fail.
*/ if (!snd_soc_dai_stream_valid(cpu_dai, stream)) continue;
/* second calculate min/max only for CODECs in the DAI link */
for_each_rtd_codec_dais(rtd, i, codec_dai) {
/* * Skip CODECs which don't support the current stream type. * Otherwise, since the rate, channel, and format values will * zero in that case, we would have no usable settings left, * causing the resulting setup to fail.
*/ if (!snd_soc_dai_stream_valid(codec_dai, stream)) continue;
/* Verify both a valid CPU DAI and a valid CODEC DAI were found */ if (!hw->channels_min) return -EINVAL;
/* * chan min/max cannot be enforced if there are multiple CODEC DAIs * connected to CPU DAI(s), use CPU DAI's directly and let * channel allocation be fixed up later
*/ if (rtd->dai_link->num_codecs > 1) {
hw->channels_min = cpu_chan_min;
hw->channels_max = cpu_chan_max;
}
/* * At least one CPU and one CODEC should match. Otherwise, we should * have bailed out on a higher level, since there would be no CPU or * CODEC to support the transfer direction in that case.
*/
snd_soc_runtime_calc_hw(rtd, hw, substream->stream);
if (formats)
hw->formats &= formats;
}
staticint soc_pcm_components_open(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; int i, ret = 0;
for_each_rtd_components(rtd, i, component) {
ret = snd_soc_component_module_get_when_open(component, substream); if (ret < 0) break;
ret = snd_soc_component_open(component, substream); if (ret < 0) break;
}
return ret;
}
staticint soc_pcm_components_close(struct snd_pcm_substream *substream, int rollback)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; int i, ret = 0;
for_each_rtd_components(rtd, i, component) { int r = snd_soc_component_close(component, substream, rollback); if (r < 0)
ret = r; /* use last ret */
staticint soc_pcm_clean(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream, int rollback)
{ struct snd_soc_component *component; struct snd_soc_dai *dai; int i;
snd_soc_dpcm_mutex_assert_held(rtd);
if (!rollback) {
snd_soc_runtime_deactivate(rtd, substream->stream);
/* Make sure DAI parameters cleared if the DAI becomes inactive */
for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 0)
soc_pcm_set_dai_params(dai, NULL);
}
}
for_each_rtd_dais_reverse(rtd, i, dai)
snd_soc_dai_shutdown(dai, substream, rollback);
for_each_rtd_components(rtd, i, component) if (!snd_soc_component_active(component))
pinctrl_pm_select_sleep_state(component->dev);
return 0;
}
/* * Called by ALSA when a PCM substream is closed. Private data can be * freed here. The cpu DAI, codec DAI, machine and components are also * shutdown.
*/ staticint __soc_pcm_close(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream)
{ return soc_pcm_clean(rtd, substream, 0);
}
/* PCM close ops for non-DPCM streams */ staticint soc_pcm_close(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
/* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls * startup for the cpu DAI, component, machine and codec DAI.
*/ staticint __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream)
{ struct snd_soc_component *component; struct snd_soc_dai *dai; int i, ret = 0;
snd_soc_dpcm_mutex_assert_held(rtd);
for_each_rtd_components(rtd, i, component)
pinctrl_pm_select_default_state(component->dev);
ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream); if (ret < 0) goto err;
ret = soc_pcm_components_open(substream); if (ret < 0) goto err;
ret = snd_soc_link_startup(substream); if (ret < 0) goto err;
/* startup the audio subsystem */
for_each_rtd_dais(rtd, i, dai) {
ret = snd_soc_dai_startup(dai, substream); if (ret < 0) goto err;
}
/* Dynamic PCM DAI links compat checks use dynamic capabilities */ if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) goto dynamic;
/* Check that the codec and cpu DAIs are compatible */
soc_pcm_init_runtime_hw(substream);
soc_pcm_update_symmetry(substream);
ret = soc_hw_sanity_check(substream); if (ret < 0) goto err;
soc_pcm_apply_msb(substream);
/* Symmetry only applies if we've already got an active stream. */
for_each_rtd_dais(rtd, i, dai) {
ret = soc_pcm_apply_symmetry(substream, dai); if (ret != 0) goto err;
}
dynamic:
snd_soc_runtime_activate(rtd, substream->stream);
ret = 0;
err: if (ret < 0)
soc_pcm_clean(rtd, substream, 1);
return soc_pcm_ret(rtd, ret);
}
/* PCM open ops for non-DPCM streams */ staticint soc_pcm_open(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_open(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd); return ret;
}
/* * Called by ALSA when the PCM substream is prepared, can set format, sample * rate, etc. This function is non atomic and can be called multiple times, * it can refer to the runtime info.
*/ staticint __soc_pcm_prepare(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream)
{ struct snd_soc_dai *dai; int i, ret = 0;
snd_soc_dpcm_mutex_assert_held(rtd);
ret = snd_soc_link_prepare(substream); if (ret < 0) goto out;
ret = snd_soc_pcm_component_prepare(substream); if (ret < 0) goto out;
ret = snd_soc_pcm_dai_prepare(substream); if (ret < 0) goto out;
/* cancel any delayed stream shutdown that is pending */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
rtd->pop_wait) {
rtd->pop_wait = 0;
cancel_delayed_work(&rtd->delayed_work);
}
for_each_rtd_dais(rtd, i, dai) { if (!snd_soc_dai_mute_is_ctrled_at_trigger(dai))
snd_soc_dai_digital_mute(dai, 0, substream->stream);
}
out: /* * Don't use soc_pcm_ret() on .prepare callback to lower error log severity * * We don't want to log an error since we do not want to give userspace a way to do a * denial-of-service attack on the syslog / diskspace.
*/ return ret;
}
/* PCM prepare ops for non-DPCM streams */ staticint soc_pcm_prepare(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_prepare(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd);
/* * Don't use soc_pcm_ret() on .prepare callback to lower error log severity * * We don't want to log an error since we do not want to give userspace a way to do a * denial-of-service attack on the syslog / diskspace.
*/ return ret;
}
staticint soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream, int rollback)
{ struct snd_soc_dai *dai; int i;
snd_soc_dpcm_mutex_assert_held(rtd);
/* clear the corresponding DAIs parameters when going to be inactive */
for_each_rtd_dais(rtd, i, dai) { if (snd_soc_dai_active(dai) == 1)
soc_pcm_set_dai_params(dai, NULL);
if (snd_soc_dai_stream_active(dai, substream->stream) == 1) { if (!snd_soc_dai_mute_is_ctrled_at_trigger(dai))
snd_soc_dai_digital_mute(dai, 1, substream->stream);
}
}
/* run the stream event */
snd_soc_dapm_stream_stop(rtd, substream->stream);
/* free any machine hw params */
snd_soc_link_hw_free(substream, rollback);
/* free any component resources */
snd_soc_pcm_component_hw_free(substream, rollback);
/* now free hw params for the DAIs */
for_each_rtd_dais(rtd, i, dai) if (snd_soc_dai_stream_valid(dai, substream->stream))
snd_soc_dai_hw_free(dai, substream, rollback);
return 0;
}
/* * Frees resources allocated by hw_params, can be called multiple times
*/ staticint __soc_pcm_hw_free(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_substream *substream)
{ return soc_pcm_hw_clean(rtd, substream, 0);
}
/* hw_free PCM ops for non-DPCM streams */ staticint soc_pcm_hw_free(struct snd_pcm_substream *substream)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret;
snd_soc_dpcm_mutex_lock(rtd);
ret = __soc_pcm_hw_free(rtd, substream);
snd_soc_dpcm_mutex_unlock(rtd); return ret;
}
/* * Called by ALSA when the hardware params are set by application. This * function can also be called multiple times and can allocate buffers * (using snd_pcm_lib_* ). It's non-atomic.
*/ staticint __soc_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; struct snd_pcm_hw_params tmp_params; int i, ret = 0;
snd_soc_dpcm_mutex_assert_held(rtd);
ret = soc_pcm_params_symmetry(substream, params); if (ret) goto out;
ret = snd_soc_link_hw_params(substream, params); if (ret < 0) goto out;
for_each_rtd_codec_dais(rtd, i, codec_dai) { unsignedint tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream);
/* * Skip CODECs which don't support the current stream type, * the idea being that if a CODEC is not used for the currently * set up transfer direction, it should not need to be * configured, especially since the configuration used might * not even be supported by that CODEC. There may be cases * however where a CODEC needs to be set up although it is * actually not being used for the transfer, e.g. if a * capture-only CODEC is acting as an LRCLK and/or BCLK master * for the DAI link including a playback-only CODEC. * If this becomes necessary, we will have to augment the * machine driver setup with information on how to act, so * we can do the right thing here.
*/ if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) continue;
/* copy params for each codec */
tmp_params = *params;
/* fixup params based on TDM slot masks */ if (tdm_mask)
soc_pcm_codec_params_fixup(&tmp_params, tdm_mask);
ret = snd_soc_dai_hw_params(codec_dai, substream,
&tmp_params); if(ret < 0) goto out;
for_each_rtd_cpu_dais(rtd, i, cpu_dai) { struct snd_soc_dai_link_ch_map *ch_maps; unsignedint ch_mask = 0; int j;
/* * Skip CPUs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details
*/ if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream)) continue;
/* copy params for each cpu */
tmp_params = *params;
/* * construct cpu channel mask by combining ch_mask of each * codec which maps to the cpu. * see * soc.h :: [dai_link->ch_maps Image sample]
*/
for_each_rtd_ch_maps(rtd, j, ch_maps) if (ch_maps->cpu == i)
ch_mask |= ch_maps->ch_mask;
/* fixup cpu channel number */ if (ch_mask)
soc_pcm_codec_params_fixup(&tmp_params, ch_mask);
ret = snd_soc_dai_hw_params(cpu_dai, substream, &tmp_params); if (ret < 0) goto out;
/* store the parameters for each DAI */
soc_pcm_set_dai_params(cpu_dai, &tmp_params);
snd_soc_dapm_update_dai(substream, &tmp_params, cpu_dai);
}
ret = snd_soc_pcm_component_hw_params(substream, params);
out: if (ret < 0)
soc_pcm_hw_clean(rtd, substream, 1);
/* * START
*/ switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: for (i = 0; i < TRIGGER_MAX; i++) {
r = trigger[start][i](substream, cmd, 0); if (r < 0) break;
}
}
/* * Rollback if START failed * find correspond STOP command
*/ if (r < 0) {
rollback = 1;
ret = r; switch (cmd) { case SNDRV_PCM_TRIGGER_START:
cmd = SNDRV_PCM_TRIGGER_STOP; break; case SNDRV_PCM_TRIGGER_RESUME:
cmd = SNDRV_PCM_TRIGGER_SUSPEND; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
cmd = SNDRV_PCM_TRIGGER_PAUSE_PUSH; break;
}
}
/* * STOP
*/ switch (cmd) { case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: for (i = TRIGGER_MAX; i > 0; i--) {
r = trigger[stop][i - 1](substream, cmd, rollback); if (r < 0)
ret = r;
}
}
return ret;
}
/* * soc level wrapper for pointer callback * If cpu_dai, codec_dai, component driver has the delay callback, then * the runtime->delay will be updated via snd_soc_pcm_component/dai_delay().
*/ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream)
{ struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_uframes_t offset = 0;
snd_pcm_sframes_t codec_delay = 0;
snd_pcm_sframes_t cpu_delay = 0;
/* should be called *after* snd_soc_pcm_component_pointer() */
snd_soc_pcm_dai_delay(substream, &cpu_delay, &codec_delay);
snd_soc_pcm_component_delay(substream, &cpu_delay, &codec_delay);
runtime->delay = cpu_delay + codec_delay;
return offset;
}
/* connect a FE and BE */ staticint dpcm_be_connect(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ struct snd_pcm_substream *fe_substream; struct snd_pcm_substream *be_substream; struct snd_soc_dpcm *dpcm;
snd_soc_dpcm_mutex_assert_held(fe);
/* only add new dpcms */
for_each_dpcm_be(fe, stream, dpcm) if (dpcm->be == be) return 0;
if (!fe_substream->pcm->nonatomic && be_substream->pcm->nonatomic) return snd_soc_ret(be->dev, -EINVAL, "%s: %s is atomic but %s is nonatomic, invalid configuration\n",
__func__, fe->dai_link->name, be->dai_link->name);
if (fe_substream->pcm->nonatomic && !be_substream->pcm->nonatomic) {
dev_dbg(be->dev, "FE is nonatomic but BE is not, forcing BE as nonatomic\n");
be_substream->pcm->nonatomic = 1;
}
dpcm = kzalloc(sizeof(struct snd_soc_dpcm), GFP_KERNEL); if (!dpcm) return -ENOMEM;
/* reparent a BE onto another FE */ staticvoid dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, struct snd_soc_pcm_runtime *be, int stream)
{ struct snd_soc_dpcm *dpcm; struct snd_pcm_substream *fe_substream, *be_substream;
/* reparent if BE is connected to other FEs */ if (!be->dpcm[stream].users) return;
be_substream = snd_soc_dpcm_get_substream(be, stream); if (!be_substream) return;
for_each_dpcm_fe(be, stream, dpcm) { if (dpcm->fe == fe) continue;
/* disconnect a BE and FE */ void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream)
{ struct snd_soc_dpcm *dpcm, *d; struct snd_pcm_substream *substream = snd_soc_dpcm_get_substream(fe, stream);
LIST_HEAD(deleted_dpcms);
snd_soc_dpcm_mutex_assert_held(fe);
snd_pcm_stream_lock_irq(substream);
for_each_dpcm_be_safe(fe, stream, dpcm, d) {
dev_dbg(fe->dev, "ASoC: BE %s disconnect check for %s\n",
snd_pcm_direction_name(stream),
dpcm->be->dai_link->name);
if (dpcm->state != SND_SOC_DPCM_LINK_STATE_FREE) continue;
/* get BE for DAI widget and stream */ staticstruct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card, struct snd_soc_dapm_widget *widget, int stream)
{ struct snd_soc_pcm_runtime *be; struct snd_soc_dapm_widget *w; struct snd_soc_dai *dai; int i;
dev_dbg(card->dev, "ASoC: find BE for widget %s\n", widget->name);
for_each_card_rtds(card, be) {
if (!be->dai_link->no_pcm) continue;
if (!snd_soc_dpcm_get_substream(be, stream)) continue;
for_each_rtd_dais(be, i, dai) {
w = snd_soc_dai_get_widget(dai, stream);
dev_dbg(card->dev, "ASoC: try BE : %s\n",
w ? w->name : "(not set)");
if (w == widget) return be;
}
}
/* Widget provided is not a BE */ return NULL;
}
int widget_in_list(struct snd_soc_dapm_widget_list *list, struct snd_soc_dapm_widget *widget)
{ struct snd_soc_dapm_widget *w; int i;
for_each_dapm_widgets(list, i, w) if (widget == w) return 1;
int dpcm_path_get(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list)
{ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(fe, 0); int paths;
if (fe->dai_link->num_cpus > 1) return snd_soc_ret(fe->dev, -EINVAL, "%s doesn't support Multi CPU yet\n", __func__);
/* get number of valid DAI paths and their widgets */
paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list,
fe->card->component_chaining ?
NULL : dpcm_end_walk_at_be);
if (paths > 0)
dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths,
snd_pcm_direction_name(stream)); elseif (paths == 0)
dev_dbg(fe->dev, "ASoC: %s no valid %s path\n", fe->dai_link->name,
snd_pcm_direction_name(stream));
/* is there a valid DAI widget for this BE */
for_each_rtd_dais(dpcm->be, i, dai) { struct snd_soc_dapm_widget *widget = snd_soc_dai_get_widget(dai, stream);
/* * The BE is pruned only if none of the dai * widgets are in the active list.
*/ if (widget && widget_in_list(list, widget)) returntrue;
}
returnfalse;
}
staticint dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list_)
{ struct snd_soc_dpcm *dpcm; int prune = 0;
/* Destroy any old FE <--> BE connections */
for_each_dpcm_be(fe, stream, dpcm) { if (dpcm_be_is_active(dpcm, stream, *list_)) continue;
dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n",
snd_pcm_direction_name(stream),
dpcm->be->dai_link->name, fe->dai_link->name);
dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE;
dpcm_set_be_update_state(dpcm->be, stream, SND_SOC_DPCM_UPDATE_BE);
prune++;
}
dev_dbg(fe->dev, "ASoC: found %d old BE paths for pruning\n", prune); return prune;
}
int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, struct snd_soc_dapm_widget_list **list_)
{ struct snd_soc_card *card = fe->card; struct snd_soc_dapm_widget_list *list = *list_; struct snd_soc_pcm_runtime *be; struct snd_soc_dapm_widget *widget; struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); int i, new = 0, err;
/* don't connect if FE is not running */ if (!fe_substream->runtime && !fe->fe_compr) returnnew;
/* Create any new FE <--> BE connections */
for_each_dapm_widgets(list, i, widget) {
switch (widget->id) { case snd_soc_dapm_dai_in: if (stream != SNDRV_PCM_STREAM_PLAYBACK) continue; break; case snd_soc_dapm_dai_out: if (stream != SNDRV_PCM_STREAM_CAPTURE) continue; break; default: continue;
}
/* is there a valid BE rtd for this widget */
be = dpcm_get_be(card, widget, stream); if (!be) {
dev_dbg(fe->dev, "ASoC: no BE found for %s\n",
widget->name); continue;
}
/* * Filter for systems with 'component_chaining' enabled. * This helps to avoid unnecessary re-configuration of an * already active BE on such systems and ensures the BE DAI * widget is powered ON after hw_params() BE DAI callback.
*/ if (fe->card->component_chaining &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) continue;
/* newly connected FE and BE */
err = dpcm_be_connect(fe, be, stream); if (err < 0) {
dev_err(fe->dev, "ASoC: can't connect %s\n",
widget->name); break;
} elseif (err == 0) /* already connected */ continue;
/* new */
dpcm_set_be_update_state(be, stream, SND_SOC_DPCM_UPDATE_BE); new++;
}
dev_dbg(fe->dev, "ASoC: found %d new BE paths\n", new); returnnew;
}
void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream)
{ struct snd_soc_dpcm *dpcm;
void dpcm_be_dai_stop(struct snd_soc_pcm_runtime *fe, int stream, int do_hw_free, struct snd_soc_dpcm *last)
{ struct snd_soc_dpcm *dpcm;
/* disable any enabled and non active backends */
for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_pcm_substream *be_substream =
snd_soc_dpcm_get_substream(be, stream);
if (dpcm == last) return;
/* is this op for this BE ? */ if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue;
if (be->dpcm[stream].users == 0) {
dev_err(be->dev, "ASoC: no users %s at close - state %s\n",
snd_pcm_direction_name(stream),
dpcm_state_string(be->dpcm[stream].state)); continue;
}
if (--be->dpcm[stream].users != 0) continue;
if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) { if (!do_hw_free) continue;
int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream)
{ struct snd_pcm_substream *fe_substream = snd_soc_dpcm_get_substream(fe, stream); struct snd_soc_pcm_runtime *be; struct snd_soc_dpcm *dpcm; int err, count = 0;
/* only startup BE DAIs that are either sinks or sources to this FE DAI */
for_each_dpcm_be(fe, stream, dpcm) { struct snd_pcm_substream *be_substream;
be = dpcm->be;
be_substream = snd_soc_dpcm_get_substream(be, stream);
if (!be_substream) {
dev_err(be->dev, "ASoC: no backend %s stream\n",
snd_pcm_direction_name(stream)); continue;
}
/* is this op for this BE ? */ if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue;
/* first time the dpcm is open ? */ if (be->dpcm[stream].users == DPCM_MAX_BE_USERS) {
dev_err(be->dev, "ASoC: too many users %s at open %s\n",
snd_pcm_direction_name(stream),
dpcm_state_string(be->dpcm[stream].state)); continue;
}
if (be->dpcm[stream].users++ != 0) continue;
if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_NEW) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) continue;
dev_dbg(be->dev, "ASoC: open %s BE %s\n",
snd_pcm_direction_name(stream), be->dai_link->name);
be_substream->runtime = fe_substream->runtime;
err = __soc_pcm_open(be, be_substream); if (err < 0) {
be->dpcm[stream].users--; if (be->dpcm[stream].users < 0)
dev_err(be->dev, "ASoC: no users %s at unwind %s\n",
snd_pcm_direction_name(stream),
dpcm_state_string(be->dpcm[stream].state));
for_each_rtd_cpu_dais(fe, i, dai) { conststruct snd_soc_pcm_stream *cpu_stream;
/* * Skip CPUs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details
*/ if (!snd_soc_dai_stream_valid(dai, stream)) continue;
for_each_rtd_codec_dais(be, i, dai) { /* * Skip CODECs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details
*/ if (!snd_soc_dai_stream_valid(dai, stream)) continue;
for_each_rtd_cpu_dais(be, i, dai) { /* * Skip CPUs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details
*/ if (!snd_soc_dai_stream_valid(dai, stream)) continue;
/* * chan min/max cannot be enforced if there are multiple CODEC * DAIs connected to a single CPU DAI, use CPU DAI's directly
*/ if (be->dai_link->num_codecs == 1) { conststruct snd_soc_pcm_stream *codec_stream = snd_soc_dai_get_pcm_stream(
snd_soc_rtd_to_codec(be, 0), stream);
for_each_rtd_dais(be, i, dai) { /* * Skip DAIs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details
*/ if (!snd_soc_dai_stream_valid(dai, stream)) continue;
pcm = snd_soc_dai_get_pcm_stream(dai, stream);
soc_pcm_hw_update_rate(hw, pcm);
}
}
}
staticint dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, int stream)
{ struct snd_soc_dpcm *dpcm; struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); struct snd_soc_dai *fe_cpu_dai; int err = 0; int i;
/* apply symmetry for FE */
soc_pcm_update_symmetry(fe_substream);
for_each_rtd_cpu_dais (fe, i, fe_cpu_dai) { /* Symmetry only applies if we've got an active stream. */
err = soc_pcm_apply_symmetry(fe_substream, fe_cpu_dai); if (err < 0) goto error;
}
/* disable any enabled and non active backends */
for_each_dpcm_be_rollback(fe, stream, dpcm) {
be = dpcm->be;
be_substream = snd_soc_dpcm_get_substream(be, stream);
if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue;
/* only allow hw_free() if no connected FEs are running */ if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) continue;
int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, int cmd)
{ struct snd_soc_pcm_runtime *be; bool pause_stop_transition; struct snd_soc_dpcm *dpcm; unsignedlong flags; int ret = 0;
be->dpcm[stream].be_start++; if (be->dpcm[stream].be_start != 1) goto next;
if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_PAUSED)
ret = soc_pcm_trigger(be_substream,
SNDRV_PCM_TRIGGER_PAUSE_RELEASE); else
ret = soc_pcm_trigger(be_substream,
SNDRV_PCM_TRIGGER_START); if (ret) {
be->dpcm[stream].be_start--; goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_RESUME: if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) goto next;
be->dpcm[stream].be_start++; if (be->dpcm[stream].be_start != 1) goto next;
ret = soc_pcm_trigger(be_substream, cmd); if (ret) {
be->dpcm[stream].be_start--; goto next;
}
be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (!be->dpcm[stream].be_start &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) &&
(be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) goto next;
if (be->dpcm[stream].be_pause != 0)
ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); else
ret = soc_pcm_trigger(be_substream, SNDRV_PCM_TRIGGER_STOP);
if (ret) { if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START)
be->dpcm[stream].be_start++; if (pause_stop_transition) {
fe->dpcm[stream].fe_pause = true;
be->dpcm[stream].be_pause++;
} goto next;
}
staticint dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, int cmd, bool fe_first)
{ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int ret;
/* call trigger on the frontend before the backend. */ if (fe_first) {
dev_dbg(fe->dev, "ASoC: pre trigger FE %s cmd %d\n",
fe->dai_link->name, cmd);
ret = soc_pcm_trigger(substream, cmd); if (ret < 0) goto end;
ret = dpcm_be_dai_trigger(fe, substream->stream, cmd);
} /* call trigger on the frontend after the backend. */ else {
ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); if (ret < 0) goto end;
dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n",
fe->dai_link->name, cmd);
ret = soc_pcm_trigger(substream, cmd);
}
end: return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd);
}
staticint dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd)
{ struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int stream = substream->stream; int ret = 0; int fe_first; enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream];
switch (trigger) { case SND_SOC_DPCM_TRIGGER_PRE:
fe_first = true; break; case SND_SOC_DPCM_TRIGGER_POST:
fe_first = false; break; default:
dev_err(fe->dev, "ASoC: invalid trigger cmd %d for %s\n", cmd,
fe->dai_link->name);
ret = -EINVAL; goto out;
}
switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_DRAIN:
ret = dpcm_dai_trigger_fe_be(substream, cmd, fe_first); break;
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.24 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.