Detailed explanation of audio Asoc driver architecture

        Explanation: The article may be a bit messy, it is the usual debugging record, but it is more detailed.

        Linux kernel version: 4.19.172

   Hardware Driver: Audio hardware device driver, which consists of three parts, namely Machine, Platform, and Codec.

ASoC audio driver consists of three parts: platform, codec, machine.

The machine is controlled by the i2s connection between the platform and the codec,

platform (cpu_dai) <----->codec_dai

Platform: Platform drivers include audio DMA engine drivers, digital audio interface (DAI) drivers (such as I2S, AC97, PCM), and any audio DSP drivers for that platform.
cpu dai: In embedded systems, it usually refers to the I2S and PCM bus controllers of the CPU, which are responsible for transferring audio data from the I2S tx FIFO to the CODEC (in the case of playback, the direction of recording is opposite). cpu_dai is registered through snd_soc_register_dai(). Note: DAI is the abbreviation of Digital Audio Interface, which is divided into cpu_dai and codec_dai, which are connected through I2S/PCM/pdm bus;
       AIF is the abbreviation of Audio Interface, generally divided into I2S and PCM interfaces.
2 pcm dma: Responsible for transferring the audio data in the dmabuffer to the I2S tx FIFO. The logic of this part is more complicated, and the following articles will explain it in detail. The audio dma driver is registered through snd_soc_register_platform(). It is worth noting that the dma operation is not required in some cases, such as the direct connection between the Modem and the CODEC, because the Modem itself has already sent the data to the PCM FIFO. At this time, it is only necessary to start the codec_dai to receive the data; in this case, Machine driver dai_link needs to specify .platform_name = “snd-soc-dummy”, which is a virtual platform driver, see sound/soc/soc-utils.c for implementation.

Codec: The Codec driver is platform independent and contains audio controls, audio interface functions, codec DAPM definitions and codec IO functions. This class can be extended to BT, FM and MODEM IC if required. Codec class drivers should be generic code that can run on any architecture and machine.
For Playback, the PCM data sent by the userspace is a digital signal that has been sampled and quantized. The codec is converted into an analog signal by the DAC and sent to the external headphone output, so that we can hear the sound. Codec literally means codec, but there are many functional components in the chip, common ones are AIF, DAC, ADC, Mixer, PGA, Line-in, Line-out, and some high-end codec chips also have EQ, DSP, SRC, DRC, AGC, Echo canceller, Noise suppression and other components.


Machine: The Machine driver acts as the glue that describes and binds other component drivers to form an ALSA "sound card device". It handles any machine-specific controls and machine-level audio events (for example, turning on the amp when playback starts).
Machine can be understood as an abstraction of the development board. The development board may include multiple sound cards, and the corresponding Machine part contains multiple links.
Machine refers to a certain machine, which links the audio interfaces of cpu_dai, codec_dai, and modem_dai by defining dai_link, and then registers snd_soc_card. Unlike the above two, Platform and CODEC drivers are generally reusable, while Machine has its specific hardware characteristics and is almost not reusable. The so-called hardware features refer to: the link between DAIs; opening the Amplifier through a certain GPIO; detecting the insertion and removal of headphones through a certain GPIO; using a certain clock such as MCLK/External OSC as the reference clock source of I2S and CODEC modules, etc.
dai_link: The audio data link defined in the machine driver, which specifies the cpu_dai, codec_dai, platform, and codec used.
PCM data flow
For playback (Playback), the PCM data flow is roughly as follows:

User Space---[copy_from_user()]--->DMA Buffer(kernel space)---DMA-->i2s TX FIFO--i2s-->CODEC--DAC->PGA/Mixer-->SPK/
For Linux, HP/Earp is divided into user space and kernel space, and the two cannot access each other casually. Therefore, if the user plays audio, he needs to call copy_from_user() to copy user data from user space to kernel space (DMA Buffer).
DMA is responsible for moving the audio data in DMA Buffer to I2S TX FIFO.
Transmit audio data to Codec through I2S bus.
The Codec is internally converted by DAC, and the analog signal is transmitted to the speaker SPK (headphone HP, earphone Earp).
For the case of recording (Capture), the direction of PCM data flow is as follows:
User Space<---[copy_from_user() ]--DMA Buffer(kernel space)<---DMA--i2s RX FIFO<--i2s--CODEC<--ADC--MIC

Platform driver: The platform driver is the control code of the cpu part, which abstracts two structures snd_soc_dai_driver and snd_soc_platform_driver;

codec driver: The codec driver is the control code of the codec part, which abstracts two structures, namely snd_soc_dai_driver and snd_soc_codec_driver;

Machine driver: The machine driver controls and manages the connection matching between platform and codec, manages controls, widgets and routes, and its abstract structure is snd_soc_card.

 ASoC audio driver registration process:
 register PCM DMA call snd_register_platform(), register struct snd_soc_platform_driver, configure pcm_new(), snd_pcm_ops. | register
      CPU_dai
 call snd_soc_register_component(), register struct snd_soc_dai_driver
      |
 register codec and codec_dai call snd_soc _register_codec(), register struct snd_soc_dai_driver and struct snd_soc_codec_driver
      |
  Register sound card call snd_soc_register_card(), register struct snd_soc_card. Initialize cpu_dai\pcm dma\codec\codec_dai under dai_link, and create pcm logical device.          


Platform drivers include audio DMA engine drivers (PCM DMA), digital audio interface (CPU DAI) drivers (such as I2S, AC97, PCM), and any audio DSP drivers for the platform. The commonly used ones are CPU DAI and PCM DMA drivers.

CPU DAI: In embedded systems, it usually refers to the I2S and PCM bus controllers of the CPU. For playback, it is responsible for moving audio data from I2S TX FIFO to CODEC (Capture is in the opposite direction). cpu_dai is registered through snd_soc_register_dai().

PCM DMA: For playback, it is responsible for moving the audio data in the dma buffer to the I2S TX FIFO (Capture is in the opposite direction). The audio dma driver is registered through snd_soc_register_platform().

 CPU DAI
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
        /* DAI description */
        const char *name;
        unsigned int id;
        unsigned int base;
        struct snd_soc_dobj dobj;

        /* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        int (*suspend)(struct snd_soc_dai *dai);
        int (*resume)(struct snd_soc_dai *dai);
        /* compress dai */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* Optional Callback used at pcm creation*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);
        /* DAI is also used for the control bus */
        bool bus_control;

        /* ops */
        const struct snd_soc_dai_ops *ops;
        const struct snd_soc_cdai_ops *cops;

        /* DAI capabilities */
        struct snd_soc_pcm_stream capture; // important data structure recording stream
        struct snd_soc_pcm_stream playback; // playback stream
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_sample bits: 1;

        /* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
};


name: the name identification of cpu_dai, the dai_link in the machine matches cpu_dai through cpu_dai_name;
probe: the probe function of cpu_dai, which is called back by snd_soc_instantiate_card();
playback: playback data stream performance description information, such as the number of channels supported, sampling rate, Audio format;
capture: recording data stream performance description information, such as the number of supported channels, sampling rate, and audio format;
ops: points to the operation function set of cpu_dai, these function sets are very important, it defines the clock configuration and format configuration of DAI , digital mute, PCM audio interface, FIFO delay reporting and other callbacks.
snd_soc_register_dai()
Strictly speaking, register cpu_dai through snd_soc_register_component()->snd_soc_register_dai().
When registering multiple cpu_dai, use snd_soc_register_component()->snd_soc_register_dais()
PCM DMA
2.2.1. Data structure struct snd_soc_platform_driver
In the ASoC audio driver, use the struct snd_soc_platform_driver structure to describe the PCM DMA.

Controls, widgets, and routes:
controls are controls, and its abstract structure is struct snd_kcontrol_new, which can be a single control to control a certain function of the sound card, such as playing sound of the sound card, or use To connect two components (widgets) to form a route (route), the registration function snd_soc_add_codec_controls() of controls;
widgets are components, and its abstract structure is struct snd_soc_dapm_widget. I personally feel that a component is an abstraction of a node inside a sound card, and it is also an abstraction of a certain node. Some controls are packaged in one layer, such as recording input pins, output pins, and a mixer that controls multi-channel sound mixing in the middle. The widgets register function snd_soc_dapm_new_controls; route represents routing, and its abstract structure is struct snd_soc_dapm_route
. Two widgets are connected through controls to form a route (it seems that controls can be empty), and the routes registration function snd_soc_dapm_add_routes();

snd_pcm_ops

open: When the pcm logical device is opened, this function will be called back to set hardware constraints for the runtime, apply for a private structure for the private_data of the runtime, and save dma resources such as channel numbers, transmission units, buffer information, and IO device information.
close: close function, which is opposite to the open operation;
ioctl: you don’t need to write this yourself, use the kernel’s snd_pcm_lib_ioctl() function;
hw_params: When setting pcm hardware parameters (cmd: SNDRV_PCM_IOCTL_HW_PARAMS), this function will be called back, generally used to initialize dma resources , including channel number, transmission unit, buffer information, IO device information, etc.
prepare: When the data is ready (cmd: SNDRV_PCM_IOCTL_PREPARE), this function will be called back to inform dma that the data is ready.
trigger: When pcm data transmission starts, stops, pauses, and resumes, the function will be called back to start or stop dma transmission (supplement: when the upper layer calls pcm_write() for the first time, the trigger is triggered to start dma transmission; when the upper layer calls pcm_stop() or When pcm_drop(), the trigger is triggered to stop dma transmission). The operations in the trigger function must be atomic, there must be no operations that may cause sleep, and it should be as simple as possible.
pointer: This callback function returns the current position of the transmitted data. When dma completes each transmission, it will call this function to obtain the current position of the transmitted data, so that pcm native can calculate the dma buffer pointer position and available space based on it. The function is also atomic.


Register PCM DMA: snd_soc_register_platform()
The audio dma driver is registered through snd_soc_register_platform(). (very similar to snd_soc_register_dai() function)

 In section 2.2.1 of DMA Buffer Allocation
, the dma buffer is mentioned several times, that is, the dma data buffer, which is used to save the audio data copied from the upper layer and recorded by the microphone.
Use the struct snd_dma_buffer structure to describe the dma buffer

struct snd_dma_buffer {     struct snd_dma_device dev; /* device type */     unsigned char *area; /* virtual pointer */     dma_addr_t addr; /* physical address */     size_t bytes; /* buffer size in bytes */     void *private_data; /* private for allocator; don't touch */ }; The allocation of dma buffer generally occurs in the pcm_dma driver (struct snd_soc_platform_driver) initialization phase (probe) or pcm logical device creation phase (pcm_new). probe() and pcm_new() callback functions







对于Playback来说,user space送过来的PCM数据是经过抽样量化出来的数字信号,在codec经过DAC转换成模拟信号送到外放耳机输出,这样我们就可以听到声音了。
Codec字面意思是编解码器,但芯片里面的功能部件很多,常见的有AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的codec芯片还有EQ、DSP、SRC、DRC、AGC、Echo canceller、Noise suppression等部件。

本文重点关注codec_daiI驱动和codec驱动。
Codec指音频codec共有的部分,包括codec初始化函数、控制接口、寄存器缓存、控件列表、dapm部件列表、音频路由列表、偏置电压设置函数等描述信息.
Codec_dai指codec上的数字音频接口DAI驱动描述,各个接口的描述信息不一定都是一致的,所以各个音频接口都有它自身的驱动描述,包括音频接口的初始化函数、操作函数集、能力描述等。

注:DAPM,Dynamic Audio Power Management,动态音频电源管理,为移动Linux设备设计,使得音频系统任何时候都工作在最低功耗状态。

控件(controls)、部件(widgets)、路由(routes):
controls 为控件,其抽象的结构体为struct snd_kcontrol_new,可以为单独的一个控件,控制声卡某一功能,如声卡的播放声音,也可以用于连接两个部件(widget)形成一条通路(route),controls的注册函数snd_soc_add_codec_controls();
widgets 为部件,其抽象的结构体为struct snd_soc_dapm_widget,个人感觉部件就是声卡内部的节点的抽象,也是对某些controls做了一层封装,如录音输入引脚、输出引脚以及中间控制多路声音混合的混音器,widgets注册函数snd_soc_dapm_new_controls;
route 表示路由,其抽象的结构体为struct snd_soc_dapm_route。两个widget中间通过controls连接形成一条route(貌似controls可以为空),routes注册函数snd_soc_dapm_add_routes();

注册Codec
调用kzalloc()创建一个snd_soc_codec实例codec;
调用fmt_single_name()创建codec name和id;
初始化snd_soc_dai实例codec相关参数,包括读写相关的操作函数(实际上是由codec_drv来打理);还包括dev和driver指针的链接;
调用kmemdup()分配 CODEC register cache;
如果有需要,设定default volatile_register、readable_register、writable_register回调函数;
调用fixup_codec_formats()设置数据的格式(big and little endian);
调用list_add()将snd_soc_codec实例codec插入到codec_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的codec并绑定。
调用snd_soc_register_dais()注册codec_dai,下一小节我们详细分析该函数。


Codec DAI
2.2.1. 数据结构struct snd_soc_dai_driver
通过struct snd_soc_dai_driver结构体来定义一个codec_dai。struct snd_soc_dai_driver结构体如下:

*
 * Digital Audio Interface Driver.
 *
 * Describes the Digital Audio Interface in terms of its ALSA, DAI and AC97
 * operations and capabilities. Codec and platform drivers will register this
 * structure for every DAI they have.
 *
 * This structure covers the clocking, formating and ALSA operations for each
 * interface.
 */
struct snd_soc_dai_driver {
        /* DAI description */
        const char *name;
        unsigned int id;
        unsigned int base;
        struct snd_soc_dobj dobj;

        /* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        int (*suspend)(struct snd_soc_dai *dai);
        int (*resume)(struct snd_soc_dai *dai);
        /* compress dai */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* Optional Callback used at pcm creation*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);
        /* DAI is also used for the control bus */
        bool bus_control;

        /* ops */
        const struct snd_soc_dai_ops *ops;
        const struct snd_soc_cdai_ops *cops;

        /* DAI capabilities */
        struct snd_soc_pcm_stream capture;
        struct snd_soc_pcm_stream playback;
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_samplebits:1;

        /* probe ordering - for components with runtime dependencies */
        int probe_order;
        int remove_order;
};

snd_soc_dai_ops //name: the name of codec_dai, the dai_link in the machine matches codec_dai through codec_dai_name;
probe: the probe function of codec_dai, which is called back by snd_soc_instantiate_card();
playback: playback data stream performance description information, such as the number of channels supported, Sampling rate, audio format;
capture: record data stream performance description information, such as the number of supported channels, sampling rate, audio format;
ops: point to the operation function set of codec_dai, these function sets are very important, it defines the clock configuration of DAI , format configuration, digital mute, PCM audio interface, FIFO delay reporting and other callbacks. The annotation of the struct snd_soc_dai_ops structure is very detailed, so I won’t go into details here.

struct snd_soc_dai_ops {
        /*
         * DAI clocking configuration, all optional.
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_sysclk)(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
        int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

        /*
         * DAI format configuration
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
        int (*xlate_tdm_slot_mask)(unsigned int slots,
                unsigned int *tx_mask, unsigned int *rx_mask);
        int (*set_tdm_slot)(struct snd_soc_dai *dai,
                unsigned int tx_mask, unsigned int rx_mask,
                int slots, int slot_width);
        int (*set_channel_map)(struct snd_soc_dai *dai,
                unsigned int tx_num, unsigned int *tx_slot,
                unsigned int rx_num, unsigned int *rx_slot);
        int (*get_channel_map)(struct snd_soc_dai *dai,
                        unsigned int *tx_num, unsigned int *tx_slot,
                        unsigned int *rx_num, unsigned int *rx_slot);
        int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

        int (*set_sdw_stream)(struct snd_soc_dai *dai,
                        void *stream, int direction);
        /*
         * DAI digital mute - optional.
         * Called by soc-core to minimise any pops.
         */
        int (*digital_mute)(struct snd_soc_dai *dai, int mute);
        int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

        /*
         * ALSA PCM audio operations - all optional.
         * Called by soc-core during audio PCM operations.
         */
        int (*startup)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        void (*shutdown)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*hw_params)(struct snd_pcm_substream *,
                struct snd_pcm_hw_params *, struct snd_soc_dai *);
        int (*hw_free)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        int (*prepare)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
        /*
         * NOTE: Commands passed to the trigger function are not necessarily
         * compatible with the current state of the dai. For example this
         * sequence of commands is possible: START STOP STOP.
         * So do not unconditionally use refcounting functions in the trigger
         * function, e.g. clk_enable/disable.
         */
        int (*trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        int (*bespoke_trigger)(struct snd_pcm_substream *, int,
                struct snd_soc_dai *);
        /*
         * For hardware based FIFO caused delay reporting.
         * Optional.
         */
        snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
                struct snd_soc_dai *);
};


/* SoC PCM stream information */
struct snd_soc_pcm_stream {
        const char *stream_name;
        u64 formats;                    /* SNDRV_PCM_FMTBIT_* */
        unsigned int rates;             /* SNDRV_PCM_RATE_* */
        unsigned int rate_min;          /* min rate */
        unsigned int rate_max;          /* max rate */
        unsigned int channels_min;      /* min channels */
        unsigned int channels_max;      /* max channels */
        unsigned int sig_bits;          /* number of bits of content */
        const char *aif_name;           /* DAPM AIF widget name */
};


/* SoC audio ops */
struct snd_soc_ops {
        int (*startup)(struct snd_pcm_substream *);
        void (*shutdown)(struct snd_pcm_substream *);
        int (*hw_params)(struct snd_pcm_substream *, struct snd_pcm_hw_params *);
        int (*hw_free)(struct snd_pcm_substream *);
        int (*prepare)(struct snd_pcm_substream *);
        int (*trigger)(struct snd_pcm_substream *, int);
};

Machine
Linux audio driver (2) Platform driver of ASoC audio driver and Linux audio driver (3) Codec driver of ASoC audio driver respectively introduces platform driver and codec driver, but only platform driver and codec driver cannot work, and a The role is to link codec, codec_dai, cpu_dai, and platform to form a complete audio loop, and this role is assumed by the machine.
Machine can be understood as an abstraction of the development board. The development board may include multiple sound cards, and the corresponding machine part contains multiple links.
Machine driver control manages connection matching between platform and codec, its abstract structure is struct snd_soc_dai_link.
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,






         * the CPU-side DAI is matched using .cpu_dai_name only, which hence
         * 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;
        /*
         * 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;

        /*
         * 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 */

        enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
/* codec/machine specific init - e.g. add machine controls */
        int (*init)(struct snd_soc_pcm_runtime *rtd);

        /* optional hw_params re-writing for BE and FE sync */
        int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
                        struct snd_pcm_hw_params *params);

        /* machine stream operations */
        const struct snd_soc_ops *ops;
        const struct snd_soc_compr_ops *compr_ops;

        /* Mark this pcm with non atomic ops */
        bool nonatomic;

        /* For unidirectional dai links */
        unsigned int playback_only:1;
        unsigned int capture_only:1;

        /* Keep DAI active over suspend */
        unsigned int ignore_suspend:1;

        /* Symmetry requirements */
        unsigned int symmetric_rates:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_samplebits:1;

        /* Do not create a PCM for this DAI link (Backend link) */
        unsigned int no_pcm:1;

        /* This DAI link can route to other DAI links at runtime (Frontend)*/
        unsigned int dynamic:1;

        /* This DAI link can be reconfigured at runtime (Backend) */
        unsigned int dynamic_be:1;

        /*
         * This DAI can support no host IO (no pcm data is
         * copied to from host)
         */
        unsigned int no_host_mode:2;

        /* DPCM capture and Playback support */
        unsigned int dpcm_capture:1;
        unsigned int dpcm_playback:1;

        /* DPCM used FE & BE merged format */
        unsigned int dpcm_merged_format:1;
        /* DPCM used FE & BE merged channel */
        unsigned int dpcm_merged_chan:1;
                    ................
