[bootlin/training-materials updates] master: audio: checkpoint (3d209f62)

Alexandre Belloni alexandre.belloni at bootlin.com
Tue Apr 25 01:50:13 CEST 2023


Repository : https://github.com/bootlin/training-materials
On branch  : master
Link       : https://github.com/bootlin/training-materials/commit/3d209f62db19328eb38fb20478c1c3b9973b7b31

>---------------------------------------------------------------

commit 3d209f62db19328eb38fb20478c1c3b9973b7b31
Author: Alexandre Belloni <alexandre.belloni at bootlin.com>
Date:   Tue Apr 25 01:50:13 2023 +0200

    audio: checkpoint
    
    Signed-off-by: Alexandre Belloni <alexandre.belloni at bootlin.com>


>---------------------------------------------------------------

3d209f62db19328eb38fb20478c1c3b9973b7b31
 mk/audio.mk                                        |   1 +
 slides/audio-asoc-DAPM/audio-asoc-DAPM.tex         | 199 ++++++++++
 .../.audio-asoc-component-callbacks.tex.swp        | Bin 12288 -> 0 bytes
 .../audio-asoc-component-callbacks.tex             | 399 +++++++++++++++++++++
 slides/audio-asoc/audio-asoc.tex                   |  58 ++-
 slides/audio-auxiliary/audio-auxiliary.tex         |  76 +++-
 slides/audio-debugging/audio-debugging.tex         |  29 +-
 slides/audio-hardware/audio-hardware.tex           |   5 +-
 8 files changed, 713 insertions(+), 54 deletions(-)

diff --git a/mk/audio.mk b/mk/audio.mk
index 75e70209..051cb6bc 100644
--- a/mk/audio.mk
+++ b/mk/audio.mk
@@ -10,5 +10,6 @@ AUDIO_SLIDES = \
 	audio-regmap \
 	audio-asoc-component-callbacks \
 	audio-auxiliary \
+	audio-asoc-DAPM \
 	audio-debugging \
 	last-slides
