\documentclass[aspectratio=169,obeyspaces,spaces,hyphens,dvipsnames]{beamer}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}% http://ctan.org/pkg/lm
\usepackage{minted}
\usepackage{hyperref}
\usepackage{xcolor}
\usepackage{pgfplots}
\usepackage{tikz}
\usepackage[normalem]{ulem}

\mode<presentation>
\usetheme{Bootlin}

\def\signed #1{{\leavevmode\unskip\nobreak\hfil\penalty50\hskip2em
  \hbox{}\nobreak\hfil(#1)
  \parfillskip=0pt \finalhyphendemerits=0 \endgraf}}

\newsavebox\mybox
\newenvironment{aquote}[1]
  {\savebox\mybox{#1}\begin{quotation}}
  {\signed{\usebox\mybox}\end{quotation}}

\title{Introduction to DAPM}
\authors{Luca Ceresoli}
\email{luca.ceresoli@bootlin.com}
\slidesurl{https://bootlin.com/pub/conferences/}
\institute{Bootlin}
\conference{Embedded Linux Conference 2024}

\begin{document}

\addtocontents{toc}{\protect\setcounter{tocdepth}{-1}}
\section{Introduction to DAPM: Linux power management for embedded audio devices}
\addtocontents{toc}{\protect\setcounter{tocdepth}{2}}

\begin{frame}{Luca Ceresoli}
  \begin{itemize}
  \item Embedded Linux engineer at Bootlin
    \begin{itemize}
    \item Embedded Linux {\bf expertise}
    \item {\bf Development}, consulting and training
    \item Strong open-source focus
    \end{itemize}
  \item Linux kernel device driver developer
  \item Bootloaders, Buildroot and Yocto integration
  \item Open-source contributor
  \item Living in {\bf Bergamo}, Italy
  \item \code{luca.ceresoli@bootlin.com}
  \end{itemize}
  {\small \url{https://bootlin.com/company/staff/luca-ceresoli/}}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Background\\
(ALSA, ASoC, DAPM)}

\begin{frame}{ALSA = Advanced Linux Sound Architecture}
  \begin{columns}
    \column{0.6\textwidth}
    \begin{itemize}
    \item Merged in v2.5, 2002
    \item 1 sound card = 1 device = 1 driver
    \item Consistent user space API based on
      \begin{itemize}
        \item Streams: capture, playback
        \item kcontrols to change settings
      \end{itemize}
    \item User space API still in use today
    \item Hard to reuse code for components used on different cards
    \end{itemize}

    \column{0.3\textwidth}
    \centering
    \pause
    \includegraphics[width=0.7\textwidth]{intro-alsa-hw.pdf}
    \\[0.15\textheight]
    \pause
    \includegraphics[height=0.5\textheight]{intro-alsa-linux.pdf}

    \column{0.1\textwidth}
    \pause
    \includegraphics[height=0.8\textheight]{alsamixer-control.png}
  \end{columns}
\end{frame}

\begin{frame}{ASoC = ALSA System on Chip}
    \centering\includegraphics[height=0.3\textheight]{intro-anatomy.pdf}
    \begin{itemize}
    \item Merged in 2006
    \item Additional ALSA layer ``to provide better ALSA support for embedded
      System-on-Chip processors'' {\tiny (\url{https://docs.kernel.org/sound/soc/overview.html})}
      \begin{itemize}
      \item Great for embedded systems where different SoCs, codecs and
        other components are mixed and matched
      \end{itemize}
    \item 1 sound card = N components and their interconnections + glue
    \item Each component has a separate driver
    \item Same user space API
    \end{itemize}
\end{frame}

\begin{frame}{Power management with ASoC}
  \begin{itemize}
    \item Many components allow flexible routing
    \item Different routings require different components to be turned on
    \item Many combinations: code to enable only what is needed tends to be
      complex and not easy to maintain
  \end{itemize}
  \centering\includegraphics[height=0.4\textheight]{adau1372.png}
  {\tiny (\url{https://www.analog.com/media/en/technical-documentation/data-sheets/ADAU1372.pdf})}
\end{frame}

\begin{frame}{DAPM: Dynamic Audio Power Management}
  \begin{itemize}
  \item The power management component of ASoC
  \item LinuxRuntime PM works at the device level, $\rightarrow$ not suitable
  \item DAPM is independent from kernel Runtime PM, and co-existing
  \item Transparent to user space applications
  \item Describes every power-related element as a node of a graph
  \item Every power control is called a DAPM {\bf widget} (graph node)
  \item Every connection between widgets is called a DAPM {\bf route}
    (directed graph edge)
  \item DAPM automatically enables widgets based on active routes
  \end{itemize}
  \centering
  \includegraphics[height=0.23\textheight]{dapm-widgets-ain0.pdf}
  \hfill
  \includegraphics[height=0.23\textheight]{dapm-widgets-dmic0.pdf}
\end{frame}

\begin{frame}{DAPM: Dynamic Audio Power Management}
  \begin{itemize}
  \item DAPM widgets can be controlled by a regular kcontrol
  \end{itemize}
  \centering\includegraphics[height=0.35\textheight]{dapm-mux.pdf}
\end{frame}

\begin{frame}{DAPM: Dynamic Audio Power Management}
  \begin{itemize}
  \item The DAPM tree spans the whole card
    \begin{itemize}
    \item In-component widgets and routes are implemented by the component
      driver
    \item Border widgets and cross-component routes are added by the card
    \end{itemize}
  \end{itemize}
  \centering\includegraphics[width=\textwidth]{dapm-widgets-cross-components.pdf}
\end{frame}

\begin{frame}{Documentation}
  \begin{itemize}
  \item Documented in the official kernel docs
  \item \url{https://docs.kernel.org/sound/soc/dapm.html}
  \item Proposed improvement:
    {\tiny\url{https://lore.kernel.org/all/20240416-dapm-docs-v1-0-a818d2819bf6@bootlin.com/}}
  \end{itemize}
\end{frame}

\begin{frame}{References}
  \begin{itemize}
  \item Audio on Linux: The End of a Golden Age?\\
    Lars-Peter Clausen, ELCE 2016\\
    Slides: \url{https://elinux.org/images/e/e7/Audio_on_Linux.pdf}\\
    Video: \url{https://www.youtube.com/watch?v=6oQF2TzCYtQ}
  \item Making the Most of Dynamic Audio Power Management\\
    Lars-Peter Clausen, ELCE 2015\\
    Slides: \url{https://elinux.org/images/c/c1/Dapm_clausen.pdf}\\
    Video not available
  \end{itemize}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{DAPM widgets}

\begin{frame}[t]{Endpoint widgets}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{widgets-endpoint.pdf}
  \vspace{1cm}
  \begin{itemize}
  \item Widgets where the sound stream originates from or terminates at
  \item ADC, DAC (PCM waveform to/from memory)
  \item Speaker, Line out, Microphone, \dots
  \end{itemize}
\end{frame}

\begin{frame}[t]{Pass-through widgets}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{widgets-pass-through.pdf}
  \vspace{1cm}
  \begin{itemize}
  \item Widgets on a route between other widgets
  \item Sound modifiers (PGA, Effect)
  \item Routing: Mixer, Mux, Demux, Switch
  \end{itemize}
\end{frame}

\begin{frame}[t]{Supply widgets}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{widgets-supply.pdf}
  \vspace{0.5cm}
  \begin{itemize}
  \item Suppliers to other widgets
  \item Clock, current, voltage
  \end{itemize}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{DAPM in action}

\begin{frame}[t]{Phase 1: determining power state}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{powerstate1.pdf}
  \vspace{0.5cm}
  \begin{itemize}
  \item Source widgets are powered if they are active (used by a stream)
    and have a route to an active sink widget
  \item Sink widgets are powered if they are active (used by a stream) and
    have a route to an active source widget
  \end{itemize}
\end{frame}

\begin{frame}[t]{Phase 1: determining power state}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{powerstate2.pdf}
  \vspace{0.5cm}
  \begin{itemize}
  \item Pass-through widgets are powered if they are on the route between two
    powered endpoint widgets
  \item Computed by DAPM automatically
  \end{itemize}
\end{frame}

\begin{frame}[t]{Phase 1: determining power state}
  \vspace{0.5cm}
  \centering\includegraphics[width=0.7\textwidth]{powerstate3.pdf}
  \vspace{0.5cm}
  \begin{itemize}
  \item Supply widgets are powered if they have a path to a powered widget
  \item Computed by DAPM automatically
  \end{itemize}
\end{frame}

\begin{frame}{Phase 2: Powering sequence}
  \begin{enumerate}
  \item Compute difference between previous and new configurations
  \item Power down newly-disabled widgets
  \item Apply routing changes
  \item Power up newly-enabled widgets
  \end{enumerate}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Using DAPM in device drivers}

\begin{frame}[fragile]{Defining DAPM widgets}
  \begin{itemize}
  \item A widget is defined by \kstruct{snd_soc_dapm_widget}
  \end{itemize}
  \begin{block}{\kfile{include/sound/soc-dapm.h}}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
struct snd_soc_dapm_widget {
    enum snd_soc_dapm_type id;
    const char *name;                             /* widget name */
    const char *sname;                            /* stream name */
    struct snd_soc_dapm_context *dapm;
    /* ... */
    struct pinctrl *pinctrl;                      /* attached pinctrl */
    /* ... */
    int reg;                                      /* negative reg = no direct dapm */
    unsigned char shift;                          /* bits to shift */
    unsigned int mask;                            /* non-shifted mask */
    unsigned int on_val;                          /* on state value */
    unsigned int off_val;                         /* off state value */
    /* ... */
    unsigned short event_flags;                   /* flags to specify event types */
    int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);
};
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Defining DAPM widgets}
  \begin{itemize}
  \item Do {\bf not} use \kstruct{snd_soc_dapm_widget} directly!
  \item Use the \code{SND_SOC_DAPM_*()} macros defined in \kfile{include/sound/soc-dapm.h}
  \item Each macro fills a \kstruct{snd_soc_dapm_widget}
  \end{itemize}
  \begin{block}{\kfile{sound/soc/codecs/adau1372.c}}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
static const struct snd_soc_dapm_widget adau1372_dapm_widgets[] = {
    SND_SOC_DAPM_INPUT("AIN0"), /* An input pin */
    SND_SOC_DAPM_SUPPLY("MICBIAS0", ADAU1372_REG_MICBIAS, 4, 0, NULL, 0),
    SND_SOC_DAPM_PGA("PGA0", ADAU1372_REG_PGA_CTRL(0), 6, 1, NULL, 0),
    SND_SOC_DAPM_ADC("ADC0", NULL, ADAU1372_REG_ADC_CTRL2, 0, 0),
    SND_SOC_DAPM_SUPPLY("ADC0 Filter", ADAU1372_REG_DECIM_PWR, 0, 0, NULL, 0),
    SND_SOC_DAPM_SUPPLY("Output ASRC0 Decimator", ADAU1372_REG_DECIM_PWR, 4, 0, NULL, 0),
    SND_SOC_DAPM_MUX("Decimator0 Mux", SND_SOC_NOPM, 0, 0, &adau1372_decimator0_1_mux_control),
    SND_SOC_DAPM_OUTPUT("HPOUTL"), /* An output pin */
    /* ... */
};
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{regmap}
  \begin{itemize}
  \item Widget macros take register offset but no base
  \item Each widget can set the correct register thanks \code{regmap}
  \item Regmap abstracts register access from the underlying bus
    \begin{itemize}
    \item Originated from ASoC, now a generic kernel feature
    \item Allows a single driver for dual (I²C/SPI) CODECs
    \item Optimizes access via register cache
    \item and much more
    \end{itemize}
  \item \code{regmap} is recommended for register access in ASoC
    \begin{itemize}
    \item In ASoC, a \code{regmap} is automatically added
    \item By \kfunc{snd_soc_add_component}
    \item In \code{widget->component->regmap}
    \end{itemize}
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{Defining DAPM routes}
  \begin{itemize}
  \item A route is defined by \kstruct{snd_soc_dapm_route}
  \end{itemize}
  \begin{block}{\kfile{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;
    /* ... */
};
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Defining DAPM routes}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
static const char * const adau1372_decimator_mux_text[] = { "ADC", "DMIC", };
static SOC_ENUM_SINGLE_DECL(adau1372_decimator0_1_mux_enum, ADAU1372_REG_ADC_CTRL2,
                            2, adau1372_decimator_mux_text);

static const struct snd_soc_dapm_route adau1372_dapm_routes[] = {
    { "PGA0",           NULL,   "AIN0"        },
    { "ADC0",           NULL,   "PGA0"        },
    { "Decimator0 Mux", "ADC",  "ADC0"        },
    { "Decimator0 Mux", "DMIC", "DMIC0_1"     },
    { "HPOUTL",         NULL,   "OP_STAGE_LP" },
    { "HPOUTL",         NULL,   "OP_STAGE_LN" },
    /* ... */
};
    \end{minted}
  \end{block}
  \begin{itemize}
  \item Control is a standard ALSA kcontrol for selection of mux input,
    demux output, mixer levels, PGA gain, \dots
  \item Matching based on strings
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{Adding DAPM widgets and routes}
  \begin{itemize}
  \item Just point to the defined arrays in
    \kstruct{snd_soc_component_driver}
  \end{itemize}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
static const struct snd_soc_component_driver adau1372_driver = {
    .set_bias_level = adau1372_set_bias_level,
    .controls = adau1372_controls,
    .num_controls = ARRAY_SIZE(adau1372_controls),
    .dapm_widgets = adau1372_dapm_widgets,
    .num_dapm_widgets = ARRAY_SIZE(adau1372_dapm_widgets),
    .dapm_routes = adau1372_dapm_routes,
    .num_dapm_routes = ARRAY_SIZE(adau1372_dapm_routes),
};
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Adding DAPM widgets and routes dynamically}
  \begin{itemize}
  \item DAPM routes can be added dynamically, e.g. based on codec model
  \end{itemize}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { ... };
static const struct snd_soc_dapm_route wm8994_intercon[] = { ... };

static int wm8994_component_probe(struct snd_soc_component *component)
{
    /* ... */
    switch (control->type) {
    case WM8994:
        snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets,
                                  ARRAY_SIZE(wm8994_specific_dapm_widgets));
        /* ... */
        snd_soc_dapm_add_routes(dapm, wm8994_intercon,
                                ARRAY_SIZE(wm8994_intercon));
        /* ... */
    }
}
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{Connecting widgets to the DAI stream}
  \begin{itemize}
  \item The endpoints of the DAPM graph are
    \begin{itemize}
    \item The external input/output pins
      \begin{itemize}
        \item \code{SND_SOC_DAPM_INPUT()}, \code{SND_SOC_DAPM_OUTPUT()}
      \end{itemize}
    \item The DAI (digital audio interface)
      \begin{itemize}
      \item Via the stream name defined by the DAI driver, using
        (sub)string-based matching
      \end{itemize}
    \end{itemize}
  \end{itemize}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{c}
static const struct snd_soc_dapm_widget wm9705_dapm_widgets[] = {
    SND_SOC_DAPM_DAC("Left DAC",  "Left HiFi Playback",  SND_SOC_NOPM, 0, 0),
    SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback", SND_SOC_NOPM, 0, 0),

...

static struct snd_soc_dai_driver wm9705_dai[] = {
    {
        .name = "wm9705-hifi",
        .playback = {
            .stream_name = "HiFi Playback",
...
    \end{minted}
  \end{block}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Inspecting the DAPM state at runtime}

\begin{frame}{Inspecting DAPM}
  \begin{itemize}
  \item DAPM is internal, not exposed to user space
  \item It is managed automatically
  \pause
  \item It just works, no need to inspect it! \code{:-)}
  \pause
  \item OK, so you want to learn? Debug? Well\dots
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{debugfs}
  \begin{itemize}
  \item Each widget is exposed as a file in  debugfs
  \item \code{/sys/kernel/debug/asoc/${CARD}/${COMPONENT}/dapm/${WIDGET}}
  \item \code{/sys/kernel/debug/asoc/${CARD}/dapm/${WIDGET}} for card-level widgets
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{shell}
# cat "/sys/kernel/debug/asoc/STM32MP15-DK/cs42l51.0-004a/dapm/Left ADC"
Left ADC: Off  in 1 out 0 - R2(0x2) mask 0x2
 stream Left HiFi Capture inactive
 out  "static" "Capture"
 in  "static" "Left PGA"
#
    \end{minted}
  \end{block}
  \item Widget name can be ambiguous (same widget name in different components)
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{debugfs: proposed improvement}
  \begin{itemize}
  \item Proposed improvements:
    {\tiny\url{https://lore.kernel.org/all/20240416-vizdapm-ng-v1-0-5d33c0b57bc5@bootlin.com}}
  \item Add widget type
  \item Add component name for in/out routes
  \end{itemize}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{shell}
# cat "/sys/kernel/debug/asoc/STM32MP15-DK/cs42l51.0-004a/dapm/Left ADC"
Left ADC: Off  in 1 out 0 - R2(0x2) mask 0x2
 stream Left HiFi Capture inactive
 widget-type adc
 out  "static" "Capture" "cs42l51.0-004a"
 in  "static" "Left PGA" "cs42l51.0-004a"
#
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}[fragile]{vizdapm}
  \begin{itemize}
  \item Simple shell script developed by Dimitris Papastamos, Wolfson Micro
  \item Generates a graph of DAPM widgets and routes as a PNG picture
  \item Based on \code{dot} from graphviz
  \item Repository disappeared, still available in some git forks
  \end{itemize}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{shell}
# vizdapm /sys/kernel/debug/asoc/STM32MP15-DK/cs42l51.0-004a/dapm out.png
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}{vizdapm}
    \centering\includegraphics[height=0.8\textheight]{graphviz.png}
\end{frame}

\begin{frame}{dapm-graph: new proposed tool}
  \begin{itemize}
  \item Inspired by vizdapm and also based on \code{dot} from graphviz
  \item Yet another shell script but more powerful and simpler to use
  \item Shows all components and their connections
  \item Works with BuyBox shell
  \item Basic usage:
    \begin{itemize}
    \item \code{dapm-graph -o dapm.svg -c STM32MP15-DK}
    \end{itemize}
  \item Remote mode:
    \begin{itemize}
    \item \code{dapm-graph -o dapm.svg -c STM32MP15-DK -r root@192.168.0.1}
    \item Gets the status from target, processes on the host
    \end{itemize}
  \item And more
  \item Proposed for kernel inclusion in the same series:
    {\tiny\url{https://lore.kernel.org/all/20240416-vizdapm-ng-v1-0-5d33c0b57bc5@bootlin.com}}
  \end{itemize}
\end{frame}

\begin{frame}[fragile]{dapm-graph: new proposed tool}
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{text}
Usage:
    dapm-graph [options] -c CARD                  - Local sound card
    dapm-graph [options] -c CARD -r REMOTE_TARGET - Card on remote system
    dapm-graph [options] -d STATE_DIR             - Local directory

Options:
    -c CARD             Sound card to get DAPM state of
    -r REMOTE_TARGET    Get DAPM state from REMOTE_TARGET via SSH and SCP
                        instead of using a local sound card
    -d STATE_DIR        Get DAPM state from a local copy of a debugfs tree
    -o OUT_FILE         Output file (default: dapm.dot)
    -D                  Show verbose debugging info
    -h                  Print this help and exit
    \end{minted}
  \end{block}
\end{frame}

\begin{frame}{dapm-graph}
    \centering\includegraphics[height=0.8\textheight]{dapm-graph.pdf}
\end{frame}

\begin{frame}[fragile]{Inspecting the internals with ftrace}
  \begin{itemize}
  \item Using ftrace
  \begin{block}{}
    \fontsize{9}{9}\selectfont
    \begin{minted}{shell}
    # trace-cmd record -e 'snd_soc_*' -l 'rk3308_codec*' -p function_graph play ...
    # trace-cmd report
    \end{minted}
  \end{block}

  \item \code{rk3308_codec_hw_params()}
  \item \code{dapm_power_widgets()}
    \begin{itemize}
    \item \code{snd_soc_dapm_start:   card=rk3308card event=1}
      \begin{itemize}
      \item \code{snd_soc_dapm_widget_power} fills list of widgets to be
        powered on/off
      \item \code{snd_soc_dapm_path} propagates state through routes
      \item \code{snd_soc_dapm_walk_done: rk3308card: checks 34 power, 26 path, 36 neighbour}
      \item \code{snd_soc_bias_level_start}/\code{done} for each component (in separate kthreads)\\
        and \code{snd_soc_dapm_widget_event_start}/\code{done} for widgets having events
      \end{itemize}
    \item \code{snd_soc_dapm_done:    card=rk3308card event=1}
    \end{itemize}
  \item Audio stream\dots
  \item Rollback
  \end{itemize}
\end{frame}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\questionslide

\end{document}