codec_name: the name of the codec to be bound to the audio link, the codec_list will be traversed in the soc-core, and the codec with the same name will be found and bound; platform_name
: the name of the platform to be bound to the audio link, the platform_list will be traversed in the soc-core, Find the platform with the same name and bind it;
cpu_dai_name: the cpu_dai name identification that the audio link needs to be bound to, soc-core will traverse the dai_list, find the dai with the same name and bind it;
codec_dai_name: the codec_dai name identification that the audio link needs to bind, The soc-core will traverse the dai_list, find the dai with the same name and bind it;
ops: machine data flow operation function set. Pay attention to the hw_params callback. Generally speaking, this callback is to be implemented to configure the clock and format of codec and cpu_dai.          
                                          

struct snd_soc_card
/* SoC card */
struct snd_soc_card {
    const char *name;
    ......
    bool instantiated;

    int (*probe)(struct snd_soc_card *card);
    ......
    /* CPU <--> Codec DAI links  */
    struct snd_soc_dai_link *dai_link;
    int num_links;
    struct snd_soc_pcm_runtime *rtd;
    int num_rtd;
    ......
    const struct snd_kcontrol_new *controls;
    int num_controls;
    ......
};
instantiated:用于标记声卡是否已经初始化完毕,在snd_soc_instantiate_card()函数最后会置1;
probe:声卡的probe函数,由snd_soc_instantiate_card()回调;
dai_link:声卡音频回路集合指针,即machine driver指针;
rtd:非常重要的数据指针,整个ASoC都以snd_soc_pcm_runtime为桥梁来操作,可以这么理解:每一个音频物理链路对应一个dai_link,而每个dai_link都有着自身的设备私有数据,这些私有数据保存在snd_soc_pcm_runtime中;
controls:声卡控件指针;


 Register sound card snd_soc_register_card()
When we were learning audio drivers, we encountered platform many times. However, every time a platform is mentioned, it must be combined with the context to determine what it really refers to. Pay attention to the distinction:
1. When we talk about ASoC drivers including platform driver, codec driver, and machine driver, the platform mentioned at this time refers to a certain SoC platform, such as exynos, omap, qcom, mtk, etc.; 2. When we talk
about When the machine driver is snd_soc_dai_link, the platform mentioned at this time refers to the PCM DMA, namely snd_soc_platform_driver;
3. When we talk about registering a sound card, the sound card is abstracted as a platform device hanging on the platform bus. Therefore, there will be a standard Linux platform device (platform_device) named "soc-audio", and a standard Linux platform driver (platform_driver) will be registered. The platform mentioned at this time refers to the virtual platform bus.


The components of the Codec, Platform, and Machine drivers in the PCM ASoC audio driver and their registration process are all related to physical devices. Everyone should have a certain understanding of the audio physical link . Then analyze the middle layer of the audio driver. Since these are not real physical devices, we call them logical devices.

The PCM logical device, which we are used to call the PCM middle layer or pcm native, plays a connecting role:
1. Upward is the interaction with the user mode interface, which realizes the copying of audio data between the user mode and the kernel mode, namely user space <-> kernel space;
2. The next step is to trigger the operation functions of codec, platform, and machine to realize the transmission of audio data between dma_buffer<->cpu_dai<->codec.

Create a PCM logical device snd_soc_instantiate_card This function is executed below. Here is a logic diagram, free to understand

snd_register_device_for_dev  

The process of snd_register_device_for_dev() to create pcmCxDxp, (currently the kernel does not use this function to create sound_insert_unit) pcmCxDxc device node is as follows:
static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, con st char *name, umode_t mode, struct device *dev)
{         struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);         int r;

        if (!s)
                return -ENOMEM;

        spin_lock(&sound_loader_lock);
retry:
        r = __sound_insert_unit(s, list, fops, index, low, top);
        spin_unlock(&sound_loader_lock);

        if (r < 0)
                goto fail;
        else if (r < SOUND_STEP)
                sprintf(s->name, "sound/%s", name);
        else
                sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
         ..............................
         
        device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),
                      NULL, "%s", s->name+6);
        return s->unit_minor;

fail:
        kfree(s);
        return r;
}

First, allocate and initialize each field in a snd_minor structure;
type: SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
card: card number
device: pcm instance number, most cases are 0
f_ops: pcmCxDxp, pcmCxDxc device node file operation function set, snd_pcm_f_ops [*]
private_data: point to the instance object of the snd_pcm.
According to the number of type, card and pcm, determine the index value minor of the array, and minor is also used as the minor device number of the pcm device; put the address
of the snd_minor structure into the global array snd_minors[minor ];
finally, call device_create to create a device node.

Note 1: C0D0 represents Card 0 Device 0, that is, device 0 in sound card 0, the last c of pcmC0D0c represents capture, and the last p of pcmC0D0p represents playback. These are the naming rules in alsa-driver.
Note 2: snd_minors[] is very important. It is used to save the context information of a logical device under the sound card. It is filled in the logical device creation phase, and the corresponding information can be obtained from the structure when the logical device is used.
Note 3: sound_class is the sound card class in the sysfs file system, which is created in init_soundcore().
rk66_gnrc:/dev/snd # ls -l
total 0
crw-rw---- 1 system audio 116, 4 2017-08-04 09:00 controlC0
crw-rw---- 1 system audio 116, 6 2017-08 -04 09:00 controlC1
crw-rw---- 1 system audio 116, 3 2017-08-04 09:00 pcmC0D0c
crw-rw---- 1 system audio 116, 2 2017-08-04 09:00 pcmC0D0p
crw-rw---- 1 system audio 116, 5 2017-08-04 09:00 pcmC1D0c
crw-rw---- 1 system audio 116, 1 2017-08-04 09:00 seq
crw-rw--- - 1 system audio 116, 33 2017-08-04 09:00 timer

rk66_gnrc:/proc/asound # cat devices                                                                                                                                  
  1:        : sequencer
  2: [ 0- 0]: digital audio playback
  3: [ 0- 0]: digital audio capture
  4: [ 0]   : control
  5: [ 1- 0]: digital audio capture
  6: [ 1]   : control
 33:        : timer

It can be seen that the Major=116 of these device nodes, and the Minor corresponds to those listed in the system logical device information file /proc/asound/devices, all of which are character devices. The upper layer can operate the sound card device through system calls such as open/close/read/write/ioctl, similar to other character devices. However, in general, we use packaged user interface libraries such as tinyalsa and alsa-lib.

The following will introduce in detail based on tinyslsa. Tinyalsa provides a complete set of pcm logical device user interfaces, such as pcm_open(), pcm_start(), pcm_prepare(), pcm_read(), pcm_write().


 PCM logical device file operation function set: snd_pcm_f_ops[]
The PCM logical device file operation function set is defined separately for Playback and Capture. The operation function set is as follows:

const struct file_operations snd_pcm_f_ops[2] = {         {                 .owner = THIS_MODULE,                 .write = snd_pcm_write,//for single-channel audio signal writing.write_iter                 = snd_pcm_writev,                 .open = snd_pcm_playback_open,                 .release = snd_pcm_release,                 .llseek = no_llseek,                 . poll = snd_pcm_poll,                 .unlocked_ioctl = snd_pcm_ioctl,//for multi-channel audio signal writing.compat_ioctl                 = snd_pcm_ioctl_compat,                 .mmap = snd_pcm_mmap,











                .fasync = snd_pcm_fasync,
                .get_unmapped_area = snd_pcm_get_unmapped_area,
        },
        {                 .owner = THIS_MODULE,                 .read = snd_pcm_read,//for single-channel audio signal reading                 .read_iter = snd_pcm_readv,                 .open = snd_pcm_capture_open,                 . release = snd_pcm_release,                 .llseek = no_llseek,                 .poll = snd_pcm_poll,                 .unlocked_ioctl = snd_pcm_ioctl,//for multi-channel audio signal reading.compat_ioctl                 = snd_pcm_ioctl_compat,









                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        }
};

Sample length (sample): A sample is the most basic unit for recording audio data, and the common ones are 8 bits and 16 bits.

Number of channels (channel): This parameter is 1 for mono, 2 for stereo.

Frame: A frame records a sound unit whose length is the product of the sample length and the number of channels.
Sampling rate (rate): The number of samples per second, which is for frames.
Period (period): The number of frames required by the audio device for one processing, and the data access of the audio device and the storage of audio data are all based on this unit.

Interleaved mode (interleaved): It is a recording method of audio data. In interleaved mode, the data is stored in the form of continuous frames, that is, the left channel sample and the right channel sample of frame 1 are first recorded (assumed to be in stereo format) , and then start the recording of frame 2. In the non-interlaced mode, the left channel samples of all frames in a period are recorded first, and then the right channel samples are recorded, and the data is stored in the form of continuous channels. But in most cases, we only need to use interleaved mode.

period (period): The interval between interrupts in the hardware. It indicates input delay.

There is a pointer in the sound card interface to indicate the current read and write position in the sound card hardware buffer. As long as the interface is running, this pointer will recursively point to a location in the buffer.
frame size = sizeof(one sample) * nChannels
The buffer and cycle size configured in alsa are stored in the form of frames in the runtime.
period_bytes = frames_to_bytes(runtime, runtime->period_size);
bytes_to_frames()

Sound caching and data transfer

Every sound card has a hardware buffer to hold recorded samples. When the buffer is full enough, the sound card will generate an interrupt. The kernel sound driver then transfers the samples to the application buffer in memory using a direct memory access (DMA) channel. Similarly, for playback, any application uses DMA to transfer its own buffer data into the sound card's hardware buffer.
Thus the hardware cache is a ring cache. That is to say, when the data reaches the end of the buffer, it will return to the beginning of the buffer. ALSA maintains a pointer to the current location of data operations in the hardware cache and application cache. From outside the kernel, we are only interested in the application cache, so this article only discusses the application cache.
The size of the application buffer can be controlled through ALSA library function calls. The buffer area can be very large, and a transfer operation may cause an unacceptable delay, which we call latency. In order to solve this problem, ALSA splits the buffer area into a series of periods (periods) (called fragments in OSS/Free). ALSA transmits data in units of periods.
A period stores some frames. Each frame contains samples taken at a point in time. For stereo devices, a frame will contain samples on both channels. Breakdown process: A buffer is broken down into cycles, then frames, then samples. Left and right channel information is alternately stored within one frame. This is called interleaved mode. In non-interleaved mode, all sample data for one channel is stored after the data for the other channel.

Over and Under Run
When a sound card is active, data is always continuously transferred between the hardware buffer and the application buffer. But there are exceptions. In the recording example, if the application does not read the data fast enough, the circular buffer will be overwritten with new data. This loss of data is called over run. In the replay example, if the application cannot write data to the buffer fast enough, the buffer will "starve to death". Such errors are called "under run". In the ALSA documentation, these two situations are sometimes collectively referred to as "XRUN". Appropriately designed applications can minimize and recover from XRUN.
snd_pcm_read() // Whether interleaved is an interleaved mode depends on this parameter
    |--->snd_pcm_lib_read(substream, buf, count); |
         --->__snd_pcm_lib_xfer(substream, (void __force *)buf, \ //(interleaved mode ) runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED //judgment storage mode
         true, frames, false);///* the common loop for read/write data */
             |--->pcm_sanity_check(substream);           
        |--->transfer = substream->ops->copy_kernel; / transfer = (pcm_transfer_f)substream->ops->copy_user;    
        |--->snd_pcm_start(substream);
             |--->snd_pcm_action(&snd_pcm_action_start, substream,
                  SNDRV_PCM_STATE_RUNNING);
                  |--->snd_pcm_stream_linked(substream)
                  |--->snd_pcm_action_group(ops, substream, state, 1);/ /This function is the core of processing link stream
                      |--->ops->pre_action(s, state); = snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) // callback function
                      |--->ops->do_action(s , state); = snd_pcm_do_start(struct snd_pcm_substream *substream, int state)
                          |--->substream->runtime->trigger_master
                          |--->substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
                               |--->soc_pcm_trigger;>>>>soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) assignment
                                     |--->codec_dai->driver->ops->trigger(substream,cmd, codec_dai); //codec driver Middle assignment
                                     |--->component->driver->ops->trigger(substream, cmd);
                                     |--->cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); // codec driver Middle assignment\
                                     snd_soc_dai_driver ->snd_soc_dai_ops->int (*trigger)(struct snd_pcm_substream *, int,struct snd_soc_dai *); |---
                                     >rtd->dai_link->ops->trigger(substream, cmd);
        |-- ->snd_pcm_avail(substream);
        |--->pcm_lib_apply_appl_ptr(substream, appl_ptr);
        
        

snd_pcm_write()//
    |--->snd_pcm_lib_write(substream, buf, count);
        |--->__snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);    
        |--->snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,
                     void *data, bool interleaved,
                     snd_pcm_uframes_t size, bool in_kernel)
                     ..........与snd_pcm_read()类似
        

 Open PCM logical device
 User Space: The application directly calls the system call open() through pcm_open() to open pcmCxDxp or pcmCxDxc ./external/tinyalsa/pcm.c,
 struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags , struct pcm_config *config)
{     struct pcm *pcm;     struct snd_pcm_info info;     struct snd_pcm_hw_params params;     struct snd_pcm_sw_params sparams;     char fn[256];     int rc;





    if (!config) {
        return &bad_pcm; /* TODO: could support default config here */
    }
    pcm = calloc(1, sizeof(struct pcm));
    if (!pcm)
        return &bad_pcm; /* TODO: could support default config here */

    pcm->config = *config;

    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
             flags & PCM_IN ? 'c' : 'p');

    pcm->flags = flags;
    pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
..............
}

Kernel Space: The system call open() called by the application layer to the kernel space is to call the snd_pcm_f_ops.open file operation function
sound/core/pcm_native.c of the PCM logical device

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
        struct snd_pcm *pcm;
        int err = nonseekable_open(inode, file);
        if (err < 0)
                return err;
        pcm = snd_lookup_minor_data(iminor(inode),
                                    SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
        err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
        if (pcm)
                snd_card_unref(pcm->card);
        return err;
}

static int snd_pcm_capture_open(struct inode *inode, struct file *file)
{
        struct snd_pcm *pcm;
        int err = nonseekable_open(inode, file);
        if (err < 0)
                return err;
        pcm = snd_lookup_minor_data(iminor(inode),
                                    SNDRV_DEVICE_TYPE_PCM_CAPTURE);
        err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
        if (pcm)
                snd_card_unref(pcm->card);
        return err;
}
static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
    | snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
        | snd_pcm_open_file(file, pcm, stream);
            | snd_pcm_open_substream(pcm, stream, file, &substream);
                | substream->ops->open(substream); //即soc_pcm_open(), 在soc_new_pcm()函数中配置的
                    |--> cpu_dai->driver->ops->startup()
                    |--> platform->driver->ops->open()
                    |--> codec_dai->driver->ops->startup()
                    |--> rtd->dai_link->ops->startup()
}

snd_pcm_capture_open similar

Write/Read PCM logical device
In my source code package, tinyalsa write PCM logical device is completed through the ioctl() function, that is, the application program passes the audio data to be played to the kernel through pcm_write() --> ioctl().

User Space: Application write PCM logic device is done by pcm_write() --> ioctl()
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{     struct snd_xferi x;

    if (pcm->flags & PCM_IN)
        return -EINVAL;

    x.buf = (void*)data;
    x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);
 ........................
        }
        if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
            pcm->prepared = 0;
         .......................
}

Kernel Space: The system call ioctl() called by the application layer to the kernel space is to call the snd_pcm_f_ops.ioctl file operation function of the PCM logical device.unlocked_ioctl
= snd_pcm_ioctl,

static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg)
{
        struct snd_pcm_file *pcm_file;

        pcm_file = file->private_data;

        if (((cmd >> 8) & 0xff) != 'A')
                return -ENOTTY;

        return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
                                     (void __user *)arg);
}
cmd record:
In the driver program, the variable cmd transmitted by the ioctl() function is used by the application program to distinguish the request processing content of the device driver value. In addition to distinguishable numbers, cmd also contains several corresponding information that is helpful for processing. The size of cmd is 32 bits, and it is divided into 4 domains:
     bit31~bit30 2 bits are "differential read and write" area, which is used to distinguish between reading and writing commands.
     The 14 bits of bit29~bit15 are the "data size" area, indicating the memory size transferred by the arg variable in ioctl().
     The 8 bits of bit20~bit08 are the "magic number" (also called "magic number") area, and this value is used to distinguish it from the ioctl commands of other device drivers.
     The 8 bits of bit07~bit00 are the "differential serial number" area, which is the command sequence number for distinguishing commands.
The value in the "distinguished read-write area" in the command code may be _IOC_NONE (0 value) means no data transmission, _IOC_READ (read), _IOC_WRITE (write), _IOC_READ|_IOC_WRITE (two-way).
The kernel defines 4 macros _IO(), _IOR(), IOW() and _IOWR() to assist in generating the above cmd. The following analyzes the implementation of _IO(), and others are similar:
the usage format of these macros is:

_IO (magic number, cardinal number);

_IOR (magic number, cardinality, variable type)

_IOW (magic number, cardinality, variable type)

_IOWR (magic number, cardinality, variable type)

The range of magic number
      is 0~255. Usually, it is represented by English characters "A" ~ "Z" or "a" ~ "z". The device driver obtains the magic number from the command passed in, and then compares it with the magic number it handles. If it is the same, it will process it, and if it is different, it will not process it. The magic number is a preliminary auxiliary state that rejects misuse. The device driver can get the magic number by _IOC_TYPE (cmd). It is better to set different magic numbers for different device drivers, but it is not required to be absolute, and it is also possible to use magic numbers that have been used by other device drivers.
Base (Serial Number) Number
      The base number is used to distinguish various commands. Typically, incremented from 0, the value can be reused on the same device driver. For example, if the same radix is ​​used in the read and write commands, the device driver can also distinguish, because the device driver uses switch when distinguishing commands, and directly uses the value of the command variable cmd. The value generated by the macro that creates the command is composed of multiple fields, so even if it is the same cardinality, it will be judged as a different command. A device driver that wants to get the radix from a command uses the following macro:
_IOC_NR (cmd)
Usually, the case value in the switch uses the command itself.
Variable type
   The variable type uses the arg variable to specify the size of the transmitted data, but it is not directly substituted into the input, but into the variable or the type of the variable. The reason is that the macro creation command has included the sizeof() compilation command #define SNDRV_PCM_IOCTL_PVERSION _IOR
   
( 'A', 0x00, int)
   #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
        // #define _IOC_NRBITS    8
       #define _IOC_TYPEBITS    8
        #define _IOC_NRSHIFT    0
        #ifndef _IOC_SIZEBITS
        # define _IOC_SIZEBITS    14
        #endif
        #ifndef _IOC_DIRBITS
        # define _IOC_DIRBITS    2