diff --git a/slides/audio-asoc-DAPM/audio-asoc-DAPM.tex b/slides/audio-asoc-DAPM/audio-asoc-DAPM.tex
new file mode 100644
index 00000000..264ce904
--- /dev/null
+++ b/slides/audio-asoc-DAPM/audio-asoc-DAPM.tex
@@ -0,0 +1,199 @@
+\subsection{ASoC DAPM}
+
+\begin{frame}{DAPM}
+  \begin{itemize}
+  \item DAPM stands for Dynamic Audio Power Management.
+  \item The goal is to save as much power as possible by shutting down
+    audio routes that are not in use.
+  \item This may affect the whole card or just part of it.
+  \item To achieve this, the topology needs to be described. For this
+    we have two objects: DAPM widgets and DAPM routes.
+  \item The DAPM widgets represent various components of an audio
+    system, such as audio inputs, outputs, mixers, and amplifiers.
+  \item The routes are connecting widgets together.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{snd_soc_dapm_widget}}
+  \begin{itemize}
+  \item An array of \code{struct snd_soc_dapm_widget} is registered
+    by the component.
+  \item Many helpers exist to avoid filling the struct manually:
+  \begin{block}{\code{include/sound/soc-dapm.h}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+#define SND_SOC_DAPM_INPUT(wname) \
+{       .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \
+        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
+#define SND_SOC_DAPM_OUTPUT(wname) \
+{       .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \
+        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
+#define SND_SOC_DAPM_MIC(wname, wevent) \
+{       .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \
+        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
+        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
+[...]
+#define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,\
+         wcontrols, wncontrols) \
+{       .id = snd_soc_dapm_pga, .name = wname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
+[...]
+#define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \
+{       .id = snd_soc_dapm_mux, .name = wname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .kcontrol_news = wcontrols, .num_kcontrols = 1}
+#define SND_SOC_DAPM_DEMUX(wname, wreg, wshift, winvert, wcontrols) \
+{       .id = snd_soc_dapm_demux, .name = wname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .kcontrol_news = wcontrols, .num_kcontrols = 1}
+    \end{minted}
+  \end{block}
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{snd_soc_dapm_widget}}
+  \begin{block}{\code{include/sound/soc-dapm.h}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+#define SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert) \
+{       .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) }
+#define SND_SOC_DAPM_DAC_E(wname, stname, wreg, wshift, winvert, \
+                           wevent, wflags)                                \
+{       .id = snd_soc_dapm_dac, .name = wname, .sname = stname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .event = wevent, .event_flags = wflags}
+
+#define SND_SOC_DAPM_ADC(wname, stname, wreg, wshift, winvert) \
+{       .id = snd_soc_dapm_adc, .name = wname, .sname = stname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
+#define SND_SOC_DAPM_ADC_E(wname, stname, wreg, wshift, winvert, \
+                           wevent, wflags)                                \
+{       .id = snd_soc_dapm_adc, .name = wname, .sname = stname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .event = wevent, .event_flags = wflags}
+
+
+/* generic widgets */
+#define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \
+{       .id = wid, .name = wname, .kcontrol_news = NULL, .num_kcontrols = 0, \
+        .reg = wreg, .shift = wshift, .mask = wmask, \
+        .on_val = won_val, .off_val = woff_val, }
+#define SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags) \
+{       .id = snd_soc_dapm_supply, .name = wname, \
+        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+        .event = wevent, .event_flags = wflags}
+#define SND_SOC_DAPM_REGULATOR_SUPPLY(wname, wdelay, wflags)            \
+{       .id = snd_soc_dapm_regulator_supply, .name = wname, \
+        .reg = SND_SOC_NOPM, .shift = wdelay, .event = dapm_regulator_event, \
+        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD, \
+        .on_val = wflags}
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{DAPM example}
+  \begin{block}{\code{sound/soc/codecs/pcm3168a.c}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+static const struct snd_soc_dapm_widget pcm3168a_dapm_widgets[] = {
+        SND_SOC_DAPM_DAC("DAC1", "Playback", PCM3168A_DAC_OP_FLT,
+                        PCM3168A_DAC_OPEDA_SHIFT, 1),
+        SND_SOC_DAPM_DAC("DAC2", "Playback", PCM3168A_DAC_OP_FLT,
+                        PCM3168A_DAC_OPEDA_SHIFT + 1, 1),
+        SND_SOC_DAPM_DAC("DAC3", "Playback", PCM3168A_DAC_OP_FLT,
+                        PCM3168A_DAC_OPEDA_SHIFT + 2, 1),
+        SND_SOC_DAPM_DAC("DAC4", "Playback", PCM3168A_DAC_OP_FLT,
+                        PCM3168A_DAC_OPEDA_SHIFT + 3, 1),
+
+        SND_SOC_DAPM_OUTPUT("AOUT1L"),
+        SND_SOC_DAPM_OUTPUT("AOUT1R"),
+        SND_SOC_DAPM_OUTPUT("AOUT2L"),
+        SND_SOC_DAPM_OUTPUT("AOUT2R"),
+        SND_SOC_DAPM_OUTPUT("AOUT3L"),
+        SND_SOC_DAPM_OUTPUT("AOUT3R"),
+        SND_SOC_DAPM_OUTPUT("AOUT4L"),
+        SND_SOC_DAPM_OUTPUT("AOUT4R"),
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{DAPM example}
+  \begin{block}{\code{sound/soc/codecs/pcm3168a.c}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+        SND_SOC_DAPM_ADC("ADC1", "Capture", PCM3168A_ADC_PWR_HPFB,
+                        PCM3168A_ADC_PSVAD_SHIFT, 1),
+        SND_SOC_DAPM_ADC("ADC2", "Capture", PCM3168A_ADC_PWR_HPFB,
+                        PCM3168A_ADC_PSVAD_SHIFT + 1, 1),
+        SND_SOC_DAPM_ADC("ADC3", "Capture", PCM3168A_ADC_PWR_HPFB,
+                        PCM3168A_ADC_PSVAD_SHIFT + 2, 1),
+
+        SND_SOC_DAPM_INPUT("AIN1L"),
+        SND_SOC_DAPM_INPUT("AIN1R"),
+        SND_SOC_DAPM_INPUT("AIN2L"),
+        SND_SOC_DAPM_INPUT("AIN2R"),
+        SND_SOC_DAPM_INPUT("AIN3L"),
+        SND_SOC_DAPM_INPUT("AIN3R")
+};
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{\code{snd_soc_dapm_route}}
+  \begin{itemize}
+  \item An array of \code{struct snd_soc_dapm_route} is registered
+    by the component to define the routes.
+  \end{itemize}
+  \begin{block}{\code{include/sound/soc-dapm.h}}
+    \fontsize{9}{9}\selectfont
+    \begin{minted}{c}
+struct snd_soc_dapm_route {
+        const char *sink;
+        const char *control;
+        const char *source;
+
+        /* Note: currently only supported for links where source is a supply */
+        int (*connected)(struct snd_soc_dapm_widget *source,
+                         struct snd_soc_dapm_widget *sink);
+
+        struct snd_soc_dobj dobj;
+};
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{DAPM routes example}
+  \begin{block}{\code{sound/soc/codecs/pcm3168a.c}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+static const struct snd_soc_dapm_route pcm3168a_dapm_routes[] = {
+        /* Playback */
+        { "AOUT1L", NULL, "DAC1" },
+        { "AOUT1R", NULL, "DAC1" },
+
+        { "AOUT2L", NULL, "DAC2" },
+        { "AOUT2R", NULL, "DAC2" },
+
+        { "AOUT3L", NULL, "DAC3" },
+        { "AOUT3R", NULL, "DAC3" },
+
+        { "AOUT4L", NULL, "DAC4" },
+        { "AOUT4R", NULL, "DAC4" },
+
+        /* Capture */
+        { "ADC1", NULL, "AIN1L" },
+        { "ADC1", NULL, "AIN1R" },
+
+        { "ADC2", NULL, "AIN2L" },
+        { "ADC2", NULL, "AIN2R" },
+
+        { "ADC3", NULL, "AIN3L" },
+        { "ADC3", NULL, "AIN3R" }
+};
+    \end{minted}
+  \end{block}
+\end{frame}
+
+
diff --git a/slides/audio-asoc-component-callbacks/.audio-asoc-component-callbacks.tex.swp b/slides/audio-asoc-component-callbacks/.audio-asoc-component-callbacks.tex.swp
deleted file mode 100644
index cbd7fa56..00000000
Binary files a/slides/audio-asoc-component-callbacks/.audio-asoc-component-callbacks.tex.swp and /dev/null differ
diff --git a/slides/audio-asoc-component-callbacks/audio-asoc-component-callbacks.tex b/slides/audio-asoc-component-callbacks/audio-asoc-component-callbacks.tex
index 712acda3..51dc3f07 100644
--- a/slides/audio-asoc-component-callbacks/audio-asoc-component-callbacks.tex
+++ b/slides/audio-asoc-component-callbacks/audio-asoc-component-callbacks.tex
@@ -86,3 +86,402 @@ struct snd_soc_dai_ops {
     \end{itemize}
   \end{itemize}
 \end{frame}
+
+\begin{frame}[fragile]{\code{hw_params} example}
+  \begin{block}{\code{sound/soc/codecs/tlv320aic31xx.c}}
+    \fontsize{8}{7}\selectfont
+    \begin{minted}{c}
+static int aic31xx_hw_params(struct snd_pcm_substream *substream,
+                             struct snd_pcm_hw_params *params,
+                             struct snd_soc_dai *dai)
+{
+        struct snd_soc_component *component = dai->component;
+        struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component);
+        u8 data = 0;
+
+        switch (params_width(params)) {
+        case 16:
+                break;
+        case 20:
+                data = (AIC31XX_WORD_LEN_20BITS <<
+                        AIC31XX_IFACE1_DATALEN_SHIFT);
+                break;
+        case 24:
+                data = (AIC31XX_WORD_LEN_24BITS <<
+                        AIC31XX_IFACE1_DATALEN_SHIFT);
+                break;
+        case 32:
+                data = (AIC31XX_WORD_LEN_32BITS <<
+                        AIC31XX_IFACE1_DATALEN_SHIFT);
+                break;
+        default:
+                dev_err(component->dev, "%s: Unsupported width %d\n",
+                        __func__, params_width(params));
+                return -EINVAL;
+        }
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{\code{hw_params} example}
+  \begin{block}{\code{sound/soc/codecs/tlv320aic31xx.c}}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+        snd_soc_component_update_bits(component, AIC31XX_IFACE1,
+                            AIC31XX_IFACE1_DATALEN_MASK,
+                            data);
+
+        /*
+         * If BCLK is used as PLL input, the sysclk is determined by the hw
+         * params. So it must be updated here to match the input frequency.
+         */
+        if (aic31xx->sysclk_id == AIC31XX_PLL_CLKIN_BCLK) {
+                aic31xx->sysclk = params_rate(params) * params_width(params) *
+                                  params_channels(params);
+                aic31xx->p_div = 1;
+        }
+
+        return aic31xx_setup_pll(component, params);
+}
+    \end{minted}
+  \end{block}
+  \kfunc{aic31xx_setup_pll} then uses the parameters to set the CODEC
+  PLLs and clocks properly. The usual ways to achieve that are to
+  either do the calculations or prepare an array matching parameters
+  to register values.
+\end{frame}
+
+\begin{frame}{\code{set_sysclk}}
+  \begin{itemize}
+  \item This sets the system clock parameters of the component, in
+    particular which one is selected, its frequency and the direction.
+  \item This allows the component to set up PLLs and clocks.
+  \item This is called from the machine driver, using
+    \kfunc{snd_soc_dai_set_sysclk}
+  \item It can return an error in case the clock is not available or
+    the frequency is not in the supported range.
+  \item A component wide version exists, called using
+    \kfunc{snd_soc_component_set_sysclk}, very rarely used.
+  \end{itemize}
+\end{frame}
+
+
+\begin{frame}[fragile]{\code{set_sysclk} example}
+  \begin{block}{}
+    \fontsize{7}{7}\selectfont
+    \begin{minted}{c}
+static int aic31xx_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+        struct snd_soc_component *component = codec_dai->component;
+        struct aic31xx_priv *aic31xx = snd_soc_component_get_drvdata(component);
+        int i;
+[...]
+        for (i = 1; i < 8; i++)
+                if (freq / i <= 20000000)
+                        break;
+        if (freq/i > 20000000) {
+                dev_err(aic31xx->dev, "%s: Too high mclk frequency %u\n",
+                        __func__, freq);
+                return -EINVAL;
+        }
+        aic31xx->p_div = i;
+
+        for (i = 0; i < ARRAY_SIZE(aic31xx_divs); i++)
+                if (aic31xx_divs[i].mclk_p == freq / aic31xx->p_div)
+                        break;
+        if (i == ARRAY_SIZE(aic31xx_divs)) {
+                dev_err(aic31xx->dev, "%s: Unsupported frequency %d\n",
+                        __func__, freq);
+                return -EINVAL;
+        }
+
+        /* set clock on MCLK, BCLK, or GPIO1 as PLL input */
+        snd_soc_component_update_bits(component, AIC31XX_CLKMUX, AIC31XX_PLL_CLKIN_MASK,
+                            clk_id << AIC31XX_PLL_CLKIN_SHIFT);
+
+        aic31xx->sysclk_id = clk_id;
+        aic31xx->sysclk = freq;
+
+        return 0;
+}
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}{\code{set_fmt}}
+  \begin{itemize}
+  \item This sets the format of the PCM bus
+  \item This is called from the machine driver, using
+    \kfunc{snd_soc_dai_set_fmt}
+  \item Available formats are:
+    \begin{itemize}
+    \item \ksym{SND_SOC_DAIFMT_I2S}
+    \item \ksym{SND_SOC_DAIFMT_RIGHT_J}
+    \item \ksym{SND_SOC_DAIFMT_LEFT_J}
+    \item \ksym{SND_SOC_DAIFMT_DSP_A}
+    \item \ksym{SND_SOC_DAIFMT_DSP_B}
+    \item \ksym{SND_SOC_DAIFMT_AC97}
+    \item \ksym{SND_SOC_DAIFMT_PDM}
+    \end{itemize}
+  \item Also the polarity can be changed:
+    \begin{itemize}
+    \item \ksym{SND_SOC_DAIFMT_NB_NF}: normal bit clock + frame
+    \item \ksym{SND_SOC_DAIFMT_NB_IF}: normal bit clock + invert frame
+    \item \ksym{SND_SOC_DAIFMT_IB_NF}: invert bit clock + normal frame
+    \item \ksym{SND_SOC_DAIFMT_IB_IF}: invert bit clock + frame
+    \end{itemize}
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{set_fmt}}
+  \begin{itemize}
+  \item The clock directions can also be set:
+    \begin{itemize}
+    \item \ksym{SND_SOC_DAIFMT_CBP_CFP}: codec clk provider and frame provider
+    \item \ksym{SND_SOC_DAIFMT_CBC_CFP}: codec clk consumer and frame provider
+    \item \ksym{SND_SOC_DAIFMT_CBP_CFC}: codec clk provider and frame consumer
+    \item \ksym{SND_SOC_DAIFMT_CBC_CFC}: codec clk consumer and frame consumer
+    \end{itemize}
+  \item These used to have another name:
+  \begin{block}{include/sound/soc-dai.h}
+    \fontsize{9}{9}\selectfont
+    \begin{minted}{c}
+/* previous definitions kept for backwards-compatibility, do not use in new contributions */
+#define SND_SOC_DAIFMT_CBM_CFM		SND_SOC_DAIFMT_CBP_CFP
+#define SND_SOC_DAIFMT_CBS_CFM		SND_SOC_DAIFMT_CBC_CFP
+#define SND_SOC_DAIFMT_CBM_CFS		SND_SOC_DAIFMT_CBP_CFC
+#define SND_SOC_DAIFMT_CBS_CFS		SND_SOC_DAIFMT_CBC_CFC
+    \end{minted}
+  \end{block}
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{set_fmt} example}
+  \begin{block}{}
+    \fontsize{7}{7}\selectfont
+    \begin{minted}{c}
+static int aic31xx_set_dai_fmt(struct snd_soc_dai *codec_dai,
+                               unsigned int fmt)
+{
+        struct snd_soc_component *component = codec_dai->component;
+        u8 iface_reg1 = 0;
+        u8 iface_reg2 = 0;
+        u8 dsp_a_val = 0;
+[...]
+        switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+        case SND_SOC_DAIFMT_CBP_CFP:
+                iface_reg1 |= AIC31XX_BCLK_MASTER | AIC31XX_WCLK_MASTER;
+                break;
+[...]
+        }
+
+        /* signal polarity */
+        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+        case SND_SOC_DAIFMT_NB_NF:
+[...]
+        }
+
+        /* interface format */
+        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+[...]
+        }
+
+        snd_soc_component_update_bits(component, AIC31XX_IFACE1,
+                            AIC31XX_IFACE1_DATATYPE_MASK |
+                            AIC31XX_IFACE1_MASTER_MASK,
+                            iface_reg1);
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}{\code{set_tdm_slot}}
+  \begin{itemize}
+  \item This callback configures the DAI for TDM operation.
+  \item \code{slot} is the total number of slots of the TDM stream and
+    \code{slot_with} the width of each slot in bit clock cycles.
+  \item \code{tx_mask} and \code{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. A set bit means the channel is
+    active.
+  \item This is called from the machine driver, using
+    \kfunc{snd_soc_dai_set_tdm_slot}
+  \item This allows to explicitly configure mismatching stream and bus
+    sample width.
+  \item TDM mode must be disabled when \code{slots} is 0.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}{\code{trigger}}
+  \begin{itemize}
+  \item This callback is called when the stream status is updated.
+  \item It allows to listen for events.
+  \item This is called from the Alsa core, in \kfunc{soc_pcm_trigger}
+    using \kfunc{ snd_soc_pcm_dai_trigger}
+  \item A component version exists.
+  \item Available states are:
+    \begin{itemize}
+    \item \ksym{SNDRV_PCM_TRIGGER_STOP}
+    \item \ksym{SNDRV_PCM_TRIGGER_START}
+    \item \ksym{SNDRV_PCM_TRIGGER_PAUSE_PUSH}
+    \item \ksym{SNDRV_PCM_TRIGGER_PAUSE_RELEASE}
+    \item \ksym{SNDRV_PCM_TRIGGER_SUSPEND}
+    \item \ksym{SNDRV_PCM_TRIGGER_RESUME}
+    \item \ksym{SNDRV_PCM_TRIGGER_DRAIN}
+    \end{itemize}
+  \end{itemize}
+\end{frame}
+
+\begin{frame}{\code{trigger} example}
+  \begin{itemize}
+  \item The PCM1789 needs the system clock, bit clock and frame clock
+    to be synchronized as soon as it gets out of reset.
+  \item With DAPM, those clocks are disabled until a stream is ready
+    to be played.
+  \item A solution is to reset the device when a stream is played.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{trigger} example}
+  \begin{block}{sound/soc/codecs/pcm1789.c}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+static int pcm1789_trigger(struct snd_pcm_substream *substream, int cmd,
+                           struct snd_soc_dai *dai)
+{
+        struct snd_soc_component *component = dai->component;
+        struct pcm1789_private *priv = snd_soc_component_get_drvdata(component);
+        int ret = 0;
+
+        switch (cmd) {
+        case SNDRV_PCM_TRIGGER_START:
+        case SNDRV_PCM_TRIGGER_RESUME:
+        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+                schedule_work(&priv->work);
+                break;
+        case SNDRV_PCM_TRIGGER_STOP:
+        case SNDRV_PCM_TRIGGER_SUSPEND:
+        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+                break;
+        default:
+                ret = -EINVAL;
+        }
+
+        return ret;
+}
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{\code{trigger} example}
+  \begin{block}{sound/soc/codecs/pcm1789.c}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+static void pcm1789_work_queue(struct work_struct *work)
+{
+        struct pcm1789_private *priv = container_of(work,
+                                                    struct pcm1789_private,
+                                                    work);
+
+        /* Perform a software reset to remove codec from desynchronized state */
+        if (regmap_update_bits(priv->regmap, PCM1789_MUTE_CONTROL,
+                               0x3 << PCM1789_MUTE_SRET, 0) < 0)
+                dev_err(priv->dev, "Error while setting SRET");
+}
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}{\code{set_bias_level}}
+  \begin{itemize}
+  \item This callback is called by DAPM through
+    \kfunc{snd_soc_dapm_set_bias_level} and
+    \kfunc{snd_soc_component_set_bias_level} once the component gets
+    activated.
+  \item It allows to listen for power events.
+  \item Available events are:
+    \begin{itemize}
+    \item \code{SND_SOC_BIAS_ON}: Bias is fully on for audio playback
+      and capture operations.
+    \item \code{SND_SOC_BIAS_PREPARE}: Prepare for audio operations.
+      Called before DAPM switching for
+      stream start and stop operations.
+    \item \code{SND_SOC_BIAS_STANDBY}: Low power standby state when no
+      playback/capture operations are
+      in progress. NOTE: The transition time between STANDBY and ON
+      should be as fast as possible and no longer than 10ms.
+    \item \code{SND_SOC_BIAS_OFF}: Power Off. No restrictions on
+      transition times.
+    \end{itemize}
+  \end{itemize}
+\end{frame}
+
+\begin{frame}{\code{set_bias_level} example}
+  \begin{itemize}
+  \item There are CODECs that won't even listen on the control
+    bus until there are clocks on the PCM bus or that will stay
+    powered off as much as possible.
+  \item A solution is to use regcache.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{set_bias_level} example}
+  \begin{block}{sound/soc/codecs/ssm2518.c}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+static int ssm2518_set_bias_level(struct snd_soc_component *component,
+        enum snd_soc_bias_level level)
+{
+        struct ssm2518 *ssm2518 = snd_soc_component_get_drvdata(component);
+        int ret = 0;
+
+        switch (level) {
+        case SND_SOC_BIAS_ON:
+                break;
+        case SND_SOC_BIAS_PREPARE:
+                break;
+        case SND_SOC_BIAS_STANDBY:
+                if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
+                        ret = ssm2518_set_power(ssm2518, true);
+                break;
+        case SND_SOC_BIAS_OFF:
+                ret = ssm2518_set_power(ssm2518, false);
+                break;
+        }
+
+        return ret;
+}
+    \end{minted}
+  \end{block}
+\end{frame}
+
+\begin{frame}[fragile]{\code{set_bias_level} example}
+  \begin{block}{sound/soc/codecs/ssm2518.c}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+static int ssm2518_set_power(struct ssm2518 *ssm2518, bool enable)
+{
+        int ret = 0;
+
+        if (!enable) {
+                ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+                        SSM2518_POWER1_SPWDN, SSM2518_POWER1_SPWDN);
+                regcache_mark_dirty(ssm2518->regmap);
+        }
+
+        if (ssm2518->enable_gpio)
+                gpiod_set_value_cansleep(ssm2518->enable_gpio, enable);
+
+        regcache_cache_only(ssm2518->regmap, !enable);
+
+        if (enable) {
+                ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+                        SSM2518_POWER1_SPWDN | SSM2518_POWER1_RESET, 0x00);
+                regcache_sync(ssm2518->regmap);
+        }
+
+        return ret;
+}
+    \end{minted}
+  \end{block}
+\end{frame}
diff --git a/slides/audio-asoc/audio-asoc.tex b/slides/audio-asoc/audio-asoc.tex
index 7a0b159f..3ef51d4d 100644
--- a/slides/audio-asoc/audio-asoc.tex
+++ b/slides/audio-asoc/audio-asoc.tex
@@ -333,6 +333,7 @@ struct snd_soc_dai_link {
         /* config - must be set by machine driver */
         const char *name;                        /* Codec name */
         const char *stream_name;                /* Stream name */
+
         /*
          * You MAY specify the link's CPU-side device, either by device name,
          * or by DT/OF node, but not both. If this information is omitted,
@@ -340,14 +341,13 @@ struct snd_soc_dai_link {
          * must be globally unique. These fields are currently typically used
          * only for codec to codec links, or systems using device tree.
          */
-        const char *cpu_name;
-        struct device_node *cpu_of_node;
         /*
          * You MAY specify the DAI name of the CPU DAI. If this information is
          * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
          * only, which only works well when that device exposes a single DAI.
          */
-        const char *cpu_dai_name;
+        struct snd_soc_dai_link_component *cpus;
+        unsigned int num_cpus;
      \end{minted}
    \end{block}
 \end{frame}
@@ -360,51 +360,34 @@ struct snd_soc_dai_link {
          * You MUST specify the link's codec, either by device name, or by
          * DT/OF node, but not both.
          */
-        const char *codec_name;
-        struct device_node *codec_of_node;
         /* You MUST specify the DAI name within the codec */
-        const char *codec_dai_name;
-
         struct snd_soc_dai_link_component *codecs;
         unsigned int num_codecs;
-    \end{minted}
-  \end{block}
-\end{frame}
-
-\begin{frame}[fragile]{\code{struct snd_soc_dai_link}}
-  \begin{block}{}
-    \fontsize{10}{10}\selectfont
-    \begin{minted}{c}
-        /*
-         * You MAY specify the link's platform/PCM/DMA driver, either by
-         * device name, or by DT/OF node, but not both. Some forms of link
-         * do not need a platform.
-         */
-        const char *platform_name;
-        struct device_node *platform_of_node;
-        int id;        /* optional ID for machine driver link identification */
-
-        const struct snd_soc_pcm_stream *params;
-        unsigned int num_params;
-
+[...]
         unsigned int dai_fmt;           /* format to set on init */
-};
+[...]
+}
     \end{minted}
   \end{block}
 \end{frame}
 
 \begin{frame}[fragile]{Example 1}
   \begin{block}{\code{sound/soc/atmel/atmel_wm8904.c}}
