diff options
Diffstat (limited to 'sound/soc/soc-pcm.c')
| -rw-r--r-- | sound/soc/soc-pcm.c | 3765 |
1 files changed, 1683 insertions, 2082 deletions
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 03f36e534050..6b134962c71c 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -14,7 +14,6 @@ #include <linux/init.h> #include <linux/delay.h> #include <linux/pinctrl/consumer.h> -#include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/export.h> @@ -24,156 +23,389 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dpcm.h> +#include <sound/soc-link.h> #include <sound/initval.h> -#define DPCM_MAX_BE_USERS 8 +#define soc_pcm_ret(rtd, ret) _soc_pcm_ret(rtd, __func__, ret) +static inline int _soc_pcm_ret(struct snd_soc_pcm_runtime *rtd, + const char *func, int ret) +{ + return snd_soc_ret(rtd->dev, ret, + "at %s() on %s\n", func, rtd->dai_link->name); +} + +/* is the current PCM operation for this FE ? */ +#if 0 +static int 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 ? */ +static int 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; +} + +static int snd_soc_dpcm_check_state(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, + int stream, + const enum 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; +} /* - * snd_soc_dai_stream_valid() - check if a DAI supports the given stream - * - * Returns true if the DAI supports the indicated stream type. + * 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. */ -static bool snd_soc_dai_stream_valid(struct snd_soc_dai *dai, int stream) +static int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) { - struct snd_soc_pcm_stream *codec_stream; + const enum snd_soc_dpcm_state state[] = { + SND_SOC_DPCM_STATE_START, + SND_SOC_DPCM_STATE_PAUSED, + SND_SOC_DPCM_STATE_SUSPEND, + }; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_stream = &dai->driver->playback; - else - codec_stream = &dai->driver->capture; + 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. + */ +static int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) +{ + const enum 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, + }; - /* If the codec specifies any rate at all, it supports the stream. */ - return codec_stream->rates; + return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state)); } -/** - * snd_soc_runtime_activate() - Increment active count for PCM runtime components - * @rtd: ASoC PCM runtime that is activated - * @stream: Direction of the PCM stream - * - * Increments 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->pcm_mutex being held +/* + * We can only prepare a BE DAI if any of it's FE are not prepared, + * running or paused for the specified stream direction. */ -void snd_soc_runtime_activate(struct snd_soc_pcm_runtime *rtd, int stream) +static int snd_soc_dpcm_can_be_prepared(struct snd_soc_pcm_runtime *fe, + struct snd_soc_pcm_runtime *be, int stream) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int i; + const enum snd_soc_dpcm_state state[] = { + SND_SOC_DPCM_STATE_START, + SND_SOC_DPCM_STATE_PAUSED, + SND_SOC_DPCM_STATE_PREPARE, + }; - lockdep_assert_held(&rtd->pcm_mutex); + return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state)); +} - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - cpu_dai->playback_active++; - for_each_rtd_codec_dai(rtd, i, codec_dai) - codec_dai->playback_active++; - } else { - cpu_dai->capture_active++; - for_each_rtd_codec_dai(rtd, i, codec_dai) - codec_dai->capture_active++; +#define DPCM_MAX_BE_USERS 8 + +static inline const char *soc_cpu_dai_name(struct snd_soc_pcm_runtime *rtd) +{ + return (rtd)->dai_link->num_cpus == 1 ? snd_soc_rtd_to_cpu(rtd, 0)->name : "multicpu"; +} +static inline const char *soc_codec_dai_name(struct snd_soc_pcm_runtime *rtd) +{ + return (rtd)->dai_link->num_codecs == 1 ? snd_soc_rtd_to_codec(rtd, 0)->name : "multicodec"; +} + +static const char *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"; } - cpu_dai->active++; - cpu_dai->component->active++; - for_each_rtd_codec_dai(rtd, i, codec_dai) { - codec_dai->active++; - codec_dai->component->active++; + return "unknown"; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, + int stream, char *buf, size_t size) +{ + struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; + struct snd_soc_dpcm *dpcm; + ssize_t offset = 0; + + /* FE state */ + offset += scnprintf(buf + offset, size - offset, + "[%s - %s]\n", fe->dai_link->name, + stream ? "Capture" : "Playback"); + + offset += scnprintf(buf + offset, size - offset, "State: %s\n", + dpcm_state_string(fe->dpcm[stream].state)); + + if ((fe->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (fe->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += scnprintf(buf + offset, size - offset, + "Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + + /* BEs state */ + offset += scnprintf(buf + offset, size - offset, "Backends:\n"); + + if (list_empty(&fe->dpcm[stream].be_clients)) { + offset += scnprintf(buf + offset, size - offset, + " No active DSP links\n"); + goto out; } + + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_soc_pcm_runtime *be = dpcm->be; + params = &be->dpcm[stream].hw_params; + + offset += scnprintf(buf + offset, size - offset, + "- %s\n", be->dai_link->name); + + offset += scnprintf(buf + offset, size - offset, + " State: %s\n", + dpcm_state_string(be->dpcm[stream].state)); + + if ((be->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && + (be->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) + offset += scnprintf(buf + offset, size - offset, + " Hardware Params: " + "Format = %s, Channels = %d, Rate = %d\n", + snd_pcm_format_name(params_format(params)), + params_channels(params), + params_rate(params)); + } +out: + return offset; +} + +static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct snd_soc_pcm_runtime *fe = file->private_data; + ssize_t out_count = PAGE_SIZE, offset = 0, ret = 0; + int stream; + char *buf; + + if (fe->dai_link->num_cpus > 1) + return snd_soc_ret(fe->dev, -EINVAL, + "%s doesn't support Multi CPU yet\n", __func__); + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + snd_soc_dpcm_mutex_lock(fe); + for_each_pcm_streams(stream) + if (snd_soc_dai_stream_valid(snd_soc_rtd_to_cpu(fe, 0), stream)) + offset += dpcm_show_state(fe, stream, + buf + offset, + out_count - offset); + snd_soc_dpcm_mutex_unlock(fe); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); + + kfree(buf); + return ret; +} + +static const struct file_operations dpcm_state_fops = { + .open = simple_open, + .read = dpcm_state_read_file, + .llseek = default_llseek, +}; + +void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) +{ + if (!rtd->dai_link->dynamic) + return; + + if (!rtd->card->debugfs_card_root) + return; + + rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, + rtd->card->debugfs_card_root); + + debugfs_create_file("state", 0444, rtd->debugfs_dpcm_root, + rtd, &dpcm_state_fops); +} + +static void dpcm_create_debugfs_state(struct snd_soc_dpcm *dpcm, int stream) +{ + char *name; + + name = kasprintf(GFP_KERNEL, "%s:%s", dpcm->be->dai_link->name, + snd_pcm_direction_name(stream)); + if (name) { + dpcm->debugfs_state = debugfs_create_dir( + name, dpcm->fe->debugfs_dpcm_root); + debugfs_create_u32("state", 0644, dpcm->debugfs_state, + &dpcm->state); + kfree(name); + } +} + +static void dpcm_remove_debugfs_state(struct snd_soc_dpcm *dpcm) +{ + debugfs_remove_recursive(dpcm->debugfs_state); +} + +#else +static inline void dpcm_create_debugfs_state(struct snd_soc_dpcm *dpcm, + int stream) +{ +} + +static inline void dpcm_remove_debugfs_state(struct snd_soc_dpcm *dpcm) +{ +} +#endif + +/* 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. + */ +static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); +static void 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_pcm_stream_lock_irq(substream); + if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) { + dpcm_fe_dai_do_trigger(substream, + fe->dpcm[stream].trigger_pending - 1); + fe->dpcm[stream].trigger_pending = 0; + } + fe->dpcm[stream].runtime_update = state; + snd_pcm_stream_unlock_irq(substream); +} + +static void dpcm_set_be_update_state(struct snd_soc_pcm_runtime *be, + int stream, enum snd_soc_dpcm_update state) +{ + be->dpcm[stream].runtime_update = state; } /** - * snd_soc_runtime_deactivate() - Decrement active count for PCM runtime components - * @rtd: ASoC PCM runtime that is deactivated + * 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. * - * Decrements the active count for all the DAIs and components attached to a PCM - * runtime. Should typically be called when a stream is closed. + * 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->pcm_mutex being held + * Must be called with the rtd->card->pcm_mutex being held */ -void snd_soc_runtime_deactivate(struct snd_soc_pcm_runtime *rtd, int stream) +void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, + int stream, int action) { - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct snd_soc_dai *dai; int i; - lockdep_assert_held(&rtd->pcm_mutex); + snd_soc_dpcm_mutex_assert_held(rtd); - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - cpu_dai->playback_active--; - for_each_rtd_codec_dai(rtd, i, codec_dai) - codec_dai->playback_active--; - } else { - cpu_dai->capture_active--; - for_each_rtd_codec_dai(rtd, i, codec_dai) - codec_dai->capture_active--; - } + for_each_rtd_dais(rtd, i, dai) + snd_soc_dai_action(dai, stream, action); - cpu_dai->active--; - cpu_dai->component->active--; - for_each_rtd_codec_dai(rtd, i, codec_dai) { - codec_dai->component->active--; - codec_dai->active--; + /* 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 it the DAI link has + * 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_rtdcom_list *rtdcom; struct snd_soc_component *component; - bool ignore = true; + int i; if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) return true; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + for_each_rtd_components(rtd, i, component) + if (component->driver->use_pmdown_time) + /* No need to go through all components */ + return false; - ignore &= !component->driver->use_pmdown_time; - } - - return ignore; -} - -/** - * snd_soc_set_runtime_hwparams - set the runtime hardware parameters - * @substream: the pcm substream - * @hw: the hardware parameters - * - * Sets the substream runtime hardware parameters. - */ -int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, - const struct snd_pcm_hardware *hw) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - runtime->hw.info = hw->info; - runtime->hw.formats = hw->formats; - runtime->hw.period_bytes_min = hw->period_bytes_min; - runtime->hw.period_bytes_max = hw->period_bytes_max; - runtime->hw.periods_min = hw->periods_min; - runtime->hw.periods_max = hw->periods_max; - runtime->hw.buffer_bytes_max = hw->buffer_bytes_max; - runtime->hw.fifo_size = hw->fifo_size; - return 0; + return true; } -EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); /* DPCM stream event, send event to FE and all active BEs. */ -int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, - int event) +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; @@ -189,63 +421,48 @@ int dpcm_dapm_stream_event(struct snd_soc_pcm_runtime *fe, int dir, } snd_soc_dapm_stream_event(fe, dir, event); +} - return 0; +static void soc_pcm_set_dai_params(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params) +{ + if (params) { + dai->symmetric_rate = params_rate(params); + dai->symmetric_channels = params_channels(params); + dai->symmetric_sample_bits = snd_pcm_format_physical_width(params_format(params)); + } else { + dai->symmetric_rate = 0; + dai->symmetric_channels = 0; + dai->symmetric_sample_bits = 0; + } } static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, struct snd_soc_dai *soc_dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret; - if (soc_dai->rate && (soc_dai->driver->symmetric_rates || - rtd->dai_link->symmetric_rates)) { - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", - soc_dai->rate); - - ret = snd_pcm_hw_constraint_single(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - soc_dai->rate); - if (ret < 0) { - dev_err(soc_dai->dev, - "ASoC: Unable to apply rate constraint: %d\n", - ret); - return ret; - } - } - - if (soc_dai->channels && (soc_dai->driver->symmetric_channels || - rtd->dai_link->symmetric_channels)) { - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d channel(s)\n", - soc_dai->channels); + if (!snd_soc_dai_active(soc_dai)) + return 0; - ret = snd_pcm_hw_constraint_single(substream->runtime, - SNDRV_PCM_HW_PARAM_CHANNELS, - soc_dai->channels); - if (ret < 0) { - dev_err(soc_dai->dev, - "ASoC: Unable to apply channel symmetry constraint: %d\n", - ret); - return ret; - } +#define __soc_pcm_apply_symmetry(name, NAME) \ + if (soc_dai->symmetric_##name && \ + (soc_dai->driver->symmetric_##name || rtd->dai_link->symmetric_##name)) { \ + dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %s to %d\n",\ + #name, soc_dai->symmetric_##name); \ + \ + ret = snd_pcm_hw_constraint_single(substream->runtime, \ + SNDRV_PCM_HW_PARAM_##NAME,\ + soc_dai->symmetric_##name); \ + if (ret < 0) \ + return snd_soc_ret(soc_dai->dev, ret, \ + "Unable to apply %s constraint\n", #name); \ } - if (soc_dai->sample_bits && (soc_dai->driver->symmetric_samplebits || - rtd->dai_link->symmetric_samplebits)) { - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %d sample bits\n", - soc_dai->sample_bits); - - ret = snd_pcm_hw_constraint_single(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - soc_dai->sample_bits); - if (ret < 0) { - dev_err(soc_dai->dev, - "ASoC: Unable to apply sample bits symmetry constraint: %d\n", - ret); - return ret; - } - } + __soc_pcm_apply_symmetry(rate, RATE); + __soc_pcm_apply_symmetry(channels, CHANNELS); + __soc_pcm_apply_symmetry(sample_bits, SAMPLE_BITS); return 0; } @@ -253,79 +470,62 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream, static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - unsigned int rate, channels, sample_bits, symmetry, i; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai d; + struct snd_soc_dai *dai; + struct snd_soc_dai *cpu_dai; + unsigned int symmetry, i; - rate = params_rate(params); - channels = params_channels(params); - sample_bits = snd_pcm_format_physical_width(params_format(params)); + d.name = __func__; + soc_pcm_set_dai_params(&d, params); + +#define __soc_pcm_params_symmetry(xxx) \ + symmetry = rtd->dai_link->symmetric_##xxx; \ + for_each_rtd_dais(rtd, i, dai) \ + symmetry |= dai->driver->symmetric_##xxx; \ + \ + if (symmetry) \ + for_each_rtd_cpu_dais(rtd, i, cpu_dai) \ + if (!snd_soc_dai_is_dummy(cpu_dai) && \ + cpu_dai->symmetric_##xxx && \ + cpu_dai->symmetric_##xxx != d.symmetric_##xxx) \ + return snd_soc_ret(rtd->dev, -EINVAL, \ + "unmatched %s symmetry: %s:%d - %s:%d\n", \ + #xxx, cpu_dai->name, cpu_dai->symmetric_##xxx, \ + d.name, d.symmetric_##xxx); /* reject unmatched parameters when applying symmetry */ - symmetry = cpu_dai->driver->symmetric_rates || - rtd->dai_link->symmetric_rates; - - for_each_rtd_codec_dai(rtd, i, codec_dai) - symmetry |= codec_dai->driver->symmetric_rates; - - if (symmetry && cpu_dai->rate && cpu_dai->rate != rate) { - dev_err(rtd->dev, "ASoC: unmatched rate symmetry: %d - %d\n", - cpu_dai->rate, rate); - return -EINVAL; - } - - symmetry = cpu_dai->driver->symmetric_channels || - rtd->dai_link->symmetric_channels; - - for_each_rtd_codec_dai(rtd, i, codec_dai) - symmetry |= codec_dai->driver->symmetric_channels; - - if (symmetry && cpu_dai->channels && cpu_dai->channels != channels) { - dev_err(rtd->dev, "ASoC: unmatched channel symmetry: %d - %d\n", - cpu_dai->channels, channels); - return -EINVAL; - } - - symmetry = cpu_dai->driver->symmetric_samplebits || - rtd->dai_link->symmetric_samplebits; - - for_each_rtd_codec_dai(rtd, i, codec_dai) - symmetry |= codec_dai->driver->symmetric_samplebits; - - if (symmetry && cpu_dai->sample_bits && cpu_dai->sample_bits != sample_bits) { - dev_err(rtd->dev, "ASoC: unmatched sample bits symmetry: %d - %d\n", - cpu_dai->sample_bits, sample_bits); - return -EINVAL; - } + __soc_pcm_params_symmetry(rate); + __soc_pcm_params_symmetry(channels); + __soc_pcm_params_symmetry(sample_bits); return 0; } -static bool soc_pcm_has_symmetry(struct snd_pcm_substream *substream) +static void soc_pcm_update_symmetry(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai_driver *cpu_driver = rtd->cpu_dai->driver; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_dai_link *link = rtd->dai_link; - struct snd_soc_dai *codec_dai; + struct snd_soc_dai *dai; unsigned int symmetry, i; - symmetry = cpu_driver->symmetric_rates || link->symmetric_rates || - cpu_driver->symmetric_channels || link->symmetric_channels || - cpu_driver->symmetric_samplebits || link->symmetric_samplebits; + symmetry = link->symmetric_rate || + link->symmetric_channels || + link->symmetric_sample_bits; - for_each_rtd_codec_dai(rtd, i, codec_dai) + for_each_rtd_dais(rtd, i, dai) symmetry = symmetry || - codec_dai->driver->symmetric_rates || - codec_dai->driver->symmetric_channels || - codec_dai->driver->symmetric_samplebits; + dai->driver->symmetric_rate || + dai->driver->symmetric_channels || + dai->driver->symmetric_sample_bits; - return symmetry; + if (symmetry) + substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; } static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret; if (!bits) @@ -339,424 +539,390 @@ static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) static void soc_pcm_apply_msb(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai; struct snd_soc_dai *codec_dai; + int stream = substream->stream; int i; - unsigned int bits = 0, cpu_bits; + unsigned int bits = 0, cpu_bits = 0; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->playback.sig_bits == 0) { - bits = 0; - break; - } - bits = max(codec_dai->driver->playback.sig_bits, bits); + for_each_rtd_codec_dais(rtd, i, codec_dai) { + const struct snd_soc_pcm_stream *pcm_codec = snd_soc_dai_get_pcm_stream(codec_dai, stream); + + if (pcm_codec->sig_bits == 0) { + bits = 0; + break; } - cpu_bits = cpu_dai->driver->playback.sig_bits; - } else { - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->capture.sig_bits == 0) { - bits = 0; - break; - } - bits = max(codec_dai->driver->capture.sig_bits, bits); + bits = max(pcm_codec->sig_bits, bits); + } + + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + const struct snd_soc_pcm_stream *pcm_cpu = snd_soc_dai_get_pcm_stream(cpu_dai, stream); + + if (pcm_cpu->sig_bits == 0) { + cpu_bits = 0; + break; } - cpu_bits = cpu_dai->driver->capture.sig_bits; + cpu_bits = max(pcm_cpu->sig_bits, cpu_bits); } soc_pcm_set_msb(substream, bits); soc_pcm_set_msb(substream, cpu_bits); } -static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) +static void soc_pcm_hw_init(struct snd_pcm_hardware *hw, bool force) +{ + if (force) { + hw->rates = UINT_MAX; + hw->rate_min = 0; + hw->rate_max = UINT_MAX; + hw->channels_min = 0; + hw->channels_max = UINT_MAX; + hw->formats = ULLONG_MAX; + } else { + /* Preserve initialized parameters */ + if (!hw->rates) + hw->rates = UINT_MAX; + if (!hw->rate_max) + hw->rate_max = UINT_MAX; + if (!hw->channels_max) + hw->channels_max = UINT_MAX; + if (!hw->formats) + hw->formats = ULLONG_MAX; + } +} + +static void soc_pcm_hw_update_rate(struct snd_pcm_hardware *hw, + const struct snd_soc_pcm_stream *p) +{ + hw->rates = snd_pcm_rate_mask_intersect(hw->rates, p->rates); + + /* setup hw->rate_min/max via hw->rates first */ + snd_pcm_hw_limit_rates(hw); + + /* update hw->rate_min/max by snd_soc_pcm_stream */ + hw->rate_min = max(hw->rate_min, p->rate_min); + hw->rate_max = min_not_zero(hw->rate_max, p->rate_max); +} + +static void soc_pcm_hw_update_chan(struct snd_pcm_hardware *hw, + const struct snd_soc_pcm_stream *p) +{ + hw->channels_min = max(hw->channels_min, p->channels_min); + hw->channels_max = min(hw->channels_max, p->channels_max); +} + +static void soc_pcm_hw_update_format(struct snd_pcm_hardware *hw, + const struct snd_soc_pcm_stream *p) +{ + hw->formats &= p->formats; + hw->subformats &= p->subformats; +} + +/** + * 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_pcm_runtime *runtime = substream->runtime; - struct snd_pcm_hardware *hw = &runtime->hw; - struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai; - struct snd_soc_dai_driver *cpu_dai_drv = rtd->cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv; - struct snd_soc_pcm_stream *codec_stream; - struct snd_soc_pcm_stream *cpu_stream; - unsigned int chan_min = 0, chan_max = UINT_MAX; - unsigned int rate_min = 0, rate_max = UINT_MAX; - unsigned int rates = UINT_MAX; - u64 formats = ULLONG_MAX; + struct snd_soc_dai *cpu_dai; + const struct snd_soc_pcm_stream *codec_stream; + const struct snd_soc_pcm_stream *cpu_stream; + unsigned int cpu_chan_min = 0, cpu_chan_max = UINT_MAX; int i; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_stream = &cpu_dai_drv->playback; - else - cpu_stream = &cpu_dai_drv->capture; + soc_pcm_hw_init(hw, true); - /* first calculate min/max only for CODECs in the DAI link */ - for_each_rtd_codec_dai(rtd, i, codec_dai) { + /* 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; + + cpu_stream = snd_soc_dai_get_pcm_stream(cpu_dai, stream); + + soc_pcm_hw_update_chan(hw, cpu_stream); + soc_pcm_hw_update_rate(hw, cpu_stream); + soc_pcm_hw_update_format(hw, cpu_stream); + } + cpu_chan_min = hw->channels_min; + cpu_chan_max = hw->channels_max; + + /* 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. - * At least one CODEC should match, otherwise we should have - * bailed out on a higher level, since there would be no - * CODEC to support the transfer direction in that case. */ - if (!snd_soc_dai_stream_valid(codec_dai, - substream->stream)) + if (!snd_soc_dai_stream_valid(codec_dai, stream)) continue; - codec_dai_drv = codec_dai->driver; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_stream = &codec_dai_drv->playback; - else - codec_stream = &codec_dai_drv->capture; - chan_min = max(chan_min, codec_stream->channels_min); - chan_max = min(chan_max, codec_stream->channels_max); - rate_min = max(rate_min, codec_stream->rate_min); - rate_max = min_not_zero(rate_max, codec_stream->rate_max); - formats &= codec_stream->formats; - rates = snd_pcm_rate_mask_intersect(codec_stream->rates, rates); + codec_stream = snd_soc_dai_get_pcm_stream(codec_dai, stream); + + soc_pcm_hw_update_chan(hw, codec_stream); + soc_pcm_hw_update_rate(hw, codec_stream); + soc_pcm_hw_update_format(hw, codec_stream); } + /* 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 a single CPU DAI, use CPU DAI's directly and let + * connected to CPU DAI(s), use CPU DAI's directly and let * channel allocation be fixed up later */ - if (rtd->num_codecs > 1) { - chan_min = cpu_stream->channels_min; - chan_max = cpu_stream->channels_max; + if (rtd->dai_link->num_codecs > 1) { + hw->channels_min = cpu_chan_min; + hw->channels_max = cpu_chan_max; } - hw->channels_min = max(chan_min, cpu_stream->channels_min); - hw->channels_max = min(chan_max, cpu_stream->channels_max); - if (hw->formats) - hw->formats &= formats & cpu_stream->formats; - else - hw->formats = formats & cpu_stream->formats; - hw->rates = snd_pcm_rate_mask_intersect(rates, cpu_stream->rates); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_runtime_calc_hw); - snd_pcm_limit_hw_rates(runtime); +static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream) +{ + struct snd_pcm_hardware *hw = &substream->runtime->hw; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + u64 formats = hw->formats; + + /* + * 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); - hw->rate_min = max(hw->rate_min, cpu_stream->rate_min); - hw->rate_min = max(hw->rate_min, rate_min); - hw->rate_max = min_not_zero(hw->rate_max, cpu_stream->rate_max); - hw->rate_max = min_not_zero(hw->rate_max, rate_max); + if (formats) + hw->formats &= formats; } -static int soc_pcm_components_close(struct snd_pcm_substream *substream, - struct snd_soc_component *last) +static int soc_pcm_components_open(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; + int i, ret = 0; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (component == last) + for_each_rtd_components(rtd, i, component) { + ret = snd_soc_component_module_get_when_open(component, substream); + if (ret < 0) break; - if (!component->driver->ops || - !component->driver->ops->close) - continue; - - component->driver->ops->close(substream); + ret = snd_soc_component_open(component, substream); + if (ret < 0) + break; } - return 0; + return ret; } -/* - * 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. - */ -static int soc_pcm_open(struct snd_pcm_substream *substream) +static int soc_pcm_components_close(struct snd_pcm_substream *substream, + int rollback) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - const char *codec_dai_name = "multicodec"; int i, ret = 0; - pinctrl_pm_select_default_state(cpu_dai->dev); - for_each_rtd_codec_dai(rtd, i, codec_dai) - pinctrl_pm_select_default_state(codec_dai->dev); - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + for_each_rtd_components(rtd, i, component) { + int r = snd_soc_component_close(component, substream, rollback); + if (r < 0) + ret = r; /* use last ret */ - pm_runtime_get_sync(component->dev); + snd_soc_component_module_put_when_close(component, substream, rollback); } - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); - - /* startup the audio subsystem */ - if (cpu_dai->driver->ops->startup) { - ret = cpu_dai->driver->ops->startup(substream, cpu_dai); - if (ret < 0) { - dev_err(cpu_dai->dev, "ASoC: can't open interface" - " %s: %d\n", cpu_dai->name, ret); - goto out; - } - } - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + return ret; +} - if (!component->driver->ops || - !component->driver->ops->open) - continue; +static int 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; - ret = component->driver->ops->open(substream); - if (ret < 0) { - dev_err(component->dev, - "ASoC: can't open component %s: %d\n", - component->name, ret); - goto component_err; - } - } - component = NULL; - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->startup) { - ret = codec_dai->driver->ops->startup(substream, - codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, - "ASoC: can't open codec %s: %d\n", - codec_dai->name, ret); - goto codec_dai_err; - } - } + snd_soc_dpcm_mutex_assert_held(rtd); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_dai->tx_mask = 0; - else - codec_dai->rx_mask = 0; - } + if (!rollback) { + snd_soc_runtime_deactivate(rtd, substream->stream); - if (rtd->dai_link->ops->startup) { - ret = rtd->dai_link->ops->startup(substream); - if (ret < 0) { - pr_err("ASoC: %s startup failed: %d\n", - rtd->dai_link->name, ret); - goto machine_err; + /* 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); } } - /* 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); - - if (rtd->num_codecs == 1) - codec_dai_name = rtd->codec_dai->name; - - if (soc_pcm_has_symmetry(substream)) - runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; - - ret = -EINVAL; - if (!runtime->hw.rates) { - printk(KERN_ERR "ASoC: %s <-> %s No matching rates\n", - codec_dai_name, cpu_dai->name); - goto config_err; - } - if (!runtime->hw.formats) { - printk(KERN_ERR "ASoC: %s <-> %s No matching formats\n", - codec_dai_name, cpu_dai->name); - goto config_err; - } - if (!runtime->hw.channels_min || !runtime->hw.channels_max || - runtime->hw.channels_min > runtime->hw.channels_max) { - printk(KERN_ERR "ASoC: %s <-> %s No matching channels\n", - codec_dai_name, cpu_dai->name); - goto config_err; - } - - soc_pcm_apply_msb(substream); + for_each_rtd_dais_reverse(rtd, i, dai) + snd_soc_dai_shutdown(dai, substream, rollback); - /* Symmetry only applies if we've already got an active stream. */ - if (cpu_dai->active) { - ret = soc_pcm_apply_symmetry(substream, cpu_dai); - if (ret != 0) - goto config_err; - } + snd_soc_link_shutdown(substream, rollback); - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->active) { - ret = soc_pcm_apply_symmetry(substream, codec_dai); - if (ret != 0) - goto config_err; - } - } + soc_pcm_components_close(substream, rollback); - pr_debug("ASoC: %s <-> %s info:\n", - codec_dai_name, cpu_dai->name); - pr_debug("ASoC: rate mask 0x%x\n", runtime->hw.rates); - pr_debug("ASoC: min ch %d max ch %d\n", runtime->hw.channels_min, - runtime->hw.channels_max); - pr_debug("ASoC: min rate %d max rate %d\n", runtime->hw.rate_min, - runtime->hw.rate_max); + snd_soc_pcm_component_pm_runtime_put(rtd, substream, rollback); -dynamic: + for_each_rtd_components(rtd, i, component) + if (!snd_soc_component_active(component)) + pinctrl_pm_select_sleep_state(component->dev); - snd_soc_runtime_activate(rtd, substream->stream); - - mutex_unlock(&rtd->pcm_mutex); return 0; +} -config_err: - if (rtd->dai_link->ops->shutdown) - rtd->dai_link->ops->shutdown(substream); - -machine_err: - i = rtd->num_codecs; - -codec_dai_err: - for_each_rtd_codec_dai_rollback(rtd, i, codec_dai) { - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); - } - -component_err: - soc_pcm_components_close(substream, component); - - if (cpu_dai->driver->ops->shutdown) - cpu_dai->driver->ops->shutdown(substream, cpu_dai); -out: - mutex_unlock(&rtd->pcm_mutex); - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - pm_runtime_mark_last_busy(component->dev); - pm_runtime_put_autosuspend(component->dev); - } +/* + * 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. + */ +static int __soc_pcm_close(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + return soc_pcm_clean(rtd, substream, 0); +} - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); - } - if (!cpu_dai->active) - pinctrl_pm_select_sleep_state(cpu_dai->dev); +/* PCM close ops for non-DPCM streams */ +static int soc_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - return ret; + snd_soc_dpcm_mutex_lock(rtd); + __soc_pcm_close(rtd, substream); + snd_soc_dpcm_mutex_unlock(rtd); + return 0; } -/* - * Power down the audio subsystem pmdown_time msecs after close is called. - * This is to ensure there are no pops or clicks in between any music tracks - * due to DAPM power cycling. - */ -static void close_delayed_work(struct work_struct *work) +static int soc_hw_sanity_check(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = - container_of(work, struct snd_soc_pcm_runtime, delayed_work.work); - struct snd_soc_dai *codec_dai = rtd->codec_dais[0]; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_pcm_hardware *hw = &substream->runtime->hw; + const char *name_cpu = soc_cpu_dai_name(rtd); + const char *name_codec = soc_codec_dai_name(rtd); + const char *err_msg; + struct device *dev = rtd->dev; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + err_msg = "rates"; + if (!hw->rates) + goto config_err; - dev_dbg(rtd->dev, "ASoC: pop wq checking: %s status: %s waiting: %s\n", - codec_dai->driver->playback.stream_name, - codec_dai->playback_active ? "active" : "inactive", - rtd->pop_wait ? "yes" : "no"); + err_msg = "formats"; + if (!hw->formats) + goto config_err; - /* are we waiting on this codec DAI stream */ - if (rtd->pop_wait == 1) { - rtd->pop_wait = 0; - snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, - SND_SOC_DAPM_STREAM_STOP); - } + err_msg = "channels"; + if (!hw->channels_min || !hw->channels_max || + hw->channels_min > hw->channels_max) + goto config_err; + + dev_dbg(dev, "ASoC: %s <-> %s info:\n", name_codec, + name_cpu); + dev_dbg(dev, "ASoC: rate mask 0x%x\n", hw->rates); + dev_dbg(dev, "ASoC: ch min %d max %d\n", hw->channels_min, + hw->channels_max); + dev_dbg(dev, "ASoC: rate min %d max %d\n", hw->rate_min, + hw->rate_max); - mutex_unlock(&rtd->pcm_mutex); + return 0; + +config_err: + return snd_soc_ret(dev, -EINVAL, + "%s <-> %s No matching %s\n", name_codec, name_cpu, err_msg); } /* - * 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. + * 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. */ -static int soc_pcm_close(struct snd_pcm_substream *substream) +static int __soc_pcm_open(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int i; - - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + struct snd_soc_dai *dai; + int i, ret = 0; - snd_soc_runtime_deactivate(rtd, substream->stream); + snd_soc_dpcm_mutex_assert_held(rtd); - /* clear the corresponding DAIs rate when inactive */ - if (!cpu_dai->active) - cpu_dai->rate = 0; + for_each_rtd_components(rtd, i, component) + pinctrl_pm_select_default_state(component->dev); - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (!codec_dai->active) - codec_dai->rate = 0; - } + ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream); + if (ret < 0) + goto err; - snd_soc_dai_digital_mute(cpu_dai, 1, substream->stream); + ret = soc_pcm_components_open(substream); + if (ret < 0) + goto err; - if (cpu_dai->driver->ops->shutdown) - cpu_dai->driver->ops->shutdown(substream, cpu_dai); + ret = snd_soc_link_startup(substream); + if (ret < 0) + goto err; - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + /* startup the audio subsystem */ + for_each_rtd_dais(rtd, i, dai) { + ret = snd_soc_dai_startup(dai, substream); + if (ret < 0) + goto err; } - if (rtd->dai_link->ops->shutdown) - rtd->dai_link->ops->shutdown(substream); + /* Dynamic PCM DAI links compat checks use dynamic capabilities */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; - soc_pcm_components_close(substream, NULL); + /* Check that the codec and cpu DAIs are compatible */ + soc_pcm_init_runtime_hw(substream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (snd_soc_runtime_ignore_pmdown_time(rtd)) { - /* powered down playback stream now */ - snd_soc_dapm_stream_event(rtd, - SNDRV_PCM_STREAM_PLAYBACK, - SND_SOC_DAPM_STREAM_STOP); - } else { - /* start delayed pop wq here for playback streams */ - rtd->pop_wait = 1; - queue_delayed_work(system_power_efficient_wq, - &rtd->delayed_work, - msecs_to_jiffies(rtd->pmdown_time)); - } - } else { - /* capture streams can be powered down now */ - snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, - SND_SOC_DAPM_STREAM_STOP); - } + soc_pcm_update_symmetry(substream); - mutex_unlock(&rtd->pcm_mutex); + ret = soc_hw_sanity_check(substream); + if (ret < 0) + goto err; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + soc_pcm_apply_msb(substream); - pm_runtime_mark_last_busy(component->dev); - pm_runtime_put_autosuspend(component->dev); + /* 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); - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (!codec_dai->active) - pinctrl_pm_select_sleep_state(codec_dai->dev); - } - if (!cpu_dai->active) - pinctrl_pm_select_sleep_state(cpu_dai->dev); + return soc_pcm_ret(rtd, ret); +} - return 0; +/* PCM open ops for non-DPCM streams */ +static int 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; } /* @@ -764,62 +930,25 @@ static int soc_pcm_close(struct snd_pcm_substream *substream) * rate, etc. This function is non atomic and can be called multiple times, * it can refer to the runtime info. */ -static int soc_pcm_prepare(struct snd_pcm_substream *substream) +static int __soc_pcm_prepare(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; + struct snd_soc_dai *dai; int i, ret = 0; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); - - if (rtd->dai_link->ops->prepare) { - ret = rtd->dai_link->ops->prepare(substream); - if (ret < 0) { - dev_err(rtd->card->dev, "ASoC: machine prepare error:" - " %d\n", ret); - goto out; - } - } - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->prepare) - continue; + snd_soc_dpcm_mutex_assert_held(rtd); - ret = component->driver->ops->prepare(substream); - if (ret < 0) { - dev_err(component->dev, - "ASoC: platform prepare error: %d\n", ret); - goto out; - } - } + ret = snd_soc_link_prepare(substream); + if (ret < 0) + goto out; - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->prepare) { - ret = codec_dai->driver->ops->prepare(substream, - codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, - "ASoC: codec DAI prepare error: %d\n", - ret); - goto out; - } - } - } + ret = snd_soc_pcm_component_prepare(substream); + if (ret < 0) + goto out; - if (cpu_dai->driver->ops->prepare) { - ret = cpu_dai->driver->ops->prepare(substream, cpu_dai); - if (ret < 0) { - dev_err(cpu_dai->dev, - "ASoC: cpu DAI prepare error: %d\n", ret); - 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 && @@ -831,13 +960,37 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START); - for_each_rtd_codec_dai(rtd, i, codec_dai) - snd_soc_dai_digital_mute(codec_dai, 0, - substream->stream); - snd_soc_dai_digital_mute(cpu_dai, 0, substream->stream); + 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: - mutex_unlock(&rtd->pcm_mutex); + /* + * 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 */ +static int 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; } @@ -852,57 +1005,61 @@ static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, interval->max = channels; } -int soc_dai_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream, int rollback) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - int ret; + struct snd_soc_dai *dai; + int i; - /* perform any topology hw_params fixups before DAI */ - if (rtd->dai_link->be_hw_params_fixup) { - ret = rtd->dai_link->be_hw_params_fixup(rtd, params); - if (ret < 0) { - dev_err(rtd->dev, - "ASoC: hw_params topology fixup failed %d\n", - ret); - return ret; - } - } + 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 (dai->driver->ops->hw_params) { - ret = dai->driver->ops->hw_params(substream, params, dai); - if (ret < 0) { - dev_err(dai->dev, "ASoC: can't set %s hw params: %d\n", - dai->name, ret); - return ret; + 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); } } - return 0; -} + /* run the stream event */ + snd_soc_dapm_stream_stop(rtd, substream->stream); -static int soc_pcm_components_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_component *last) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; + /* free any machine hw params */ + snd_soc_link_hw_free(substream, rollback); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + /* free any component resources */ + snd_soc_pcm_component_hw_free(substream, rollback); - if (component == last) - break; + /* 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); - if (!component->driver->ops || - !component->driver->ops->hw_free) - continue; + return 0; +} - component->driver->ops->hw_free(substream); - } +/* + * Frees resources allocated by hw_params, can be called multiple times + */ +static int __soc_pcm_hw_free(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + return soc_pcm_hw_clean(rtd, substream, 0); +} - return 0; +/* hw_free PCM ops for non-DPCM streams */ +static int 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; } /* @@ -910,28 +1067,27 @@ static int soc_pcm_components_hw_free(struct snd_pcm_substream *substream, * function can also be called multiple times and can allocate buffers * (using snd_pcm_lib_* ). It's non-atomic. */ -static int soc_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + 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; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); - if (rtd->dai_link->ops->hw_params) { - ret = rtd->dai_link->ops->hw_params(substream, params); - if (ret < 0) { - dev_err(rtd->card->dev, "ASoC: machine hw_params" - " failed: %d\n", ret); - goto out; - } - } + 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_dai(rtd, i, codec_dai) { - struct snd_pcm_hw_params codec_params; + for_each_rtd_codec_dais(rtd, i, codec_dai) { + unsigned int tdm_mask = snd_soc_dai_tdm_mask_get(codec_dai, substream->stream); /* * Skip CODECs which don't support the current stream type, @@ -951,255 +1107,190 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, continue; /* copy params for each codec */ - codec_params = *params; + tmp_params = *params; /* fixup params based on TDM slot masks */ - if (codec_dai->tx_mask) - soc_pcm_codec_params_fixup(&codec_params, - codec_dai->tx_mask); - if (codec_dai->rx_mask) - soc_pcm_codec_params_fixup(&codec_params, - codec_dai->rx_mask); - - ret = soc_dai_hw_params(substream, &codec_params, codec_dai); + 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 codec_err; + goto out; - codec_dai->rate = params_rate(&codec_params); - codec_dai->channels = params_channels(&codec_params); - codec_dai->sample_bits = snd_pcm_format_physical_width( - params_format(&codec_params)); + soc_pcm_set_dai_params(codec_dai, &tmp_params); + snd_soc_dapm_update_dai(substream, &tmp_params, codec_dai); } - ret = soc_dai_hw_params(substream, params, cpu_dai); - if (ret < 0) - goto interface_err; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + struct snd_soc_dai_link_ch_map *ch_maps; + unsigned int ch_mask = 0; + int j; - if (!component->driver->ops || - !component->driver->ops->hw_params) + /* + * 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; - ret = component->driver->ops->hw_params(substream, params); - if (ret < 0) { - dev_err(component->dev, - "ASoC: %s hw params failed: %d\n", - component->name, ret); - goto component_err; - } - } - component = NULL; - - /* store the parameters for each DAIs */ - cpu_dai->rate = params_rate(params); - cpu_dai->channels = params_channels(params); - cpu_dai->sample_bits = - snd_pcm_format_physical_width(params_format(params)); + /* copy params for each cpu */ + tmp_params = *params; - ret = soc_pcm_params_symmetry(substream, params); - if (ret) - goto component_err; -out: - mutex_unlock(&rtd->pcm_mutex); - return ret; - -component_err: - soc_pcm_components_hw_free(substream, component); + /* + * 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; - if (cpu_dai->driver->ops->hw_free) - cpu_dai->driver->ops->hw_free(substream, cpu_dai); + /* fixup cpu channel number */ + if (ch_mask) + soc_pcm_codec_params_fixup(&tmp_params, ch_mask); -interface_err: - i = rtd->num_codecs; + ret = snd_soc_dai_hw_params(cpu_dai, substream, &tmp_params); + if (ret < 0) + goto out; -codec_err: - for_each_rtd_codec_dai_rollback(rtd, i, codec_dai) { - if (codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); - codec_dai->rate = 0; + /* 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); } - if (rtd->dai_link->ops->hw_free) - rtd->dai_link->ops->hw_free(substream); + ret = snd_soc_pcm_component_hw_params(substream, params); +out: + if (ret < 0) + soc_pcm_hw_clean(rtd, substream, 1); - mutex_unlock(&rtd->pcm_mutex); - return ret; + return soc_pcm_ret(rtd, ret); } -/* - * Frees resources allocated by hw_params, can be called multiple times - */ -static int soc_pcm_hw_free(struct snd_pcm_substream *substream) +/* hw_params PCM ops for non-DPCM streams */ +static int soc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - bool playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - int i; - - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); - - /* clear the corresponding DAIs parameters when going to be inactive */ - if (cpu_dai->active == 1) { - cpu_dai->rate = 0; - cpu_dai->channels = 0; - cpu_dai->sample_bits = 0; - } - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->active == 1) { - codec_dai->rate = 0; - codec_dai->channels = 0; - codec_dai->sample_bits = 0; - } - } - - /* apply codec digital mute */ - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if ((playback && codec_dai->playback_active == 1) || - (!playback && codec_dai->capture_active == 1)) - snd_soc_dai_digital_mute(codec_dai, 1, - substream->stream); - } - - /* free any machine hw params */ - if (rtd->dai_link->ops->hw_free) - rtd->dai_link->ops->hw_free(substream); - - /* free any component resources */ - soc_pcm_components_hw_free(substream, NULL); - - /* now free hw params for the DAIs */ - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); - } - - if (cpu_dai->driver->ops->hw_free) - cpu_dai->driver->ops->hw_free(substream, cpu_dai); + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; - mutex_unlock(&rtd->pcm_mutex); - return 0; + snd_soc_dpcm_mutex_lock(rtd); + ret = __soc_pcm_hw_params(substream, params); + snd_soc_dpcm_mutex_unlock(rtd); + return ret; } +#define TRIGGER_MAX 3 +static int (* const trigger[][TRIGGER_MAX])(struct snd_pcm_substream *substream, int cmd, int rollback) = { + [SND_SOC_TRIGGER_ORDER_DEFAULT] = { + snd_soc_link_trigger, + snd_soc_pcm_component_trigger, + snd_soc_pcm_dai_trigger, + }, + [SND_SOC_TRIGGER_ORDER_LDC] = { + snd_soc_link_trigger, + snd_soc_pcm_dai_trigger, + snd_soc_pcm_component_trigger, + }, +}; + static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int i, ret; - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->trigger) { - ret = codec_dai->driver->ops->trigger(substream, - cmd, codec_dai); - if (ret < 0) - return ret; - } - } + int ret = 0, r = 0, i; + int rollback = 0; + int start = 0, stop = 0; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->trigger) - continue; - - ret = component->driver->ops->trigger(substream, cmd); - if (ret < 0) - return ret; - } + /* + * select START/STOP sequence + */ + for_each_rtd_components(rtd, i, component) { + if (component->driver->trigger_start) + start = component->driver->trigger_start; + if (component->driver->trigger_stop) + stop = component->driver->trigger_stop; + } + if (rtd->dai_link->trigger_start) + start = rtd->dai_link->trigger_start; + if (rtd->dai_link->trigger_stop) + stop = rtd->dai_link->trigger_stop; + + if (start < 0 || start >= SND_SOC_TRIGGER_ORDER_MAX || + stop < 0 || stop >= SND_SOC_TRIGGER_ORDER_MAX) + return -EINVAL; - if (cpu_dai->driver->ops->trigger) { - ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); - if (ret < 0) - return ret; + /* + * 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; + } } - if (rtd->dai_link->ops->trigger) { - ret = rtd->dai_link->ops->trigger(substream, cmd); - if (ret < 0) - return ret; + /* + * 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; + } } - return 0; -} - -static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, - int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; - int i, ret; - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->bespoke_trigger) { - ret = codec_dai->driver->ops->bespoke_trigger(substream, - cmd, codec_dai); - if (ret < 0) - return ret; + /* + * 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; } } - if (cpu_dai->driver->ops->bespoke_trigger) { - ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_dai); - if (ret < 0) - return ret; - } - return 0; + 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 accordingly. + * 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_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai; struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t offset = 0; - snd_pcm_sframes_t delay = 0; snd_pcm_sframes_t codec_delay = 0; - int i; - - /* clearing the previous total delay */ - runtime->delay = 0; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->pointer) - continue; - - /* FIXME: use 1st pointer */ - offset = component->driver->ops->pointer(substream); - break; - } - /* base delay if assigned in pointer callback */ - delay = runtime->delay; + snd_pcm_sframes_t cpu_delay = 0; - if (cpu_dai->driver->ops->delay) - delay += cpu_dai->driver->ops->delay(substream, cpu_dai); + offset = snd_soc_pcm_component_pointer(substream); - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->ops->delay) - codec_delay = max(codec_delay, - codec_dai->driver->ops->delay(substream, - codec_dai)); - } - delay += codec_delay; + /* 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 = delay; + runtime->delay = cpu_delay + codec_delay; return offset; } @@ -1208,12 +1299,28 @@ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) static int 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 && dpcm->fe == fe) + for_each_dpcm_be(fe, stream, dpcm) + if (dpcm->be == be) return 0; + + fe_substream = snd_soc_dpcm_get_substream(fe, stream); + be_substream = snd_soc_dpcm_get_substream(be, stream); + + 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); @@ -1222,20 +1329,18 @@ static int dpcm_be_connect(struct snd_soc_pcm_runtime *fe, dpcm->be = be; dpcm->fe = fe; - be->dpcm[stream].runtime = fe->dpcm[stream].runtime; dpcm->state = SND_SOC_DPCM_LINK_STATE_NEW; + snd_pcm_stream_lock_irq(fe_substream); list_add(&dpcm->list_be, &fe->dpcm[stream].be_clients); list_add(&dpcm->list_fe, &be->dpcm[stream].fe_clients); + snd_pcm_stream_unlock_irq(fe_substream); dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n", - stream ? "capture" : "playback", fe->dai_link->name, + snd_pcm_direction_name(stream), fe->dai_link->name, stream ? "<-" : "->", be->dai_link->name); -#ifdef CONFIG_DEBUG_FS - if (fe->debugfs_dpcm_root) - dpcm->debugfs_state = debugfs_create_u32(be->dai_link->name, 0644, - fe->debugfs_dpcm_root, &dpcm->state); -#endif + dpcm_create_debugfs_state(dpcm, stream); + return 1; } @@ -1251,13 +1356,15 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, 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; dev_dbg(fe->dev, "reparent %s path %s %s %s\n", - stream ? "capture" : "playback", + snd_pcm_direction_name(stream), dpcm->fe->dai_link->name, stream ? "<-" : "->", dpcm->be->dai_link->name); @@ -1271,27 +1378,37 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *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", - stream ? "capture" : "playback", + snd_pcm_direction_name(stream), dpcm->be->dai_link->name); if (dpcm->state != SND_SOC_DPCM_LINK_STATE_FREE) continue; dev_dbg(fe->dev, "freed DSP %s path %s %s %s\n", - stream ? "capture" : "playback", fe->dai_link->name, + snd_pcm_direction_name(stream), fe->dai_link->name, stream ? "<-" : "->", dpcm->be->dai_link->name); /* BEs still alive need new FE */ dpcm_be_reparent(fe, dpcm->be, stream); -#ifdef CONFIG_DEBUG_FS - debugfs_remove(dpcm->debugfs_state); -#endif list_del(&dpcm->list_be); + list_move(&dpcm->list_fe, &deleted_dpcms); + } + snd_pcm_stream_unlock_irq(substream); + + while (!list_empty(&deleted_dpcms)) { + dpcm = list_first_entry(&deleted_dpcms, struct snd_soc_dpcm, + list_fe); list_del(&dpcm->list_fe); + dpcm_remove_debugfs_state(dpcm); kfree(dpcm); } } @@ -1301,167 +1418,136 @@ static struct 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); - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - for_each_card_rtds(card, be) { - - if (!be->dai_link->no_pcm) - continue; + for_each_card_rtds(card, be) { - dev_dbg(card->dev, "ASoC: try BE : %s\n", - be->cpu_dai->playback_widget ? - be->cpu_dai->playback_widget->name : "(not set)"); - - if (be->cpu_dai->playback_widget == widget) - return be; - - for_each_rtd_codec_dai(be, i, dai) { - if (dai->playback_widget == widget) - return be; - } - } - } else { + if (!be->dai_link->no_pcm) + continue; - for_each_card_rtds(card, be) { + if (!snd_soc_dpcm_get_substream(be, stream)) + continue; - if (!be->dai_link->no_pcm) - 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", - be->cpu_dai->capture_widget ? - be->cpu_dai->capture_widget->name : "(not set)"); + dev_dbg(card->dev, "ASoC: try BE : %s\n", + w ? w->name : "(not set)"); - if (be->cpu_dai->capture_widget == widget) + if (w == widget) return be; - - for_each_rtd_codec_dai(be, i, dai) { - if (dai->capture_widget == widget) - return be; - } } } - /* dai link name and stream name set correctly ? */ - dev_err(card->dev, "ASoC: can't get %s BE for %s\n", - stream ? "capture" : "playback", widget->name); + /* Widget provided is not a BE */ return NULL; } -static inline struct snd_soc_dapm_widget * - dai_get_widget(struct snd_soc_dai *dai, int stream) -{ - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - return dai->playback_widget; - else - return dai->capture_widget; -} - -static int widget_in_list(struct snd_soc_dapm_widget_list *list, +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 (i = 0; i < list->num_widgets; i++) { - if (widget == list->widgets[i]) + for_each_dapm_widgets(list, i, w) + if (widget == w) return 1; - } return 0; } +EXPORT_SYMBOL_GPL(widget_in_list); -static bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, - enum snd_soc_dapm_direction dir) +bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir) { - struct snd_soc_card *card = widget->dapm->card; + struct snd_soc_card *card = snd_soc_dapm_to_card(widget->dapm); struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dai *dai; - int i; - - if (dir == SND_SOC_DAPM_DIR_OUT) { - for_each_card_rtds(card, rtd) { - if (!rtd->dai_link->no_pcm) - continue; - - if (rtd->cpu_dai->playback_widget == widget) - return true; - - for_each_rtd_codec_dai(rtd, i, dai) { - if (dai->playback_widget == widget) - return true; - } - } - } else { /* SND_SOC_DAPM_DIR_IN */ - for_each_card_rtds(card, rtd) { - if (!rtd->dai_link->no_pcm) - continue; + int stream; - if (rtd->cpu_dai->capture_widget == widget) - return true; + /* adjust dir to stream */ + if (dir == SND_SOC_DAPM_DIR_OUT) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; - for_each_rtd_codec_dai(rtd, i, dai) { - if (dai->capture_widget == widget) - return true; - } - } - } + rtd = dpcm_get_be(card, widget, stream); + if (rtd) + return true; return false; } +EXPORT_SYMBOL_GPL(dpcm_end_walk_at_be); 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 = fe->cpu_dai; + 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, - dpcm_end_walk_at_be); + fe->card->component_chaining ? + NULL : dpcm_end_walk_at_be); - dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, - stream ? "capture" : "playback"); + if (paths > 0) + dev_dbg(fe->dev, "ASoC: found %d audio %s paths\n", paths, + snd_pcm_direction_name(stream)); + else if (paths == 0) + dev_dbg(fe->dev, "ASoC: %s no valid %s path\n", fe->dai_link->name, + snd_pcm_direction_name(stream)); return paths; } +void dpcm_path_put(struct snd_soc_dapm_widget_list **list) +{ + snd_soc_dapm_dai_free_widgets(list); +} + +static bool dpcm_be_is_active(struct snd_soc_dpcm *dpcm, int stream, + struct snd_soc_dapm_widget_list *list) +{ + struct snd_soc_dai *dai; + unsigned int i; + + /* 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)) + return true; + } + + return false; +} + static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, - struct snd_soc_dapm_widget_list **list_) + struct snd_soc_dapm_widget_list **list_) { struct snd_soc_dpcm *dpcm; - struct snd_soc_dapm_widget_list *list = *list_; - struct snd_soc_dapm_widget *widget; - struct snd_soc_dai *dai; int prune = 0; /* Destroy any old FE <--> BE connections */ for_each_dpcm_be(fe, stream, dpcm) { - unsigned int i; - - /* is there a valid CPU DAI widget for this BE */ - widget = dai_get_widget(dpcm->be->cpu_dai, stream); - - /* prune the BE if it's no longer in our active list */ - if (widget && widget_in_list(list, widget)) + if (dpcm_be_is_active(dpcm, stream, *list_)) continue; - /* is there a valid CODEC DAI widget for this BE */ - for_each_rtd_codec_dai(dpcm->be, i, dai) { - widget = dai_get_widget(dai, stream); - - /* prune the BE if it's no longer in our active list */ - if (widget && widget_in_list(list, widget)) - continue; - } - dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", - stream ? "capture" : "playback", + snd_pcm_direction_name(stream), dpcm->be->dai_link->name, fe->dai_link->name); dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; - dpcm->be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + dpcm_set_be_update_state(dpcm->be, stream, SND_SOC_DPCM_UPDATE_BE); prune++; } @@ -1469,18 +1555,24 @@ static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, return prune; } -static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, - struct snd_soc_dapm_widget_list **list_) +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) + return new; + /* Create any new FE <--> BE connections */ - for (i = 0; i < list->num_widgets; i++) { + for_each_dapm_widgets(list, i, widget) { - switch (list->widgets[i]->id) { + switch (widget->id) { case snd_soc_dapm_dai_in: if (stream != SNDRV_PCM_STREAM_PLAYBACK) continue; @@ -1494,32 +1586,37 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, } /* is there a valid BE rtd for this widget */ - be = dpcm_get_be(card, list->widgets[i], stream); + be = dpcm_get_be(card, widget, stream); if (!be) { - dev_err(fe->dev, "ASoC: no BE found for %s\n", - list->widgets[i]->name); + dev_dbg(fe->dev, "ASoC: no BE found for %s\n", + widget->name); continue; } - /* make sure BE is a real BE */ - if (!be->dai_link->no_pcm) - continue; - - /* don't connect if FE is not running */ - if (!fe->dpcm[stream].runtime && !fe->fe_compr) + /* + * 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", - list->widgets[i]->name); + widget->name); break; } else if (err == 0) /* already connected */ continue; /* new */ - be->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_BE; + dpcm_set_be_update_state(be, stream, SND_SOC_DPCM_UPDATE_BE); new++; } @@ -1527,52 +1624,53 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream, return new; } -/* - * Find the corresponding BE DAIs that source or sink audio to this - * FE substream. - */ -int dpcm_process_paths(struct snd_soc_pcm_runtime *fe, - int stream, struct snd_soc_dapm_widget_list **list, int new) -{ - if (new) - return dpcm_add_paths(fe, stream, list); - else - return dpcm_prune_paths(fe, stream, list); -} - void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; for_each_dpcm_be(fe, stream, dpcm) - dpcm->be->dpcm[stream].runtime_update = - SND_SOC_DPCM_UPDATE_NO; + dpcm_set_be_update_state(dpcm->be, stream, SND_SOC_DPCM_UPDATE_NO); } -static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, - int stream) +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 (be->dpcm[stream].users == 0) - dev_err(be->dev, "ASoC: no users %s at close - state %d\n", - stream ? "capture" : "playback", - be->dpcm[stream].state); + if (dpcm == last) + return; - if (--be->dpcm[stream].users != 0) + /* is this op for this BE ? */ + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue; - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + 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; + } - soc_pcm_close(be_substream); + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) { + if (!do_hw_free) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) { + __soc_pcm_hw_free(be, be_substream); + be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; + } + } + + __soc_pcm_close(be, be_substream); be_substream->runtime = NULL; be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; } @@ -1580,31 +1678,35 @@ static void dpcm_be_dai_startup_unwind(struct snd_soc_pcm_runtime *fe, 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; - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + 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", - stream ? "capture" : "playback"); + snd_pcm_direction_name(stream)); continue; } /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + 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 %d\n", - stream ? "capture" : "playback", - be->dpcm[stream].state); + 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; @@ -1614,22 +1716,21 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) continue; dev_dbg(be->dev, "ASoC: open %s BE %s\n", - stream ? "capture" : "playback", be->dai_link->name); + snd_pcm_direction_name(stream), be->dai_link->name); - be_substream->runtime = be->dpcm[stream].runtime; - err = soc_pcm_open(be_substream); + be_substream->runtime = fe_substream->runtime; + err = __soc_pcm_open(be, be_substream); if (err < 0) { - dev_err(be->dev, "ASoC: BE open failed %d\n", err); be->dpcm[stream].users--; if (be->dpcm[stream].users < 0) - dev_err(be->dev, "ASoC: no users %s at unwind %d\n", - stream ? "capture" : "playback", - be->dpcm[stream].state); + dev_err(be->dev, "ASoC: no users %s at unwind %s\n", + snd_pcm_direction_name(stream), + dpcm_state_string(be->dpcm[stream].state)); be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; goto unwind; } - + be->dpcm[stream].be_start = 0; be->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; count++; } @@ -1637,52 +1738,46 @@ int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) return count; unwind: - /* disable any enabled and non active backends */ - for_each_dpcm_be_rollback(fe, stream, dpcm) { - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + dpcm_be_dai_startup_rollback(fe, stream, dpcm); - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) - continue; + return soc_pcm_ret(fe, err); +} + +static void dpcm_runtime_setup_fe(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct snd_soc_dai *dai; + int stream = substream->stream; + int i; - if (be->dpcm[stream].users == 0) - dev_err(be->dev, "ASoC: no users %s at close %d\n", - stream ? "capture" : "playback", - be->dpcm[stream].state); + soc_pcm_hw_init(hw, false); - if (--be->dpcm[stream].users != 0) - continue; + for_each_rtd_cpu_dais(fe, i, dai) { + const struct snd_soc_pcm_stream *cpu_stream; - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) + /* + * 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; - soc_pcm_close(be_substream); - be_substream->runtime = NULL; - be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; - } + cpu_stream = snd_soc_dai_get_pcm_stream(dai, stream); - return err; -} + soc_pcm_hw_update_rate(hw, cpu_stream); + soc_pcm_hw_update_chan(hw, cpu_stream); + soc_pcm_hw_update_format(hw, cpu_stream); + } -static void dpcm_init_runtime_hw(struct snd_pcm_runtime *runtime, - struct snd_soc_pcm_stream *stream) -{ - runtime->hw.rate_min = stream->rate_min; - runtime->hw.rate_max = min_not_zero(stream->rate_max, UINT_MAX); - runtime->hw.channels_min = stream->channels_min; - runtime->hw.channels_max = stream->channels_max; - if (runtime->hw.formats) - runtime->hw.formats &= stream->formats; - else - runtime->hw.formats = stream->formats; - runtime->hw.rates = stream->rates; } -static void dpcm_runtime_merge_format(struct snd_pcm_substream *substream, - u64 *formats) +static void dpcm_runtime_setup_be_format(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; struct snd_soc_dpcm *dpcm; struct snd_soc_dai *dai; int stream = substream->stream; @@ -1697,11 +1792,10 @@ static void dpcm_runtime_merge_format(struct snd_pcm_substream *substream, for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai_driver *codec_dai_drv; - struct snd_soc_pcm_stream *codec_stream; + const struct snd_soc_pcm_stream *codec_stream; int i; - for_each_rtd_codec_dai(be, i, dai) { + 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 @@ -1709,22 +1803,18 @@ static void dpcm_runtime_merge_format(struct snd_pcm_substream *substream, if (!snd_soc_dai_stream_valid(dai, stream)) continue; - codec_dai_drv = dai->driver; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_stream = &codec_dai_drv->playback; - else - codec_stream = &codec_dai_drv->capture; + codec_stream = snd_soc_dai_get_pcm_stream(dai, stream); - *formats &= codec_stream->formats; + soc_pcm_hw_update_format(hw, codec_stream); } } } -static void dpcm_runtime_merge_chan(struct snd_pcm_substream *substream, - unsigned int *channels_min, - unsigned int *channels_max) +static void dpcm_runtime_setup_be_chan(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; struct snd_soc_dpcm *dpcm; int stream = substream->stream; @@ -1738,45 +1828,41 @@ static void dpcm_runtime_merge_chan(struct snd_pcm_substream *substream, for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai_driver *cpu_dai_drv = be->cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv; - struct snd_soc_pcm_stream *codec_stream; - struct snd_soc_pcm_stream *cpu_stream; + const struct snd_soc_pcm_stream *cpu_stream; + struct snd_soc_dai *dai; + int i; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_stream = &cpu_dai_drv->playback; - else - cpu_stream = &cpu_dai_drv->capture; + 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; - *channels_min = max(*channels_min, cpu_stream->channels_min); - *channels_max = min(*channels_max, cpu_stream->channels_max); + cpu_stream = snd_soc_dai_get_pcm_stream(dai, stream); + + soc_pcm_hw_update_chan(hw, cpu_stream); + } /* * 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->num_codecs == 1) { - codec_dai_drv = be->codec_dais[0]->driver; + if (be->dai_link->num_codecs == 1) { + const struct snd_soc_pcm_stream *codec_stream = snd_soc_dai_get_pcm_stream( + snd_soc_rtd_to_codec(be, 0), stream); - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_stream = &codec_dai_drv->playback; - else - codec_stream = &codec_dai_drv->capture; - - *channels_min = max(*channels_min, - codec_stream->channels_min); - *channels_max = min(*channels_max, - codec_stream->channels_max); + soc_pcm_hw_update_chan(hw, codec_stream); } } } -static void dpcm_runtime_merge_rate(struct snd_pcm_substream *substream, - unsigned int *rates, - unsigned int *rate_min, - unsigned int *rate_max) +static void dpcm_runtime_setup_be_rate(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; struct snd_soc_dpcm *dpcm; int stream = substream->stream; @@ -1790,104 +1876,42 @@ static void dpcm_runtime_merge_rate(struct snd_pcm_substream *substream, for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai_driver *cpu_dai_drv = be->cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv; - struct snd_soc_pcm_stream *codec_stream; - struct snd_soc_pcm_stream *cpu_stream; + const struct snd_soc_pcm_stream *pcm; struct snd_soc_dai *dai; int i; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - cpu_stream = &cpu_dai_drv->playback; - else - cpu_stream = &cpu_dai_drv->capture; - - *rate_min = max(*rate_min, cpu_stream->rate_min); - *rate_max = min_not_zero(*rate_max, cpu_stream->rate_max); - *rates = snd_pcm_rate_mask_intersect(*rates, cpu_stream->rates); - - for_each_rtd_codec_dai(be, i, dai) { + for_each_rtd_dais(be, i, dai) { /* - * Skip CODECs which don't support the current stream + * 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; - codec_dai_drv = dai->driver; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - codec_stream = &codec_dai_drv->playback; - else - codec_stream = &codec_dai_drv->capture; + pcm = snd_soc_dai_get_pcm_stream(dai, stream); - *rate_min = max(*rate_min, codec_stream->rate_min); - *rate_max = min_not_zero(*rate_max, - codec_stream->rate_max); - *rates = snd_pcm_rate_mask_intersect(*rates, - codec_stream->rates); + soc_pcm_hw_update_rate(hw, pcm); } } } -static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback); - else - dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture); - - dpcm_runtime_merge_format(substream, &runtime->hw.formats); - dpcm_runtime_merge_chan(substream, &runtime->hw.channels_min, - &runtime->hw.channels_max); - dpcm_runtime_merge_rate(substream, &runtime->hw.rates, - &runtime->hw.rate_min, &runtime->hw.rate_max); -} - -static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd); - -/* 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. - */ -static void 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_pcm_stream_lock_irq(substream); - if (state == SND_SOC_DPCM_UPDATE_NO && fe->dpcm[stream].trigger_pending) { - dpcm_fe_dai_do_trigger(substream, - fe->dpcm[stream].trigger_pending - 1); - fe->dpcm[stream].trigger_pending = 0; - } - fe->dpcm[stream].runtime_update = state; - snd_pcm_stream_unlock_irq(substream); -} - static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, int stream) { struct snd_soc_dpcm *dpcm; - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_soc_dai *fe_cpu_dai = fe->cpu_dai; - int err; + 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 */ - if (soc_pcm_has_symmetry(fe_substream)) - fe_substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + soc_pcm_update_symmetry(fe_substream); - /* Symmetry only applies if we've got an active stream. */ - if (fe_cpu_dai->active) { + 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) - return err; + goto error; } /* apply symmetry for BE */ @@ -1895,138 +1919,85 @@ static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, struct snd_soc_pcm_runtime *be = dpcm->be; struct snd_pcm_substream *be_substream = snd_soc_dpcm_get_substream(be, stream); - struct snd_soc_pcm_runtime *rtd = be_substream->private_data; - struct snd_soc_dai *codec_dai; - int i; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *dai; + + /* A backend may not have the requested substream */ + if (!be_substream) + continue; + rtd = snd_soc_substream_to_rtd(be_substream); if (rtd->dai_link->be_hw_params_fixup) continue; - if (soc_pcm_has_symmetry(be_substream)) - be_substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; + soc_pcm_update_symmetry(be_substream); /* Symmetry only applies if we've got an active stream. */ - if (rtd->cpu_dai->active) { - err = soc_pcm_apply_symmetry(fe_substream, - rtd->cpu_dai); + for_each_rtd_dais(rtd, i, dai) { + err = soc_pcm_apply_symmetry(fe_substream, dai); if (err < 0) - return err; - } - - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->active) { - err = soc_pcm_apply_symmetry(fe_substream, - codec_dai); - if (err < 0) - return err; - } + goto error; } } - - return 0; +error: + return soc_pcm_ret(fe, err); } static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) { - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_pcm_runtime *runtime = fe_substream->runtime; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); int stream = fe_substream->stream, ret = 0; dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); - ret = dpcm_be_dai_startup(fe, fe_substream->stream); - if (ret < 0) { - dev_err(fe->dev,"ASoC: failed to start some BEs %d\n", ret); + ret = dpcm_be_dai_startup(fe, stream); + if (ret < 0) goto be_err; - } dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name); /* start the DAI frontend */ - ret = soc_pcm_open(fe_substream); - if (ret < 0) { - dev_err(fe->dev,"ASoC: failed to start FE %d\n", ret); + ret = __soc_pcm_open(fe, fe_substream); + if (ret < 0) goto unwind; - } fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; - dpcm_set_fe_runtime(fe_substream); - snd_pcm_limit_hw_rates(runtime); + dpcm_runtime_setup_fe(fe_substream); - ret = dpcm_apply_symmetry(fe_substream, stream); - if (ret < 0) { - dev_err(fe->dev, "ASoC: failed to apply dpcm symmetry %d\n", - ret); - goto unwind; - } + dpcm_runtime_setup_be_format(fe_substream); + dpcm_runtime_setup_be_chan(fe_substream); + dpcm_runtime_setup_be_rate(fe_substream); - dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - return 0; + ret = dpcm_apply_symmetry(fe_substream, stream); unwind: - dpcm_be_dai_startup_unwind(fe, fe_substream->stream); + if (ret < 0) + dpcm_be_dai_startup_unwind(fe, stream); be_err: dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - return ret; -} - -int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) -{ - struct snd_soc_dpcm *dpcm; - - /* only shutdown BEs that are either sinks or sources to this FE DAI */ - 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); - - /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) - continue; - - if (be->dpcm[stream].users == 0) - dev_err(be->dev, "ASoC: no users %s at close - state %d\n", - stream ? "capture" : "playback", - be->dpcm[stream].state); - - if (--be->dpcm[stream].users != 0) - continue; - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN)) { - soc_pcm_hw_free(be_substream); - be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; - } - - dev_dbg(be->dev, "ASoC: close BE %s\n", - be->dai_link->name); - - soc_pcm_close(be_substream); - be_substream->runtime = NULL; - - be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; - } - return 0; + return soc_pcm_ret(fe, ret); } static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int stream = substream->stream; + snd_soc_dpcm_mutex_assert_held(fe); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); /* shutdown the BEs */ - dpcm_be_dai_shutdown(fe, substream->stream); + dpcm_be_dai_shutdown(fe, stream); dev_dbg(fe->dev, "ASoC: close FE %s\n", fe->dai_link->name); /* now shutdown the frontend */ - soc_pcm_close(substream); + __soc_pcm_close(fe, substream); - /* run the stream event for each BE */ + /* run the stream stop event */ dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_STOP); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; @@ -2034,7 +2005,7 @@ static int dpcm_fe_dai_shutdown(struct snd_pcm_substream *substream) return 0; } -int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) +void dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; @@ -2047,7 +2018,7 @@ int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) snd_soc_dpcm_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue; /* only free hw when no longer used - check all FEs */ @@ -2069,71 +2040,65 @@ int dpcm_be_dai_hw_free(struct snd_soc_pcm_runtime *fe, int stream) dev_dbg(be->dev, "ASoC: hw_free BE %s\n", be->dai_link->name); - soc_pcm_hw_free(be_substream); + __soc_pcm_hw_free(be, be_substream); be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; } - - return 0; } static int dpcm_fe_dai_hw_free(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; - int err, stream = substream->stream; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); + int stream = substream->stream; - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + snd_soc_dpcm_mutex_lock(fe); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); dev_dbg(fe->dev, "ASoC: hw_free FE %s\n", fe->dai_link->name); /* call hw_free on the frontend */ - err = soc_pcm_hw_free(substream); - if (err < 0) - dev_err(fe->dev,"ASoC: hw_free FE %s failed\n", - fe->dai_link->name); + soc_pcm_hw_clean(fe, substream, 0); /* only hw_params backends that are either sinks or sources * to this frontend DAI */ - err = dpcm_be_dai_hw_free(fe, stream); + dpcm_be_dai_hw_free(fe, stream); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_FREE; dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - mutex_unlock(&fe->card->mutex); + snd_soc_dpcm_mutex_unlock(fe); return 0; } int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) { + struct snd_soc_pcm_runtime *be; + struct snd_pcm_substream *be_substream; struct snd_soc_dpcm *dpcm; int ret; for_each_dpcm_be(fe, stream, dpcm) { + struct snd_pcm_hw_params hw_params; - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + be = dpcm->be; + be_substream = snd_soc_dpcm_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue; /* copy params for each dpcm */ - memcpy(&dpcm->hw_params, &fe->dpcm[stream].hw_params, + memcpy(&hw_params, &fe->dpcm[stream].hw_params, sizeof(struct snd_pcm_hw_params)); /* perform any hw_params fixups */ - if (be->dai_link->be_hw_params_fixup) { - ret = be->dai_link->be_hw_params_fixup(be, - &dpcm->hw_params); - if (ret < 0) { - dev_err(be->dev, - "ASoC: hw_params BE fixup failed %d\n", - ret); - goto unwind; - } - } + ret = snd_soc_link_be_hw_params_fixup(be, &hw_params); + if (ret < 0) + goto unwind; + + /* copy the fixed-up hw params for BE dai */ + memcpy(&be->dpcm[stream].hw_params, &hw_params, + sizeof(struct snd_pcm_hw_params)); /* only allow hw_params() if no connected FEs are running */ if (!snd_soc_dpcm_can_be_params(fe, be, stream)) @@ -2147,25 +2112,24 @@ int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) dev_dbg(be->dev, "ASoC: hw_params BE %s\n", be->dai_link->name); - ret = soc_pcm_hw_params(be_substream, &dpcm->hw_params); - if (ret < 0) { - dev_err(dpcm->be->dev, - "ASoC: hw_params BE failed %d\n", ret); + ret = __soc_pcm_hw_params(be_substream, &hw_params); + if (ret < 0) goto unwind; - } be->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; } return 0; unwind: + dev_dbg(fe->dev, "ASoC: %s() failed at %s (%d)\n", + __func__, be->dai_link->name, ret); + /* disable any enabled and non active backends */ for_each_dpcm_be_rollback(fe, stream, dpcm) { - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + be = dpcm->be; + be_substream = snd_soc_dpcm_get_substream(be, stream); - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue; /* only allow hw_free() if no connected FEs are running */ @@ -2178,7 +2142,7 @@ unwind: (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) continue; - soc_pcm_hw_free(be_substream); + __soc_pcm_hw_free(be, be_substream); } return ret; @@ -2187,195 +2151,254 @@ unwind: static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int ret, stream = substream->stream; - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + snd_soc_dpcm_mutex_lock(fe); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); - memcpy(&fe->dpcm[substream->stream].hw_params, params, + memcpy(&fe->dpcm[stream].hw_params, params, sizeof(struct snd_pcm_hw_params)); - ret = dpcm_be_dai_hw_params(fe, substream->stream); - if (ret < 0) { - dev_err(fe->dev,"ASoC: hw_params BE failed %d\n", ret); + ret = dpcm_be_dai_hw_params(fe, stream); + if (ret < 0) goto out; - } dev_dbg(fe->dev, "ASoC: hw_params FE %s rate %d chan %x fmt %d\n", fe->dai_link->name, params_rate(params), params_channels(params), params_format(params)); /* call hw_params on the frontend */ - ret = soc_pcm_hw_params(substream, params); - if (ret < 0) { - dev_err(fe->dev,"ASoC: hw_params FE failed %d\n", ret); + ret = __soc_pcm_hw_params(substream, params); + if (ret < 0) dpcm_be_dai_hw_free(fe, stream); - } else + else fe->dpcm[stream].state = SND_SOC_DPCM_STATE_HW_PARAMS; out: dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - mutex_unlock(&fe->card->mutex); - return ret; -} + snd_soc_dpcm_mutex_unlock(fe); -static int dpcm_do_trigger(struct snd_soc_dpcm *dpcm, - struct snd_pcm_substream *substream, int cmd) -{ - int ret; - - dev_dbg(dpcm->be->dev, "ASoC: trigger BE %s cmd %d\n", - dpcm->be->dai_link->name, cmd); - - ret = soc_pcm_trigger(substream, cmd); - if (ret < 0) - dev_err(dpcm->be->dev,"ASoC: trigger BE failed %d\n", ret); - - return ret; + return soc_pcm_ret(fe, ret); } 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; + unsigned long flags; int ret = 0; for_each_dpcm_be(fe, stream, dpcm) { + struct snd_pcm_substream *be_substream; - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + be = dpcm->be; + be_substream = snd_soc_dpcm_get_substream(be, stream); + + snd_pcm_stream_lock_irqsave_nested(be_substream, flags); /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) - continue; + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) + goto next; + + dev_dbg(be->dev, "ASoC: trigger BE %s cmd %d\n", + be->dai_link->name, cmd); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) - continue; - - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + if (!be->dpcm[stream].be_start && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + goto next; + + 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)) - continue; + goto next; + + be->dpcm[stream].be_start++; + if (be->dpcm[stream].be_start != 1) + goto next; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + 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].state != SND_SOC_DPCM_STATE_PAUSED)) - continue; - - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + 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; + + fe->dpcm[stream].fe_pause = false; + be->dpcm[stream].be_pause--; + + 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_STOP: - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - continue; + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) + goto next; - if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) - continue; + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_START) + be->dpcm[stream].be_start--; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + if (be->dpcm[stream].be_start != 0) + goto next; + + pause_stop_transition = false; + if (fe->dpcm[stream].fe_pause) { + pause_stop_transition = true; + fe->dpcm[stream].fe_pause = false; + be->dpcm[stream].be_pause--; + } + + 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; + } + + if (be->dpcm[stream].be_pause != 0) + be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + else + be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; - be->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; break; case SNDRV_PCM_TRIGGER_SUSPEND: if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - continue; + goto next; - if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) - continue; + be->dpcm[stream].be_start--; + if (be->dpcm[stream].be_start != 0) + goto next; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + ret = soc_pcm_trigger(be_substream, cmd); + if (ret) { + be->dpcm[stream].be_start++; + goto next; + } be->dpcm[stream].state = SND_SOC_DPCM_STATE_SUSPEND; break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - continue; + goto next; - if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) - continue; + fe->dpcm[stream].fe_pause = true; + be->dpcm[stream].be_pause++; + + be->dpcm[stream].be_start--; + if (be->dpcm[stream].be_start != 0) + goto next; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + ret = soc_pcm_trigger(be_substream, cmd); + if (ret) { + be->dpcm[stream].be_start++; + goto next; + } be->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; break; } +next: + snd_pcm_stream_unlock_irqrestore(be_substream, flags); + if (ret) + break; } - - return ret; + return soc_pcm_ret(fe, ret); } EXPORT_SYMBOL_GPL(dpcm_be_dai_trigger); -static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd) +static int dpcm_dai_trigger_fe_be(struct snd_pcm_substream *substream, + int cmd, bool fe_first) { - struct snd_soc_pcm_runtime *fe = substream->private_data; - int stream = substream->stream, ret; - enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; - - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; - - switch (trigger) { - case SND_SOC_DPCM_TRIGGER_PRE: - /* call trigger on the frontend before the backend. */ + 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); + fe->dai_link->name, cmd); ret = soc_pcm_trigger(substream, cmd); - if (ret < 0) { - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret); - goto out; - } + if (ret < 0) + goto end; ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); - break; - case SND_SOC_DPCM_TRIGGER_POST: - /* call trigger on the frontend after the backend. */ - + } + /* call trigger on the frontend after the backend. */ + else { ret = dpcm_be_dai_trigger(fe, substream->stream, cmd); - if (ret < 0) { - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret); - goto out; - } + if (ret < 0) + goto end; dev_dbg(fe->dev, "ASoC: post trigger FE %s cmd %d\n", - fe->dai_link->name, cmd); + fe->dai_link->name, cmd); ret = soc_pcm_trigger(substream, cmd); - break; - case SND_SOC_DPCM_TRIGGER_BESPOKE: - /* bespoke trigger() - handles both FE and BEs */ + } +end: + return snd_soc_ret(fe->dev, ret, "trigger FE cmd: %d failed\n", cmd); +} - dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd %d\n", - fe->dai_link->name, cmd); +static int 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]; - ret = soc_pcm_bespoke_trigger(substream, cmd); - if (ret < 0) { - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret); - goto out; - } + fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + + 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, @@ -2388,6 +2411,26 @@ static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int 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; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = dpcm_dai_trigger_fe_be(substream, cmd, !fe_first); + break; + default: + ret = -EINVAL; + break; + } + + if (ret < 0) + goto out; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: fe->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_STOP: @@ -2406,7 +2449,7 @@ out: static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int stream = substream->stream; /* if FE's runtime_update is already set, we're in race; @@ -2433,148 +2476,107 @@ int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) snd_soc_dpcm_get_substream(be, stream); /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) + continue; + + if (!snd_soc_dpcm_can_be_prepared(fe, be, stream)) continue; if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED)) continue; dev_dbg(be->dev, "ASoC: prepare BE %s\n", be->dai_link->name); - ret = soc_pcm_prepare(be_substream); - if (ret < 0) { - dev_err(be->dev, "ASoC: backend prepare failed %d\n", - ret); + ret = __soc_pcm_prepare(be, be_substream); + if (ret < 0) break; - } be->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; } + + /* + * 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; } static int dpcm_fe_dai_prepare(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = substream->private_data; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream); int stream = substream->stream, ret = 0; - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + snd_soc_dpcm_mutex_lock(fe); dev_dbg(fe->dev, "ASoC: prepare FE %s\n", fe->dai_link->name); dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); - /* there is no point preparing this FE if there are no BEs */ - if (list_empty(&fe->dpcm[stream].be_clients)) { - dev_err(fe->dev, "ASoC: no backend DAIs enabled for %s\n", - fe->dai_link->name); - ret = -EINVAL; - goto out; - } - - ret = dpcm_be_dai_prepare(fe, substream->stream); + ret = dpcm_be_dai_prepare(fe, stream); if (ret < 0) goto out; /* call prepare on the frontend */ - ret = soc_pcm_prepare(substream); - if (ret < 0) { - dev_err(fe->dev,"ASoC: prepare FE %s failed\n", - fe->dai_link->name); + ret = __soc_pcm_prepare(fe, substream); + if (ret < 0) goto out; - } - /* run the stream event for each BE */ - dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_START); fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PREPARE; out: dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - mutex_unlock(&fe->card->mutex); + snd_soc_dpcm_mutex_unlock(fe); + /* + * 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; } -static int soc_pcm_ioctl(struct snd_pcm_substream *substream, - unsigned int cmd, void *arg) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->ioctl) - continue; - - /* FIXME: use 1st ioctl */ - return component->driver->ops->ioctl(substream, cmd, arg); - } - - return snd_pcm_lib_ioctl(substream, cmd, arg); -} - static int dpcm_run_update_shutdown(struct snd_soc_pcm_runtime *fe, int stream) { - struct snd_pcm_substream *substream = - snd_soc_dpcm_get_substream(fe, stream); - enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; int err; dev_dbg(fe->dev, "ASoC: runtime %s close on FE %s\n", - stream ? "capture" : "playback", fe->dai_link->name); + snd_pcm_direction_name(stream), fe->dai_link->name); - if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { - /* call bespoke trigger - FE takes care of all BE triggers */ - dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd stop\n", - fe->dai_link->name); - - err = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_STOP); - if (err < 0) - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", err); - } else { - dev_dbg(fe->dev, "ASoC: trigger FE %s cmd stop\n", - fe->dai_link->name); + err = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); - err = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); - if (err < 0) - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", err); - } - - err = dpcm_be_dai_hw_free(fe, stream); - if (err < 0) - dev_err(fe->dev,"ASoC: hw_free FE failed %d\n", err); + dpcm_be_dai_hw_free(fe, stream); - err = dpcm_be_dai_shutdown(fe, stream); - if (err < 0) - dev_err(fe->dev,"ASoC: shutdown FE failed %d\n", err); + dpcm_be_dai_shutdown(fe, stream); /* run the stream event for each BE */ dpcm_dapm_stream_event(fe, stream, SND_SOC_DAPM_STREAM_NOP); - return 0; + return soc_pcm_ret(fe, err); } static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) { - struct snd_pcm_substream *substream = - snd_soc_dpcm_get_substream(fe, stream); struct snd_soc_dpcm *dpcm; - enum snd_soc_dpcm_trigger trigger = fe->dai_link->trigger[stream]; - int ret; + int ret = 0; dev_dbg(fe->dev, "ASoC: runtime %s open on FE %s\n", - stream ? "capture" : "playback", fe->dai_link->name); + snd_pcm_direction_name(stream), fe->dai_link->name); /* Only start the BE if the FE is ready */ if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_FREE || - fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) - return -EINVAL; + fe->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE) { + dev_err(fe->dev, "ASoC: FE %s is not ready %s\n", + fe->dai_link->name, dpcm_state_string(fe->dpcm[stream].state)); + ret = -EINVAL; + goto disconnect; + } /* startup must always be called for new BEs */ ret = dpcm_be_dai_startup(fe, stream); @@ -2593,7 +2595,6 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) if (fe->dpcm[stream].state == SND_SOC_DPCM_STATE_HW_PARAMS) return 0; - ret = dpcm_be_dai_prepare(fe, stream); if (ret < 0) goto hw_free; @@ -2606,27 +2607,9 @@ static int dpcm_run_update_startup(struct snd_soc_pcm_runtime *fe, int stream) fe->dpcm[stream].state == SND_SOC_DPCM_STATE_STOP) return 0; - if (trigger == SND_SOC_DPCM_TRIGGER_BESPOKE) { - /* call trigger on the frontend - FE takes care of all BE triggers */ - dev_dbg(fe->dev, "ASoC: bespoke trigger FE %s cmd start\n", - fe->dai_link->name); - - ret = soc_pcm_bespoke_trigger(substream, SNDRV_PCM_TRIGGER_START); - if (ret < 0) { - dev_err(fe->dev,"ASoC: bespoke trigger FE failed %d\n", ret); - goto hw_free; - } - } else { - dev_dbg(fe->dev, "ASoC: trigger FE %s cmd start\n", - fe->dai_link->name); - - ret = dpcm_be_dai_trigger(fe, stream, - SNDRV_PCM_TRIGGER_START); - if (ret < 0) { - dev_err(fe->dev,"ASoC: trigger FE failed %d\n", ret); - goto hw_free; - } - } + ret = dpcm_be_dai_trigger(fe, stream, SNDRV_PCM_TRIGGER_START); + if (ret < 0) + goto hw_free; return 0; @@ -2635,118 +2618,82 @@ hw_free: close: dpcm_be_dai_shutdown(fe, stream); disconnect: - /* disconnect any non started BEs */ + /* disconnect any pending BEs */ for_each_dpcm_be(fe, stream, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; - if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) - dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; - } - - return ret; -} -static int dpcm_run_new_update(struct snd_soc_pcm_runtime *fe, int stream) -{ - int ret; - - dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); - ret = dpcm_run_update_startup(fe, stream); - if (ret < 0) - dev_err(fe->dev, "ASoC: failed to startup some BEs\n"); - dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - - return ret; -} - -static int dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) -{ - int ret; + /* is this op for this BE ? */ + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) + continue; - dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); - ret = dpcm_run_update_shutdown(fe, stream); - if (ret < 0) - dev_err(fe->dev, "ASoC: failed to shutdown some BEs\n"); - dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + if (be->dpcm[stream].state == SND_SOC_DPCM_STATE_CLOSE || + be->dpcm[stream].state == SND_SOC_DPCM_STATE_NEW) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + } - return ret; + return soc_pcm_ret(fe, ret); } static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) { struct snd_soc_dapm_widget_list *list; + int stream; int count, paths; if (!fe->dai_link->dynamic) return 0; + if (fe->dai_link->num_cpus > 1) + return snd_soc_ret(fe->dev, -EINVAL, + "%s doesn't support Multi CPU yet\n", __func__); + /* only check active links */ - if (!fe->cpu_dai->active) + if (!snd_soc_dai_active(snd_soc_rtd_to_cpu(fe, 0))) return 0; /* DAPM sync will call this to update DSP paths */ dev_dbg(fe->dev, "ASoC: DPCM %s runtime update for FE %s\n", new ? "new" : "old", fe->dai_link->name); - /* skip if FE doesn't have playback capability */ - if (!fe->cpu_dai->driver->playback.channels_min || - !fe->codec_dai->driver->playback.channels_min) - goto capture; - - /* skip if FE isn't currently playing */ - if (!fe->cpu_dai->playback_active || !fe->codec_dai->playback_active) - goto capture; + for_each_pcm_streams(stream) { - paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_PLAYBACK, &list); - if (paths < 0) { - dev_warn(fe->dev, "ASoC: %s no valid %s path\n", - fe->dai_link->name, "playback"); - return paths; - } - - /* update any playback paths */ - count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, new); - if (count) { - if (new) - dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); - else - dpcm_run_old_update(fe, SNDRV_PCM_STREAM_PLAYBACK); - - dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); - dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); - } - - dpcm_path_put(&list); - -capture: - /* skip if FE doesn't have capture capability */ - if (!fe->cpu_dai->driver->capture.channels_min || - !fe->codec_dai->driver->capture.channels_min) - return 0; + /* skip if FE doesn't have playback/capture capability */ + if (!snd_soc_dai_stream_valid(snd_soc_rtd_to_cpu(fe, 0), stream) || + !snd_soc_dai_stream_valid(snd_soc_rtd_to_codec(fe, 0), stream)) + continue; - /* skip if FE isn't currently capturing */ - if (!fe->cpu_dai->capture_active || !fe->codec_dai->capture_active) - return 0; + /* skip if FE isn't currently playing/capturing */ + if (!snd_soc_dai_stream_active(snd_soc_rtd_to_cpu(fe, 0), stream) || + !snd_soc_dai_stream_active(snd_soc_rtd_to_codec(fe, 0), stream)) + continue; - paths = dpcm_path_get(fe, SNDRV_PCM_STREAM_CAPTURE, &list); - if (paths < 0) { - dev_warn(fe->dev, "ASoC: %s no valid %s path\n", - fe->dai_link->name, "capture"); - return paths; - } + paths = dpcm_path_get(fe, stream, &list); + if (paths < 0) + return paths; - /* update any old capture paths */ - count = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, new); - if (count) { + /* update any playback/capture paths */ + /* + * Find the corresponding BE DAIs that source or sink audio to this + * FE substream. + */ if (new) - dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); + count = dpcm_add_paths(fe, stream, &list); else - dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); + count = dpcm_prune_paths(fe, stream, &list); + if (count) { + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_BE); + if (new) + dpcm_run_update_startup(fe, stream); + else + dpcm_run_update_shutdown(fe, stream); + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); - dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); - dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); - } + dpcm_clear_pending_state(fe, stream); + dpcm_be_disconnect(fe, stream); + } - dpcm_path_put(&list); + dpcm_path_put(&list); + } return 0; } @@ -2754,12 +2701,12 @@ capture: /* Called by DAPM mixer/mux changes to update audio routing between PCMs and * any DAI links. */ -int soc_dpcm_runtime_update(struct snd_soc_card *card) +int snd_soc_dpcm_runtime_update(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *fe; int ret = 0; - mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); + snd_soc_dpcm_mutex_lock(card); /* shutdown all old paths first */ for_each_card_rtds(card, fe) { ret = soc_dpcm_fe_runtime_update(fe, 0); @@ -2775,316 +2722,221 @@ int soc_dpcm_runtime_update(struct snd_soc_card *card) } out: - mutex_unlock(&card->mutex); - return ret; + snd_soc_dpcm_mutex_unlock(card); + + return snd_soc_ret(card->dev, ret, "%s() failed\n", __func__); } -int soc_dpcm_be_digital_mute(struct snd_soc_pcm_runtime *fe, int mute) +EXPORT_SYMBOL_GPL(snd_soc_dpcm_runtime_update); + +static void dpcm_fe_dai_cleanup(struct snd_pcm_substream *fe_substream) { + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); struct snd_soc_dpcm *dpcm; - struct snd_soc_dai *dai; + int stream = fe_substream->stream; - for_each_dpcm_be(fe, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + snd_soc_dpcm_mutex_assert_held(fe); - struct snd_soc_pcm_runtime *be = dpcm->be; - int i; + /* mark FE's links ready to prune */ + for_each_dpcm_be(fe, stream, dpcm) + dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; - if (be->dai_link->ignore_suspend) - continue; + dpcm_be_disconnect(fe, stream); +} - for_each_rtd_codec_dai(be, i, dai) { - struct snd_soc_dai_driver *drv = dai->driver; +static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); + int ret; - dev_dbg(be->dev, "ASoC: BE digital mute %s\n", - be->dai_link->name); + snd_soc_dpcm_mutex_lock(fe); + ret = dpcm_fe_dai_shutdown(fe_substream); - if (drv->ops && drv->ops->digital_mute && - dai->playback_active) - drv->ops->digital_mute(dai, mute); - } - } + dpcm_fe_dai_cleanup(fe_substream); - return 0; + snd_soc_dpcm_mutex_unlock(fe); + return ret; } static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) { - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_soc_dpcm *dpcm; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); struct snd_soc_dapm_widget_list *list; int ret; int stream = fe_substream->stream; - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - fe->dpcm[stream].runtime = fe_substream->runtime; + snd_soc_dpcm_mutex_lock(fe); ret = dpcm_path_get(fe, stream, &list); - if (ret < 0) { - mutex_unlock(&fe->card->mutex); - return ret; - } else if (ret == 0) { - dev_dbg(fe->dev, "ASoC: %s no valid %s route\n", - fe->dai_link->name, stream ? "capture" : "playback"); - } + if (ret < 0) + goto open_end; /* calculate valid and active FE <-> BE dpcms */ - dpcm_process_paths(fe, stream, &list, 1); + dpcm_add_paths(fe, stream, &list); - ret = dpcm_fe_dai_startup(fe_substream); - if (ret < 0) { - /* clean up all links */ - for_each_dpcm_be(fe, stream, dpcm) - dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + /* There is no point starting up this FE if there are no BEs. */ + if (list_empty(&fe->dpcm[stream].be_clients)) { + /* dev_err_once() for visibility, dev_dbg() for debugging UCM profiles. */ + dev_err_once(fe->dev, "ASoC: no backend DAIs enabled for %s, possibly missing ALSA mixer-based routing or UCM profile\n", + fe->dai_link->name); + dev_dbg(fe->dev, "ASoC: no backend DAIs enabled for %s\n", fe->dai_link->name); - dpcm_be_disconnect(fe, stream); - fe->dpcm[stream].runtime = NULL; + ret = -EINVAL; + goto put_path; } + ret = dpcm_fe_dai_startup(fe_substream); + if (ret < 0) + dpcm_fe_dai_cleanup(fe_substream); + dpcm_clear_pending_state(fe, stream); +put_path: dpcm_path_put(&list); - mutex_unlock(&fe->card->mutex); - return ret; -} - -static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) -{ - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_soc_dpcm *dpcm; - int stream = fe_substream->stream, ret; - - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - ret = dpcm_fe_dai_shutdown(fe_substream); - - /* mark FE's links ready to prune */ - for_each_dpcm_be(fe, stream, dpcm) - dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; - - dpcm_be_disconnect(fe, stream); - - fe->dpcm[stream].runtime = NULL; - mutex_unlock(&fe->card->mutex); +open_end: + snd_soc_dpcm_mutex_unlock(fe); return ret; } -static void soc_pcm_private_free(struct snd_pcm *pcm) +static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, + int *playback, int *capture) { - struct snd_soc_pcm_runtime *rtd = pcm->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - - /* need to sync the delayed work before releasing resources */ - flush_delayed_work(&rtd->delayed_work); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (component->driver->pcm_free) - component->driver->pcm_free(pcm); - } -} - -static int soc_rtdcom_ack(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->ack) - continue; + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *cpu_dai; + struct snd_soc_dai *codec_dai; + struct snd_soc_dai_link_ch_map *ch_maps; + struct snd_soc_dai *dummy_dai = snd_soc_find_dai(&snd_soc_dummy_dlc); + int cpu_capture; + int cpu_playback; + int has_playback = 0; + int has_capture = 0; + int i; - /* FIXME. it returns 1st ask now */ - return component->driver->ops->ack(substream); - } + if (dai_link->dynamic && dai_link->num_cpus > 1) + return snd_soc_ret(rtd->dev, -EINVAL, + "DPCM doesn't support Multi CPU for Front-Ends yet\n"); - return -EINVAL; -} + /* Adapt stream for codec2codec links */ + cpu_capture = snd_soc_get_stream_cpu(dai_link, SNDRV_PCM_STREAM_CAPTURE); + cpu_playback = snd_soc_get_stream_cpu(dai_link, SNDRV_PCM_STREAM_PLAYBACK); -static int soc_rtdcom_copy_user(struct snd_pcm_substream *substream, int channel, - unsigned long pos, void __user *buf, - unsigned long bytes) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + /* + * see + * soc.h :: [dai_link->ch_maps Image sample] + */ + for_each_rtd_ch_maps(rtd, i, ch_maps) { + cpu_dai = snd_soc_rtd_to_cpu(rtd, ch_maps->cpu); + codec_dai = snd_soc_rtd_to_codec(rtd, ch_maps->codec); - if (!component->driver->ops || - !component->driver->ops->copy_user) - continue; + /* + * FIXME + * + * DPCM Codec has been no checked before. + * It should be checked, but it breaks compatibility. + * + * For example there is a case that CPU have loopback capabilities which is used + * for tests on boards where the Codec has no capture capabilities. In this case, + * Codec capture validation check will be fail, but system should allow capture + * capabilities. We have no solution for it today. + */ + if (dai_link->dynamic || dai_link->no_pcm) + codec_dai = dummy_dai; - /* FIXME. it returns 1st copy now */ - return component->driver->ops->copy_user(substream, channel, - pos, buf, bytes); + if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) && + snd_soc_dai_stream_valid(cpu_dai, cpu_playback)) + has_playback = 1; + if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_CAPTURE) && + snd_soc_dai_stream_valid(cpu_dai, cpu_capture)) + has_capture = 1; } - return -EINVAL; -} + if (dai_link->playback_only) + has_capture = 0; -static int soc_rtdcom_copy_kernel(struct snd_pcm_substream *substream, int channel, - unsigned long pos, void *buf, unsigned long bytes) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; + if (dai_link->capture_only) + has_playback = 0; - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + if (!has_playback && !has_capture) + return snd_soc_ret(rtd->dev, -EINVAL, + "substream %s has no playback, no capture\n", dai_link->stream_name); - if (!component->driver->ops || - !component->driver->ops->copy_kernel) - continue; - - /* FIXME. it returns 1st copy now */ - return component->driver->ops->copy_kernel(substream, channel, - pos, buf, bytes); - } + *playback = has_playback; + *capture = has_capture; - return -EINVAL; + return 0; } -static int soc_rtdcom_fill_silence(struct snd_pcm_substream *substream, int channel, - unsigned long pos, unsigned long bytes) +static int soc_create_pcm(struct snd_pcm **pcm, + struct snd_soc_pcm_runtime *rtd, + int playback, int capture) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->fill_silence) - continue; - - /* FIXME. it returns 1st silence now */ - return component->driver->ops->fill_silence(substream, channel, - pos, bytes); - } - - return -EINVAL; -} + char new_name[64]; + int ret; -static struct page *soc_rtdcom_page(struct snd_pcm_substream *substream, - unsigned long offset) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - struct page *page; + /* create the PCM */ + if (rtd->dai_link->c2c_params) { + snprintf(new_name, sizeof(new_name), "codec2codec(%s)", + rtd->dai_link->stream_name); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; + ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, rtd->id, + playback, capture, pcm); + } else if (rtd->dai_link->no_pcm) { + snprintf(new_name, sizeof(new_name), "(%s)", + rtd->dai_link->stream_name); - if (!component->driver->ops || - !component->driver->ops->page) - continue; + ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, rtd->id, + playback, capture, pcm); + } else { + if (rtd->dai_link->dynamic) + snprintf(new_name, sizeof(new_name), "%s (*)", + rtd->dai_link->stream_name); + else + snprintf(new_name, sizeof(new_name), "%s %s-%d", + rtd->dai_link->stream_name, + soc_codec_dai_name(rtd), rtd->id); - /* FIXME. it returns 1st page now */ - page = component->driver->ops->page(substream, offset); - if (page) - return page; + ret = snd_pcm_new(rtd->card->snd_card, new_name, rtd->id, playback, + capture, pcm); } + if (ret < 0) + return snd_soc_ret(rtd->dev, ret, + "can't create pcm %s for dailink %s\n", new_name, rtd->dai_link->name); - return NULL; -} - -static int soc_rtdcom_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_rtdcom_list *rtdcom; - struct snd_soc_component *component; - - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->ops || - !component->driver->ops->mmap) - continue; - - /* FIXME. it returns 1st mmap now */ - return component->driver->ops->mmap(substream, vma); - } + dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n", rtd->id, new_name); - return -EINVAL; + return 0; } /* create a new pcm */ -int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) +int soc_new_pcm(struct snd_soc_pcm_runtime *rtd) { - struct snd_soc_dai *codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_component *component; - struct snd_soc_rtdcom_list *rtdcom; struct snd_pcm *pcm; - char new_name[64]; int ret = 0, playback = 0, capture = 0; int i; - if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { - playback = rtd->dai_link->dpcm_playback; - capture = rtd->dai_link->dpcm_capture; - } else { - for_each_rtd_codec_dai(rtd, i, codec_dai) { - if (codec_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min) - capture = 1; - } - - capture = capture && cpu_dai->driver->capture.channels_min; - playback = playback && cpu_dai->driver->playback.channels_min; - } - - if (rtd->dai_link->playback_only) { - playback = 1; - capture = 0; - } - - if (rtd->dai_link->capture_only) { - playback = 0; - capture = 1; - } - - /* create the PCM */ - if (rtd->dai_link->no_pcm) { - snprintf(new_name, sizeof(new_name), "(%s)", - rtd->dai_link->stream_name); - - ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, - playback, capture, &pcm); - } else { - if (rtd->dai_link->dynamic) - snprintf(new_name, sizeof(new_name), "%s (*)", - rtd->dai_link->stream_name); - else - snprintf(new_name, sizeof(new_name), "%s %s-%d", - rtd->dai_link->stream_name, - (rtd->num_codecs > 1) ? - "multicodec" : rtd->codec_dai->name, num); + ret = soc_get_playback_capture(rtd, &playback, &capture); + if (ret < 0) + return ret; - ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, - capture, &pcm); - } - if (ret < 0) { - dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n", - rtd->dai_link->name); + ret = soc_create_pcm(&pcm, rtd, playback, capture); + if (ret < 0) return ret; - } - dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n",num, new_name); /* DAPM dai link stream work */ - INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); + /* + * Currently nothing to do for c2c links + * Since c2c links are internal nodes in the DAPM graph and + * don't interface with the outside world or application layer + * we don't have to do any special handling on close. + */ + if (!rtd->dai_link->c2c_params) + rtd->close_delayed_work_func = snd_soc_close_delayed_work; - pcm->nonatomic = rtd->dai_link->nonatomic; rtd->pcm = pcm; + pcm->nonatomic = rtd->dai_link->nonatomic; pcm->private_data = rtd; + pcm->no_device_suspend = true; - if (rtd->dai_link->no_pcm) { + if (rtd->dai_link->no_pcm || rtd->dai_link->c2c_params) { if (playback) pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; if (capture) @@ -3101,7 +2953,6 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.hw_free = dpcm_fe_dai_hw_free; rtd->ops.close = dpcm_fe_dai_close; rtd->ops.pointer = soc_pcm_pointer; - rtd->ops.ioctl = soc_pcm_ioctl; } else { rtd->ops.open = soc_pcm_open; rtd->ops.hw_params = soc_pcm_hw_params; @@ -3110,27 +2961,23 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) rtd->ops.hw_free = soc_pcm_hw_free; rtd->ops.close = soc_pcm_close; rtd->ops.pointer = soc_pcm_pointer; - rtd->ops.ioctl = soc_pcm_ioctl; } - for_each_rtdcom(rtd, rtdcom) { - const struct snd_pcm_ops *ops = rtdcom->component->driver->ops; - - if (!ops) - continue; + for_each_rtd_components(rtd, i, component) { + const struct snd_soc_component_driver *drv = component->driver; - if (ops->ack) - rtd->ops.ack = soc_rtdcom_ack; - if (ops->copy_user) - rtd->ops.copy_user = soc_rtdcom_copy_user; - if (ops->copy_kernel) - rtd->ops.copy_kernel = soc_rtdcom_copy_kernel; - if (ops->fill_silence) - rtd->ops.fill_silence = soc_rtdcom_fill_silence; - if (ops->page) - rtd->ops.page = soc_rtdcom_page; - if (ops->mmap) - rtd->ops.mmap = soc_rtdcom_mmap; + if (drv->ioctl) + rtd->ops.ioctl = snd_soc_pcm_component_ioctl; + if (drv->sync_stop) + rtd->ops.sync_stop = snd_soc_pcm_component_sync_stop; + if (drv->copy) + rtd->ops.copy = snd_soc_pcm_component_copy; + if (drv->page) + rtd->ops.page = snd_soc_pcm_component_page; + if (drv->mmap) + rtd->ops.mmap = snd_soc_pcm_component_mmap; + if (drv->ack) + rtd->ops.ack = snd_soc_pcm_component_ack; } if (playback) @@ -3139,50 +2986,15 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) if (capture) snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); - for_each_rtdcom(rtd, rtdcom) { - component = rtdcom->component; - - if (!component->driver->pcm_new) - continue; - - ret = component->driver->pcm_new(rtd); - if (ret < 0) { - dev_err(component->dev, - "ASoC: pcm constructor failed: %d\n", - ret); - return ret; - } - } - - pcm->private_free = soc_pcm_private_free; + ret = snd_soc_pcm_component_new(rtd); + if (ret < 0) + return ret; out: - dev_info(rtd->card->dev, "%s <-> %s mapping ok\n", - (rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name, - cpu_dai->name); + dev_dbg(rtd->card->dev, "%s <-> %s mapping ok\n", + soc_codec_dai_name(rtd), soc_cpu_dai_name(rtd)); return ret; } -/* is the current PCM operation for this FE ? */ -int snd_soc_dpcm_fe_can_update(struct snd_soc_pcm_runtime *fe, int stream) -{ - if (fe->dpcm[stream].runtime_update == SND_SOC_DPCM_UPDATE_FE) - return 1; - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_fe_can_update); - -/* is the current PCM operation for this BE ? */ -int snd_soc_dpcm_be_can_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; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_can_update); - /* get the substream for this BE */ struct snd_pcm_substream * snd_soc_dpcm_get_substream(struct snd_soc_pcm_runtime *be, int stream) @@ -3190,214 +3002,3 @@ struct snd_pcm_substream * return be->pcm->streams[stream].substream; } EXPORT_SYMBOL_GPL(snd_soc_dpcm_get_substream); - -/* get the BE runtime state */ -enum snd_soc_dpcm_state - snd_soc_dpcm_be_get_state(struct snd_soc_pcm_runtime *be, int stream) -{ - return be->dpcm[stream].state; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_get_state); - -/* set the BE runtime state */ -void snd_soc_dpcm_be_set_state(struct snd_soc_pcm_runtime *be, - int stream, enum snd_soc_dpcm_state state) -{ - be->dpcm[stream].state = state; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_be_set_state); - -/* - * 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. - */ -int snd_soc_dpcm_can_be_free_stop(struct snd_soc_pcm_runtime *fe, - struct snd_soc_pcm_runtime *be, int stream) -{ - struct snd_soc_dpcm *dpcm; - int state; - - for_each_dpcm_fe(be, stream, dpcm) { - - if (dpcm->fe == fe) - continue; - - state = dpcm->fe->dpcm[stream].state; - if (state == SND_SOC_DPCM_STATE_START || - state == SND_SOC_DPCM_STATE_PAUSED || - state == SND_SOC_DPCM_STATE_SUSPEND) - return 0; - } - - /* it's safe to free/stop this BE DAI */ - return 1; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_free_stop); - -/* - * 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. - */ -int snd_soc_dpcm_can_be_params(struct snd_soc_pcm_runtime *fe, - struct snd_soc_pcm_runtime *be, int stream) -{ - struct snd_soc_dpcm *dpcm; - int state; - - for_each_dpcm_fe(be, stream, dpcm) { - - if (dpcm->fe == fe) - continue; - - state = dpcm->fe->dpcm[stream].state; - if (state == SND_SOC_DPCM_STATE_START || - state == SND_SOC_DPCM_STATE_PAUSED || - state == SND_SOC_DPCM_STATE_SUSPEND || - state == SND_SOC_DPCM_STATE_PREPARE) - return 0; - } - - /* it's safe to change hw_params */ - return 1; -} -EXPORT_SYMBOL_GPL(snd_soc_dpcm_can_be_params); - -#ifdef CONFIG_DEBUG_FS -static const char *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"; - } - - return "unknown"; -} - -static ssize_t dpcm_show_state(struct snd_soc_pcm_runtime *fe, - int stream, char *buf, size_t size) -{ - struct snd_pcm_hw_params *params = &fe->dpcm[stream].hw_params; - struct snd_soc_dpcm *dpcm; - ssize_t offset = 0; - - /* FE state */ - offset += snprintf(buf + offset, size - offset, - "[%s - %s]\n", fe->dai_link->name, - stream ? "Capture" : "Playback"); - - offset += snprintf(buf + offset, size - offset, "State: %s\n", - dpcm_state_string(fe->dpcm[stream].state)); - - if ((fe->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && - (fe->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) - offset += snprintf(buf + offset, size - offset, - "Hardware Params: " - "Format = %s, Channels = %d, Rate = %d\n", - snd_pcm_format_name(params_format(params)), - params_channels(params), - params_rate(params)); - - /* BEs state */ - offset += snprintf(buf + offset, size - offset, "Backends:\n"); - - if (list_empty(&fe->dpcm[stream].be_clients)) { - offset += snprintf(buf + offset, size - offset, - " No active DSP links\n"); - goto out; - } - - for_each_dpcm_be(fe, stream, dpcm) { - struct snd_soc_pcm_runtime *be = dpcm->be; - params = &dpcm->hw_params; - - offset += snprintf(buf + offset, size - offset, - "- %s\n", be->dai_link->name); - - offset += snprintf(buf + offset, size - offset, - " State: %s\n", - dpcm_state_string(be->dpcm[stream].state)); - - if ((be->dpcm[stream].state >= SND_SOC_DPCM_STATE_HW_PARAMS) && - (be->dpcm[stream].state <= SND_SOC_DPCM_STATE_STOP)) - offset += snprintf(buf + offset, size - offset, - " Hardware Params: " - "Format = %s, Channels = %d, Rate = %d\n", - snd_pcm_format_name(params_format(params)), - params_channels(params), - params_rate(params)); - } - -out: - return offset; -} - -static ssize_t dpcm_state_read_file(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct snd_soc_pcm_runtime *fe = file->private_data; - ssize_t out_count = PAGE_SIZE, offset = 0, ret = 0; - char *buf; - - buf = kmalloc(out_count, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (fe->cpu_dai->driver->playback.channels_min) - offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_PLAYBACK, - buf + offset, out_count - offset); - - if (fe->cpu_dai->driver->capture.channels_min) - offset += dpcm_show_state(fe, SNDRV_PCM_STREAM_CAPTURE, - buf + offset, out_count - offset); - - ret = simple_read_from_buffer(user_buf, count, ppos, buf, offset); - - kfree(buf); - return ret; -} - -static const struct file_operations dpcm_state_fops = { - .open = simple_open, - .read = dpcm_state_read_file, - .llseek = default_llseek, -}; - -void soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) -{ - if (!rtd->dai_link) - return; - - if (!rtd->card->debugfs_card_root) - return; - - rtd->debugfs_dpcm_root = debugfs_create_dir(rtd->dai_link->name, - rtd->card->debugfs_card_root); - if (!rtd->debugfs_dpcm_root) { - dev_dbg(rtd->dev, - "ASoC: Failed to create dpcm debugfs directory %s\n", - rtd->dai_link->name); - return; - } - - debugfs_create_file("state", 0444, rtd->debugfs_dpcm_root, - rtd, &dpcm_state_fops); -} -#endif |