#endif
     #define _IOC_TYPECHECK(t) (sizeof(t))
    
    # define _IOC_NONE    0U  //无操作
    # define _IOC_WRITE    1U  //写
    # define _IOC_READ    2U  //读

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \    #define _IOC_DIRSHIFT    (_IOC_SIZESHIFT+_IOC_SIZEBITS)
     ((type) << _IOC_TYPESHIFT) | \   #define _IOC_TYPESHIFT    (_IOC_NRSHIFT+_IOC_NRBITS)
     ((nr)   << _IOC_NRSHIFT) | \       #define _IOC_NRSHIFT    0    
     ((size) << _IOC_SIZESHIFT))      #define _IOC_SIZESHIFT    (_IOC_TYPESHIFT+_IOC_TYPEBITS)
             
#define _IOC(dir,type,nr,size) \
    (((dir)  << 30) | \       _IOC_DIRSHIFT = 30
     ((type) << 8) | \  // _IOC_TYPESHIFT = 8
     ((nr)   << 0) | \        _IOC_NRSHIFT = 0
     ((size) << 16))     // _IOC_SIZESHIFT =16
    
 #define _IOR(type,nr,size)    _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))    
        _IOR('A', 0x00, int)
    
_IOC(2U,('A'),(0x00),(sizeof(int)))


#define SNDRV_PCM_IOCTL_PVERSION    _IOR('A', 0x00, int)  = \
#define _IOC(dir,type,nr,size) \
    (((2U)  << 30) | \
     (('A') << 8) | \
     ((0x00)   << 0) | \
     ((sizeof(int)) << 16))
     SNDRV_PCM_IOCTL_PVERSION = 80044100 //32位

        
        
        
        
//The previous entry of the kernel is snd_pcm_playback_ioctl, and the current entry is snd_pcm_ioctl to follow up
snd_pcm_ioctlsnd_pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    

static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    |--> //./kernel-3.10/sound/core/pcm_native.c, line 2624
    | snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd, (void __user *)arg);
        |--> //./kernel-3.10/sound/core/pcm_lib.c, line 2101
        | snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
            |--> //./kernel-3.10/sound/core/pcm_lib.c, line 1985
            | snd_pcm_lib_write1(pcm, stream, file, &substream);
                |--> //./kernel-3.10/sound/core/pcm_lib.c, line 1962
                | transfer(substream); //即snd_pcm_lib_write_transfer(). snd_pcm_lib_write1()函数的最后一个入参
                    |--> hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
                    | //Data transfer between memory and DMA Buffer, circular transfer until the playback is complete
                    |--> copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames))
                |--> //./kernel-3.10/sound/core/pcm_native.c, line 904
                | snd_pcm_start() //Start DMA transfer (only at the beginning, call once)
                    |--> //. /kernel-3.10/sound/core/pcm_native.c, line 785
                    | snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
                        |--> //./kernel-3.10/sound/core/pcm_native.c, line 716 or line 765
                        | snd_pcm_action_group()/snd_pcm_action_single()
                            |--> //ops is the first input parameter of the snd_pcm_action() function
                            | res = ops->do_action(substream, state); //snd_pcm_action_start.do_action(), snd_pcm_do_start()
                                |--> //./kernel -3.10/sound/core/soc-pcm.c, line 609
                                | substream->ops->trigger(); //namely soc_pcm_trigger(), configured in soc_new_pcm()
                                    |--> codec_dai->driver- >ops->trigger()
                                    |--> platform->driver->ops->trigger()
                                    |--> cpu_dai->driver->ops->trigger()
}

static int snd_pcm_action(const struct action_ops *ops,
                          struct snd_pcm_substream *substream,
                          int state)
The first input parameter of snd_pcm_action() is very important. When writing PCM logical device, the first input parameter of snd_pcm_action() function is struct action_ops snd_pcm_action_ start .
Careful readers may have noticed that the PCM logic device provides the snd_pcm_f_ops[0].write() file operation function, so why doesn't the application program write audio data to the kernel through the system call write()?
This question, my own thinking:
Comparing the write() and ioctl() of the PCM logical device, you can find that the snd_pcm_write() function is a simplified version of the snd_pcm_playback_ioctl() function.
For monophonic audio data, the application program can use the system call write() to meet the requirements.
For multi-channel audio data, the application should use the system call ioctl(). In multi-channel, snd_pcm_playback_ioctl() will call snd_pcm_lib_writev().
Regarding the read PCM logical device, the application is also completed through the system call ioctl(), and calls snd_pcm_capture_ioctl() after entering the kernel space. code flow and write are very similar.

Close PCM logical device
User Space: The application closes pcmCxDxp or pcmCxDxc through pcm_close() --> pcm_stop() --> ioctl()

int pcm_close(struct pcm *pcm)
{
    if (pcm == &bad_pcm)
        return 0;

    pcm_hw_munmap_status(pcm);

    if (pcm->flags & PCM_MMAP) {
        pcm_stop(pcm);
        munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
    }
............
}

Kernel Space: The system call ioctl() called by the application layer to the kernel space is to call the snd_pcm_f_ops.ioctl file operation function of the PCM logical device

 .unlocked_ioctl = snd_pcm_ioctl,//for multi-channel audio signal reading
 //This function sees the above analysis
 static long snd_pcm_ioctl(struct file *file, unsigned int cmd,
                          unsigned long arg)
{         struct snd_pcm_file *pcm_file;

        pcm_file = file->private_data;

        if (((cmd >> 8) & 0xff) != 'A')
                return -ENOTTY;

        return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,
                                     (void __user *)arg);
}
//The entry of the previous kernel is in snd_pcm_playback_ioctl, and the current entry is in snd_pcm_ioctl to follow up by yourself

 static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    | snd_pcm_playback_ioctl1(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
        | snd_pcm_common_ioctl1(file, substream, cmd, arg);
            | snd_pcm_drop(substream);          
                | snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);
                    | snd_pcm_action(&snd_pcm_action_stop, substream, state);
                        | snd_pcm_action_group()/snd_pcm_action_single()
                            |--> //ops为snd_pcm_action()函数第一个入参
                            | res = ops->do_action(substream, state); //snd_pcm_action_stop.do_action(), snd_pcm_do_stop()
                                | substream->ops->trigger(); //即soc_pcm_trigger(), 在soc_new_pcm()函数中配置的
                                    |--> codec_dai->driver->ops->trigger()
                                    |--> platform->driver->ops->trigger()
                                    |--> cpu_dai->driver->ops->trigger()
}                                    

 snd_pcm_action()的第一个入参很重要,close PCM逻辑设备时,snd_pcm_action()函数的第一个入参为struct action_ops snd_pcm_action_stop。
注:请注意区分write/close PCM逻辑设备时snd_pcm_action()的第一个入参。

PCM Write data transfer process of ALSA audio driver
In this article, we will take playback (Playback, play audio) as an example to explain how PCM Data is transferred from user space to kernel space, and finally to Codec.
Process: [User Space]---copy_from_user()-->[DMA Buffer(kernel space)]----DMA--->[i2s TX FIFO]---i2s---->[Codec]- --DAC->PGA/Mixer---> [SPK/HP/Earp]

For Linux, since it is divided into user space and kernel space, and the two cannot visit each other casually. Therefore, if the user plays audio, he needs to call copy_from_user() to copy user data from user space to kernel space (DMA Buffer).
DMA is responsible for moving the audio data in DMA Buffer to I2S TX FIFO.
Transmit audio data to Codec through I2S bus.
The Codec is internally converted by DAC, and the analog signal is transmitted to the speaker SPK (headphone HP, earphone Earp).


PCM Data Flow

User Space
The user space application uses the interface write PCM Data provided by tinyalsa, that is, to play audio files.
Write PCM logical device is done through the ioctl() function, that is, the application program passes the audio data to be played to the kernel through pcm_write() --> ioctl().
pcm_write() The above is pasted with the introduction of this function.
Several important concepts in audio data:
Format: sample length (sampling precision or sampling depth), the most basic unit of audio data, commonly 8-bit and 16-bit;
Channel: number of channels, divided into mono and mono Stereo stereo;
Frame: frame, which constitutes a complete sound unit, Frame = Format * Channel;
Rate: also known as sample rate: sampling rate, that is, the number of samples per second, for a frame;
Period size: cycle, each hardware Interrupt the number of frames for processing audio data. For the data reading and writing of audio equipment, this is the unit;
Buffer size: the size of the data buffer, here refers to the buffer size of the runtime, not the buffer_bytes_max defined in the structure snd_pcm_hardware; generally speaking, buffer_size = period_size * period_count, period_count is equivalent to the number of hardware interrupts required to process a buffer data.
In order to transfer audio data through the system call ioctl(), struct snd_xferi x is defined, x.buf points to the audio data to be played this time, and x.frames indicates the total number of frames (frames) of the audio data this time.


Kernel Space
passes data to the kernel through the system call ioctl(), and in the kernel space is snd_pcm_f_ops[0].unlocked_ioctl() corresponding to the PCM logical device. Mentioned above.
snd_pcm_ioctl()
    --->snd_pcm_common_ioctl(file, pcm_file->substream, cmd, (void __user *)arg); ---
        >snd_pcm_lib_write(struct snd_pcm_substream *substream, const void __user *buf, snd_pcm_uframes_t frames)
           ---> /* the common loop for read/write data */
               snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, void *data, bool interleaved, snd_pcm_uframes_t size, bool in_kernel)
                            ...................


PCM write时的数据传递: (该数据传递是内核3.10 的版本,如需分析,需要从snd_pcm_common_ioctl() 函数分析)
应用程序调用tinyalsa提供的接口pcm_write()-->ioctl()将需要回放的音频数据指针和帧数传递给内核。
 内核在snd_pcm_lib_write_transfer()函数中使用copy_from_user()将音频数据从user space拷贝到kernel space,即从应用程序的buffer拷贝到DMA buffer。
内核在snd_pcm_start()中启动DMA传输,将音频数据从DMA buffer拷贝到I2S TX FIFO。(实质上是通过pcm_dma的trigger函数来做的。)


 #define SNDRV_CTL_ELEM_IFACE_MIXER      ((__force snd_ctl_elem_iface_t) 2) /* virtual mixer device */

#define SOC_ENUM_EXT(xname, xenum, xhandler_get, xhandler_put) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_enum_double, \
        .get = xhandler_get, .put = xhandler_put, \
        .private_value = (unsigned long)&xenum }
ALSA声卡驱动中的DAPM详解之一:kcontrol

DAPM is the abbreviation of Dynamic Audio Power Management. The literal translation is the meaning of dynamic audio power management. DAPM is to make the audio subsystem on the Linux-based mobile device work in the minimum power consumption state at all times. DAPM is transparent to user-space applications, and all power-related switching is done in ASoc core. The application in user space does not need to modify the code or recompile. DAPM determines whether the power switch of those audio controls is turned on or off according to the currently active audio stream (playback/capture) and the configuration of the mixer in the sound card.


snd_kcontrol_new structure

Before formally discussing DAPM, we need to clarify an important concept in ASoc: kcontrol. Unfamiliar readers need to browse my previous article: Linux ALSA sound card driver four: the creation of Control devices. Usually, a kcontrol represents a mixer (mixer), or a mux (multi-way switch), or a volume controller, etc. struct snd_kcontrol_new {
snd_ctl_elem_iface_t         iface; /* interface identifier */         unsigned int device; / * device/client number */         unsigned int subdevice; /* subdevice (substream) number */         const unsigned char *name; /* ASCII name of item */         unsigned int index; /* index of item */         unsigned int access; / * access rights */         unsigned int count; /* count of same elements */         snd_kcontrol_info_t *info;         snd_kcontrol_get_t *get;









        snd_kcontrol_put_t *put;
        union {                 snd_kcontrol_tlv_rw_t *c;                 const unsigned int *p;         } tlv;         unsigned long private_value; }; xer or alsamixer and other tools to view and set the state of these controls. In the snd_kcontrol_new structure, several main fields are get, put, private_value, the get callback function is used to obtain the current state value of the control, and the put callback function is used to set the state value of the control, while the private_value field is based on different controls Types have different meanings. For example, for ordinary controls, the private_value field can be used to define the address of the register corresponding to the control and the position information of the corresponding control bit in the register. Fortunately, the ASoc system has prepared a large number of macro definitions for us to define commonly used controls. These macro definitions are located in include/sound/soc.h







SOC_SINGLE SOC_SINGLE should be regarded as the simplest control. This control has only one control amount, such as a switch, or a numerical variable (such as a certain frequency in Codec, FIFO size, etc. #define SOC_SINGLE(xname, reg, shift,
max , invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
        .put = snd_soc_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg , shift, max, invert, 0) }


Mixer control
The Mixer control is used for routing control of the audio channel. It consists of multiple inputs and an output. Multiple inputs can be freely mixed together to form a mixed output:

For the Mixer control, we can think of it as a combination of multiple simple controls. Usually, we will define a simple control for each input of the mixer to control the opening and closing of the input. The reaction in the code is to define a soc_kcontrol_new array:

    static const struct snd_kcontrol_new left_speaker_mixer[] = {
    SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
    SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
    SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
    SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
    };

The above mixer uses bits 3, 5, 6, and 7 of the register WM8993_SPEAKER_MIXER to control the opening and closing of the four input terminals respectively.

Mux control

The mux control is similar to the mixer control, and it is also a combined control of multiple input terminals and one output terminal. Unlike the mixer control, only one of the multiple input terminals of the mux control can be selected at the same time. Therefore, the register corresponding to the mux control can usually set a continuous value, and each different value corresponds to a different input port to be opened. Unlike the above mixer control, ASoc uses the soc_enum structure to describe the register information of the mux control:

    /* enumerated kcontrol */
    struct soc_enum {
            unsigned short reg;
            unsigned short reg2;
            unsigned char shift_l;
            unsigned char shift_r;
            unsigned int max;
            unsigned int mask;
            const char * const *texts;
            const unsigned int *values;
    };

Two register address and displacement fields: reg, reg2, shift_l, shift_r, used to describe the control register information of the left and right channels. The string array pointer is used to describe the name corresponding to each input, and the value field points to an array, which defines the values ​​​​that can be selected by the register. Each value corresponds to an input. If the value is a set of continuous values, usually We can ignore the values ​​parameter.


Widget- kcontrol with path and power management information
Using kcontrol, we can complete the control of mixer, mux, volume control, sound control, and various switching values ​​in the audio system. Through the control of various kcontrols, the audio hardware Able to work according to our envisioned results. At the same time, we can see that kcontrol still has the following shortcomings:

    It can only describe itself, but cannot describe the connection relationship between each kcontrol;
    there is no corresponding power management mechanism;
    there is no corresponding time processing mechanism to respond to audio events such as playback, stop, power-on, and power-off;
    in order to prevent pop-pop sounds, The user program needs to pay attention to the power-on and power-off sequence of each kcontrol;
    when an audio path is no longer valid, all kcontrols on the path cannot be automatically turned off;

 The basic unit of DAPM: widget

At present, there are some deficiencies of kcontrol. In order to solve these problems, the DAPM framework introduces the concept of widget. The so-called widget can actually be understood as a further upgrade and packaging of kcontrol. She also refers to a certain component in the audio system, such as a mixer. , mux, input and output pins, power supply, etc., and even, we can define virtual widgets, such as playback stream widgets. Widget organically combines kcontrol and dynamic power management, and also has the function of linking audio paths. A widget can have a certain dynamic link relationship with its adjacent widgets. In the DAPM framework, widgets are described by the structure snd_soc_dapm_widget

/* dapm widget */
struct snd_soc_dapm_widget {         enum snd_soc_dapm_type id; //The type value of the widget, such as snd_soc_dapm_output, snd_soc_dapm_mixer and so on.         const char *name; /* widget name */ The name of the widget         const char *sname; /* stream name */ Represents the name of the stream where the widget is located. For example, for snd_soc_dapm_dai_in type widgets, this field will be used         struct list_head list; / / All widgets registered in the system will be linked to the header field of the widgets list of the snd_soc_card structure representing the sound card through this list.         struct snd_soc_dapm_context *dapm; //snd_soc_dapm_context structure pointer, ASoc divides the system into multiple dapm domains, each widget belongs to a certain dapm domain, and the same domain represents the same bias voltage power supply strategy, for example, in the same codec Widgets are usually located in the same dapm domain, and widgets on a platform may be located in another platform domain.





        void *priv;                             /* widget specific data */有些widget可能需要一些专有的数据,可以使用该字段来保存,像snd_soc_dapm_dai_in类型的widget,会使用该字段来记住与之相关联的snd_soc_dai结构指针。
        struct regulator *regulator;            /* attached regulator */对于snd_soc_dapm_regulator_supply类型的widget,该字段指向与之相关的regulator结构指针。
        struct pinctrl *pinctrl;                /* attached pinctrl */
        const struct snd_soc_pcm_stream *params; /* params for dai links */目前对于snd_soc_dapm_dai_link类型的widget,指向该dai的配置信息的snd_soc_pcm_stream结构。
        unsigned int num_params; /* number of params for dai links */
        unsigned int params_select; /* currently selected param for dai link */

        /* dapm control */
        int reg; /* negative reg = no direct dapm */reg shift mask These 3 fields are used to control the power state of the widget, \respectively
                                                        correspond to the register address, shift value and mask value where the control information is located .
        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 char power:1 ; /* block power status */
        unsigned char active:1; /* active stream on DAC, ADC's */
        unsigned char connected:1;              /* connected codec pin */
        unsigned char new:1;                    /* cnew complete */
        unsigned char force:1;                  /* force state */
        unsigned char ignore_suspend:1;         /* kept enabled over suspend */
        unsigned char new_power:1;              /* power from this run */
        unsigned char power_checked:1;          /* power checked this run */
        unsigned char is_supply:1;              /* Widget is a supply type widget */
        unsigned char is_ep:2;                  /* Widget is a endpoint type widget */
        int subseq;                             /* sort within widget type */

        int (*power_check)(struct snd_soc_dapm_widget *w);

        /* external events */
        unsigned short event_flags;             /* flags to specify event types */
        int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);

        /* kcontrols that relate to this widget */
        int num_kcontrols;
        const struct snd_kcontrol_new *kcontrol_news;
    struct snd_kcontrol **kcontrols;
        struct snd_soc_dobj dobj;

        /* widget input and output edges */
        struct list_head edges[2];

        /* used during DAPM updates */
        struct list_head work_list;
        struct list_head power_list;
        struct list_head dirty;
        int endpoints[2];

        struct clk *clk;
};