-    \fontsize{10}{10}\selectfont
+    \fontsize{8}{8}\selectfont
     \begin{minted}{c}
+SND_SOC_DAILINK_DEFS(pcm,
+        DAILINK_COMP_ARRAY(COMP_EMPTY()),
+        DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")),
+        DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
 static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {
         .name = "WM8904",
         .stream_name = "WM8904 PCM",
-        .codec_dai_name = "wm8904-hifi",
         .dai_fmt = SND_SOC_DAIFMT_I2S
                 | SND_SOC_DAIFMT_NB_NF
-                | SND_SOC_DAIFMT_CBM_CFM,
+                | SND_SOC_DAIFMT_CBP_CFP,
         .ops = &atmel_asoc_wm8904_ops,
+        SND_SOC_DAILINK_REG(pcm),
 };
 
 static struct snd_soc_card atmel_asoc_wm8904_card = {
@@ -437,8 +420,8 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev)
                 ret = -EINVAL;
                 return ret;
         }
-        dailink->cpu_of_node = cpu_np;
-        dailink->platform_of_node = cpu_np;
+        dailink->cpus->of_node = cpu_np;
+        dailink->platforms->of_node = cpu_np;
         of_node_put(cpu_np);
 
         codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
@@ -447,7 +430,8 @@ static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev)
                 ret = -EINVAL;
                 return ret;
         }
