[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