value on_val off_val The current value of the power state, the value corresponding to when it is turned on and when it is turned off.
power invert is used to indicate whether the widget is currently powered on, and invert is used to indicate whether the power field needs logic inversion.
active connected respectively indicates whether the widget is in the active state and connected state. When there is a connection relationship with the adjacent widget, the connected bit will be set to 1, otherwise it will be set to 0.
new The widget (snd_soc_dapm_widget structure) we defined needs to be instantiated when it is registered in the sound card. This field is used to indicate whether the widget has been instantiated.
ext indicates whether the widget currently has external connections, such as mic, earphone, speaker, etc.
force When this bit is set, the widget will be forced to update to the new power state regardless of the current state of the widget.
ignore_suspend new_power power_checked These power management related fields.
subseq The sort number of the widget currently in the power-on or power-off queue. In order to prevent pop-pop sounds during power-on and power-off, DAPM will assign a reasonable power-on and power-off sequence to each widget.
*power_check The callback function pointer used to check whether the widget should be powered on or off.
event_flags This field is a bit or field, and each bit indicates that the widget will pay attention to a certain DAPM event notification. Only notification events that are concerned will be sent to the widget's event handler callback function.
*event DAPM event processing callback function pointer.
num_kcontrols *kcontrol_news **kcontrols These 3 fields are used to describe the kcontrol controls contained in the widget, such as a mixer control or a mux control.
sources sinks Two linked list fields, if there is a connection between two widgets, they will be connected through a snd_soc_dapm_path structure, the sources linked list is used to link all input paths, and the sinks linked list is used to link all output paths.
power_list Every time the power state of the entire dapm is updated, all widgets will be scanned according to a certain algorithm, and then the widgets that need to change the power state will be linked to a linked list of power-on or power-off using this field. After scanning, dapm system These two linked lists will be traversed to perform corresponding power-on or power-off operations.
The dirty linked list field, after the status of the widget changes, the dapm system will use this field to add the widget to a dirty linked list, and then scan the dirty linked list to update the entire path.
inputs The number of paths connected to inputs among all valid paths for this widget.
outputs The number of paths connected to outputs among all valid paths of this widget.
*clk For widgets of snd_soc_dapm_clock_supply type, point to the associated clk structure pointer.

Types of widgets

In the DAPM framework, various widgets are divided into different types. The id field in the snd_soc_dapm_widget structure is used to indicate the type of the widget, and the optional types are defined in an enumeration:

/* dapm widget types */
enum snd_soc_dapm_type {
        snd_soc_dapm_input = 0,         /* input pin */
        snd_soc_dapm_output,            /* output pin */
        snd_soc_dapm_mux,                       /* selects 1 analog signal from many inputs */
        snd_soc_dapm_demux,                     /* connects the input to one of multiple outputs */
        snd_soc_dapm_mixer,                     /* mixes several analog signals together */
        snd_soc_dapm_mixer_named_ctl,           /* mixer with named controls */
        snd_soc_dapm_pga,                       /* programmable gain/attenuation (volume) */
        snd_soc_dapm_out_drv,                   /* output driver */
        snd_soc_dapm_adc,                       /* analog to digital converter */
        snd_soc_dapm_dac,                       /* digital to analog converter */
        snd_soc_dapm_micbias,           /* microphone bias (power) - DEPRECATED: use snd_soc_dapm_supply */
        snd_soc_dapm_mic,                       /* microphone */
        snd_soc_dapm_hp,                        /* headphones */
        snd_soc_dapm_spk,                       /* speaker */
        snd_soc_dapm_line,                      /* line input/output */
        snd_soc_dapm_switch,            /* analog switch */
        snd_soc_dapm_vmid,                      /* codec bias/vmid - to minimise pops */
        snd_soc_dapm_pre,                       /* machine specific pre widget - exec first */
        snd_soc_dapm_post,                      /* machine specific post widget - exec last */
        snd_soc_dapm_supply,            /* power/clock supply */
        snd_soc_dapm_pinctrl,           /* pinctrl */
        snd_soc_dapm_regulator_supply,  /* external regulator */
        snd_soc_dapm_clock_supply,      /* external clock */
        snd_soc_dapm_aif_in,            /* audio interface input */
        snd_soc_dapm_aif_out,           /* audio interface output */
        snd_soc_dapm_siggen,            /* signal generator */
        snd_soc_dapm_sink,
        snd_soc_dapm_dai_in,            /* link to DAI structure */
        snd_soc_dapm_dai_out,
        snd_soc_dapm_dai_link,          /* link between two DAI structures */
        snd_soc_dapm_kcontrol,          /* Auto-disabled kcontrol */
        snd_soc_dapm_buffer,            /* DSP/CODEC internal buffer */
        snd_soc_dapm_scheduler,         /* DSP/CODEC internal scheduler */
        snd_soc_dapm_effect,            /* DSP/CODEC effect component */
        snd_soc_dapm_src,               /* DSP/CODEC SRC component */
        snd_soc_dapm_asrc,              /* DSP/CODEC ASRC component */
        snd_soc_dapm_encoder,           /* FW/SW audio encoder component */
        snd_soc_dapm_decoder,           /* FW/SW audio decoder component */
};

snd_soc_dapm_input This widget corresponds to an input pin.
snd_soc_dapm_output This widget corresponds to an output pin.
snd_soc_dapm_mux This widget corresponds to a mux control.
snd_soc_dapm_virt_mux This widget corresponds to a virtual mux control.
snd_soc_dapm_value_mux This widget corresponds to a value type mux control.
snd_soc_dapm_mixer This widget corresponds to a mixer control.
snd_soc_dapm_mixer_named_ctl The widget corresponds to a mixer control, but the name of the corresponding kcontrol will not be prefixed with the name of the widget.
snd_soc_dapm_pga This widget corresponds to a pga control (programmable gain control).
snd_soc_dapm_out_drv This widget corresponds to an output driver control
snd_soc_dapm_adc This widget corresponds to an ADC
snd_soc_dapm_dac This widget corresponds to a DAC
snd_soc_dapm_micbias This widget corresponds to a microphone bias voltage control
snd_soc_dapm_mic This widget corresponds to a microphone.
snd_soc_dapm_hp This widget corresponds to a headset.
snd_soc_dapm_spk This widget corresponds to a speaker.
snd_soc_dapm_line This widget corresponds to a line input.
snd_soc_dapm_switch This widget corresponds to an analog switch.
snd_soc_dapm_vmid This widget corresponds to the vmid bias voltage of a codec.
A dedicated widget at the snd_soc_dapm_pre machine level will perform checks before other widgets.
The snd_soc_dapm_post machine-level dedicated widget performs check operations after other widgets.
snd_soc_dapm_supply corresponds to a power supply or clock source.
snd_soc_dapm_regulator_supply corresponds to an external regulator voltage regulator.
snd_soc_dapm_clock_supply corresponds to an external clock source.
snd_soc_dapm_aif_in corresponds to a digital audio input interface, such as the input port of the I2S interface.
snd_soc_dapm_aif_out corresponds to a digital audio output interface, such as the output port of the I2S interface.
snd_soc_dapm_siggen corresponds to a signal generator.
snd_soc_dapm_dai_in corresponds to the input DAI structure of a platform or codec domain.
snd_soc_dapm_dai_out corresponds to the output DAI structure of a platform or codec domain.
snd_soc_dapm_dai_link is used to link a pair of input/output DAI structures.

//
Connector between widgets: path

As mentioned before, a widget has input and output, and widgets can be dynamically connected, so what do they use to connect two widgets? DAPM proposes the concept of path for us. Path is equivalent to a jumper in the circuit. It connects the output terminal of one widget with the input terminal of another widget. The path is described by the snd_soc_dapm_path structure: /* dapm
audio path between two widgets */
struct snd_soc_dapm_path {         const char *name;

        /*
         * source (input) and sink (output) widgets
         * The union is for convience, since it is a lot nicer to type
         * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
         */
        union {
                struct {
                        struct snd_soc_dapm_widget *f;
                        struct snd_soc_dapm_widget *sink;
                };
                struct snd_soc_dapm_widget *node[2];
        };

        /* status */
        u32 connect:1;  /* source and sink widgets are connected */
        u32 walking:1;  /* path is in the process of being walked */
        u32 weak:1;     /* path ignored for power management */
        u32 is_supply:1;        /* At least one of the connected widgets is a supply */

        int (*connected)(struct snd_soc_dapm_widget *source,
                         struct snd_soc_dapm_widget *sink);

        struct list_head list_node[2];
        struct list_head list_kcontrol;
        struct list_head list;
};
num snd_soc_dapm_direction {
        SND_SOC_DAPM_DIR_IN,
        SND_SOC_DAPM_DIR_OUT
};


When a connection relationship occurs between widgets, snd_soc_dapm_path acts as the connector, its source field will point to the starting end widget of the connection, and its sink field will point to the arriving end widget of the connection, remember the two in the previous snd_soc_dapm_widget structure Linked list header fields: sources and sinks? The input and output terminals of the widget may be connected to multiple paths. The snd_soc_dapm_path structures of all input terminals are linked to the widget’s sources linked list through the list_sink field. Similarly, the snd_soc_dapm_path structures of all output terminals are linked to the widget’s sinks linked list through the list_source field. You may get dizzy here, source for a while, sink for a while, it doesn’t matter, just remember, the connection path is like this: the output of the starting widget --> the input of the path --> the output of the path --> Arrival widget input.
In addition, the list field of the snd_soc_dapm_path structure is used to register all paths to the sound card, which is actually hung in the paths list header field of the snd_soc_card structure. If you want to define your own method to check the current connection status of path, you can provide your own connected callback function pointer.

connect, walked, walking, weak are several auxiliary fields, which are used to help the traversal of all paths.

The connection relationship of widgets: route route
The connection of a path contains at least the following elements: the starting terminal widget, the jumper path, and the arriving terminal widget. In DAPM, the snd_soc_dapm_route structure is used to describe such a connection relationship: /* * DAPM
audio
 route definition.
 *
 * Defines an audio route originating at source via control and finishing
 * at sink.
 */
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);
};

sink points to the name string of the arriving widget, source points to the name string of the starting widget, control points to the name string of the kcontrol corresponding to the control of the connection, and the connected callback defines the custom connection check mentioned in the previous section Callback. The meaning of this structure is obvious: the source is connected to the sink through a kcontrol, if it is in the connected state now, please call the connected callback function to check.

Here, the name string is directly used to describe the connection relationship. All defined routes must be registered in the dapm system at last. dapm will find out the corresponding widget according to these names, and dynamically generate the required snd_soc_dapm_path structure to handle it correctly. The relationship between each linked list and pointer realizes the connection between two widgets, and the specific connection code analysis will be discussed in future chapters.


How to define various widgets
snd_soc_dapm_widget, snd_soc_dapm_path, snd_soc_dapm_route. Among them, snd_soc_dapm_path does not need to be defined by ourselves, it will be dynamically generated when registering snd_soc_dapm_route, but for the widget and route in the system, we need to define it ourselves. In addition, the kcontrol contained in the widget is different from the ordinary kcontrol. They The definition method of kcontrol is also different from the standard kcontrol. In this section, I will introduce how to use some auxiliary macro definitions provided by the DAPM system to define various types of widgets and the kcontrols they use.

define widgets

Like ordinary kcontrol, the DAPM framework provides us with a large number of auxiliary macros to define various widget controls. These macro definitions are divided into several domains according to the type of widget and the domain where their power is located. They are:

    The codec domain provides reference voltage widgets such as VREF and VMID. These widgets are usually controlled in the probe/remove callback of the codec. Of course, if there is no audio stream during work, they can also be properly turned on and off.
    platform domain The widgets located on this domain are usually some input/output interfaces for platforms or boards that require physical connections, such as earphones, speakers, and microphones. Because these interfaces may be different on each board, they are usually in the They are defined and controlled in the machine driver, and can also be turned on and off by user-space applications in some way.
    The audio path domain generally refers to the widgets such as the mixer and mux inside the codec that control the audio path. These widgets can automatically set their power status according to the connection relationship set in the user space.
    Audio data stream domain refers to those widgets that need to process audio data streams, such as ADC, DAC, etc.

Definition of codec domain widget

Currently, the DAPM framework only provides an auxiliary macro for defining a codec domain widget:
/* codec domain */
#define SND_SOC_DAPM_VMID(wname) \
{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0 }


Definition of platform domain widgets
The DAPM framework provides us with various auxiliary definition macros for platform domain widgets:
/* platform domain */
#define SND_SOC_DAPM_SIGGEN(wname) \
{ .id = snd_soc_dapm_siggen, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_SINK(wname) \
{ .id = snd_soc_dapm_sink, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define S ND_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_HP(wname, wevent) \
{       .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_SPK(wname, wevent) \
{       .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}
#define SND_SOC_DAPM_LINE(wname, wevent) \
{       .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD}

#define SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert) \
        .reg = wreg, .mask = 1, .shift = wshift, \
        .on_val = winvert ? 0 : 1, .off_val = winvert ? 1 : 0

The above widgets correspond to the signal generator, input pin, output pin, microphone, earphone, speaker, and line input interface respectively. The reg field is set to SND_SOC_NOPM (-1), indicating that these widgets do not have register control bits to control the power state of the widget. Widgets such as microphones, earphones, speakers, and line input interfaces can also define a dapm event callback function wevent. From the setting of the event_flags field, it can be seen that they will only respond to SND_SOC_DAPM_POST_PMU (after power-on) and SND_SOC_DAPM_PMD (before power-off) event, these widgets are usually defined in the machine driver, and SND_SOC_DAPM_INPUT and SND_SOC_DAPM_OUTPUT are used to define the output and input pins of the codec chip, usually defined in the codec driver, and finally, add the corresponding route in the machine driver, and connect the microphone and Widgets such as headphones are connected to the corresponding codec input and output pin widgets.


Definition of audio path (path) domain widget
This kind of widget is usually a repackage of ordinary kcontrols controls, adding audio path and power management functions, so this widget will contain one or more kcontrols. The definition method of ordinary kcontrols is in ALSA One of the detailed explanations of DAPM in the sound card driver: kcontrol has been introduced, but these included kcontrols cannot be defined using this method, they need to be defined using the definition macro provided by the dapm framework, and we will introduce the detailed discussion later. Here are the definition macros of these widgets:
/* path domain */
#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_OUT_DRV(wname, wreg, wshift, winvert,\
         wcontrols, wncontrols) \
{ .id = snd_soc_dapm_out_drv, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
         wcontrols, wncontrols)\
{       .id = snd_soc_dapm_mixer, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MIXER_NAMED_CTL(wname, wreg, wshift, winvert, \
         wcontrols, wncontrols)\
{       .id = snd_soc_dapm_mixer_named_ctl, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
/* DEPRECATED: use SND_SOC_DAPM_SUPPLY */
#define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_micbias, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = NULL, .num_kcontrols = 0}
#define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \
{       .id = snd_soc_dapm_switch, .name = wname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .kcontrol_news = wcontrols, .num_kcontrols = 1}
#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}

It can be seen that the reg and shift fields of these widgets need to be assigned values, indicating that these widgets have corresponding power control registers. When the DAPM framework scans and updates the audio path, it will use these registers to control the power status of the widgets. Their power states are assigned on demand, powered up when needed (on active audio paths) and powered off when not needed (on audio paths that are no longer active). These widgets need to complete the same functions as the mixer, mux and other controls introduced earlier. In fact, this is done through the kcontrol controls they contain. We need to define these kcontrols before defining the widget, and then pass the wcontrols and num_kcontrols parameters Pass to these helper definition macros.


Definition of audio data stream (stream) domain widgets
These widgets mainly include audio input/output interfaces, ADC/DAC, etc.:

/* stream domain */
#define SND_SOC_DAPM_AIF_IN(wname, stname, wslot, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_IN_E(wname, stname, wslot, wreg, wshift, winvert, \
                              wevent, wflags)                           \
{       .id = snd_soc_dapm_aif_in, .name = wname, .sname = stname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .event = wevent, .event_flags = wflags }
#define SND_SOC_DAPM_AIF_OUT(wname, stname, wslot, wreg, wshift, winvert) \
{       .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
#define SND_SOC_DAPM_AIF_OUT_E(wname, stname, wslot, wreg, wshift, winvert, \
                             wevent, wflags)                            \
{       .id = snd_soc_dapm_aif_out, .name = wname, .sname = stname, \
        SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
        .event = wevent, .event_flags = wflags }
#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}
#define SND_SOC_DAPM_CLOCK_SUPPLY(wname) \
{       .id = snd_soc_dapm_clock_supply, .name = wname, \
        .reg = SND_SOC_NOPM, .event = dapm_clock_event, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }


Each codec has multiple dai, and cpu (usually refers to a certain soc cpu chip) also has multiple dai. When dai is registered, dapm system will create a widget of type snd_soc_dapm_dai_in or snd_soc_dapm_dai_out for each dai. Usually, this The two widgets will be connected to the widget with the same stream name in the codec. In another case, when there are multiple audio processors (such as multiple codecs) in the system, they may be connected through two dais. When the machine driver confirms that there is such a configuration (by judging the dai_links structure param field), a dai link will be created for them to bind them together, because of the connection relationship, the power status of the widget between the two audio processors can be transferred to each other.


/* 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}
#define SND_SOC_DAPM_PINCTRL(wname, active, sleep) \
{       .id = snd_soc_dapm_pinctrl, .name = wname, \
        .priv = (&(struct snd_soc_dapm_pinctrl_priv) \
                { .active_state = active, .sleep_state = sleep,}), \
        .reg = SND_SOC_NOPM, .event = dapm_pinctrl_event, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD }


Define dapm kcontrol
For mixer or mux type widgets on the audio path, they contain several kcontrols. These contained kcontrols are actually the mixer and mux we discussed before. dapm uses these kcontrols to complete the control of the audio path. However, for widgets, its tasks are more than that. dapm also dynamically manages the connection relationship of these audio paths, so that the power status of these widgets can be controlled according to these connection relationships. If these kcontrols are defined in the usual way, It is impossible to achieve this goal. Therefore, dapm provides us with another set of definition macros, which complete the definition of these kcontrols included in the widget.

static const struct snd_kcontrol_new left_speaker_mixer[] = {
SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
};

/* dapm kcontrol types */
#define SOC_DAPM_DOUBLE(xname, reg, lshift, rshift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_DOUBLE_VALUE(reg, lshift, rshift, max, invert, 0) }
#define SOC_DAPM_DOUBLE_R(xname, lreg, rreg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_DOUBLE_R_VALUE(lreg, rreg, shift, max, invert) }
#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_AUTODISABLE(xname, reg, shift, max, invert) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_VIRT(xname, max) \
        SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0)
#define SOC_DAPM_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
        .tlv.p = (tlv_array), \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_DAPM_SINGLE_TLV_AUTODISABLE(xname, reg, shift, max, invert, tlv_array) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_volsw, \
        .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE,\
        .tlv.p = (tlv_array), \
        .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \
        .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 1) }
#define SOC_DAPM_SINGLE_TLV_VIRT(xname, max, tlv_array) \
        SOC_DAPM_SINGLE(xname, SND_SOC_NOPM, 0, max, 0, tlv_array)
#define SOC_DAPM_ENUM(xname, xenum) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_enum_double, \
        .get = snd_soc_dapm_get_enum_double, \
        .put = snd_soc_dapm_put_enum_double, \
        .private_value = (unsigned long)&xenum }
#define SOC_DAPM_ENUM_EXT(xname, xenum, xget, xput) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
        .info = snd_soc_info_enum_double, \
        .get = xget, \
        .put = xput, \
        .private_value = (unsigned long)&xenum }
#define SOC_DAPM_PIN_SWITCH(xname) \
{       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \
        .info = snd_soc_dapm_info_pin_switch, \
        .get = snd_soc_dapm_get_pin_switch, \
        .put = snd_soc_dapm_put_pin_switch, \
        .private_value = (unsigned long)xname }

It can be seen that SOC_DAPM_SINGLE corresponds to SOC_SINGLE of ordinary controls, and SOC_DAPM_SINGLE_TLV corresponds to SOC_SINGLE_TLV... Compared with ordinary kcontrol controls, dapm's kcontrol controls just replace the info, get, and put callback functions. The put callback function of dapm kcontrol will not only update the state of the control itself, but also pass this change to the adjacent dapm kcontrol, and the adjacent dapm kcontrol will pass this change to its own adjacent dapm kcontrol. The end of the audio path, through this mechanism, as long as the connection state of one of the widgets is changed, all widgets related to it will be scanned and tested to see if they are still in a valid audio path, so that they can dynamically change their power state , this is the essence of dapm.