-        dailink->codec_of_node = codec_np;
+        dailink->codecs->of_node = codec_np;
+        of_node_put(codec_np);
     \end{minted}
   \end{block}
 \end{frame}
@@ -469,7 +453,7 @@ static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
                 return ret;
         }
 
-        id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc");
+        id = of_alias_get_id((struct device_node *)dailink->cpus->of_node, "ssc");
         ret = atmel_ssc_set_audio(id);
         if (ret != 0) {
                 dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id);
@@ -477,6 +461,10 @@ static int atmel_asoc_wm8904_probe(struct platform_device *pdev)
         }
 
         ret = snd_soc_register_card(card);
+        if (ret) {
+                dev_err(&pdev->dev, "snd_soc_register_card failed\n");
+                goto err_set_audio;
+        }
 [...]
 }
     \end{minted}
diff --git a/slides/audio-auxiliary/audio-auxiliary.tex b/slides/audio-auxiliary/audio-auxiliary.tex
index 01444723..9ac1418f 100644
--- a/slides/audio-auxiliary/audio-auxiliary.tex
+++ b/slides/audio-auxiliary/audio-auxiliary.tex
@@ -1,6 +1,6 @@
 \subsection{Auxiliary devices}
 
-\begin{frame}[fragile]{Amplifier}
+\begin{frame}{Amplifier}
   What about the amplifier?
   \begin{itemize}
   \item Supported using {\em auxiliary devices}
