diff options
Diffstat (limited to 'sound/soc/soc-pcm.c')
| -rw-r--r-- | sound/soc/soc-pcm.c | 3520 |
1 files changed, 2078 insertions, 1442 deletions
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index b6c640332a17..6b134962c71c 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1,25 +1,19 @@ -/* - * soc-pcm.c -- ALSA SoC PCM - * - * Copyright 2005 Wolfson Microelectronics PLC. - * Copyright 2005 Openedhand Ltd. - * Copyright (C) 2010 Slimlogic Ltd. - * Copyright (C) 2010 Texas Instruments Inc. - * - * Authors: Liam Girdwood <lrg@ti.com> - * Mark Brown <broonie@opensource.wolfsonmicro.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// +// soc-pcm.c -- ALSA SoC PCM +// +// Copyright 2005 Wolfson Microelectronics PLC. +// Copyright 2005 Openedhand Ltd. +// Copyright (C) 2010 Slimlogic Ltd. +// Copyright (C) 2010 Texas Instruments Inc. +// +// Authors: Liam Girdwood <lrg@ti.com> +// Mark Brown <broonie@opensource.wolfsonmicro.com> #include <linux/kernel.h> #include <linux/init.h> #include <linux/delay.h> -#include <linux/pm_runtime.h> +#include <linux/pinctrl/consumer.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/export.h> @@ -29,404 +23,906 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dpcm.h> +#include <sound/soc-link.h> #include <sound/initval.h> +#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; +} + +/* + * 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 int snd_soc_dpcm_can_be_free_stop(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, + }; + + 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, + }; + + return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state)); +} + +/* + * We can only prepare a BE DAI if any of it's FE are not prepared, + * running or paused for the specified stream direction. + */ +static int snd_soc_dpcm_can_be_prepared(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_PREPARE, + }; + + return snd_soc_dpcm_check_state(fe, be, stream, state, ARRAY_SIZE(state)); +} + #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"; + } + + 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_set_runtime_hwparams - set the runtime hardware parameters - * @substream: the pcm substream - * @hw: the hardware parameters + * snd_soc_runtime_action() - Increment/Decrement active count for + * PCM runtime components + * @rtd: ASoC PCM runtime that is activated + * @stream: Direction of the PCM stream + * @action: Activate stream if 1. Deactivate if -1. + * + * Increments/Decrements the active count for all the DAIs and components + * attached to a PCM runtime. + * Should typically be called when a stream is opened. * - * Sets the substream runtime hardware parameters. + * Must be called with the rtd->card->pcm_mutex being held */ -int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream, - const struct snd_pcm_hardware *hw) +void snd_soc_runtime_action(struct snd_soc_pcm_runtime *rtd, + int stream, int action) { - 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; + struct snd_soc_component *component; + struct snd_soc_dai *dai; + int i; + + snd_soc_dpcm_mutex_assert_held(rtd); + + for_each_rtd_dais(rtd, i, dai) + snd_soc_dai_action(dai, stream, action); + + /* Increments/Decrements the active count for components without DAIs */ + for_each_rtd_components(rtd, i, component) { + if (component->num_dai) + continue; + component->active += action; + } +} +EXPORT_SYMBOL_GPL(snd_soc_runtime_action); + +/** + * snd_soc_runtime_ignore_pmdown_time() - Check whether to ignore the power down delay + * @rtd: The ASoC PCM runtime that should be checked. + * + * This function checks whether the power down delay should be ignored for a + * specific PCM runtime. Returns true if the delay is 0, if the DAI link has + * been configured to ignore the delay, or if none of the components benefits + * from having the delay. + */ +bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component; + int i; + + if (!rtd->pmdown_time || rtd->dai_link->ignore_pmdown_time) + return true; + + for_each_rtd_components(rtd, i, component) + if (component->driver->use_pmdown_time) + /* No need to go through all components */ + return false; + + return true; } -EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams); /* DPCM stream event, send event to FE and all active BEs. */ -static 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; - list_for_each_entry(dpcm, &fe->dpcm[dir].be_clients, list_be) { + snd_soc_dpcm_mutex_assert_held(fe); + + for_each_dpcm_be(fe, dir, dpcm) { struct snd_soc_pcm_runtime *be = dpcm->be; dev_dbg(be->dev, "ASoC: BE %s event %d dir %d\n", be->dai_link->name, event, dir); + if ((event == SND_SOC_DAPM_STREAM_STOP) && + (be->dpcm[dir].users >= 1)) + continue; + snd_soc_dapm_stream_event(be, dir, event); } 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->driver->symmetric_rates && - !rtd->dai_link->symmetric_rates) + if (!snd_soc_dai_active(soc_dai)) return 0; - /* This can happen if multiple streams are starting simultaneously - - * the second can need to get its constraints before the first has - * picked a rate. Complain and allow the application to carry on. - */ - if (!soc_dai->rate) { - dev_warn(soc_dai->dev, - "ASoC: Not enforcing symmetric_rates due to race\n"); - return 0; +#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); \ } - dev_dbg(soc_dai->dev, "ASoC: Symmetry forces %dHz rate\n", soc_dai->rate); - - ret = snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - soc_dai->rate, soc_dai->rate); - if (ret < 0) { - dev_err(soc_dai->dev, - "ASoC: Unable to apply rate 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; } -/* - * List of sample sizes that might go over the bus for parameter - * application. There ought to be a wildcard sample size for things - * like the DAC/ADC resolution to use but there isn't right now. - */ -static int sample_sizes[] = { - 24, 32, -}; - -static void soc_pcm_apply_msb(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int soc_pcm_params_symmetry(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - int ret, i, bits; + 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; + + 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 */ + __soc_pcm_params_symmetry(rate); + __soc_pcm_params_symmetry(channels); + __soc_pcm_params_symmetry(sample_bits); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - bits = dai->driver->playback.sig_bits; - else - bits = dai->driver->capture.sig_bits; + return 0; +} - if (!bits) - return; +static void soc_pcm_update_symmetry(struct snd_pcm_substream *substream) +{ + 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 *dai; + unsigned int symmetry, i; - for (i = 0; i < ARRAY_SIZE(sample_sizes); i++) { - if (bits >= sample_sizes[i]) - continue; + symmetry = link->symmetric_rate || + link->symmetric_channels || + link->symmetric_sample_bits; - ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, - sample_sizes[i], bits); - if (ret != 0) - dev_warn(dai->dev, - "ASoC: Failed to set MSB %d/%d: %d\n", - bits, sample_sizes[i], ret); - } -} + for_each_rtd_dais(rtd, i, dai) + symmetry = symmetry || + dai->driver->symmetric_rate || + dai->driver->symmetric_channels || + dai->driver->symmetric_sample_bits; -static void soc_pcm_init_runtime_hw(struct snd_pcm_hardware *hw, - struct snd_soc_pcm_stream *codec_stream, - struct snd_soc_pcm_stream *cpu_stream) -{ - hw->rate_min = max(codec_stream->rate_min, cpu_stream->rate_min); - hw->rate_max = max(codec_stream->rate_max, cpu_stream->rate_max); - hw->channels_min = max(codec_stream->channels_min, - cpu_stream->channels_min); - hw->channels_max = min(codec_stream->channels_max, - cpu_stream->channels_max); - hw->formats = codec_stream->formats & cpu_stream->formats; - hw->rates = codec_stream->rates & cpu_stream->rates; - if (codec_stream->rates - & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) - hw->rates |= cpu_stream->rates; - if (cpu_stream->rates - & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS)) - hw->rates |= codec_stream->rates; + if (symmetry) + substream->runtime->hw.info |= SNDRV_PCM_INFO_JOINT_DUPLEX; } -/* - * 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, platform, machine and codec DAI. - */ -static int soc_pcm_open(struct snd_pcm_substream *substream) +static void soc_pcm_set_msb(struct snd_pcm_substream *substream, int bits) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver; - struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver; - int ret = 0; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; - pm_runtime_get_sync(cpu_dai->dev); - pm_runtime_get_sync(codec_dai->dev); - pm_runtime_get_sync(platform->dev); + if (!bits) + return; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + ret = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 0, bits); + if (ret != 0) + dev_warn(rtd->dev, "ASoC: Failed to set MSB %d: %d\n", + bits, ret); +} - /* 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; - } - } +static void soc_pcm_apply_msb(struct snd_pcm_substream *substream) +{ + 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 = 0; - if (platform->driver->ops && platform->driver->ops->open) { - ret = platform->driver->ops->open(substream); - if (ret < 0) { - dev_err(platform->dev, "ASoC: can't open platform" - " %s: %d\n", platform->name, ret); - goto platform_err; - } - } + 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 (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; + if (pcm_codec->sig_bits == 0) { + bits = 0; + break; } + bits = max(pcm_codec->sig_bits, bits); } - if (rtd->dai_link->ops && 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; + 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 = max(pcm_cpu->sig_bits, cpu_bits); } - /* Dynamic PCM DAI links compat checks use dynamic capabilities */ - if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) - goto dynamic; + soc_pcm_set_msb(substream, bits); + soc_pcm_set_msb(substream, cpu_bits); +} - /* Check that the codec and cpu DAIs are compatible */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - soc_pcm_init_runtime_hw(&runtime->hw, &codec_dai_drv->playback, - &cpu_dai_drv->playback); +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 { - soc_pcm_init_runtime_hw(&runtime->hw, &codec_dai_drv->capture, - &cpu_dai_drv->capture); + /* 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; } +} - ret = -EINVAL; - snd_pcm_limit_hw_rates(runtime); - 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; - } +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); - soc_pcm_apply_msb(substream, codec_dai); - soc_pcm_apply_msb(substream, cpu_dai); + /* setup hw->rate_min/max via hw->rates first */ + snd_pcm_hw_limit_rates(hw); - /* 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; + /* 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_soc_dai *codec_dai; + 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; + + soc_pcm_hw_init(hw, true); + + /* 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; - if (codec_dai->active) { - ret = soc_pcm_apply_symmetry(substream, codec_dai); - if (ret != 0) - goto config_err; + /* second calculate min/max only for CODECs in the DAI link */ + for_each_rtd_codec_dais(rtd, i, codec_dai) { + + /* + * Skip CODECs which don't support the current stream type. + * Otherwise, since the rate, channel, and format values will + * zero in that case, we would have no usable settings left, + * causing the resulting setup to fail. + */ + if (!snd_soc_dai_stream_valid(codec_dai, stream)) + continue; + + 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); } - 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); + /* Verify both a valid CPU DAI and a valid CODEC DAI were found */ + if (!hw->channels_min) + return -EINVAL; -dynamic: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - cpu_dai->playback_active++; - codec_dai->playback_active++; - } else { - cpu_dai->capture_active++; - codec_dai->capture_active++; + /* + * chan min/max cannot be enforced if there are multiple CODEC DAIs + * connected to CPU DAI(s), use CPU DAI's directly and let + * channel allocation be fixed up later + */ + if (rtd->dai_link->num_codecs > 1) { + hw->channels_min = cpu_chan_min; + hw->channels_max = cpu_chan_max; } - cpu_dai->active++; - codec_dai->active++; - rtd->codec->active++; - mutex_unlock(&rtd->pcm_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_runtime_calc_hw); -config_err: - if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) - rtd->dai_link->ops->shutdown(substream); +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; -machine_err: - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); + /* + * 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); -codec_dai_err: - if (platform->driver->ops && platform->driver->ops->close) - platform->driver->ops->close(substream); + if (formats) + hw->formats &= formats; +} -platform_err: - if (cpu_dai->driver->ops->shutdown) - cpu_dai->driver->ops->shutdown(substream, cpu_dai); -out: - mutex_unlock(&rtd->pcm_mutex); +static int soc_pcm_components_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component; + int i, ret = 0; - pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); - pm_runtime_put(cpu_dai->dev); + for_each_rtd_components(rtd, i, component) { + ret = snd_soc_component_module_get_when_open(component, substream); + if (ret < 0) + break; + + ret = snd_soc_component_open(component, substream); + if (ret < 0) + break; + } return ret; } -/* - * 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_pcm_components_close(struct snd_pcm_substream *substream, + int rollback) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component; + int i, ret = 0; + + for_each_rtd_components(rtd, i, component) { + int r = snd_soc_component_close(component, substream, rollback); + if (r < 0) + ret = r; /* use last ret */ + + snd_soc_component_module_put_when_close(component, substream, rollback); + } + + return ret; +} + +static int soc_pcm_clean(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream, int rollback) { - 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_dai; + struct snd_soc_component *component; + struct snd_soc_dai *dai; + int i; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + snd_soc_dpcm_mutex_assert_held(rtd); - 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"); + if (!rollback) { + snd_soc_runtime_deactivate(rtd, substream->stream); - /* 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); + /* 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); + } } - mutex_unlock(&rtd->pcm_mutex); + for_each_rtd_dais_reverse(rtd, i, dai) + snd_soc_dai_shutdown(dai, substream, rollback); + + snd_soc_link_shutdown(substream, rollback); + + soc_pcm_components_close(substream, rollback); + + snd_soc_pcm_component_pm_runtime_put(rtd, substream, rollback); + + for_each_rtd_components(rtd, i, component) + if (!snd_soc_component_active(component)) + pinctrl_pm_select_sleep_state(component->dev); + + return 0; } /* * Called by ALSA when a PCM substream is closed. Private data can be - * freed here. The cpu DAI, codec DAI, machine and platform are also + * 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); +} + +/* PCM close ops for non-DPCM streams */ static int soc_pcm_close(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + snd_soc_dpcm_mutex_lock(rtd); + __soc_pcm_close(rtd, substream); + snd_soc_dpcm_mutex_unlock(rtd); + return 0; +} - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - cpu_dai->playback_active--; - codec_dai->playback_active--; - } else { - cpu_dai->capture_active--; - codec_dai->capture_active--; +static int soc_hw_sanity_check(struct snd_pcm_substream *substream) +{ + 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; + + err_msg = "rates"; + if (!hw->rates) + goto config_err; + + err_msg = "formats"; + if (!hw->formats) + goto config_err; + + 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); + + 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 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_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream) +{ + struct snd_soc_component *component; + struct snd_soc_dai *dai; + int i, ret = 0; + + snd_soc_dpcm_mutex_assert_held(rtd); + + for_each_rtd_components(rtd, i, component) + pinctrl_pm_select_default_state(component->dev); + + ret = snd_soc_pcm_component_pm_runtime_get(rtd, substream); + if (ret < 0) + goto err; + + ret = soc_pcm_components_open(substream); + if (ret < 0) + goto err; + + ret = snd_soc_link_startup(substream); + if (ret < 0) + goto err; + + /* startup the audio subsystem */ + for_each_rtd_dais(rtd, i, dai) { + ret = snd_soc_dai_startup(dai, substream); + if (ret < 0) + goto err; } - cpu_dai->active--; - codec_dai->active--; - codec->active--; + /* Dynamic PCM DAI links compat checks use dynamic capabilities */ + if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) + goto dynamic; - /* clear the corresponding DAIs rate when inactive */ - if (!cpu_dai->active) - cpu_dai->rate = 0; + /* Check that the codec and cpu DAIs are compatible */ + soc_pcm_init_runtime_hw(substream); - if (!codec_dai->active) - codec_dai->rate = 0; + soc_pcm_update_symmetry(substream); - /* Muting the DAC suppresses artifacts caused during digital - * shutdown, for example from stopping clocks. - */ - snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); - - if (cpu_dai->driver->ops->shutdown) - cpu_dai->driver->ops->shutdown(substream, cpu_dai); - - if (codec_dai->driver->ops->shutdown) - codec_dai->driver->ops->shutdown(substream, codec_dai); - - if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown) - rtd->dai_link->ops->shutdown(substream); - - if (platform->driver->ops && platform->driver->ops->close) - platform->driver->ops->close(substream); - cpu_dai->runtime = NULL; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (!rtd->pmdown_time || codec->ignore_pmdown_time || - rtd->dai_link->ignore_pmdown_time) { - /* 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; - schedule_delayed_work(&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); + ret = soc_hw_sanity_check(substream); + if (ret < 0) + goto err; + + soc_pcm_apply_msb(substream); + + /* Symmetry only applies if we've already got an active stream. */ + for_each_rtd_dais(rtd, i, dai) { + ret = soc_pcm_apply_symmetry(substream, dai); + if (ret != 0) + goto err; } +dynamic: + snd_soc_runtime_activate(rtd, substream->stream); + ret = 0; +err: + if (ret < 0) + soc_pcm_clean(rtd, substream, 1); - mutex_unlock(&rtd->pcm_mutex); + return soc_pcm_ret(rtd, ret); +} - pm_runtime_put(platform->dev); - pm_runtime_put(codec_dai->dev); - pm_runtime_put(cpu_dai->dev); +/* 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; - return 0; + snd_soc_dpcm_mutex_lock(rtd); + ret = __soc_pcm_open(rtd, substream); + snd_soc_dpcm_mutex_unlock(rtd); + return ret; } /* @@ -434,51 +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_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + struct snd_soc_dai *dai; + int i, ret = 0; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + snd_soc_dpcm_mutex_assert_held(rtd); - if (rtd->dai_link->ops && 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; - } - } - - if (platform->driver->ops && platform->driver->ops->prepare) { - ret = platform->driver->ops->prepare(substream); - if (ret < 0) { - dev_err(platform->dev, "ASoC: platform prepare error:" - " %d\n", ret); - goto out; - } - } + ret = snd_soc_link_prepare(substream); + if (ret < 0) + goto out; - if (codec_dai->driver->ops->prepare) { - ret = codec_dai->driver->ops->prepare(substream, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: 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: 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 && @@ -490,209 +960,337 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(rtd, substream->stream, SND_SOC_DAPM_STREAM_START); - snd_soc_dai_digital_mute(codec_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; } -/* - * Called by ALSA when the hardware params are set by application. This - * function can also be called multiple times and can allocate buffers - * (using snd_pcm_lib_* ). It's non-atomic. - */ -static int soc_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) +/* PCM prepare ops for non-DPCM streams */ +static int soc_pcm_prepare(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret = 0; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + snd_soc_dpcm_mutex_lock(rtd); + ret = __soc_pcm_prepare(rtd, substream); + snd_soc_dpcm_mutex_unlock(rtd); - if (rtd->dai_link->ops && 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; - } - } + /* + * 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; +} - if (codec_dai->driver->ops->hw_params) { - ret = codec_dai->driver->ops->hw_params(substream, params, codec_dai); - if (ret < 0) { - dev_err(codec_dai->dev, "ASoC: can't set %s hw params:" - " %d\n", codec_dai->name, ret); - goto codec_err; - } - } +static void soc_pcm_codec_params_fixup(struct snd_pcm_hw_params *params, + unsigned int mask) +{ + struct snd_interval *interval; + int channels = hweight_long(mask); - if (cpu_dai->driver->ops->hw_params) { - ret = cpu_dai->driver->ops->hw_params(substream, params, cpu_dai); - if (ret < 0) { - dev_err(cpu_dai->dev, "ASoC: %s hw params failed: %d\n", - cpu_dai->name, ret); - goto interface_err; - } - } + interval = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + interval->min = channels; + interval->max = channels; +} + +static int soc_pcm_hw_clean(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_substream *substream, int rollback) +{ + struct snd_soc_dai *dai; + int i; - if (platform->driver->ops && platform->driver->ops->hw_params) { - ret = platform->driver->ops->hw_params(substream, params); - if (ret < 0) { - dev_err(platform->dev, "ASoC: %s hw params failed: %d\n", - platform->name, ret); - goto platform_err; + snd_soc_dpcm_mutex_assert_held(rtd); + + /* clear the corresponding DAIs parameters when going to be inactive */ + for_each_rtd_dais(rtd, i, dai) { + if (snd_soc_dai_active(dai) == 1) + soc_pcm_set_dai_params(dai, NULL); + + if (snd_soc_dai_stream_active(dai, substream->stream) == 1) { + if (!snd_soc_dai_mute_is_ctrled_at_trigger(dai)) + snd_soc_dai_digital_mute(dai, 1, substream->stream); } } - /* store the rate for each DAIs */ - cpu_dai->rate = params_rate(params); - codec_dai->rate = params_rate(params); - -out: - mutex_unlock(&rtd->pcm_mutex); - return ret; + /* run the stream event */ + snd_soc_dapm_stream_stop(rtd, substream->stream); -platform_err: - if (cpu_dai->driver->ops->hw_free) - cpu_dai->driver->ops->hw_free(substream, cpu_dai); + /* free any machine hw params */ + snd_soc_link_hw_free(substream, rollback); -interface_err: - if (codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + /* free any component resources */ + snd_soc_pcm_component_hw_free(substream, rollback); -codec_err: - if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) - rtd->dai_link->ops->hw_free(substream); + /* 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); - mutex_unlock(&rtd->pcm_mutex); - return ret; + return 0; } /* * 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); +} + +/* 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 = substream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + int ret; - mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); + snd_soc_dpcm_mutex_lock(rtd); + ret = __soc_pcm_hw_free(rtd, substream); + snd_soc_dpcm_mutex_unlock(rtd); + return ret; +} - /* apply codec digital mute */ - if (!codec->active) - snd_soc_dai_digital_mute(codec_dai, 1, substream->stream); +/* + * Called by ALSA when the hardware params are set by application. This + * function can also be called multiple times and can allocate buffers + * (using snd_pcm_lib_* ). It's non-atomic. + */ +static int __soc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai; + struct snd_soc_dai *codec_dai; + struct snd_pcm_hw_params tmp_params; + int i, ret = 0; - /* free any machine hw params */ - if (rtd->dai_link->ops && rtd->dai_link->ops->hw_free) - rtd->dai_link->ops->hw_free(substream); + snd_soc_dpcm_mutex_assert_held(rtd); - /* free any DMA resources */ - if (platform->driver->ops && platform->driver->ops->hw_free) - platform->driver->ops->hw_free(substream); + ret = soc_pcm_params_symmetry(substream, params); + if (ret) + goto out; - /* now free hw params for the DAIs */ - if (codec_dai->driver->ops->hw_free) - codec_dai->driver->ops->hw_free(substream, codec_dai); + ret = snd_soc_link_hw_params(substream, params); + if (ret < 0) + goto out; - if (cpu_dai->driver->ops->hw_free) - cpu_dai->driver->ops->hw_free(substream, cpu_dai); + 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, + * the idea being that if a CODEC is not used for the currently + * set up transfer direction, it should not need to be + * configured, especially since the configuration used might + * not even be supported by that CODEC. There may be cases + * however where a CODEC needs to be set up although it is + * actually not being used for the transfer, e.g. if a + * capture-only CODEC is acting as an LRCLK and/or BCLK master + * for the DAI link including a playback-only CODEC. + * If this becomes necessary, we will have to augment the + * machine driver setup with information on how to act, so + * we can do the right thing here. + */ + if (!snd_soc_dai_stream_valid(codec_dai, substream->stream)) + continue; - mutex_unlock(&rtd->pcm_mutex); - return 0; -} + /* copy params for each codec */ + tmp_params = *params; -static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - int ret; + /* fixup params based on TDM slot masks */ + if (tdm_mask) + soc_pcm_codec_params_fixup(&tmp_params, tdm_mask); - if (codec_dai->driver->ops->trigger) { - ret = codec_dai->driver->ops->trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; - } + ret = snd_soc_dai_hw_params(codec_dai, substream, + &tmp_params); + if(ret < 0) + goto out; - if (platform->driver->ops && platform->driver->ops->trigger) { - ret = platform->driver->ops->trigger(substream, cmd); - if (ret < 0) - return ret; + soc_pcm_set_dai_params(codec_dai, &tmp_params); + snd_soc_dapm_update_dai(substream, &tmp_params, codec_dai); } - if (cpu_dai->driver->ops->trigger) { - ret = cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); + 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; + + /* + * Skip CPUs which don't support the current stream + * type. See soc_pcm_init_runtime_hw() for more details + */ + if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream)) + continue; + + /* copy params for each cpu */ + tmp_params = *params; + + /* + * construct cpu channel mask by combining ch_mask of each + * codec which maps to the cpu. + * see + * soc.h :: [dai_link->ch_maps Image sample] + */ + for_each_rtd_ch_maps(rtd, j, ch_maps) + if (ch_maps->cpu == i) + ch_mask |= ch_maps->ch_mask; + + /* fixup cpu channel number */ + if (ch_mask) + soc_pcm_codec_params_fixup(&tmp_params, ch_mask); + + ret = snd_soc_dai_hw_params(cpu_dai, substream, &tmp_params); if (ret < 0) - return ret; + goto out; + + /* store the parameters for each DAI */ + soc_pcm_set_dai_params(cpu_dai, &tmp_params); + snd_soc_dapm_update_dai(substream, &tmp_params, cpu_dai); } - return 0; + + ret = snd_soc_pcm_component_hw_params(substream, params); +out: + if (ret < 0) + soc_pcm_hw_clean(rtd, substream, 1); + + return soc_pcm_ret(rtd, ret); } -static int soc_pcm_bespoke_trigger(struct snd_pcm_substream *substream, - int cmd) +/* 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_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); int ret; - if (codec_dai->driver->ops->bespoke_trigger) { - ret = codec_dai->driver->ops->bespoke_trigger(substream, cmd, codec_dai); - if (ret < 0) - return ret; + 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 = snd_soc_substream_to_rtd(substream); + struct snd_soc_component *component; + int ret = 0, r = 0, i; + int rollback = 0; + int start = 0, stop = 0; + + /* + * 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; + + /* + * 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 (platform->driver->bespoke_trigger) { - ret = platform->driver->bespoke_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; + } } - if (cpu_dai->driver->ops->bespoke_trigger) { - ret = cpu_dai->driver->ops->bespoke_trigger(substream, cmd, cpu_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; + } } - return 0; + + return ret; } + /* * soc level wrapper for pointer callback - * If cpu_dai, codec_dai, platform driver has the delay callback, than - * the runtime->delay will be updated accordingly. + * If cpu_dai, codec_dai, component driver has the delay callback, then + * the runtime->delay will be updated via snd_soc_pcm_component/dai_delay(). */ static snd_pcm_uframes_t soc_pcm_pointer(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_uframes_t offset = 0; - snd_pcm_sframes_t delay = 0; - - if (platform->driver->ops && platform->driver->ops->pointer) - offset = platform->driver->ops->pointer(substream); + snd_pcm_sframes_t codec_delay = 0; + 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); - if (codec_dai->driver->ops->delay) - delay += codec_dai->driver->ops->delay(substream, codec_dai); + /* 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); - if (platform->driver->delay) - delay += platform->driver->delay(substream, codec_dai); - - runtime->delay = delay; + runtime->delay = cpu_delay + codec_delay; return offset; } @@ -701,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 */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { - 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); @@ -715,19 +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, + dev_dbg(fe->dev, "connected new DPCM %s path %s %s %s\n", + snd_pcm_direction_name(stream), fe->dai_link->name, stream ? "<-" : "->", be->dai_link->name); -#ifdef CONFIG_DEBUG_FS - 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; } @@ -743,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; - list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { + 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", + dev_dbg(fe->dev, "reparent %s path %s %s %s\n", + snd_pcm_direction_name(stream), dpcm->fe->dai_link->name, stream ? "<-" : "->", dpcm->be->dai_link->name); @@ -760,30 +1375,40 @@ static void dpcm_be_reparent(struct snd_soc_pcm_runtime *fe, } /* disconnect a BE and FE */ -static void dpcm_be_disconnect(struct snd_soc_pcm_runtime *fe, int stream) +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); - list_for_each_entry_safe(dpcm, d, &fe->dpcm[stream].be_clients, list_be) { + 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, + dev_dbg(fe->dev, "freed DSP %s path %s %s %s\n", + 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); } } @@ -793,126 +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; - if (stream == SNDRV_PCM_STREAM_PLAYBACK) { - for (i = 0; i < card->num_links; i++) { - be = &card->rtd[i]; + dev_dbg(card->dev, "ASoC: find BE for widget %s\n", widget->name); - if (!be->dai_link->no_pcm) - continue; + for_each_card_rtds(card, be) { - if (be->cpu_dai->playback_widget == widget || - be->codec_dai->playback_widget == widget) - return be; - } - } else { + if (!be->dai_link->no_pcm) + continue; - for (i = 0; i < card->num_links; i++) { - be = &card->rtd[i]; + 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", + w ? w->name : "(not set)"); - if (be->cpu_dai->capture_widget == widget || - be->codec_dai->capture_widget == widget) + if (w == widget) return be; } } - 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 * - rtd_get_cpu_widget(struct snd_soc_pcm_runtime *rtd, int stream) -{ - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - return rtd->cpu_dai->playback_widget; - else - return rtd->cpu_dai->capture_widget; -} - -static inline struct snd_soc_dapm_widget * - rtd_get_codec_widget(struct snd_soc_pcm_runtime *rtd, int stream) -{ - if (stream == SNDRV_PCM_STREAM_PLAYBACK) - return rtd->codec_dai->playback_widget; - else - return rtd->codec_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 int dpcm_path_get(struct snd_soc_pcm_runtime *fe, - int stream, struct snd_soc_dapm_widget_list **list_) +bool dpcm_end_walk_at_be(struct snd_soc_dapm_widget *widget, enum snd_soc_dapm_direction dir) { - struct snd_soc_dai *cpu_dai = fe->cpu_dai; - struct snd_soc_dapm_widget_list *list; + struct snd_soc_card *card = snd_soc_dapm_to_card(widget->dapm); + struct snd_soc_pcm_runtime *rtd; + int stream; + + /* adjust dir to stream */ + if (dir == SND_SOC_DAPM_DIR_OUT) + stream = SNDRV_PCM_STREAM_PLAYBACK; + else + stream = SNDRV_PCM_STREAM_CAPTURE; + + 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 = snd_soc_rtd_to_cpu(fe, 0); int paths; - list = kzalloc(sizeof(struct snd_soc_dapm_widget_list) + - sizeof(struct snd_soc_dapm_widget *), GFP_KERNEL); - if (list == NULL) - return -ENOMEM; + 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); + paths = snd_soc_dapm_dai_get_connected_widgets(cpu_dai, stream, list, + 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)); - *list_ = list; return paths; } -static inline void dpcm_path_put(struct snd_soc_dapm_widget_list **list) +void dpcm_path_put(struct snd_soc_dapm_widget_list **list) { - kfree(*list); + snd_soc_dapm_dai_free_widgets(list); } -static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, - struct snd_soc_dapm_widget_list **list_) +static bool dpcm_be_is_active(struct snd_soc_dpcm *dpcm, int stream, + 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; - int prune = 0; + struct snd_soc_dai *dai; + unsigned int i; - /* Destroy any old FE <--> BE connections */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { - - /* is there a valid CPU DAI widget for this BE */ - widget = rtd_get_cpu_widget(dpcm->be, stream); + /* is there a valid DAI widget for this BE */ + for_each_rtd_dais(dpcm->be, i, dai) { + struct snd_soc_dapm_widget *widget = snd_soc_dai_get_widget(dai, stream); - /* prune the BE if it's no longer in our active list */ + /* + * The BE is pruned only if none of the dai + * widgets are in the active list. + */ if (widget && widget_in_list(list, widget)) - continue; + return true; + } - /* is there a valid CODEC DAI widget for this BE */ - widget = rtd_get_codec_widget(dpcm->be, stream); + return false; +} - /* prune the BE if it's no longer in our active list */ - if (widget && widget_in_list(list, widget)) +static int dpcm_prune_paths(struct snd_soc_pcm_runtime *fe, int stream, + struct snd_soc_dapm_widget_list **list_) +{ + struct snd_soc_dpcm *dpcm; + int prune = 0; + + /* Destroy any old FE <--> BE connections */ + for_each_dpcm_be(fe, stream, dpcm) { + if (dpcm_be_is_active(dpcm, stream, *list_)) continue; dev_dbg(fe->dev, "ASoC: pruning %s BE %s for %s\n", - 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++; } @@ -920,52 +1555,68 @@ 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; + break; case snd_soc_dapm_dai_out: + if (stream != SNDRV_PCM_STREAM_CAPTURE) + continue; break; default: continue; } /* is there a valid BE rtd for this widget */ - be = dpcm_get_be(card, 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) + /* + * 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++; } @@ -973,78 +1624,89 @@ 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. - */ -static 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); -} - -static void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) +void dpcm_clear_pending_state(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) - dpcm->be->dpcm[stream].runtime_update = - SND_SOC_DPCM_UPDATE_NO; + for_each_dpcm_be(fe, stream, dpcm) + 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 */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { - + 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; + } + + if (--be->dpcm[stream].users != 0) + continue; + + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN) { + if (!do_hw_free) + continue; - soc_pcm_close(be_substream); + 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; } } -static int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) +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 */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + 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", + 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; @@ -1053,22 +1715,22 @@ static int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) (be->dpcm[stream].state != SND_SOC_DPCM_STATE_CLOSE)) continue; - dev_dbg(be->dev, "ASoC: open BE %s\n", be->dai_link->name); + dev_dbg(be->dev, "ASoC: open %s BE %s\n", + 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++; } @@ -1076,237 +1738,368 @@ static int dpcm_be_dai_startup(struct snd_soc_pcm_runtime *fe, int stream) return count; unwind: - /* disable any enabled and non active backends */ - list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { - 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); + + soc_pcm_hw_update_rate(hw, cpu_stream); + soc_pcm_hw_update_chan(hw, cpu_stream); + soc_pcm_hw_update_format(hw, cpu_stream); } - return err; } -static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream) +static void dpcm_runtime_setup_be_format(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_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) { - runtime->hw.rate_min = cpu_dai_drv->playback.rate_min; - runtime->hw.rate_max = cpu_dai_drv->playback.rate_max; - runtime->hw.channels_min = cpu_dai_drv->playback.channels_min; - runtime->hw.channels_max = cpu_dai_drv->playback.channels_max; - runtime->hw.formats &= cpu_dai_drv->playback.formats; - runtime->hw.rates = cpu_dai_drv->playback.rates; - } else { - runtime->hw.rate_min = cpu_dai_drv->capture.rate_min; - runtime->hw.rate_max = cpu_dai_drv->capture.rate_max; - runtime->hw.channels_min = cpu_dai_drv->capture.channels_min; - runtime->hw.channels_max = cpu_dai_drv->capture.channels_max; - runtime->hw.formats &= cpu_dai_drv->capture.formats; - runtime->hw.rates = cpu_dai_drv->capture.rates; + struct snd_pcm_hardware *hw = &runtime->hw; + struct snd_soc_dpcm *dpcm; + struct snd_soc_dai *dai; + int stream = substream->stream; + + if (!fe->dai_link->dpcm_merged_format) + return; + + /* + * It returns merged BE codec format + * if FE want to use it (= dpcm_merged_format) + */ + + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_soc_pcm_runtime *be = dpcm->be; + const struct snd_soc_pcm_stream *codec_stream; + int i; + + for_each_rtd_codec_dais(be, i, dai) { + /* + * Skip CODECs which don't support the current stream + * type. See soc_pcm_init_runtime_hw() for more details + */ + if (!snd_soc_dai_stream_valid(dai, stream)) + continue; + + codec_stream = snd_soc_dai_get_pcm_stream(dai, stream); + + soc_pcm_hw_update_format(hw, codec_stream); + } } } -static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) +static void dpcm_runtime_setup_be_chan(struct snd_pcm_substream *substream) { - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_pcm_runtime *runtime = fe_substream->runtime; - int stream = fe_substream->stream, ret = 0; + 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; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + if (!fe->dai_link->dpcm_merged_chan) + return; - 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); - goto be_err; - } + /* + * It returns merged BE codec channel; + * if FE want to use it (= dpcm_merged_chan) + */ - dev_dbg(fe->dev, "ASoC: open FE %s\n", fe->dai_link->name); + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_soc_pcm_runtime *be = dpcm->be; + const struct snd_soc_pcm_stream *cpu_stream; + struct snd_soc_dai *dai; + int i; + + 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; - /* 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); - goto unwind; + 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->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); + + soc_pcm_hw_update_chan(hw, codec_stream); + } } +} - fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; +static void dpcm_runtime_setup_be_rate(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_dpcm *dpcm; + int stream = substream->stream; - dpcm_set_fe_runtime(fe_substream); - snd_pcm_limit_hw_rates(runtime); + if (!fe->dai_link->dpcm_merged_rate) + return; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; - return 0; + /* + * It returns merged BE codec channel; + * if FE want to use it (= dpcm_merged_chan) + */ -unwind: - dpcm_be_dai_startup_unwind(fe, fe_substream->stream); -be_err: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; - return ret; + for_each_dpcm_be(fe, stream, dpcm) { + struct snd_soc_pcm_runtime *be = dpcm->be; + const struct snd_soc_pcm_stream *pcm; + struct snd_soc_dai *dai; + int i; + + for_each_rtd_dais(be, i, dai) { + /* + * Skip DAIs which don't support the current stream + * type. See soc_pcm_init_runtime_hw() for more details + */ + if (!snd_soc_dai_stream_valid(dai, stream)) + continue; + + pcm = snd_soc_dai_get_pcm_stream(dai, stream); + + soc_pcm_hw_update_rate(hw, pcm); + } + } } -static int dpcm_be_dai_shutdown(struct snd_soc_pcm_runtime *fe, int stream) +static int dpcm_apply_symmetry(struct snd_pcm_substream *fe_substream, + int stream) { struct snd_soc_dpcm *dpcm; + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); + struct snd_soc_dai *fe_cpu_dai; + int err = 0; + int i; - /* only shutdown BEs that are either sinks or sources to this FE DAI */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + /* apply symmetry for FE */ + soc_pcm_update_symmetry(fe_substream); + for_each_rtd_cpu_dais (fe, i, fe_cpu_dai) { + /* Symmetry only applies if we've got an active stream. */ + err = soc_pcm_apply_symmetry(fe_substream, fe_cpu_dai); + if (err < 0) + goto error; + } + + /* apply symmetry for BE */ + 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); + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *dai; - /* is this op for this BE ? */ - if (!snd_soc_dpcm_be_can_update(fe, be, stream)) + /* A backend may not have the requested substream */ + if (!be_substream) 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) + rtd = snd_soc_substream_to_rtd(be_substream); + if (rtd->dai_link->be_hw_params_fixup) continue; - if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_OPEN)) - continue; + soc_pcm_update_symmetry(be_substream); - dev_dbg(be->dev, "ASoC: close BE %s\n", - dpcm->fe->dai_link->name); + /* Symmetry only applies if we've got an active stream. */ + for_each_rtd_dais(rtd, i, dai) { + err = soc_pcm_apply_symmetry(fe_substream, dai); + if (err < 0) + goto error; + } + } +error: + return soc_pcm_ret(fe, err); +} - soc_pcm_close(be_substream); - be_substream->runtime = NULL; +static int dpcm_fe_dai_startup(struct snd_pcm_substream *fe_substream) +{ + struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(fe_substream); + int stream = fe_substream->stream, ret = 0; - be->dpcm[stream].state = SND_SOC_DPCM_STATE_CLOSE; - } - return 0; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); + + 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, fe_substream); + if (ret < 0) + goto unwind; + + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_OPEN; + + dpcm_runtime_setup_fe(fe_substream); + + dpcm_runtime_setup_be_format(fe_substream); + dpcm_runtime_setup_be_chan(fe_substream); + dpcm_runtime_setup_be_rate(fe_substream); + + ret = dpcm_apply_symmetry(fe_substream, stream); + +unwind: + 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 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; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + 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; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); return 0; } -static 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; /* only hw_params backends that are either sinks or sources * to this frontend DAI */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + 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)) + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) continue; /* only free hw when no longer used - check all FEs */ if (!snd_soc_dpcm_can_be_free_stop(fe, be, stream)) continue; + /* do not free hw if this BE is used by other FE */ + if (be->dpcm[stream].users > 1) + continue; + if ((be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_PARAMS) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PREPARE) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_HW_FREE) && (be->dpcm[stream].state != SND_SOC_DPCM_STATE_PAUSED) && - (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP)) + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_STOP) && + (be->dpcm[stream].state != SND_SOC_DPCM_STATE_SUSPEND)) continue; dev_dbg(be->dev, "ASoC: hw_free BE %s\n", - dpcm->fe->dai_link->name); + 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); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + 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; - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + 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; } -static int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) +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; - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + 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(&hw_params, &fe->dpcm[stream].hw_params, + sizeof(struct snd_pcm_hw_params)); + + /* perform any hw_params fixups */ + 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)) continue; @@ -1317,43 +2110,26 @@ static int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream) continue; dev_dbg(be->dev, "ASoC: hw_params BE %s\n", - dpcm->fe->dai_link->name); + be->dai_link->name); - /* copy params for each dpcm */ - memcpy(&dpcm->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 = 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 */ - list_for_each_entry_continue_reverse(dpcm, &fe->dpcm[stream].be_clients, list_be) { - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_pcm_substream *be_substream = - snd_soc_dpcm_get_substream(be, stream); + for_each_dpcm_be_rollback(fe, stream, dpcm) { + 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 */ @@ -1366,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; @@ -1375,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); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_FE; + 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: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; - mutex_unlock(&fe->card->mutex); - return ret; -} - -static int dpcm_do_trigger(struct snd_soc_dpcm *dpcm, - struct snd_pcm_substream *substream, int cmd) -{ - int ret; + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + snd_soc_dpcm_mutex_unlock(fe); - dev_dbg(dpcm->be->dev, "ASoC: trigger BE %s cmd %d\n", - dpcm->fe->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); } -static int dpcm_be_dai_trigger(struct snd_soc_pcm_runtime *fe, int stream, +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; - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + 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; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + be->dpcm[stream].be_start++; + if (be->dpcm[stream].be_start != 1) + goto next; + + ret = soc_pcm_trigger(be_substream, cmd); + if (ret) { + be->dpcm[stream].be_start--; + goto next; + } be->dpcm[stream].state = SND_SOC_DPCM_STATE_START; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if ((be->dpcm[stream].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--; + + if (be->dpcm[stream].be_start != 0) + goto next; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + 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_STOP) - continue; + if (be->dpcm[stream].state != SND_SOC_DPCM_STATE_START) + 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++; - ret = dpcm_do_trigger(dpcm, be_substream, cmd); - if (ret) - return ret; + be->dpcm[stream].be_start--; + if (be->dpcm[stream].be_start != 0) + 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_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_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, @@ -1576,13 +2411,35 @@ static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int 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; + 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: + case SNDRV_PCM_TRIGGER_SUSPEND: fe->dpcm[stream].state = SND_SOC_DPCM_STATE_STOP; break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + fe->dpcm[stream].state = SND_SOC_DPCM_STATE_PAUSED; + break; } out: @@ -1590,149 +2447,136 @@ out: return ret; } -static int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) +static int dpcm_fe_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + 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; + * process this trigger later at exit + */ + if (fe->dpcm[stream].runtime_update != SND_SOC_DPCM_UPDATE_NO) { + fe->dpcm[stream].trigger_pending = cmd + 1; + return 0; /* delayed, assuming it's successful */ + } + + /* we're alone, let's trigger */ + return dpcm_fe_dai_do_trigger(substream, cmd); +} + +int dpcm_be_dai_prepare(struct snd_soc_pcm_runtime *fe, int stream) { struct snd_soc_dpcm *dpcm; int ret = 0; - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + 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)) + 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_STOP) && + (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", - dpcm->fe->dai_link->name); + 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); - fe->dpcm[stream].runtime_update = 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; - } + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE); - 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: - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; - mutex_unlock(&fe->card->mutex); - + dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_NO); + 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_platform *platform = rtd->platform; - - if (platform->driver->ops->ioctl) - return platform->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); - - 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); + snd_pcm_direction_name(stream), fe->dai_link->name); - 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_trigger(fe, stream, SNDRV_PCM_TRIGGER_STOP); - 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); @@ -1751,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; @@ -1764,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; @@ -1793,272 +2618,325 @@ hw_free: close: dpcm_be_dai_shutdown(fe, stream); disconnect: - /* disconnect any non started BEs */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { + /* 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; + /* is this op for this BE ? */ + if (!snd_soc_dpcm_can_be_update(fe, be, stream)) + continue; - fe->dpcm[stream].runtime_update = 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"); - fe->dpcm[stream].runtime_update = 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 dpcm_run_old_update(struct snd_soc_pcm_runtime *fe, int stream) +static int soc_dpcm_fe_runtime_update(struct snd_soc_pcm_runtime *fe, int new) { - int ret; + struct snd_soc_dapm_widget_list *list; + int stream; + int count, paths; - fe->dpcm[stream].runtime_update = 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"); - fe->dpcm[stream].runtime_update = SND_SOC_DPCM_UPDATE_NO; + if (!fe->dai_link->dynamic) + return 0; - return ret; -} + if (fe->dai_link->num_cpus > 1) + return snd_soc_ret(fe->dev, -EINVAL, + "%s doesn't support Multi CPU yet\n", __func__); -/* Called by DAPM mixer/mux changes to update audio routing between PCMs and - * any DAI links. - */ -int soc_dpcm_runtime_update(struct snd_soc_dapm_widget *widget) -{ - struct snd_soc_card *card; - int i, old, new, paths; + /* only check active links */ + if (!snd_soc_dai_active(snd_soc_rtd_to_cpu(fe, 0))) + return 0; - if (widget->codec) - card = widget->codec->card; - else if (widget->platform) - card = widget->platform->card; - else - return -EINVAL; + /* 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); - mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_dapm_widget_list *list; - struct snd_soc_pcm_runtime *fe = &card->rtd[i]; + for_each_pcm_streams(stream) { - /* make sure link is FE */ - if (!fe->dai_link->dynamic) + /* 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; - /* only check active links */ - if (!fe->cpu_dai->active) + /* 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; - /* DAPM sync will call this to update DSP paths */ - dev_dbg(fe->dev, "ASoC: DPCM runtime update for FE %s\n", - fe->dai_link->name); - - /* skip if FE doesn't have playback capability */ - if (!fe->cpu_dai->driver->playback.channels_min) - goto capture; - - 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"); - mutex_unlock(&card->mutex); + paths = dpcm_path_get(fe, stream, &list); + if (paths < 0) return paths; - } - /* update any new playback paths */ - new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 1); - if (new) { - dpcm_run_new_update(fe, SNDRV_PCM_STREAM_PLAYBACK); - dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_PLAYBACK); - dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_PLAYBACK); - } - - /* update any old playback paths */ - old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_PLAYBACK, &list, 0); - if (old) { - 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); + /* update any playback/capture paths */ + /* + * Find the corresponding BE DAIs that source or sink audio to this + * FE substream. + */ + if (new) + count = dpcm_add_paths(fe, stream, &list); + else + 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, stream); + dpcm_be_disconnect(fe, stream); } -capture: - /* skip if FE doesn't have capture capability */ - if (!fe->cpu_dai->driver->capture.channels_min) - continue; + dpcm_path_put(&list); + } - 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"); - mutex_unlock(&card->mutex); - return paths; - } + return 0; +} - /* update any new capture paths */ - new = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 1); - if (new) { - dpcm_run_new_update(fe, SNDRV_PCM_STREAM_CAPTURE); - dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); - dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); - } +/* Called by DAPM mixer/mux changes to update audio routing between PCMs and + * any DAI links. + */ +int snd_soc_dpcm_runtime_update(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *fe; + int ret = 0; - /* update any old capture paths */ - old = dpcm_process_paths(fe, SNDRV_PCM_STREAM_CAPTURE, &list, 0); - if (old) { - dpcm_run_old_update(fe, SNDRV_PCM_STREAM_CAPTURE); - dpcm_clear_pending_state(fe, SNDRV_PCM_STREAM_CAPTURE); - dpcm_be_disconnect(fe, SNDRV_PCM_STREAM_CAPTURE); - } + 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); + if (ret) + goto out; + } - dpcm_path_put(&list); + /* bring new paths up */ + for_each_card_rtds(card, fe) { + ret = soc_dpcm_fe_runtime_update(fe, 1); + if (ret) + goto out; } - mutex_unlock(&card->mutex); - return 0; +out: + 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 list_head *clients = - &fe->dpcm[SNDRV_PCM_STREAM_PLAYBACK].be_clients; + int stream = fe_substream->stream; - list_for_each_entry(dpcm, clients, list_be) { + snd_soc_dpcm_mutex_assert_held(fe); - struct snd_soc_pcm_runtime *be = dpcm->be; - struct snd_soc_dai *dai = be->codec_dai; - struct snd_soc_dai_driver *drv = dai->driver; + /* 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); +} - dev_dbg(be->dev, "ASoC: BE digital mute %s\n", be->dai_link->name); +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; - if (drv->ops->digital_mute && dai->playback_active) - drv->ops->digital_mute(dai, mute); - } + snd_soc_dpcm_mutex_lock(fe); + ret = dpcm_fe_dai_shutdown(fe_substream); - return 0; + dpcm_fe_dai_cleanup(fe_substream); + + 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); - if (dpcm_path_get(fe, stream, &list) <= 0) { - dev_dbg(fe->dev, "ASoC: %s no valid %s route\n", - fe->dai_link->name, stream ? "capture" : "playback"); - } + ret = dpcm_path_get(fe, stream, &list); + 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 */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) - 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); +open_end: + snd_soc_dpcm_mutex_unlock(fe); return ret; } -static int dpcm_fe_dai_close(struct snd_pcm_substream *fe_substream) +static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, + int *playback, int *capture) { - struct snd_soc_pcm_runtime *fe = fe_substream->private_data; - struct snd_soc_dpcm *dpcm; - int stream = fe_substream->stream, ret; + 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; - mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME); - ret = dpcm_fe_dai_shutdown(fe_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"); - /* mark FE's links ready to prune */ - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) - dpcm->state = SND_SOC_DPCM_LINK_STATE_FREE; + /* 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); - dpcm_be_disconnect(fe, stream); + /* + * 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); + + /* + * 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; + + 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; + } + + if (dai_link->playback_only) + has_capture = 0; + + if (dai_link->capture_only) + has_playback = 0; + + if (!has_playback && !has_capture) + return snd_soc_ret(rtd->dev, -EINVAL, + "substream %s has no playback, no capture\n", dai_link->stream_name); + + *playback = has_playback; + *capture = has_capture; - fe->dpcm[stream].runtime = NULL; - mutex_unlock(&fe->card->mutex); - return ret; + return 0; } -/* create a new pcm */ -int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) +static int soc_create_pcm(struct snd_pcm **pcm, + struct snd_soc_pcm_runtime *rtd, + int playback, int capture) { - struct snd_soc_platform *platform = rtd->platform; - struct snd_soc_dai *codec_dai = rtd->codec_dai; - struct snd_soc_dai *cpu_dai = rtd->cpu_dai; - struct snd_pcm *pcm; char new_name[64]; - int ret = 0, playback = 0, capture = 0; - - if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { - if (cpu_dai->driver->playback.channels_min) - playback = 1; - if (cpu_dai->driver->capture.channels_min) - capture = 1; - } else { - if (codec_dai->driver->playback.channels_min && - cpu_dai->driver->playback.channels_min) - playback = 1; - if (codec_dai->driver->capture.channels_min && - cpu_dai->driver->capture.channels_min) - capture = 1; - } + int ret; /* create the PCM */ - if (rtd->dai_link->no_pcm) { + if (rtd->dai_link->c2c_params) { + snprintf(new_name, sizeof(new_name), "codec2codec(%s)", + rtd->dai_link->stream_name); + + 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); - ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, - playback, capture, &pcm); + 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, codec_dai->name, num); + rtd->dai_link->stream_name, + soc_codec_dai_name(rtd), rtd->id); - ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, - capture, &pcm); + ret = snd_pcm_new(rtd->card->snd_card, new_name, rtd->id, playback, + capture, pcm); } - if (ret < 0) { - dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\n", - rtd->dai_link->name); + 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); + + dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\n", rtd->id, new_name); + + return 0; +} + +/* create a new pcm */ +int soc_new_pcm(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component; + struct snd_pcm *pcm; + int ret = 0, playback = 0, capture = 0; + int i; + + ret = soc_get_playback_capture(rtd, &playback, &capture); + if (ret < 0) + return ret; + + 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; 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) @@ -2075,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; @@ -2084,15 +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; } - if (platform->driver->ops) { - rtd->ops.ack = platform->driver->ops->ack; - rtd->ops.copy = platform->driver->ops->copy; - rtd->ops.silence = platform->driver->ops->silence; - rtd->ops.page = platform->driver->ops->page; - rtd->ops.mmap = platform->driver->ops->mmap; + for_each_rtd_components(rtd, i, component) { + const struct snd_soc_component_driver *drv = component->driver; + + 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) @@ -2101,44 +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); - if (platform->driver->pcm_new) { - ret = platform->driver->pcm_new(rtd); - if (ret < 0) { - dev_err(platform->dev, - "ASoC: pcm constructor failed: %d\n", - ret); - return ret; - } - } - - pcm->private_free = platform->driver->pcm_free; + ret = snd_soc_pcm_component_new(rtd); + if (ret < 0) + return ret; out: - dev_info(rtd->card->dev, " %s <-> %s mapping ok\n", 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) @@ -2146,223 +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; - - list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { - - 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; - - list_for_each_entry(dpcm, &be->dpcm[stream].fe_clients, list_fe) { - - 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); - -int snd_soc_platform_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_platform *platform) -{ - if (platform->driver->ops->trigger) - return platform->driver->ops->trigger(substream, cmd); - return 0; -} -EXPORT_SYMBOL_GPL(snd_soc_platform_trigger); - -#ifdef CONFIG_DEBUG_FS -static 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; - } - - list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) { - 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, -}; - -int soc_dpcm_debugfs_add(struct snd_soc_pcm_runtime *rtd) -{ - if (!rtd->dai_link) - return 0; - - 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 -EINVAL; - } - - rtd->debugfs_dpcm_state = debugfs_create_file("state", 0444, - rtd->debugfs_dpcm_root, - rtd, &dpcm_state_fops); - - return 0; -} -#endif |