The first step in building widgets and routes
is to use auxiliary macros to define dapm kcontrol required by widgets:

    static const struct snd_kcontrol_new left_speaker_mixer[] = {
    SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
    SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
    SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
    SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
    };
     
    static const struct snd_kcontrol_new right_speaker_mixer[] = {
    SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
    SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0),
    SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0),
    SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0),
    };
     
    static const char *aif_text[] = {
            "Left", "Right"
    };
     
    static const struct soc_enum aifinl_enum =
            SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 15, 2, aif_text);
     
     
    static const struct snd_kcontrol_new aifinl_mux =
            SOC_DAPM_ENUM("AIFINL Mux", aifinl_enum);
     
     
    static const struct soc_enum aifinr_enum =
            SOC_ENUM_SINGLE(WM8993_AUDIO_INTERFACE_2, 14, 2, aif_text);
     
     
    static const struct snd_kcontrol_new aifinr_mux =
            SOC_DAPM_ENUM("AIFINR Mux", aifinr_enum);

Above, we defined the speaker mixer controls of the left and right channels in wm8993: left_speaker_mixer and right_speaker_mixer, and also defined an input selection mux control called AIFINL Mux and AIFINR Mux for the left and right channels.


The second step is to define the real widget, including the dapm control defined in the first step:

    static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
        ......
        SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
        ......    
        SND_SOC_DAPM_MUX("DACL Mux", SND_SOC_NOPM, 0, 0, &aifinl_mux),
        SND_SOC_DAPM_MUX("DACR Mux", SND_SOC_NOPM, 0, 0, &aifinr_mux),
     
        SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0,
                       left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
        SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0,
                       right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
        ......
    };

这一步,为左右声道各自定义了一个mux widget:DACL Mux和DACR Mux,实际的多路选择由dapm kcontrol:aifinl_mux和aifinr_mux,来完成,因为传入了SND_SOC_NOPM参数,这两个widget不具备电源属性,但是mux的切换会影响到与之相连的其它具备电源属性的电源状态。我们还为左右声道的扬声器各自定义了一个mixer widget:SPKL和SPKR,具体的mixer控制由上一步定义的left_speaker_mixer和right_speaker_mixer来完成,两个widget具备电源属性,所以,当这两个widget在一条有效的音频路径上时,dapm框架可以通过寄存器WM8993_POWER_MANAGEMENT_3的第8位和第9位控制它的电源状态。

第三步,定义这些widget的连接路径:

    static const struct snd_soc_dapm_route routes[] = {
            ......
     
            { "DACL Mux", "Left", "AIFINL" },
            { "DACL Mux", "Right", "AIFINR" },
            { "DACR Mux", "Left", "AIFINL" },
            { "DACR Mux", "Right", "AIFINR" },
     
            ......
     
            { "SPKL", "DAC Switch", "DACL" },
            { "SPKL", NULL, "CLK_SYS" },
     
            { "SPKR", "DAC Switch", "DACR" },
            { "SPKR", NULL, "CLK_SYS" },
    };

Through the definition of the first step, we know that DACL Mux and DACR Mux have two input pins, which are

    Left
    Right

And SPKL and SPKR have four input selection pins, which are:

    Input Switch
    IN1LP Switch/IN1RP Switch
    Output Switch
    DAC Switch

So, obviously, the above path definition means:

    Connect AIFINL to the Left input pin of DACL Mux
    AIFINR connect to the Right input pin of DACL Mux
    AIFINL connect to the Left input pin of DACR Mux
    AIFINR connect to the Right input pin of DACR Mux
    DACL connect to DAC Switch input pin
    of SPKL DAC Switch input pin

The fourth step is to register these widgets and paths in the codec-driven probe callback:

    static int wm8993_probe(struct snd_soc_codec *codec)
    {
            ......
            snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
                                      ARRAY_SIZE(wm8993_dapm_widgets));
            ......
     
            snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
            ......
    }


In the machine driver, we can define and register board-specific widgets and path information in the same way


    How to register a widget
    How to connect two widgets
    How to transfer the status of a widget to the entire audio path

dapm context
dapm context, literal translation means dapm context, this seems difficult to understand, in fact, we can understand it this way: dapm divides the entire audio system into several power domains according to functions and bias voltage levels, each domain contains For the respective widgets, all widgets in each domain are usually at the same bias voltage level, and a power domain is a dapm context, usually there are the following dapm contexts:

    The widgets belonging to the codec are located in a dapm context
    The widgets belonging to the platform are located in a dapm context The
    widgets belonging to the entire sound card are located in a dapm context
For the hardware of the audio system, it is usually necessary to provide an appropriate bias voltage to work normally. With the organization of dapm context, we can conveniently perform uniform bias voltage management on the same group of widgets. ASoc uses the snd_soc_dapm_context structure to represent a dapm context: /* DAPM context */ struct snd_soc_dapm_context { enum snd_soc_bias_level
    
bias_level
;         unsigned         int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */         /* Go to BIAS_OFF in suspend if the DAPM context is idle */         unsigned int suspend_bias_off:1;         void (*seq_notifier)(struct snd_soc_dapm_context *,                              enum snd_soc_dapm_ type, int);





        struct device *dev; /* from parent - for debug */
        struct snd_soc_component *component; /* parent component */
        struct snd_soc_card *card; /* parent card */

        /* used during DAPM updates */
        enum snd_soc_bias_level target_bias_level;
        struct list_head list;

        int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
        int (*set_bias_level)(struct snd_soc_dapm_context *dapm,
                              enum snd_soc_bias_level level);

        struct snd_soc_dapm_wcache path_sink_cache;
        struct snd_soc_dapm_wcache path_source_cache;

#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_dapm;
#endif
};

The value range of snd_soc_bias_level is as follows:

    SND_SOC_BIAS_OFF
    SND_SOC_BIAS_STANDBY
    SND_SOC_BIAS_PREPARE
    SND_SOC_BIAS_ON

snd_soc_dapm_context is embedded into the structure representing codec, platform, card, dai:

struct snd_soc_codec {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
 
struct snd_soc_platform {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
 
struct snd_soc_card {
        ......
        /* dapm */
        struct snd_soc_dapm_context dapm;
        ......
};
:
struct snd_soc_dai {
        ......
        /* dapm */
        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;
        struct snd_soc_dapm_context dapm;
        ......
};

In the widget structure snd_soc_dapm_widget, there is a snd_soc_dapm_context structure pointer, which points to the dapm structure of the codec, platform, card, or dai to which it belongs. At the same time, all dapm structures, through its list field, are linked to the dapm_list linked list header field of the snd_soc_card structure representing the sound card.

We already know that a widget is described by the snd_soc_dapm_widget structure. Usually, we will define a group of widgets in the codec driver, platform driver, and machine driver of the sound card according to the composition of the audio hardware. These widgets are organized in arrays. We generally A large number of auxiliary macros provided by the DAPM framework will be used to define these widget arrays. For the description of the auxiliary macros, please refer to the previous article: Detailed Explanation of DAPM in ALSA Sound Card Driver Part 3: How to Define Various Widgets.

Registration in the codec driver We know that we will register a codec driver through the api function snd_soc_register_codec provided by ASoc. The second parameter of this function is a snd_soc_component_driver structure pointer. This snd_soc_component_driver structure requires us to explicitly define it in the codec driver. There are several fields related to the dapm framework:

/* component interface */
struct snd_soc_component_driver {
        const char *name;
       ................
        /* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        unsigned int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets;
        unsigned int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        unsigned int num_dapm_routes;
       ...............
        
};

我们只要把我们定义好的snd_soc_dapm_widget结构数组的地址和widget的数量赋值到dapm_widgets和num_dapm_widgets字段即可,这样,经过devm_snd_soc_register_card注册codec后,在machine驱动匹配上该codec时,系统会判断这两个字段是否被赋值,如果有,它会调佣dapm框架提供的api来创建和注册widget,注意这里我说还要创建这个词,你可能比较奇怪,既然代表widget的snd_soc_dapm_widget结构数组已经在codec驱动中定义好了,为什么还要在创建?事实上,我们在codec驱动中定义的widget数组只是作为一个模板,dapm框架会根据该模板重新申请内存并初始化各个widget。

static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
        ......
        SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),
        SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
        SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
        ......
};
 
static struct snd_soc_component_driver soc_codec_dev_wm8993 = {
        .probe =        codec_xxx_probe,
        ......
        .dapm_widgets =       &wm8993_dapm_widgets[0],
        .num_dapm_widgets =      ARRAY_SIZE(wm8993_dapm_widgets),
        ......
};
 
static int codec_wm8993_i2c_probe(struct i2c_client *i2c,
                            const struct i2c_device_id *id)
{
        ......
        ret = devm_snd_soc_register_card(&i2c->dev,
                        &soc_codec_dev_wm8993, &wm8993_dai, 1);
        ......
}

The above registration method has a disadvantage. Sometimes we may define different widgets into multiple arrays according to their functions for the sake of code clarity. However, there is only one dapm_widgets field in snd_soc_codec_driver, and multiple widget arrays cannot be set. At this time, We need to actively call the API provided by the dapm framework in the codec's probe callback to create these widgets:

    static int wm8993_probe(struct snd_soc_codec *codec)
    {
            ......
            snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
                                      ARRAY_SIZE(wm8993_dapm_widgets));
            ......
    }

In fact, for the first method, snd_soc_register_codec actually calls snd_soc_dapm_new_controls to complete. There will be a detailed analysis of this function later.


Registering in the platform driver is the same as the codec driver, we will register a platform driver through the api function snd_soc_register_platform provided by ASoc. The second parameter of this function is a snd_soc_platform_driver structure pointer, and the snd_soc_platform_driver structure also contains dapm-related fields:

/* component interface */
struct snd_soc_component_driver {
        const char *name;

        /* Default control and setup, added after probe() is run */
        const struct snd_kcontrol_new *controls;
        unsigned int num_controls;
        const struct snd_soc_dapm_widget *dapm_widgets
        ; unsigned int num_dapm_widgets; _soc_dapm_route
        *dapm_routes;
        unsigned int num_dapm_routes;
  .... ..........
};
To register platform-level widgets, just like the codec driver, just assign the defined widget array to dapm_widgets and num_dapm_widgets fields. After the snd_soc_register_platform function registers the paltform, When the machine driver matches the platform, the system will automatically complete the creation and registration work. Similarly, we can also actively use snd_soc_dapm_new_controls in the platform-driven probe callback function to complete the creation of widgets. The specific code is similar to the codec driver, so I won't post it here.


Registering in the machine driver Some widgets may not be located in the codec, such as an independent headphone amplifier, or a speaker amplifier, etc. Such widgets usually need to be registered in the machine driver, and usually their dapm context also belongs to the sound card (snd_soc_card) domain. The method is still similar to the codec driver, through several dapm fields in the snd_soc_card structure representing the sound card:

/* SoC card */
struct snd_soc_card {
        const char *name;
        const char *long_name;
        const char *driver_name;
        char dmi_longname[80];
        char topology_shortname[32];

        struct device *dev;
        struct snd_card *snd_card;
        struct module *owner;

        struct mutex mutex;
        struct mutex dapm_mutex;
        struct mutex dapm_power_mutex;

        bool instantiated;
        bool topology_shortname_created;

        int (*probe)(struct snd_soc_card *card);
        int (*late_probe)(struct snd_soc_card *card);
        int (*remove)(struct snd_soc_card *card);

        /* the pre and post PM functions are used to do any PM work before and
         * after the codec and DAI's do any PM work. */
        int (*suspend_pre)(struct snd_soc_card *card);
        int (*suspend_post)(struct snd_soc_card *card);
        int (*resume_pre)(struct snd_soc_card *card);
        int (*resume_post)(struct snd_soc_card *card);

        /* callbacks */
        int (*set_bias_level)(struct snd_soc_card *,
                              struct snd_soc_dapm_context *dapm,
                              enum snd_soc_bias_level level);
        int (*set_bias_level_post)(struct snd_soc_card *,
                                   struct snd_soc_dapm_context *dapm,
     enum snd_soc_bias_level level);

        int (*add_dai_link)(struct snd_soc_card *,
                            struct snd_soc_dai_link *link);
        void (*remove_dai_link)(struct snd_soc_card *,
                            struct snd_soc_dai_link *link);

        long pmdown_time;

        /* CPU <--> Codec DAI links  */
        struct snd_soc_dai_link *dai_link;  /* predefined links only */
        int num_links;  /* predefined links only */
        struct list_head dai_link_list; /* all links */
        int num_dai_links;

        struct list_head rtd_list;
        int num_rtd;

        /* optional codec specific configuration */
        struct snd_soc_codec_conf *codec_conf;
        int num_configs;

        /*
         * optional auxiliary devices such as amplifiers or codecs with DAI
         * link unused
         */
        struct snd_soc_aux_dev *aux_dev;
        int num_aux_devs;
        struct list_head aux_comp_list;

        const struct snd_kcontrol_new *controls;
        int num_controls;

        /*
         * Card-specific routes and widgets.
         * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
         */
        const struct snd_soc_dapm_widget *dapm_widgets;
        int num_dapm_widgets;
        const struct snd_soc_dapm_route *dapm_routes;
        int num_dapm_routes;
        const struct snd_soc_dapm_widget *of_dapm_widgets;
    int num_of_dapm_widgets;
        const struct snd_soc_dapm_route *of_dapm_routes;
        int num_of_dapm_routes;
        bool fully_routed;

        struct work_struct deferred_resume_work;

        /* lists of probed devices belonging to this card */
        struct list_head component_dev_list;

        struct list_head widgets;
        struct list_head paths;
        struct list_head dapm_list;
        struct list_head dapm_dirty;

        /* attached dynamic objects */
        struct list_head dobj_list;

        /* Generic DAPM context for the card */
        struct snd_soc_dapm_context dapm;
        struct snd_soc_dapm_stats dapm_stats;
        struct snd_soc_dapm_update *update;

#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_card_root;
        struct dentry *debugfs_pop_time;
#endif
        u32 pop_time;

        void *drvdata;
};
    
Just assign the defined widget array and quantity to the dapm_widgets pointer and num_dapm_widgets. The api used to register the sound card: snd_soc_register_card() will also complete the creation of widgets through snd_soc_dapm_new_controls.


Registering the audio path
system The various widgets registered in the system need to be connected to each other to work in harmony. The connection relationship is defined by the snd_soc_dapm_route structure. For how to use the snd_soc_dapm_route structure to define path information, please refer to: DAPM Detailed Explanation in ALSA Sound Card Driver Part 3: How to define the content of the "Building widget and route" section in various widgets. Usually, all route information will be defined with an array of snd_soc_dapm_route structures. Like the widget, the path information also exists in the codec driver, machine driver and platform driver. We also have two ways to register the audio path information:


Through the dapm_routes and num_dapm_routes fields in the snd_soc_component_driver (formerly snd_soc_codec_driver/snd_soc_platform_driver) /snd_soc_card structure;
actively register the audio path in the probe callback of the codec and platform, and register the audio path through the init callback function of the snd_soc_dai_link structure in the machine driver;
two The method finally completes the registration of the audio route by calling the snd_soc_dapm_add_routes function. The following code snippet is the machine driver of omap's pandora board, using the second method to register path information:


static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {
        SND_SOC_DAPM_MIC("Mic (internal)", NULL),
        SND_SOC_DAPM_MIC("Mic (external)", NULL),
        SND_SOC_DAPM_LINE("Line In", NULL),
};
 
static const struct snd_soc_dapm_route omap3pandora_out_map[] = {
        {"PCM DAC", NULL, "APLL Enable"},
        {"Headphone Amplifier", NULL, "PCM DAC"},
        {"Line Out", NULL, "PCM DAC"},
        {"Headphone Jack", NULL, "Headphone Amplifier"},
};
 
static const struct snd_soc_dapm_route omap3pandora_in_map[] = {
        {"AUXL", NULL, "Line In"},
        {"AUXR", NULL, "Line In"},
 
        {"MAINMIC", NULL, "Mic (internal)"},
        {"Mic (internal)", NULL, "Mic Bias 1"},
 
        {"SUBMIC", NULL, "Mic (external)"},
        {"Mic (external)", NULL, "Mic Bias 2"},
};
static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_codec *codec = rtd->codec;
        struct snd_soc_dapm_context *dapm = &codec->dapm;
        int ret;
 
        /* All TWL4030 output pins are floating */
        snd_soc_dapm_nc_pin(dapm, "EARPIECE");
        ......
        //注册kcontrol控件
        ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,
                                ARRAY_SIZE(omap3pandora_out_dapm_widgets));
        if (ret < 0)
                return ret;
        //注册machine的音频路径
        return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,
                ARRAY_SIZE(omap3pandora_out_map));
}
 
static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_codec *codec = rtd->codec;
        struct snd_soc_dapm_context *dapm = &codec->dapm;
        int ret;
 
        /* Not comnnected */
        snd_soc_dapm_nc_pin(dapm, "HSMIC");
        ......
        //注册kcontrol控件
        ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,
                                ARRAY_SIZE(omap3pandora_in_dapm_widgets));
        if (ret < 0)
                return ret;
        //注册machine音频路径
        return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,
                ARRAY_SIZE(omap3pandora_in_map));
}
 
/* Digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link omap3pandora_dai[] = {
        {
                .name = "PCM1773",
                ......
                .init = omap3pandora_out_init,
        }, {
                .name = "TWL4030",
                .stream_name = "Line/Mic In",
                ......
                .init = omap3pandora_in_init,
        }
};

The above sections of dai widget
introduce the codec, platform, and machine-level widget and route registration methods. In the dapm framework, there is another widget that represents a dai (digital audio interface). The description of dai , please refer to: Linux ALSA sound card driver seven: Codec in ASoC architecture. Dai is divided into cpu dai and codec dai according to its location. In hardware, a cpu dai is usually connected to a codec dai. In the machine driver, we need to specify a structure called snd_soc_dai_link in the snd_soc_card structure. This structure Defines which cpu dai and codec dai the sound card uses for connection. In Asoc, a dai is represented by the snd_soc_dai structure, in which several fields are related to the dapm framework:

/*
 * Digital Audio Interface runtime data.
 *
 * Holds runtime data for a DAI.
 */
struct snd_soc_dai {
        const char *name;
        int id;
        struct device *dev;

        /* driver ops */
        struct snd_soc_dai_driver *driver;

        /* DAI runtime info */
        unsigned int capture_active;            /* stream usage count */
        unsigned int playback_active;           /* stream usage count */
        unsigned int probed:1;

        unsigned int active;

        struct snd_soc_dapm_widget *playback_widget;
        struct snd_soc_dapm_widget *capture_widget;

        /* DAI DMA data */
        void *playback_dma_data;
        void *capture_dma_data;

        /* Symmetry data - only valid if symmetry is being enforced */
        unsigned int rate;
        unsigned int channels;
        unsigned int sample_bits;

        /* parent platform/codec */
        struct snd_soc_component *component;

        /* CODEC TDM slot masks and params (for fixup) */
        unsigned int tx_mask;
        unsigned int rx_mask;

        struct list_head list;
};