@@ -92,3 +92,77 @@ arch/arm64/boot/dts/allwinner/sun50i-a64-pinebook.dts
   \end{block}
   Audio is routed through\code{AU2}, the amplifier.
 \end{frame}
+
+\begin{frame}{Input Muxing}
+  \begin{itemize}
+  \item There may be a muxer on the analog input lines.
+  \item If controlled using a gpio, the \code{simple-mux} driver is
+    available
+  \item It exposes two inputs: "IN1" and "IN2" and one output, "OUT".
+  \item The device tree binding allows to provide a prefix to make the
+    routes specific.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{simple-mux} example}
+  \begin{block}{}
+    \fontsize{8}{8}\selectfont
+    \begin{minted}{c}
+        mic_mux: mic-mux {
+                compatible = "simple-audio-mux";
+                pinctrl-names = "default";
+                pinctrl-0 = <&pinctrl_micsel>;
+                mux-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>;
+                sound-name-prefix = "Mic Mux";
+        };
+    \end{minted}
+  \end{block}
+  \begin{itemize}
+  \item This exposes routes between \code{Mic Mux IN1} and \code{Mic
+    Mux IN2} to \code{Mic Mux OUT}.
+  \item This route is controlled by \code{gpio5 5}
+  \item A control named \code{Mic Mux Muxer} will be exposed to
+    userspace
+  \end{itemize}
+\end{frame}
+
+\begin{frame}[fragile]{\code{simple-mux} example}
+  \begin{block}{}
+    \fontsize{7}{6}\selectfont
+    \begin{minted}{c}
+        sound {
+                compatible = "simple-audio-card";
+                pinctrl-names = "default";
+                pinctrl-0 = <&pinctrl_hpdet>;
+                simple-audio-card,aux-devs = <&speaker_amp>, <&mic_mux>;
+                simple-audio-card,name = "Librem 5 Devkit";
+                simple-audio-card,format = "i2s";
+                simple-audio-card,widgets =
+                        "Microphone", "Builtin Microphone",
+                        "Microphone", "Headset Microphone",
+                        "Headphone", "Headphones",
+                        "Speaker", "Builtin Speaker";
+                simple-audio-card,routing =
+                        "MIC_IN", "Mic Mux OUT",
+                        "Mic Mux IN1", "Headset Microphone",
+                        "Mic Mux IN2", "Builtin Microphone",
+                        "Mic Mux OUT", "Mic Bias",
+                        "Headphones", "HP_OUT",
+                        "Builtin Speaker", "Speaker Amp OUTR",
+                        "Speaker Amp INR", "LINE_OUT";
+                simple-audio-card,hp-det-gpio = <&gpio3 20 GPIO_ACTIVE_HIGH>;
+
+                simple-audio-card,cpu {
+                        sound-dai = <&sai2>;
+                };
+
+                simple-audio-card,codec {
+                        sound-dai = <&sgtl5000>;
+                        clocks = <&clk IMX8MQ_CLK_SAI2_ROOT>;
+                        frame-master;
+                        bitclock-master;
+                };
+        };
+    \end{minted}
+  \end{block}
+\end{frame}
diff --git a/slides/audio-debugging/audio-debugging.tex b/slides/audio-debugging/audio-debugging.tex
index a286ec83..a840df29 100644
--- a/slides/audio-debugging/audio-debugging.tex
+++ b/slides/audio-debugging/audio-debugging.tex
@@ -1,4 +1,4 @@
-\section{troubleshooting}
+\section{Troubleshooting}
 
 \begin{frame}{Troubleshooting: no sound}
   Audio seems to play for the correct duration but there is no sound:
