summaryrefslogtreecommitdiffstats
path: root/sound/soc/soc-dai.c
diff options
context:
space:
mode:
authorKees Cook <kees+treewide@kernel.org>2026-09-02 15:31:14 -0700
committerKees Cook <kees@kernel.org>2026-09-04 21:37:00 -0700
commit3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d (patch)
treec65086f9bdcd48c6360fb7cb4598bca084da1f32 /sound/soc/soc-dai.c
downloadlinux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.tar.gz
linux-stable-3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d.zip
treewide: refresh kmalloc_obj() conversionsgrafted
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
Diffstat (limited to 'sound/soc/soc-dai.c')
-rw-r--r--sound/soc/soc-dai.c991
1 files changed, 991 insertions, 0 deletions
diff --git a/sound/soc/soc-dai.c b/sound/soc/soc-dai.c
new file mode 100644
index 000000000..b098d1100
--- /dev/null
+++ b/sound/soc/soc-dai.c
@@ -0,0 +1,991 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// soc-dai.c
+//
+// Copyright (C) 2019 Renesas Electronics Corp.
+// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+//
+
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-link.h>
+
+#define soc_dai_ret(dai, ret) _soc_dai_ret(dai, __func__, ret)
+static inline int _soc_dai_ret(const struct snd_soc_dai *dai,
+ const char *func, int ret)
+{
+ return snd_soc_ret(dai->dev, ret,
+ "at %s() on %s\n", func, dai->name);
+}
+
+/*
+ * We might want to check substream by using list.
+ * In such case, we can update these macros.
+ */
+#define soc_dai_mark_push(dai, substream, tgt) ((dai)->mark_##tgt = substream)
+#define soc_dai_mark_pop(dai, tgt) ((dai)->mark_##tgt = NULL)
+#define soc_dai_mark_match(dai, substream, tgt) ((dai)->mark_##tgt == substream)
+
+/**
+ * snd_soc_dai_set_sysclk - configure DAI system or master clock.
+ * @dai: DAI
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction (SND_SOC_CLOCK_IN or SND_SOC_CLOCK_OUT)
+ *
+ * Configures the DAI master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ int ret;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_sysclk)
+ ret = dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
+ else
+ ret = snd_soc_component_set_sysclk(dai->component, clk_id, 0,
+ freq, dir);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
+
+/**
+ * snd_soc_dai_set_clkdiv - configure DAI clock dividers.
+ * @dai: DAI
+ * @div_id: DAI specific clock divider ID
+ * @div: new clock divisor.
+ *
+ * Configures the clock dividers. This is used to derive the best DAI bit and
+ * frame clocks from the system or master clock. It's best to set the DAI bit
+ * and frame clocks as low as possible to save system power.
+ */
+int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ int ret = -EINVAL;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_clkdiv)
+ ret = dai->driver->ops->set_clkdiv(dai, div_id, div);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv);
+
+/**
+ * snd_soc_dai_set_pll - configure DAI PLL.
+ * @dai: DAI
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ int ret;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_pll)
+ ret = dai->driver->ops->set_pll(dai, pll_id, source,
+ freq_in, freq_out);
+ else
+ ret = snd_soc_component_set_pll(dai->component, pll_id, source,
+ freq_in, freq_out);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
+
+/**
+ * snd_soc_dai_set_bclk_ratio - configure BCLK to sample rate ratio.
+ * @dai: DAI
+ * @ratio: Ratio of BCLK to Sample rate.
+ *
+ * Configures the DAI for a preset BCLK to sample rate ratio.
+ */
+int snd_soc_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+ int ret = -ENOTSUPP;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_bclk_ratio)
+ ret = dai->driver->ops->set_bclk_ratio(dai, ratio);
+
+ if (!ret)
+ dai->bclk_ratio = ratio;
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_ratio);
+
+/**
+ * snd_soc_dai_set_bclk_clk - set the BCLK clock for shared clock detection
+ * @dai: DAI
+ * @bclk: BCLK clock pointer (or NULL to clear)
+ *
+ * When multiple DAIs share the same physical BCLK (detected via
+ * clk_is_match()), the ASoC core will automatically constrain their
+ * hw_params so that the resulting BCLK rates are compatible.
+ */
+void snd_soc_dai_set_bclk_clk(struct snd_soc_dai *dai, struct clk *bclk)
+{
+ dai->bclk = bclk;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_bclk_clk);
+
+static int soc_dai_fmt_match_cnt(u64 fmt)
+{
+ int cnt = 0;
+
+ if (fmt & SND_SOC_POSSIBLE_DAIFMT_FORMAT_MASK)
+ cnt++;
+ if (fmt & SND_SOC_POSSIBLE_DAIFMT_CLOCK_MASK)
+ cnt++;
+ if (fmt & SND_SOC_POSSIBLE_DAIFMT_INV_MASK)
+ cnt++;
+
+ return cnt;
+}
+
+static void soc_dai_auto_select_format(u64 fmt, const struct snd_soc_pcm_runtime *rtd,
+ int idx, u64 *best_fmt)
+{
+ struct snd_soc_dai *dai;
+ const struct snd_soc_dai_ops *ops;
+ int max_idx = rtd->dai_link->num_cpus + rtd->dai_link->num_codecs;
+ u64 available_fmt;
+
+ /*
+ * NOTE
+ * It doesn't support Multi CPU/Codec for now
+ */
+ if (rtd->dai_link->num_cpus != 1 ||
+ rtd->dai_link->num_codecs != 1)
+ return;
+
+ if (idx >= max_idx)
+ return;
+
+ dai = rtd->dais[idx];
+ ops = dai->driver->ops;
+
+ /* zero chance of auto select format */
+ if (!ops || !ops->num_auto_selectable_formats)
+ return;
+
+ /*
+ ****************************
+ * NOTE
+ ****************************
+ * Using .auto_selectable_formats is not mandatory,
+ * It try to find best formats as much as possible, but automatically selecting the
+ * perfect format is impossible. So you can select full or missing format manually
+ * from Sound Card.
+ *
+ * ex)
+ * CPU Codec
+ * (A)[0] I2S/LEFT_J : IB_NF/IB_IF (X)[0] I2S/DSP_A: NB_NF : GATED
+ * (B)[1] DSP_A/DSP_B: NB_NF/IB_NF (Y)[1] LEFT_J: NB_NF : GATED
+ * (C)[2] ...
+ *
+ * 1. (A) -> (X) : I2S :update best format
+ * 2. (A) -> (Y) : LEFT_J
+ * 3. (B) -> (X) : DSP_A/NB_NF :update best format
+ * 4. (B) -> (Y) : NB_NF
+ * 5. (C) -> (X) ...
+ * 6. (C) -> (Y) ...
+ * ...
+ *
+ * In above case GATED will not be selected
+ */
+
+ /* find best formats */
+ for (int i = 0; i < ops->num_auto_selectable_formats; i++) {
+ available_fmt = fmt & ops->auto_selectable_formats[i];
+
+ /* In case of last DAI */
+ if (idx + 1 >= max_idx) {
+ int cnt1 = soc_dai_fmt_match_cnt(*best_fmt);
+ int cnt2 = soc_dai_fmt_match_cnt(available_fmt);
+
+ if (cnt1 < cnt2)
+ *best_fmt = available_fmt;
+ }
+ /* parse with next DAI */
+ else {
+ soc_dai_auto_select_format(available_fmt, rtd, idx + 1, best_fmt);
+ }
+ }
+}
+
+static unsigned int soc_dai_convert_possiblefmt_to_daifmt(u64 possible_fmt, unsigned int configured_fmt)
+{
+ unsigned int fmt = 0;
+ unsigned int mask = 0;
+
+ /*
+ * convert POSSIBLE_DAIFMT to DAIFMT
+ *
+ * Some basic/default settings on each is defined as 0.
+ * see
+ * SND_SOC_DAIFMT_NB_NF
+ * SND_SOC_DAIFMT_GATED
+ *
+ * SND_SOC_DAIFMT_xxx_MASK can't notice it if Sound Card specify
+ * these value, and will be overwrite to auto selected value.
+ *
+ * To avoid such issue, loop from 63 to 0 here.
+ * Small number of SND_SOC_POSSIBLE_xxx will be Hi priority.
+ * Basic/Default settings of each part and above are defined
+ * as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
+ */
+ for (int i = 63; i >= 0; i--) {
+ u64 pos = 1ULL << i;
+
+ switch (possible_fmt & pos) {
+ /*
+ * for format
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_I2S:
+ case SND_SOC_POSSIBLE_DAIFMT_RIGHT_J:
+ case SND_SOC_POSSIBLE_DAIFMT_LEFT_J:
+ case SND_SOC_POSSIBLE_DAIFMT_DSP_A:
+ case SND_SOC_POSSIBLE_DAIFMT_DSP_B:
+ case SND_SOC_POSSIBLE_DAIFMT_AC97:
+ case SND_SOC_POSSIBLE_DAIFMT_PDM:
+ fmt = (fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i;
+ break;
+ /*
+ * for clock
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_CONT:
+ fmt = (fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_GATED:
+ fmt = (fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED;
+ break;
+ /*
+ * for clock invert
+ */
+ case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
+ fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
+ fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
+ fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF;
+ break;
+ case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
+ fmt = (fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF;
+ break;
+ }
+ }
+
+ /*
+ * Some driver might have very complex limitation.
+ * In such case, user want to auto-select non-limitation part,
+ * and want to manually specify complex part.
+ *
+ * Or for example, if both CPU and Codec can be clock provider,
+ * but because of its quality, user want to specify it manually.
+ *
+ * Ignore already configured format if exist
+ */
+ if (!(configured_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
+ mask |= SND_SOC_DAIFMT_FORMAT_MASK;
+ if (!(configured_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
+ mask |= SND_SOC_DAIFMT_CLOCK_MASK;
+ if (!(configured_fmt & SND_SOC_DAIFMT_INV_MASK))
+ mask |= SND_SOC_DAIFMT_INV_MASK;
+ if (!(configured_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
+ mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
+
+ return configured_fmt | (fmt & mask);
+}
+
+unsigned int snd_soc_dai_auto_select_format(const struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai_link *dai_link = rtd->dai_link;
+ u64 possible_fmt = 0;
+
+ soc_dai_auto_select_format(~0, rtd, 0, &possible_fmt);
+
+ return soc_dai_convert_possiblefmt_to_daifmt(possible_fmt, dai_link->dai_fmt);
+}
+
+/**
+ * snd_soc_dai_set_fmt - configure DAI hardware audio format.
+ * @dai: DAI
+ * @fmt: SND_SOC_DAIFMT_* format value.
+ *
+ * Configures the DAI hardware format and clocking.
+ */
+int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ int ret = -ENOTSUPP;
+
+ if (dai->driver->ops && dai->driver->ops->set_fmt)
+ ret = dai->driver->ops->set_fmt(dai, fmt);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt);
+
+/**
+ * snd_soc_xlate_tdm_slot_mask - generate tx/rx slot mask.
+ * @slots: Number of slots in use.
+ * @tx_mask: bitmask representing active TX slots.
+ * @rx_mask: bitmask representing active RX slots.
+ *
+ * Generates the TDM tx and rx slot default masks for DAI.
+ */
+static int snd_soc_xlate_tdm_slot_mask(unsigned int slots,
+ unsigned int *tx_mask,
+ unsigned int *rx_mask)
+{
+ if (*tx_mask || *rx_mask)
+ return 0;
+
+ if (!slots)
+ return -EINVAL;
+
+ *tx_mask = (1 << slots) - 1;
+ *rx_mask = (1 << slots) - 1;
+
+ return 0;
+}
+
+/**
+ * snd_soc_dai_set_tdm_slot() - Configures a DAI for TDM operation
+ * @dai: The DAI to configure
+ * @tx_mask: bitmask representing active TX slots.
+ * @rx_mask: bitmask representing active RX slots.
+ * @slots: Number of slots in use.
+ * @slot_width: Width in bits for each slot.
+ *
+ * This function configures the specified DAI for TDM operation. @slot contains
+ * the total number of slots of the TDM stream and @slot_with the width of each
+ * slot in bit clock cycles. @tx_mask and @rx_mask are bitmasks specifying the
+ * active slots of the TDM stream for the specified DAI, i.e. which slots the
+ * DAI should write to or read from. If a bit is set the corresponding slot is
+ * active, if a bit is cleared the corresponding slot is inactive. Bit 0 maps to
+ * the first slot, bit 1 to the second slot and so on. The first active slot
+ * maps to the first channel of the DAI, the second active slot to the second
+ * channel and so on.
+ *
+ * TDM mode can be disabled by passing 0 for @slots. In this case @tx_mask,
+ * @rx_mask and @slot_width will be ignored.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int slots, int slot_width)
+{
+ int ret = -ENOTSUPP;
+ int stream;
+ unsigned int *tdm_mask[] = {
+ &tx_mask,
+ &rx_mask,
+ };
+
+ if (slots) {
+ if (dai->driver->ops &&
+ dai->driver->ops->xlate_tdm_slot_mask)
+ ret = dai->driver->ops->xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
+ else
+ ret = snd_soc_xlate_tdm_slot_mask(slots, &tx_mask, &rx_mask);
+ if (ret)
+ goto err;
+ }
+
+ for_each_pcm_streams(stream)
+ snd_soc_dai_tdm_mask_set(dai, stream, *tdm_mask[stream]);
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_tdm_slot)
+ ret = dai->driver->ops->set_tdm_slot(dai, tx_mask, rx_mask,
+ slots, slot_width);
+err:
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot);
+
+/**
+ * snd_soc_dai_set_tdm_idle() - Configure a DAI's TDM idle mode
+ * @dai: The DAI to configure
+ * @tx_mask: bitmask representing idle TX slots.
+ * @rx_mask: bitmask representing idle RX slots.
+ * @tx_mode: idle mode to set for TX slots.
+ * @rx_mode: idle mode to set for RX slots.
+ *
+ * This function configures the DAI to handle idle TDM slots in the
+ * specified manner. @tx_mode and @rx_mode can be one of
+ * SND_SOC_DAI_TDM_IDLE_NONE, SND_SOC_DAI_TDM_IDLE_ZERO,
+ * SND_SOC_DAI_TDM_IDLE_PULLDOWN, or SND_SOC_DAI_TDM_IDLE_HIZ.
+ * SND_SOC_TDM_IDLE_NONE represents the DAI's default/unset idle slot
+ * handling state and could be any of the other modes depending on the
+ * hardware behind the DAI. It is therefore undefined behaviour when set
+ * explicitly.
+ *
+ * Mode and mask can be set independently for both the TX and RX direction.
+ * Some hardware may ignore both TX and RX masks depending on its
+ * capabilities.
+ */
+int snd_soc_dai_set_tdm_idle(struct snd_soc_dai *dai,
+ unsigned int tx_mask, unsigned int rx_mask,
+ int tx_mode, int rx_mode)
+{
+ int ret = -EOPNOTSUPP;
+
+ /* You can't write to the RX line */
+ if (rx_mode == SND_SOC_DAI_TDM_IDLE_ZERO)
+ return soc_dai_ret(dai, -EINVAL);
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_tdm_idle)
+ ret = dai->driver->ops->set_tdm_idle(dai, tx_mask, rx_mask,
+ tx_mode, rx_mode);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_idle);
+
+/**
+ * snd_soc_dai_set_channel_map - configure DAI audio channel map
+ * @dai: DAI
+ * @tx_num: how many TX channels
+ * @tx_slot: pointer to an array which imply the TX slot number channel
+ * 0~num-1 uses
+ * @rx_num: how many RX channels
+ * @rx_slot: pointer to an array which imply the RX slot number channel
+ * 0~num-1 uses
+ *
+ * configure the relationship between channel number and TDM slot number.
+ */
+int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai,
+ unsigned int tx_num, const unsigned int *tx_slot,
+ unsigned int rx_num, const unsigned int *rx_slot)
+{
+ int ret = -ENOTSUPP;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_channel_map)
+ ret = dai->driver->ops->set_channel_map(dai, tx_num, tx_slot,
+ rx_num, rx_slot);
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map);
+
+/**
+ * snd_soc_dai_get_channel_map - Get DAI audio channel map
+ * @dai: DAI
+ * @tx_num: how many TX channels
+ * @tx_slot: pointer to an array which imply the TX slot number channel
+ * 0~num-1 uses
+ * @rx_num: how many RX channels
+ * @rx_slot: pointer to an array which imply the RX slot number channel
+ * 0~num-1 uses
+ */
+int snd_soc_dai_get_channel_map(const struct snd_soc_dai *dai,
+ unsigned int *tx_num, unsigned int *tx_slot,
+ unsigned int *rx_num, unsigned int *rx_slot)
+{
+ int ret = -ENOTSUPP;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->get_channel_map)
+ ret = dai->driver->ops->get_channel_map(dai, tx_num, tx_slot,
+ rx_num, rx_slot);
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_get_channel_map);
+
+/**
+ * snd_soc_dai_set_tristate - configure DAI system or master clock.
+ * @dai: DAI
+ * @tristate: tristate enable
+ *
+ * Tristates the DAI so that others can use it.
+ */
+int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate)
+{
+ int ret = -EINVAL;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->set_tristate)
+ ret = dai->driver->ops->set_tristate(dai, tristate);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate);
+
+int snd_soc_dai_prepare(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+
+ if (!snd_soc_dai_stream_valid(dai, substream->stream))
+ return 0;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->prepare)
+ ret = dai->driver->ops->prepare(substream, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_prepare);
+
+int snd_soc_dai_mute_is_ctrled_at_trigger(struct snd_soc_dai *dai)
+{
+ if (dai->driver->ops)
+ return dai->driver->ops->mute_unmute_on_trigger;
+
+ return 0;
+}
+
+/**
+ * snd_soc_dai_digital_mute - configure DAI system or master clock.
+ * @dai: DAI
+ * @mute: mute enable
+ * @direction: stream to mute
+ *
+ * Mutes the DAI DAC.
+ */
+int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute,
+ int direction)
+{
+ int ret = -ENOTSUPP;
+
+ /*
+ * ignore if direction was CAPTURE
+ * and it had .no_capture_mute flag
+ */
+ if (dai->driver->ops &&
+ dai->driver->ops->mute_stream &&
+ (direction == SNDRV_PCM_STREAM_PLAYBACK ||
+ !dai->driver->ops->no_capture_mute))
+ ret = dai->driver->ops->mute_stream(dai, mute, direction);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
+
+int snd_soc_dai_hw_params(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ int ret = 0;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->hw_params)
+ ret = dai->driver->ops->hw_params(substream, params, dai);
+
+ /* mark substream if succeeded */
+ if (ret == 0)
+ soc_dai_mark_push(dai, substream, hw_params);
+
+ return soc_dai_ret(dai, ret);
+}
+
+void snd_soc_dai_hw_free(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream,
+ int rollback)
+{
+ if (rollback && !soc_dai_mark_match(dai, substream, hw_params))
+ return;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->hw_free)
+ dai->driver->ops->hw_free(substream, dai);
+
+ /* remove marked substream */
+ soc_dai_mark_pop(dai, hw_params);
+}
+
+int snd_soc_dai_startup(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+
+ if (!snd_soc_dai_stream_valid(dai, substream->stream))
+ return 0;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->startup)
+ ret = dai->driver->ops->startup(substream, dai);
+
+ /* mark substream if succeeded */
+ if (ret == 0)
+ soc_dai_mark_push(dai, substream, startup);
+
+ return soc_dai_ret(dai, ret);
+}
+
+void snd_soc_dai_shutdown(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream,
+ int rollback)
+{
+ if (!snd_soc_dai_stream_valid(dai, substream->stream))
+ return;
+
+ if (rollback && !soc_dai_mark_match(dai, substream, startup))
+ return;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->shutdown)
+ dai->driver->ops->shutdown(substream, dai);
+
+ /* remove marked substream */
+ soc_dai_mark_pop(dai, startup);
+}
+
+int snd_soc_dai_compress_new(struct snd_soc_dai *dai,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ int ret = -ENOTSUPP;
+ if (dai->driver->ops &&
+ dai->driver->ops->compress_new)
+ ret = dai->driver->ops->compress_new(rtd);
+ return soc_dai_ret(dai, ret);
+}
+
+/*
+ * snd_soc_dai_stream_valid() - check if a DAI supports the given stream
+ *
+ * Returns true if the DAI supports the indicated stream type.
+ */
+bool snd_soc_dai_stream_valid(const struct snd_soc_dai *dai, int dir)
+{
+ const struct snd_soc_pcm_stream *stream = snd_soc_dai_get_pcm_stream(dai, dir);
+
+ /* If the codec specifies any channels at all, it supports the stream */
+ return stream->channels_min;
+}
+
+void snd_soc_dai_action(struct snd_soc_dai *dai,
+ int stream, int action)
+{
+ /* see snd_soc_dai_stream_active() */
+ dai->stream[stream].active += action;
+
+ /* see snd_soc_component_active() */
+ dai->component->active += action;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_action);
+
+int snd_soc_dai_active(const struct snd_soc_dai *dai)
+{
+ int stream, active;
+
+ active = 0;
+ for_each_pcm_streams(stream)
+ active += dai->stream[stream].active;
+
+ return active;
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_active);
+
+int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order)
+{
+ struct snd_soc_dai *dai;
+ int i;
+
+ for_each_rtd_dais(rtd, i, dai) {
+ if (dai->probed)
+ continue;
+
+ if (dai->driver->ops) {
+ if (dai->driver->ops->probe_order != order)
+ continue;
+
+ if (dai->driver->ops->probe) {
+ int ret = dai->driver->ops->probe(dai);
+
+ if (ret < 0)
+ return soc_dai_ret(dai, ret);
+ }
+ }
+ dai->probed = 1;
+ }
+
+ return 0;
+}
+
+int snd_soc_pcm_dai_remove(struct snd_soc_pcm_runtime *rtd, int order)
+{
+ struct snd_soc_dai *dai;
+ int i, r, ret = 0;
+
+ for_each_rtd_dais(rtd, i, dai) {
+ if (!dai->probed)
+ continue;
+
+ if (dai->driver->ops) {
+ if (dai->driver->ops->remove_order != order)
+ continue;
+
+ if (dai->driver->ops->remove) {
+ r = dai->driver->ops->remove(dai);
+ if (r < 0)
+ ret = r; /* use last error */
+ }
+ }
+ dai->probed = 0;
+ }
+
+ return ret;
+}
+
+int snd_soc_pcm_dai_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai;
+ int i;
+
+ for_each_rtd_dais(rtd, i, dai) {
+ if (dai->driver->ops &&
+ dai->driver->ops->pcm_new) {
+ int ret = dai->driver->ops->pcm_new(rtd, dai);
+ if (ret < 0)
+ return soc_dai_ret(dai, ret);
+ }
+ }
+
+ return 0;
+}
+
+int snd_soc_pcm_dai_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai;
+ int i, ret;
+
+ for_each_rtd_dais(rtd, i, dai) {
+ ret = snd_soc_dai_prepare(dai, substream);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int soc_dai_trigger(struct snd_soc_dai *dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int ret = 0;
+
+ if (!snd_soc_dai_stream_valid(dai, substream->stream))
+ return 0;
+
+ if (dai->driver->ops &&
+ dai->driver->ops->trigger)
+ ret = dai->driver->ops->trigger(substream, cmd, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+
+int snd_soc_pcm_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd, int rollback)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai;
+ int i, r, ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for_each_rtd_dais(rtd, i, dai) {
+ ret = soc_dai_trigger(dai, substream, cmd);
+ if (ret < 0)
+ break;
+
+ if (snd_soc_dai_mute_is_ctrled_at_trigger(dai))
+ snd_soc_dai_digital_mute(dai, 0, substream->stream);
+
+ soc_dai_mark_push(dai, substream, trigger);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ for_each_rtd_dais(rtd, i, dai) {
+ if (rollback && !soc_dai_mark_match(dai, substream, trigger))
+ continue;
+
+ if (snd_soc_dai_mute_is_ctrled_at_trigger(dai))
+ snd_soc_dai_digital_mute(dai, 1, substream->stream);
+
+ r = soc_dai_trigger(dai, substream, cmd);
+ if (r < 0)
+ ret = r; /* use last ret */
+ soc_dai_mark_pop(dai, trigger);
+ }
+ }
+
+ return ret;
+}
+
+void snd_soc_pcm_dai_delay(struct snd_pcm_substream *substream,
+ snd_pcm_sframes_t *cpu_delay,
+ snd_pcm_sframes_t *codec_delay)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai;
+ int i;
+
+ /*
+ * We're looking for the delay through the full audio path so it needs to
+ * be the maximum of the DAIs doing transmit and the maximum of the DAIs
+ * doing receive (ie, all CPUs and all CODECs) rather than just the maximum
+ * of all DAIs.
+ */
+
+ /* for CPU */
+ for_each_rtd_cpu_dais(rtd, i, dai)
+ if (dai->driver->ops &&
+ dai->driver->ops->delay)
+ *cpu_delay = max(*cpu_delay, dai->driver->ops->delay(substream, dai));
+
+ /* for Codec */
+ for_each_rtd_codec_dais(rtd, i, dai)
+ if (dai->driver->ops &&
+ dai->driver->ops->delay)
+ *codec_delay = max(*codec_delay, dai->driver->ops->delay(substream, dai));
+}
+
+int snd_soc_dai_compr_startup(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->startup)
+ ret = dai->driver->cops->startup(cstream, dai);
+
+ /* mark cstream if succeeded */
+ if (ret == 0)
+ soc_dai_mark_push(dai, cstream, compr_startup);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_startup);
+
+void snd_soc_dai_compr_shutdown(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ int rollback)
+{
+ if (rollback && !soc_dai_mark_match(dai, cstream, compr_startup))
+ return;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->shutdown)
+ dai->driver->cops->shutdown(cstream, dai);
+
+ /* remove marked cstream */
+ soc_dai_mark_pop(dai, compr_startup);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_shutdown);
+
+int snd_soc_dai_compr_trigger(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream, int cmd)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->trigger)
+ ret = dai->driver->cops->trigger(cstream, cmd, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_trigger);
+
+int snd_soc_dai_compr_set_params(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_params *params)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->set_params)
+ ret = dai->driver->cops->set_params(cstream, params, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_params);
+
+int snd_soc_dai_compr_get_params(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ struct snd_codec *params)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->get_params)
+ ret = dai->driver->cops->get_params(cstream, params, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_params);
+
+int snd_soc_dai_compr_ack(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ size_t bytes)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->ack)
+ ret = dai->driver->cops->ack(cstream, bytes, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_ack);
+
+int snd_soc_dai_compr_pointer(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp64 *tstamp)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->pointer)
+ ret = dai->driver->cops->pointer(cstream, tstamp, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_pointer);
+
+int snd_soc_dai_compr_set_metadata(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_metadata *metadata)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->set_metadata)
+ ret = dai->driver->cops->set_metadata(cstream, metadata, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_set_metadata);
+
+int snd_soc_dai_compr_get_metadata(struct snd_soc_dai *dai,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_metadata *metadata)
+{
+ int ret = 0;
+
+ if (dai->driver->cops &&
+ dai->driver->cops->get_metadata)
+ ret = dai->driver->cops->get_metadata(cstream, metadata, dai);
+
+ return soc_dai_ret(dai, ret);
+}
+EXPORT_SYMBOL_GPL(snd_soc_dai_compr_get_metadata);