Dai is registered by the codec driver and the iis or pcm interface driver in the platform code. The machine driver is responsible for finding a pair of cpu/codec dai specified in snd_soc_dai_link and binding them. Whether it is cpu dai or codec dai, it usually transmits the ability to play and record audio streams at the same time, so we can see that there are two widget pointers in snd_soc_dai, which represent the playback stream and the recording stream respectively.


codec dai widget    
First, when the codec driver registers the codec, it will pass in the number of dai supported by the codec and the snd_soc_dai_driver structure pointer that records the dai information:


int snd_soc_register_component(struct device *dev,
                         const struct snd_soc_component_driver *component_driver,
                         struct snd_soc_dai_driver *dai_drv, int num_dai);
int devm_snd_soc_register_component(struct device *dev,
                         const struct snd_soc_component_driver *component_driver,
                         struct snd_soc_dai_driver *dai_drv, int num_dai);


static struct snd_soc_dai_driver wm8993_dai = {
        .name = "wm8993-hifi",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
                .channels_max = 2,
                .rates = WM8993_RATES,
                .formats = WM8993_FORMATS,
                .sig_bits = 24,
        },
        .capture = {
                 .stream_name = "Capture",
                 .channels_min = 1,
                 .channels_max = 2,
                 .rates = WM8993_RATES,
                 .formats = WM8993_FORMATS,
                 .sig_bits = 24,
         },
        .ops = &wm8993_ops,
        .symmetric_rates = 1,
};
 
static int wm8993_i2c_probe(struct i2c_client *i2c,
                            const struct i2c_device_id *id)
{
        ......
        ret = snd_soc_register_component(&i2c->dev,
                        &soc_codec_dev_wm8993, &wm8993_dai, 1);
        ......
}


This time makes ASoc register the dai of the codec in the system, and hang these dais in the global linked list variable dai_list, and then, after the codec is matched by the machine driver, the soc_probe_component function will be called, and he will search through the global linked list variable dai_list For all dai belonging to the codec, call the snd_soc_dapm_new_dai_widgets function to generate the playback stream widget and recording stream widget of the dai:

static int soc_probe_component(struct snd_soc_card *card,
        struct snd_soc_component *component)
{
        struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
        struct snd_soc_dai *dai;
        int ret;

        if (!strcmp(component->name, "snd-soc-dummy"))
                return 0;

        if (component->card) {
                if (component->card != card) {
                        dev_err(component->dev,
                                "Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n",
                                card->name, component->card->name);
                        return -ENODEV;
                }
                return 0;
        }

        if (!try_module_get(component->dev->driver->owner))
                return -ENODEV;

        component->card = card;
        dapm->card = card;
        soc_set_name_prefix(card, component);

        soc_init_component_debugfs(component);

        if (component->driver->dapm_widgets) {
                ret = snd_soc_dapm_new_controls(dapm,
                                        component->driver->dapm_widgets,
                                        component->driver->num_dapm_widgets);

                if (ret != 0) {
                        dev_err(component->dev,
                                "Failed to create new controls %d\n", ret);
                        goto err_probe;
                }
        }

list_for_each_entry(dai, &component->dai_list, list) {
                ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
                if (ret != 0) {
                        dev_err(component->dev,
                                "Failed to create DAI widgets %d\n", ret);
                        goto err_probe;
                }
        }

        if (component->driver->probe) {
                ret = component->driver->probe(component);
                if (ret < 0) {
                        dev_err(component->dev,
                                "ASoC: failed to probe component %d\n", ret);
                        goto err_probe;
                }

                WARN(dapm->idle_bias_off &&
                        dapm->bias_level != SND_SOC_BIAS_OFF,
                        "codec %s can not start from non-off bias with idle_bias_off==1\n",
                        component->name);
        }

        /* machine specific init */
        if (component->init) {
                ret = component->init(component);
                if (ret < 0) {
                        dev_err(component->dev,
                                "Failed to do machine specific init %d\n", ret);
                        goto err_probe;
                }
        }

        if (component->driver->controls)
                snd_soc_add_component_controls(component,
                                               component->driver->controls,
                                               component->driver->num_controls);
        if (component->driver->dapm_routes)
                snd_soc_dapm_add_routes(dapm,
                                        component->driver->dapm_routes,
                                        component->driver->num_dapm_routes);

        list_add(&dapm->list, &card->dapm_list);
        list_add(&component->card_list, &card->component_dev_list);

        return 0;
        
err_probe:
        soc_cleanup_component_debugfs(component);
        component->card = NULL;
        module_put(component->dev->driver->owner);

        return ret;
}


The code of snd_soc_dapm_new_dai_widgets:

int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
                 struct snd_soc_dai *dai)
{
    struct snd_soc_dapm_widget template;
    struct snd_soc_dapm_widget *w;

    WARN_ON(dapm->dev != dai->dev);

    memset(&template, 0, sizeof(template));
    template.reg = SND_SOC_NOPM;

    if (dai->driver->playback.stream_name) { // 创建播放 dai widget  
        template.id = snd_soc_dapm_dai_in;
        template.name = dai->driver->playback.stream_name;
        template.sname = dai->driver->playback.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w)) {
            int ret = PTR_ERR(w);

            /* Do not nag about probe deferrals */
            if (ret != -EPROBE_DEFER)
                dev_err(dapm->dev,
                "ASoC: Failed to create %s widget (%d)\n",
                dai->driver->playback.stream_name, ret);
            return ret;
        }
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->playback.stream_name);
            return -ENOMEM;
        }

        w->priv = dai;
        dai->playback_widget = w;
    }

    if (dai->driver->capture.stream_name) {        // 创建录音 dai widget
        template.id = snd_soc_dapm_dai_out;
        template.name = dai->driver->capture.stream_name;
        template.sname = dai->driver->capture.stream_name;

        dev_dbg(dai->dev, "ASoC: adding %s widget\n",
            template.name);

        w = snd_soc_dapm_new_control_unlocked(dapm, &template);
        if (IS_ERR(w)) {
            int ret = PTR_ERR(w);

            /* Do not nag about probe deferrals */
            if (ret != -EPROBE_DEFER)
                dev_err(dapm->dev,
                "ASoC: Failed to create %s widget (%d)\n",
                dai->driver->playback.stream_name, ret);
            return ret;
        }
        if (!w) {
            dev_err(dapm->dev, "ASoC: Failed to create %s widget\n",
                dai->driver->capture.stream_name);
            return -ENOMEM;
        }

        w->priv = dai;
        dai->capture_widget = w;
    }

    return 0;
}
    
Create a widget for Playback and Capture respectively, the priv field of the widget points to the dai, so that the corresponding dai can be found through the widget, and the name of the widget is the stream_name of the snd_soc_dai_driver structure.

endpoint widget

A complete dapm audio path must have a starting point and an ending point. We call widgets located at these starting points and ending points endpoint widgets. The following types of widgets can be endpoint widgets:

Codec input and output pins:

    snd_soc_dapm_output
    snd_soc_dapm_input

External audio equipment:

    snd_soc_dapm_hp
    snd_soc_dapm_spk
    snd_soc_dapm_line

Audio stream (stream domain):

    snd_soc_dapm_adc
    snd_soc_dapm_dac
    snd_soc_dapm_aif_out
    snd_soc_dapm_aif_in
    snd_soc_dapm_dai_out
    snd_soc_dapm_dai_in

Power, clock and others:

    snd_soc_dapm_supply
    snd_soc_dapm_regulator_supply
    snd_soc_dapm_clock_supply
    snd_soc_dapm_kcontrol


Establish a connection relationship between widgets

前面我们主要着重于codec、platform、machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如何在alsa音频驱动的3大部分(codec、platform、machine)中,按照所使用的音频硬件结构,定义出相应的widget,kcontrol,以及必要的音频路径,而在本章中,我们将会深入dapm的核心部分,看看各个widget之间是如何建立连接关系,形成一条完整的音频路径。

前面我们已经简单地介绍过,驱动程序需要使用以下api函数创建widget:

    int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num);
In fact, this function is just the first step in creating widgets, it allocates memory for each widget, initializes the necessary fields, and then hangs these widgets on In the widgets linked list field of the snd_soc_card representing the sound card. To enable connection between widgets, we also need a second function:
    /* dapm path setup */
int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
This function will create the dapm kcontrol required by the widget according to the information of the widget, these The state change of dapm kcontol represents the change of the audio path, which affects the power state of each widget. It may be confusing to see the name of the function. In fact, the role of snd_soc_dapm_new_controls is more to create widgets, and the role of snd_soc_dapm_new_widget is more to create kcontrol contained in widgets, so in my opinion, these two function names It should be changed to better

Create widget: snd_soc_dapm_new_controls

The snd_soc_dapm_new_controls function completes the creation of widgets, and registers these created widgets in the widgets list of the sound card

/**
 * snd_soc_dapm_new_controls - create new dapm controls
 * @dapm: DAPM context
 * @widget: widget array
 * @num: number of widgets
 *
 * Creates new DAPM controls based upon the templates.
 *
 * Returns 0 for success else error.
 */
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num)
{
        struct snd_soc_dapm_widget *w;
        int i;
        int ret = 0;

        mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
        for (i = 0; i < num; i++) {
                w = snd_soc_dapm_new_control_unlocked(dapm, widget);
                if (IS_ERR(w)) {
                        ret = PTR_ERR(w);
                        /* Do not nag about probe deferrals */
                        if (ret == -EPROBE_DEFER)
                                break;
                        dev_err(dapm->dev,
                                "ASoC: Failed to create DAPM control %s (%d)\n",
                                widget->name, ret);
                        break;
                }
                if (!w) {
                        dev_err(dapm->dev,
                                "ASoC: Failed to create DAPM control %s\n",
                                widget->name);
                        ret = -ENOMEM;
                        break;
                }
                widget++;
        }
        mutex_unlock(&dapm->card->dapm_mutex);
        return ret;
}

struct snd_soc_dapm_widget *
snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
                         const struct snd_soc_dapm_widget *widget);

struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
                         const struct snd_soc_dapm_widget *widget);

This function is just a simple loop, calling the snd_soc_dapm_new_control function in turn for the incoming widget template array, the actual work is done by snd_soc_dapm_new_control, continue to enter this function, and see what it does.
We have said before that the snd_soc_dapm_widget array defined in the driver is only used as a template, so the first thing snd_soc_dapm_new_control_unlocked does is to reallocate memory for the widget and copy the contents of the template over struct snd_soc_dapm_widget * snd_soc_dapm_new_control_unlocked
(
struct snd_soc_dapm_context *dapm,
                         const struct snd_soc_dapm_widget *widget)
{         enum snd_soc_dapm_direction dir;         struct snd_soc_dapm_widget *w;         const char *prefix;         int ret;



        if ((w = dapm_cnew_widget(widget)) == NULL) //dapm_cnew_widget completes memory application and copy template action
                return NULL;

        switch (w->id) {
        case snd_soc_dapm_regulator_supply:
                w->regulator = devm_regulator_get(dapm->dev, w->name);
                if (IS_ERR(w->regulator)) {
                        ret = PTR_ERR(w->regulator);
                        if (ret == -EPROBE_DEFER)
                                return ERR_PTR(ret);
                        dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
                                w->name, ret);
                        return NULL;
                }

                if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
                        ret = regulator_allow_bypass(w->regulator, true);
                        if (ret != 0)
                                dev_warn(w->dapm->dev,
                                         "ASoC: Failed to bypass %s: %d\n",
                                         w->name, ret);
                }
                break;
        case snd_soc_dapm_pinctrl:
                w->pinctrl = devm_pinctrl_get(dapm->dev);
                if (IS_ERR(w->pinctrl)) {
                        ret = PTR_ERR(w->pinctrl);
                                                     

    .................
}


For a widget of snd_soc_dapm_regulator_supply type, obtain the corresponding regulator structure according to the widget name, and for a widget of snd_soc_dapm_clock_supply type, obtain the corresponding clock structure according to the widget name. Next, add the necessary prefixes to the widget's name as needed:

            if (dapm->codec && dapm->codec->name_prefix)
                    w->name = kasprintf(GFP_KERNEL, "%s %s",
                            dapm->codec->name_prefix, widget->name);
            else
                    w->name = kasprintf(GFP_KERNEL, "%s", widget->name);

Then, set the appropriate power_check power state callback function for different types of widgets. The widget type and the corresponding power_check callback function are set as shown in the following table:
                     power_check callback function of widget (specific analysis code)

When the audio path changes, the power_check callback will be called to check whether the widget's power status needs to be updated. After the power_check setting is complete, you need to set the codec, platform, and dapm context to which the widget belongs. Several linked lists for audio paths also need to be initialized. Then, add the widget to the widgets linked list of the sound card:

            w->dapm = dapm;
            w->codec = dapm->codec;
            w->platform = dapm->platform;
            INIT_LIST_HEAD(&w->sources);
            INIT_LIST_HEAD(&w->sinks);
            INIT_LIST_HEAD(&w->list);
            INIT_LIST_HEAD(&w->dirty);
            list_add(&w->list, &dapm->card->widgets);

The functions of several linked lists are as follows:

    sources is used to link all the snd_soc_path structure sinks connected to the input of the widget
    is used to link all the snd_soc_path structure list connected to the output of the widget
    is used to link to the widgets linked list of the sound card
    dirty is used to link to the dapm_dirty linked list of the sound card

Finally, set the widget to the connect state:

            /* machine layer set ups unconnected pins and insertions */
            w->connected = 1;
            return w;
    }

The connected field represents the connection status of the pin. Currently, only the following widgets use the connected field:

    snd_soc_dapm_output
    snd_soc_dapm_input
    snd_soc_dapm_hp
    snd_soc_dapm_spk
    snd_soc_dapm_line
    snd_soc_dapm_vmid
    snd_soc_dapm_mic
    snd_soc_dapm_siggen

The driver can use the following APIs to set the connection status of the pin:

    snd_soc_dapm_enable_pin
    snd_soc_dapm_force_enable_pin
    snd_soc_dapm_disable_pin
    snd_soc_dapm_nc_pin

At this point, the widget has been correctly created and initialized, and is hung in the widgets list of the sound card. In the future, we can traverse all the widgets through the widgets list of the sound card. Let me emphasize again the main functions completed by the snd_soc_dapm_new_controls function:
                                                                      // snd_soc_dapm_new_control_unlocked
     allocates memory for the widget, and copies the template defined in the driver passed in the parameter
    to set the power_check callback function
    to hang the widget in the widgets list of the sound card

To create dapm kcontrol for widgets and
define a widget, we need to specify two very important contents: one is register information such as reg/shift used to control the power state of the widget, and the other is dapm kcontrol information used to control audio path switching, These dapm kcontrols have their own reg/shift register information for switching the path connection method of the widget. In the content of the previous section, we just created instances of widgets and registered them in the widgets list of the sound card, but so far, the dapm kcontrol contained in the widget has not been established, and the dapm framework is in the initialization phase of the sound card After all the widgets (including machine, platform, and codec) are created, use the snd_soc_dapm_new_widgets function to create the dapm kcontrol contained in the widget, and initialize the initial power state of the widget and the initial connection state of the audio path. Let's look at the initialization function of the sound card, there are those initializations related to dapm:


static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_dai_link *dai_link;
        int ret, i, order;

    ................

        card->dapm.bias_level = SND_SOC_BIAS_OFF;
        card->dapm.dev = card->dev;
        card->dapm.card = card;
        list_add(&card->dapm.list, &card->dapm_list);

#ifdef CONFIG_DEBUG_FS
        snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
#endif

#ifdef CONFIG_PM_SLEEP
        /* deferred resume work */
        INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif

        if (card->dapm_widgets) /* Create machine-level widgets */
                snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
                                          card->num_dapm_widgets);

        if (card->of_dapm_widgets)
                snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
                                          card->num_of_dapm_widgets);

        /* initialise the sound card only once */
        if (card->probe) {
                ret = card->probe(card);
                if (ret < 0)
                        goto card_probe_error;
        }

      ................

        snd_soc_dapm_link_dai_widgets(card); /*  连接dai widget  */
        snd_soc_dapm_connect_dai_link_widgets(card);

        if (card->controls) /* Create machine-level ordinary kcontrol controls */
                snd_soc_add_card_controls(card, card->controls, card->num_controls);

        if (card->dapm_routes) /* Register machine-level path connection information */
                snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                        card->num_dapm_routes);

        if (card->of_dapm_routes)
                snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
                                        card->num_of_dapm_routes);

    ..............

        snd_soc_dapm_new_widgets(card);/*Initialize the dapm kcontrol, power status and connection status included in the widget*/
 


      ..........


}
As shown in the comments I added, after completing the machine-level widget and route processing, the snd_soc_dapm_new_widgets function is called to initialize the dapm kcontrol they contain for all registered widgets, and initialize the power state and path connection of the widget state. Let's take a look at the working process of the snd_soc_dapm_new_widgets function.
This function traverses all registered widgets through the widgets list of the sound card. The new field is used to judge whether the widget has executed the snd_soc_dapm_new_widgets function. If the num_kcontrols field has a value, it indicates that the widget contains several dapm kcontrols, then It is necessary to allocate an array of pointers to these kcontrols, and assign the first address of the array to the kcontrols field of the widget. This array stores pointers to these kcontrols. Of course, these are now null pointers, because the actual kcontrols have not yet been created:

int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
    struct snd_soc_dapm_widget *w;
    unsigned int val;

    mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);

    list_for_each_entry(w, &card->widgets, list)
    {
        if (w->new)
            continue;

        if (w->num_kcontrols) {             w->kcontrols = kcalloc(w->num_kcontrols,                         sizeof(struct snd_kcontrol *),                         GFP_KERNEL);             if (!w->kcontrols) {                 mutex_unlock(&card->dapm_mutex);                 return -ENOMEM ;             }         }                  //Next, for several widgets that can affect the audio path, create and initialize the dapm kcontrol they contain:         switch(w->id) {         case snd_soc_dapm_switch:         case snd_soc_dapm_mixer:         case snd_soc_dapm_mixer_named_ctl:             dapm_new_mixer(w);             break ;         case snd_soc_dapm_mux:
















        case snd_soc_dapm_demux:
            dapm_new_mux(w);
            break;
        case snd_soc_dapm_pga:
        case snd_soc_dapm_out_drv:
            dapm_new_pga(w);
            break;
        case snd_soc_dapm_dai_link:
            dapm_new_dai_link(w);
            break;
        default:
            break;
        }

        /* Read the initial power state from the device */
        if (w->reg >= 0) {//According to the current value of the widget register, initialize the power state of the widget and set it to the power field soc_dapm_read
            (w->dapm , w->reg, &val);
            val = val >> w->shift;
            val &= w->mask;
            if (val == w->on_val)
                w->power = 1;
        }

        w->new = 1;//Set the new field, indicating that the widget has been initialized, and we need to add the widget to the dapm_dirty list of the sound card, indicating that the state of the widget has changed. Later, at an appropriate moment, The dapm framework will scan the dapm_dirty linked list and process all changed widgets uniformly. Why do we need to deal with it uniformly? Because dapm needs to control the power-on and power-on sequence of various widgets, and also to reduce the number of register reads and writes (multiple widgets may use the same register):

        dapm_mark_dirty(w, "new widget");
        dapm_debugfs_add_widget(w);
    }

    dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);//Through the dapm_power_widgets function, uniformly process the state changes of all widgets on the dapm_dirty list
    mutex_unlock(&card->dapm_mutex);
    return 0;
}