@@ -58,7 +58,18 @@ underrun!!! (at least 8.558 ms long)
   \end{itemize}
 \end{frame}
 
-\begin{frame}[fragile]{Troubleshooting: going further}
+\begin{frame}{Troubleshooting: going further}
+  \begin{itemize}
+  \item Use \code{speaker-test} to generate audio an play tones.
+  \item Be careful with the 440Hz tone, it may not expose all the
+    errors. Rather play something that is not commonly divisible (e.g.
+    441Hz)
+  \item Generate tone with fade in and fade out as this allows to
+    catch DMA transfer issues more easily.
+  \end{itemize}
+\end{frame}
+
+\begin{frame}{Troubleshooting: going further}
   \begin{itemize}
   \item Have a look at the CPU DAI driver and its callback. In
     particular: \code{.set_clkdiv} and \code{.set_sysclk} to
@@ -66,18 +77,8 @@ underrun!!! (at least 8.558 ms long)
     \code{.hw_params} or \code{.set_dai_fmt} may do some muxing
   \item Have a look at the codec driver callbacks, \code{.set_sysclk}
     as the \code{clk_id} parameter is codec specific.
-  \item Remember using a codec as slave is an uncommon configuration
-    and is probably untested.
+  \item Remember using a codec as a clock consumer is an uncommon
+    configuration and is probably untested.
   \item When in doubt, use \code{devmem} or \code{i2cget}
   \end{itemize}
 \end{frame}