The creation functions that need to be used are:

    dapm_new_mixer() For mixer type, use this function to create dapm kcontrol;
    dapm_new_mux() For mux type, use this function to create dapm kcontrol;
    dapm_new_pga() For pga type, use this function to create dapm kcontrol;



In the previous section of dapm mixer kcontrol , we mentioned that for dapm kcontrol of the mixer type, we will use dapm_new_mixer to complete the specific creation work, first look at the code and then analyze:

/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
    int i, ret;
    struct snd_soc_dapm_path *path;
    struct dapm_kcontrol_data *data;

    /* add kcontrol */
   (1)    for (i = 0; i < w->num_kcontrols; i++) {
        /* match name */
   (2)        snd_soc_dapm_widget_for_each_source_path(w, path) {
            /* mixer/mux paths name must match control name */
   (3)            if (path->name != (char *)w->kcontrol_news[i].name)
                continue;

   (4)        if (!w->kcontrols[i]) {
   (5)                ret = dapm_create_or_share_kcontrol(w, i);
                if (ret < 0)
                    return ret;
            }

   (6)            dapm_kcontrol_add_path(w->kcontrols[i], path);

            data = snd_kcontrol_chip(w->kcontrols[i]);
            if (data->widget)
                   snd_soc_dapm_add_path(data->widget->dapm,
                              data->widget,
                              path->source,
                              NULL, NULL);
        }
    }

    return 0;
}

(1) Because a mixer is composed of multiple kcontrols, each kcontrol controls the opening and closing of an input terminal of the mixer, so this function will loop according to the number of kcontrols, and establish corresponding kcontrols one by one.
(2) (3) As mentioned many times before, widgets are connected using snd_soc_path, and the sources linked list of widgets saves all the snd_soc_path structures connected to the input, so we can use the name specified in the kcontrol template to match the corresponding snd_soc_path structure.
(4) Because an input pin may be connected to multiple input sources, this kcontrol may have been created when the path of the last input source was associated, so here judge the pointer value in the corresponding index in the kcontrols pointer array, if it has been assigned, It means that kcontrol has been created before, so we simply add the path connected to the input to the path_list list of kcontrol, and add a virtual shadow widget, which is connected to the source widget corresponding to the input, because using Register information such as reg/shift of the kcontrol itself, so it actually controls the opening and closing of the kcontrol. This shadow widget will only be created when the autodisable field of the kcontrol is set. This feature makes the source close , the input of the mixer connected to it can also be automatically closed, this feature is achieved through dapm_kcontrol_add_path:

static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
    struct snd_soc_dapm_path *path)
{     struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);             /* Add the path connected by kcontrol to the paths list */         /* path The dapm_kcontrol_data structure where the s linked list is located will be saved In the private_data field of kcontrol */     list_add_tail(&path->list_kcontrol, &data->paths); }




(5) If kcontrol has not been created before, create the kcontrol of this input terminal through dapm_create_or_share_mixmux_kcontrol. Similarly, the shadow widget corresponding to kcontrol will also judge whether it needs to be created through dapm_kcontrol_add_path.


dapm mux kcontrol

Because a widget can only contain at most one damp kcontrol of mux type, its creation method is slightly different. The dapm framework uses dapm_new_mux function to create dapm kcontrol of mux type: /* create new dapm mux control */ static int dapm_new_mux
(
struct snd_soc_dapm_widget *w)
{     struct snd_soc_dapm_context *dapm = w->dapm;     enum snd_soc_dapm_direction dir;     struct snd_soc_dapm_path *path;     const char *type;     int ret;




    switch (w->id) {
    case snd_soc_dapm_mux:
        dir = SND_SOC_DAPM_DIR_OUT;
        type = "mux";
        break;
    case snd_soc_dapm_demux:
        dir = SND_SOC_DAPM_DIR_IN;
        type = "demux";
        break;
    default:
        return -EINVAL;
    }

(1)    if (w->num_kcontrols != 1) {
        dev_err(dapm->dev,
            "ASoC: %s %s has incorrect number of controls\n", type,
            w->name);
        return -EINVAL;
    }

    if (list_empty(&w->edges[dir])) {
        dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name);
        return -EINVAL;
    }

(2)    ret = dapm_create_or_share_kcontrol(w, 0);
    if (ret < 0)
        return ret;

(3)    snd_soc_dapm_widget_for_each_path(w, dir, path) {
        if (path->name)
            dapm_kcontrol_add_path(w->kcontrols[0], path);
    }

    return 0;
}
(1) For mux type widgets, there is only one kcontrol, so make a judgment here.
(2) Similarly, like the mixer type, use dapm_create_or_share_mixmux_kcontrol to create this kcontrol.
(3) Add the paths connected to each input to the paths linked list of the dapm_kcontrol_data structure, and create a shadow widget to support the autodisable feature.


dapm_create_or_share_kcontrol
The mixer type and mux type widgets mentioned above, when creating the dapm kcontrol they contain, actually use the dapm_create_or_share_kcontrol function to complete the creation work, so here we need to analyze the working principle of this function . A large part of the code in this function actually deals with whether the name of kcontrol should be prefixed with codec. We will ignore this part of the code. Interested readers can check the kernel code by themselves. The path is: sound/soc/soc-dapm. In c, the simplified code is as follows

/*
 * Determine if a kcontrol is shared. If it is, look it up. If it isn't,
 * create it. Either way, add the widget into the control's widget list
 */
static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w,
    int kci)
{
    struct snd_soc_dapm_context *dapm = w->dapm;
    struct snd_card *card = dapm->card->snd_card;
    const char *prefix;
    size_t prefix_len;
    int shared;
    struct snd_kcontrol *kcontrol;
    bool wname_in_long_name, kcname_in_long_name;
    char *long_name = NULL;
    const char *name;
    int ret = 0;

    prefix = soc_dapm_prefix(dapm);
    if (prefix)
        prefix_len = strlen(prefix) + 1;
    else
        prefix_len = 0;

(1)    shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
                     &kcontrol);

(2)    if (!kcontrol) {
        if (shared) {
            wname_in_long_name = false;
            kcname_in_long_name = true;
        } else {
            switch (w->id) {
            case snd_soc_dapm_switch:
            case snd_soc_dapm_mixer:
            case snd_soc_dapm_pga:
            case snd_soc_dapm_out_drv:
                wname_in_long_name = true;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_mixer_named_ctl:
                wname_in_long_name = false;
                kcname_in_long_name = true;
                break;
            case snd_soc_dapm_demux:
            case snd_soc_dapm_mux:
                wname_in_long_name = true;
                kcname_in_long_name = false;
                break;
            default:
                return -EINVAL;
            }
        }

        if (wname_in_long_name && kcname_in_long_name) {
            /*
             * The control will get a prefix from the control
             * creation process but we're also using the same
             * prefix for widgets so cut the prefix off the
             * front of the widget name.
             */
            long_name = kasprintf(GFP_KERNEL, "%s %s",
                 w->name + prefix_len,
                 w->kcontrol_news[kci].name);
            if (long_name == NULL)
                return -ENOMEM;

            name = long_name;
        } else if (wname_in_long_name) {
            long_name = NULL;
            name = w->name + prefix_len;
        } else {
            long_name = NULL;
            name = w->kcontrol_news[kci].name;
        }

(3)        kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,
                    prefix);
        if (!kcontrol) {
            ret = -ENOMEM;
            goto exit_free;
        }

        kcontrol->private_free = dapm_kcontrol_free;

(4)        ret = dapm_kcontrol_data_alloc(w, kcontrol, name);
        if (ret) {
            snd_ctl_free_one(kcontrol);
            goto exit_free;
        }

(5)        ret = snd_ctl_add(card, kcontrol);
        if (ret < 0) {
            dev_err(dapm->dev,
                "ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
                w->name, name, ret);
            goto exit_free;
        }
    }

(6) right = dapm_kcontrol_add_widget(kcontrol, w);
    if (right == 0)
(7) w->kcontrols[kci] = kcontrol;

exit_free:
    kfree(long_name);

    return ret;
}
(1) In order to save memory, search through kcontrol name matching. If this kcontrol has already been created in other widgets, then we will not create it again. The parameter kcontrol of dapm_is_shared_kcontrol will return the pointer of the created kcontrol .
(2) If the kcontrol pointer is assigned, it means that the kcontrol with the same name in other widgets has been found in (1), and we don't need to create it again, just share the kcontrol.
(3) For the standard kcontrol creation function, please refer to: Linux ALSA sound card driver 4: Control device creation in the "Create control" section.
(4) If the widget supports the autodisable feature, create a shadow widget corresponding to the kcontrol. The type of the shadow widget is: snd_soc_dapm_kcontrol.
(5) For the standard kcontrol creation function, please refer to: Linux ALSA sound card driver 4: Control device creation in the "Create control" section.
(6) Add all shadow widgets (snd_soc_dapm_kcontrol) that share the kcontrol to the dapm_kcontrol_data structure pointed to by the private_data field of the kcontrol.
(7) Assign the created kcontrol pointer to the widget's kcontrols array.
It should be noted that if the kcontrol supports the autodisable feature, once the kcontrol is automatically closed due to the closing of the source, the user space can only operate the cache value of the kcontrol, and the cache value will be actually updated only when the kcontrol is opened again into the register.
Now. Let's summarize the work done by creating a kcontrol contained in a widget:

    Loop through each input terminal, perform the following series of operations for each input terminal in turn
    Create a kcontrol for each input terminal, and use the created kcontrol directly if it can be shared
    The private_data field of kcontrol saves the information of these shared widgets
    If supported autodisable feature, each input terminal also needs to create a virtual shadow widget of snd_soc_dapm_kcontrol type, and the shadow widget is also recorded in the private_data field. The created kcontrol
    will be stored in the kcontrols array of the widget in turn for path control and matching for.

Establish connection relationship for widgets
If there is no connection relationship between widgets, dapm will not be able to implement dynamic power management. It is the connection relationship between widgets, and these connections form a so-called completed audio path. dapm can follow This path uniformly controls the power status of all widgets on the path. We already know that the snd_soc_path structure is used to connect widgets. What the driver needs to do is to define an array of snd_soc_route structures. Each entry in the array describes the purpose The name of the widget and the source widget, and the name of the kcontrol that controls the connection. Finally, the driver uses the api function snd_soc_dapm_add_routes to register these connection information. Next, we will analyze the specific implementation of this function

/**
 * snd_soc_dapm_add_routes - Add routes between DAPM widgets
 * @dapm: DAPM context
 * @route: audio routes
 * @num: number of routes
 *
 * Connects 2 dapm widgets together via a named audio path. The sink is
 * the widget receiving the audio signal, whilst the source is the sender
 * of the audio signal.
 *
 * Returns 0 for success else error. On error all resources can be freed
 * with a call to snd_soc_card_free().
 */
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
                const struct snd_soc_dapm_route *route, int num)
{
    int i, r, ret = 0;

    mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
    for (i = 0; i < num; i++) {
        r = snd_soc_dapm_add_route(dapm, route);
        if (r < 0) {
            dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
                route->source,
                route->control ? route->control : "direct",
                route->sink);
            ret = r;
        }
        route++;
    }
    mutex_unlock(&dapm->card->dapm_mutex);

    return ret;
}

This function is just a loop, calling snd_soc_dapm_add_route on the array passed in by the parameters in turn, and the main work is done by snd_soc_dapm_add_route. Let's enter the snd_soc_dapm_add_route function to see:


static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
                  const struct snd_soc_dapm_route *route)
{
    struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
    struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
    const char *sink;
    const char *source;
    char prefixed_sink[80];
    char prefixed_source[80];
    const char *prefix;
    int ret;

    prefix = soc_dapm_prefix(dapm);
    if (prefix) {
        snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
             prefix, route->sink);
        sink = prefixed_sink;
        snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
             prefix, route->source);
        source = prefixed_source;
    } else {
        sink = route->sink;
        source = route->source;
    }

    wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
    wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);

    if (wsink && wsource)
        goto skip_search;

    /*
     * find src and dest widgets over all widgets but favor a widget from
     * current DAPM context
     */
    list_for_each_entry(w, &dapm->card->widgets, list) {
        if (!wsink && !(strcmp(w->name, sink))) {
            wtsink = w;
            if (w->dapm == dapm) {
                wsink = w;
                if (wsource)
                    break;
            }
            continue;
        }
        if (!wsource && !(strcmp(w->name, source))) {
            wtsource = w;
            if (w->dapm == dapm) {
                wsource = w;
                if (wsink)
                    break;
            }
        }
    }
    /* use widget from another DAPM context if not found from this */
    if (!wsink)
        wsink = wtsink;
    if (!wsource)
        wsource = wtsource;

    if (wsource == NULL) {
        dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
            route->source);
        return -ENODEV;
    }
    if (wsink == NULL) {
        dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
            route->sink);
        return -ENODEV;
    }

skip_search:
    dapm_wcache_update(&dapm->path_sink_cache, wsink);
    dapm_wcache_update(&dapm->path_source_cache, wsource);

    ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control, //use to add a connection information:
        route->connected);
    if (ret)
        goto err;

    return 0;
err:
    dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
         source, route->control, sink);
    return ret;
}

list_for_each_entry 宏定义分析
/*
/**
 * list_for_each_entry    -    iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_head within the struct.
 */
#define list_for_each_entry(pos, head, member)                \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

*/

/**
 * list_first_entry - get the first element from a list
 * @ptr:    the list head to take the element from.
 * @type:    the type of the struct this is embedded in.
 * @member:    the name of the list_head within the struct.
 *
 * Note, that list is expected to be not empty.
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

list_for_each_entry(w, &dapm->card->widgets, list)


#define list_for_each_entry(w, &dapm->card->widgets, list)                \
    for (w = list_first_entry(&dapm->card->widgets, typeof(*w), list);    \
         &w->list != (&dapm->card->widgets);                    \
         w = list_next_entry(w, list))
         
 #define list_for_each_entry(w, &dapm->card->widgets, list)                \
    for (w = container_of((&dapm->card->widgets)->next, snd_soc_dapm_widget, list);    \
         &w->list != (&dapm->card->widgets);                    \
         w = list_next_entry(w, list))


*/
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:    the type of the container struct this is embedded in.
 * @member:    the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                \
    void *__mptr = (void *)(ptr);                    \
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&    \
             !__same_type(*(ptr), void),            \
             "pointer type mismatch in container_of()");    \
    ((type *)(__mptr - offsetof(type, member))); })

container_of((&dapm->card->widgets)->next, snd_soc_dapm_widget, list);

#define container_of(ptr, type, member) ({                \
    void *__mptr = (void *)(ptr);                    \
    BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&    \
             !__same_type(*(ptr), void),            \
             "pointer type mismatch in container_of()");    \
    ((type *)(__mptr - offsetof(type, member))); })

/**
 * BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied
 *              error message.
 * @condition: the condition which the compiler should know is false.
 *
 * See BUILD_BUG_ON for description.
 */
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)


The snd_soc_dapm_add_path function is the key in the entire call chain, let's analyze it:

static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
    const char *control,
    int (*connected)(struct snd_soc_dapm_widget *source,
             struct snd_soc_dapm_widget *sink))
{
    struct snd_soc_dapm_widget *widgets[2];
    enum snd_soc_dapm_direction dir;
    struct snd_soc_dapm_path *path;
    int ret;

    if (wsink->is_supply && !wsource->is_supply) {
        dev_err(dapm->dev,
            "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (connected && !wsource->is_supply) {
        dev_err(dapm->dev,
            "connected() callback only supported for supply widgets (%s -> %s)\n",
            wsource->name, wsink->name);
        return -EINVAL;
    }

    if (wsource->is_supply && control) {
        dev_err(dapm->dev,
            "Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
            wsource->name, control, wsink->name);
        return -EINVAL;
    }

    ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control);
    if (ret)
        return ret;

    path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
    if (!path)
        return -ENOMEM;

    path->node[SND_SOC_DAPM_DIR_IN] = wsource;
    path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
    widgets[SND_SOC_DAPM_DIR_IN] = wsource;
    widgets[SND_SOC_DAPM_DIR_OUT] = wsink;

    path->connected = connected;
    INIT_LIST_HEAD(&path->list);
    INIT_LIST_HEAD(&path->list_kcontrol);

    if (wsource->is_supply || wsink->is_supply)
        path->is_supply = 1;

    /* connect static paths */
    if (control == NULL) {
        path->connect = 1;
    } else {
        switch (wsource->id) {
        case snd_soc_dapm_demux:
            ret = dapm_connect_mux(dapm, path, control, wsource);
            if (ret)
                goto err;
            break;
        default:
            break;
        }

        switch (wsink->id) {
        case snd_soc_dapm_mux:
            ret = dapm_connect_mux(dapm, path, control, wsink);
            if (ret != 0)
                goto err;
            break;
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            ret = dapm_connect_mixer(dapm, path, control);
            if (ret != 0)
                goto err;
            break;
        default:
            break;
        }
    }

    list_add(&path->list, &dapm->card->paths);
    snd_soc_dapm_for_each_direction(dir)
        list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);

    snd_soc_dapm_for_each_direction(dir) {
        dapm_update_widget_flags(widgets[dir]);
        dapm_mark_dirty(widgets[dir], "Route added");
    }

    if (dapm->card->instantiated && path->connect)
        dapm_path_invalidate(path);

    return 0;
err:
    kfree(path);
    return ret;
}


At the beginning of the function, a snd_soc_path structure is allocated for this connection. The source and sink fields of the path point to the source widget and the destination widget respectively. The connected field saves the connected callback function and initializes several linked lists in several snd_soc_path structures.

    /* connect static paths */
    if (control == NULL) {
        path->connect = 1;
    } else {
        switch (wsource->id) {
        case snd_soc_dapm_demux:
            ret = dapm_connect_mux(dapm, path, control, wsource);
            if (ret)
                goto err;
            break;
        default:
            break;
        }

        switch (wsink->id) {
        case snd_soc_dapm_mux:
            ret = dapm_connect_mux(dapm, path, control, wsink);
            if (ret != 0)
                goto err;
            break;
        case snd_soc_dapm_switch:
        case snd_soc_dapm_mixer:
        case snd_soc_dapm_mixer_named_ctl:
            ret = dapm_connect_mixer(dapm, path, control);
            if (ret != 0)
                goto err;
            break;
        default:
            break;
        }
    }

Judging according to the purpose widget, if it belongs to the above types, just connect them together directly. If the purpose widget is a mixer and mux type, use the dapm_connect_mixer and dapm_connect_mux functions to complete the connection work. We will talk about these two functions later.

/* widget has no PM register bit */
#define SND_SOC_NOPM    -1

/* connect mux widget to its interconnecting audio paths */
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
    struct snd_soc_dapm_path *path, const char *control_name,
    struct snd_soc_dapm_widget *w)
{
    const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0];
    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
    unsigned int val, item;
    int i;

    if (e->reg != SND_SOC_NOPM) {
        soc_dapm_read(dapm, e->reg, &val);
        val = (val >> e->shift_l) & e->mask;
        item = snd_soc_enum_val_to_item(e, val);
    } else {
        /* since a virtual mux has no backing registers to
         * decide which path to connect, it will try to match
         * with the first enumeration.  This is to ensure
         * that the default mux choice (the first) will be
         * correctly powered up during initialization.
         */
        item = 0;
    }

    i = match_string(e->texts, e->items, control_name);
    if (i < 0)
        return -ENODEV;

    path->name = e->texts[i];
    path->connect = (i == item);
    return 0;

}