-
-\begin{frame}{References}
-  \begin{itemize}
-  \item \code{Documentation/sound/alsa/soc/}
-  \item Common Inter-IC Digital Interfaces for Audio Data Transfer by Jerad Lewis, Analog Devices, Inc.
-    \url{http://www.analog.com/media/en/technical-documentation/technical-articles/MS-2275.pdf?doc=an-1327.pdf}
-  \item I²S specification
-    \url{https://web.archive.org/web/20060702004954/http://www.semiconductors.philips.com/acrobat_download/various/I2SBUS.pdf}
-  \end{itemize}
-\end{frame}
diff --git a/slides/audio-hardware/audio-hardware.tex b/slides/audio-hardware/audio-hardware.tex
index 65fe8f96..f1eaafdc 100644
--- a/slides/audio-hardware/audio-hardware.tex
+++ b/slides/audio-hardware/audio-hardware.tex
@@ -141,9 +141,6 @@
   \end{center}
 \end{frame}
 
-\begin{frame}{Digital formats - AC-link}
-\end{frame}
-
 \begin{frame}{Digital formats - IEC 61937}
 \end{frame}
 
@@ -160,7 +157,7 @@
 
 \subsection{Clocks}
 
-\begin{frame}[fragile]{Clocks: producer/consumer}
+\begin{frame}{Clocks: producer/consumer}
   \begin{itemize}
   \item One of the DAI is responsible to generate the bit clock, it is
     the bit clock producer (previously: master).




More information about the training-materials-updates mailing list