The sound card has created and initialized the required widgets, and the widgets are also connected together through paths. Next, dapm waits for user instructions. Once a dapm kcontrol is changed by user space, dapm will recreate the audio using these connections. Widgets that leave the audio path will be powered off, and widgets that join the audio path will be powered on. All power-on and power-off actions will be completed automatically. The user space application does not need to pay attention to these changes, it only needs to change a dapm kcontrol as needed That's it.

One of the main purposes of designing dapm is to distribute the power of various components on the sound card on demand, power on when needed, and power off when not needed, so that the entire audio system is always in the state of minimum power consumption, the most important The most important thing is that all this is transparent to the user space application, that is to say, the user space application does not need to care about when the component needs power, it only needs to set the audio path, play audio data, pause or stop , the dapm framework will perfectly control the power of various components according to the audio path, and precisely in a certain order to prevent unnecessary pop-pop sounds during power-on and power-off.

Count the number of paths connecting the widget to the endpoint widget.
The endpoint widget is located at the beginning or end of the audio path, so usually they refer to the widgets corresponding to the input and output pins of the codec, or the widgets corresponding to external devices. The types of these widgets are: the following

Codec input and output pins snd_soc_dapm_output
                snd_soc_dapm_input
external audio equipment snd_soc_dapm_hp
                snd_soc_dapm_spk
                snd_soc_dapm_line
                snd_soc_dapm_mic
audio stream (stream domain) snd_soc_dapm_adc
                snd_soc_dapm_dac
                snd _soc_dapm_aif_out
                snd_soc_dapm_aif_in
                snd_soc_dapm_dai_out
                snd_soc_dapm_dai_in
power supply, clock snd_soc_dapm_supply
                snd_soc_dapm_regulator_supply snd_soc_dapm_clock_supply
                shadow
widget snd_soc_dapm_kcontrol

One of the prerequisites for dapm to power on a widget is that the widget is on a complete audio path, and the two ends of a complete audio path must be input/output pins, or an external audio device, or It is an active audio stream widget, that is, the first three items in the above table, and the last two items in the above table. They can be located at the end of the path, but they are not a necessary condition for completing the audio path. We only use it to Determine the end condition of scanning a path. dapm provides two internal functions to count the number of effective paths a widget connects to output pins, input pins, and activated audio stream widgets

is_connected_output_ep Returns the number of paths of an output audio stream connected to an output pin or active state
is_connected_input_ep Returns the number of paths of an input audio stream connected to an input pin or active state

dapm_dirty list

In the snd_soc_card structure representing the sound card, there is a linked list field: dapm_dirty, for all widgets whose status has changed, dapm will not immediately process its power status, but needs to be hung under the linked list first, waiting for subsequent further processing: or Either power on or power off. dapm provides us with an api function to complete this action:

    void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)
    {
            if (!dapm_dirty_widget(w)) {
                    dev_vdbg(w->dapm->dev, "Marking %s dirty due to %s\n",
                             w->name, reason);
                    list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);
            }
    }

power_check callback function

In establishing the connection relationship between widgets, we know that when creating a widget, the power_check callback function of the widget will set different callback functions according to the type of the widget. When the status of the widget changes, dapm will traverse the dapm_dirty linked list, and use the power_check callback function to determine whether the widget needs to be powered on. The power_check callback of most widgets is set to: dapm_generic_check_power:


/* Generic check to see if a widget should be powered. */
static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
{
    int in, out;

    DAPM_UPDATE_STAT(w, power_checks);

    in = is_connected_input_ep(w, NULL, NULL);
    out = is_connected_output_ep(w, NULL, NULL);
    return out != 0 && in != 0;
}

Very simple, use is_connected_output_ep and is_connected_input_ep respectively to get whether the widget is connected to an input terminal and an output terminal at the same time, and if so, return 1 to indicate that the widget needs to be powered on.
For snd_soc_dapm_dai_out and snd_soc_dapm_dai_in types, the power_check callback is dapm_generic_check_power

Widget power-on and power-off sequence

When scanning the dapm_dirty linked list, dapm uses two linked lists to save the widgets that need to be powered on and need to be powered off:

    up_list saves the widgets that need to be powered on
    down_list saves the widgets that need to be powered off

dapm internally uses the dapm_seq_insert function to add a widget to one of the above two linked lists:

/* Insert a widget in order into a DAPM power sequence. */
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
                struct list_head *list,
                bool power_up)
{
    struct snd_soc_dapm_widget *w;

    list_for_each_entry(w, list, power_list)
        if (dapm_seq_compare(new_widget, w, power_up) < 0) {
            list_add_tail(&new_widget->power_list, &w->power_list);
            return;
        }

    list_add_tail(&new_widget->power_list, list);
}

The above function will add widgets to the linked list in a certain order, so as to ensure the correct power-on and power-off sequence:

        上电顺序           
static int dapm_up_seq[] = {
        [snd_soc_dapm_pre] = 0,
        [snd_soc_dapm_supply] = 1,
        [snd_soc_dapm_regulator_supply] = 1,
        [snd_soc_dapm_clock_supply] = 1,
        [snd_soc_dapm_micbias] = 2,
        [snd_soc_dapm_dai_link] = 2,
        [snd_soc_dapm_dai_in] = 3,
        [snd_soc_dapm_dai_out] = 3,
        [snd_soc_dapm_aif_in] = 3,
        [snd_soc_dapm_aif_out] = 3,
        [snd_soc_dapm_mic] = 4,
        [snd_soc_dapm_mux] = 5,
        [snd_soc_dapm_virt_mux] = 5,
        [snd_soc_dapm_value_mux] = 5,
        [snd_soc_dapm_dac] = 6,
        [snd_soc_dapm_switch] = 7,
        [snd_soc_dapm_mixer] = 7,
        [snd_soc_dapm_mixer_named_ctl] = 7,
        [snd_soc_dapm_pga] = 8,
        [snd_soc_dapm_adc] = 9,
        [snd_soc_dapm_out_drv] = 10,
        [snd_soc_dapm_hp] = 10,
        [snd_soc_dapm_spk] = 10,
        [snd_soc_dapm_line] = 10,
        [snd_soc_dapm_kcontrol] = 11,
        [snd_soc_dapm_post] = 12,
};    
 下电顺序
static int dapm_down_seq[] = {
        [snd_soc_dapm_pre] = 0,
        [snd_soc_dapm_kcontrol] = 1,
        [snd_soc_dapm_adc] = 2,
        [snd_soc_dapm_hp] = 3,
        [snd_soc_dapm_spk] = 3,
        [snd_soc_dapm_line] = 3,
        [snd_soc_dapm_out_drv] = 3,
        [snd_soc_dapm_pga] = 4,
        [snd_soc_dapm_switch] = 5,
        [snd_soc_dapm_mixer_named_ctl] = 5,
        [snd_soc_dapm_mixer] = 5,
        [snd_soc_dapm_dac] = 6,
        [snd_soc_dapm_mic] = 7,
        [snd_soc_dapm_micbias] = 8,
        [snd_soc_dapm_mux] = 9,
        [snd_soc_dapm_virt_mux] = 9,
        [snd_soc_dapm_value_mux] = 9,
        [snd_soc_dapm_aif_in] = 10,
        [snd_soc_dapm_aif_out] = 10,
        [snd_soc_dapm_dai_in] = 10,
        [snd_soc_dapm_dai_out] = 10,
        [snd_soc_dapm_dai_link] = 11,
        [snd_soc_dapm_clock_supply] = 12,
        [snd_soc_dapm_regulator_supply] = 12,
        [snd_soc_dapm_supply] = 12,
        [snd_soc_dapm_post] = 13,
};


Widget power on and off process
dapm_power_widgets

When the state of a widget changes, the widget will be added to the dapm_dirty list, and then the power state on the entire audio path will be changed through the dapm_power_widgets function.

/*
 * Scan each dapm widget for complete audio path.
 * A complete path is a route that has valid endpoints i.e.:-
 *
 *  o DAC to output pin.
 *  o Input pin to ADC.
 *  o Input pin to Output pin (bypass, sidetone)
 *  o DAC to ADC (loopback).
 */
static int dapm_power_widgets(struct snd_soc_card *card, int event)
{
    struct snd_soc_dapm_widget *w;
    struct snd_soc_dapm_context *d;
    LIST_HEAD(up_list);
    LIST_HEAD(down_list);
    ASYNC_DOMAIN_EXCLUSIVE(async_domain);
    enum snd_soc_bias_level bias;

    lockdep_assert_held(&card->dapm_mutex);

    trace_snd_soc_dapm_start(card);
    mutex_lock(&card->dapm_power_mutex);

    list_for_each_entry(d, &card->dapm_list, list) {
        if (dapm_idle_bias_off(d))
            d->target_bias_level = SND_SOC_BIAS_OFF;
        else
            d->target_bias_level = SND_SOC_BIAS_STANDBY;
    }

    dapm_reset(card);

    /* Check which widgets we need to power and store them in
     * lists indicating if they should be powered up or down.  We
     * only check widgets that have been flagged as dirty but note
     * that new widgets may be added to the dirty list while we
     * iterate.
     */
    list_for_each_entry(w, &card->dapm_dirty, dirty) {
        dapm_power_one_widget(w, &up_list, &down_list);
    }

    list_for_each_entry(w, &card->widgets, list) {
        switch (w->id) {
        case snd_soc_dapm_pre:
        case snd_soc_dapm_post:
            /* These widgets always need to be powered */
            break;
        default:
            list_del_init(&w->dirty);
            break;
        }

        if (w->new_power) {
            d = w->dapm;

            /* Supplies and micbiases only bring the
             * context up to STANDBY as unless something
             * else is active and passing audio they
             * generally don't require full power.  Signal
             * generators are virtual pins and have no
             * power impact themselves.
             */
            switch (w->id) {
            case snd_soc_dapm_siggen:
            case snd_soc_dapm_vmid:
                break;
            case snd_soc_dapm_supply:
            case snd_soc_dapm_regulator_supply:
            case snd_soc_dapm_pinctrl:
            case snd_soc_dapm_clock_supply:
            case snd_soc_dapm_micbias:
                if (d->target_bias_level < SND_SOC_BIAS_STANDBY)
                    d->target_bias_level = SND_SOC_BIAS_STANDBY;
                break;
            default:
                d->target_bias_level = SND_SOC_BIAS_ON;
                break;
            }
        }

    }

    /* Force all contexts in the card to the same bias state if
     * they're not ground referenced.
     */
    bias = SND_SOC_BIAS_OFF;
    list_for_each_entry(d, &card->dapm_list, list)
        if (d->target_bias_level > bias)
            bias = d->target_bias_level;
    list_for_each_entry(d, &card->dapm_list, list)
        if (!dapm_idle_bias_off(d))
            d->target_bias_level = bias;

    trace_snd_soc_dapm_walk_done(card);

    /* Run card bias changes at first */
    dapm_pre_sequence_async(&card->dapm, 0);
    /* Run other bias changes in parallel */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d != &card->dapm)
            async_schedule_domain(dapm_pre_sequence_async, d,
                        &async_domain);
    }
    async_synchronize_full_domain(&async_domain);

    list_for_each_entry(w, &down_list, power_list) {
        dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMD);
    }

    list_for_each_entry(w, &up_list, power_list) {
        dapm_seq_check_event(card, w, SND_SOC_DAPM_WILL_PMU);
    }

    /* Power down widgets first; try to avoid amplifying pops. */
    dapm_seq_run(card, &down_list, event, false);

    dapm_widget_update(card);

    /* Now power up. */
    dapm_seq_run(card, &up_list, event, true);

    /* Run all the bias changes in parallel */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d != &card->dapm)
            async_schedule_domain(dapm_post_sequence_async, d,
                        &async_domain);
    }
    async_synchronize_full_domain(&async_domain);
    /* Run card bias changes at last */
    dapm_post_sequence_async(&card->dapm, 0);

    /* do we need to notify any clients that DAPM event is complete */
    list_for_each_entry(d, &card->dapm_list, list) {
        if (d->stream_event)
            d->stream_event(d, event);
    }

    pop_dbg(card->dev, card->pop_time,
        "DAPM sequencing finished, waiting %dms\n", card->pop_time);
    pop_wait(card->pop_time);
    mutex_unlock(&card->dapm_power_mutex);

    trace_snd_soc_dapm_done(card);

    return 0;
}

It can be seen that the function traverses the dapm_dirty linked list and calls dapm_power_one_widget for each widget in the linked list. The dapm_power_one_widget function not only handles its own state changes, but also transfers its own changes to the neighbor widgets connected to it. The powered widgets will be placed in the up_list linked list, and all widgets that need to be powered off will be placed in the down_list linked list. We will discuss this function later.
Traverse the down_list linked list, send SND_SOC_DAPM_WILL_PMD event to the widgets in it, and the event callback of the widget interested in this event will be called.
Traverse the up_list linked list, send SND_SOC_DAPM_WILL_PMU event to the widgets in it, and the event callback of the widget interested in this event will be called.
Through the dapm_seq_run function, the widgets in the down_list are processed, so that they are powered off in sequence according to the defined order.
Through the dapm_widget_update function, switch the register value in the kcontrol of the widget that triggers the state change, and the corresponding result is: change the audio path.
Through the dapm_seq_run function, the widgets in the up_list are processed, so that they are powered on in sequence according to the defined order.
Emit state change callbacks for each dapm context.
Appropriate delay to prevent pop-pop sound.

dapm_power_one_widget
static void dapm_power_one_widget(struct snd_soc_dapm_widget *w,
                  struct list_head *up_list,
                  struct list_head *down_list)
{
    int power;

    switch (w->id) {
    case snd_soc_dapm_pre:
        dapm_seq_insert(w, down_list, false);
        break;
    case snd_soc_dapm_post:
        dapm_seq_insert(w, up_list, true);
        break;

    default:
        power = dapm_widget_power_check(w);

        dapm_widget_set_power(w, power, up_list, down_list);
        break;
    }
}


The first step of dapm_power_widgets is to traverse the dapm_dirty linked list, call dapm_power_one_widget for each widget in the linked list, and add the widgets that need to be powered on and need to be powered off to the up_list and down_list linked lists. At the same time, he will also add the affected neighbors The widget is added to the end of the dapm_dirty linked list again. Through this action, all affected widgets in the sound card will be "infected", added to the dapm_dirty linked list in turn, and then the dapm_power_one_widget function is executed in turn.

Through dapm_widget_power_check, call the widget's power_check callback function to obtain the new power state of the widget.
Call dapm_widget_set_power to "infect" the neighbor widgets connected to it.
Traverse the source widgets, and add the source widgets in the connected state to the dapm_dirty linked list through the dapm_widget_set_peer_power function.
Traverse the sink widgets, and add the connected sink widgets to the dapm_dirty linked list through the dapm_widget_set_peer_power function.
Add the widget to the up_list or down_list linked list according to the new power state obtained in the first step.

It can be seen that through this function, when the state of a widget changes, the neighbor widget will be "infected" and added to the end of the dapm_dirty linked list, so when the end of the linked list is scanned, the neighbor widget will also perform the same operation, thereby "infecting" the neighbor Neighbors until no new widget is added to the dapm_dirty linked list, at this time, all affected widgets are added to the up_list or down_li linked list, waiting for subsequent power-on and power-off operations.


dapm_seq_run

static void dapm_seq_run(struct snd_soc_card *card,
    struct list_head *list, int event, bool power_up)
{
    struct snd_soc_dapm_widget *w, *n;
    struct snd_soc_dapm_context *d;
    LIST_HEAD(pending);
    int cur_sort = -1;
    int cur_subseq = -1;
    int cur_reg = SND_SOC_NOPM;
    struct snd_soc_dapm_context *cur_dapm = NULL;
    int ret, i;
    int *sort;

    if (power_up)
        sort = dapm_up_seq;
    else
        sort = dapm_down_seq;

    list_for_each_entry_safe(w, n, list, power_list) {
        ret = 0;

        /* Do we need to apply any queued changes? */
        if (sort[w->id] != cur_sort || w->reg != cur_reg ||
            w->dapm != cur_dapm || w->subseq != cur_subseq) {
            if (cur_dapm && !list_empty(&pending))
                dapm_seq_run_coalesced(card, &pending);

            if (cur_dapm && cur_dapm->seq_notifier) {
                for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
                    if (sort[i] == cur_sort)
                        cur_dapm->seq_notifier(cur_dapm,
                                       i,
                                       cur_subseq);
            }

            if (cur_dapm && w->dapm != cur_dapm)
                soc_dapm_async_complete(cur_dapm);

            INIT_LIST_HEAD(&pending);
            cur_sort = -1;
            cur_subseq = INT_MIN;
            cur_reg = SND_SOC_NOPM;
            cur_dapm = NULL;
        }

        switch (w->id) {
        case snd_soc_dapm_pre:
            if (!w->event)
                list_for_each_entry_safe_continue(w, n, list,
                                  power_list);

            if (event == SND_SOC_DAPM_STREAM_START)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_PRE_PMU);
            else if (event == SND_SOC_DAPM_STREAM_STOP)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_PRE_PMD);
            break;

        case snd_soc_dapm_post:
            if (!w->event)
                list_for_each_entry_safe_continue(w, n, list,
                                  power_list);

            if (event == SND_SOC_DAPM_STREAM_START)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_POST_PMU);
            else if (event == SND_SOC_DAPM_STREAM_STOP)
                ret = w->event(w,
                           NULL, SND_SOC_DAPM_POST_PMD);
            break;

        default:
            /* Queue it up for application */
            cur_sort = sort[w->id];
            cur_subseq = w->subseq;
            cur_reg = w->reg;
            cur_dapm = w->dapm;
            list_move(&w->power_list, &pending);
            break;
        }

        if (ret < 0)
            dev_err(w->dapm->dev,
                "ASoC: Failed to apply widget power: %d\n", ret);
    }

    if (cur_dapm && !list_empty(&pending))
        dapm_seq_run_coalesced(card, &pending);

    if (cur_dapm && cur_dapm->seq_notifier) {
        for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
            if (sort[i] == cur_sort)
                cur_dapm->seq_notifier(cur_dapm,
                               i, cur_subseq);
    }

    list_for_each_entry(d, &card->dapm_list, list) {
        soc_dapm_async_complete(d);
    }
}

When all widgets that need to be powered on or off are added to the dapm_dirty linked list, then the widgets on the down_list linked list will be processed through dapm_seq_run, and the widgets on the linked list will be powered off in order, and then the kcontrol in the widget will be updated through dapm_widget_update (this kcontrol is usually the trigger source that triggers this state change), and then the widgets on the up_list linked list are processed through apm_seq_run, and the widgets on the linked list are powered on in order. The final power-on or power-off operation needs to be implemented through codec registers, because when defining a widget, if it is a widget with power control, we must provide the setting values ​​​​of fields such as reg/shift, if the widget does not need registers to control power state, the reg field must be assigned the value:

    SND_SOC_NOPM (the actual value defined by this macro is -1)

In terms of specific implementation, the dapm framework uses a trick: if several widgets in the same power-on and power-on sequence use the same register address (a register may use different bits to control the power status of different widgets), dapm_seq_run passes the dapm_seq_run_coalesced function Merge the changes of these widgets, and then only need to write the merged value into the register once.

Guess you like

Origin blog.csdn.net/qq_48709036/article/details/124293771