オーディオ Asoc ドライバー アーキテクチャの詳細な説明

        説明: 記事は少し面倒かもしれません。通常のデバッグ記録ですが、より詳細です。

        Linux カーネル バージョン: 4.19.172

   ハードウェア ドライバー: オーディオ ハードウェア デバイス ドライバー。マシン、プラットフォーム、コーデックの 3 つの部分で構成されます。

ASoC オーディオ ドライバーは、プラットフォーム、コーデック、マシンの 3 つの部分で構成されます。

マシンは、プラットフォームとコーデック間の i2s 接続によって制御されます。

プラットフォーム(cpu_dai) <----->codec_dai

Platform:Platform驱动程序包括音频DMA引擎驱动程序,数字音频接口(DAI)驱动程序(例如I2S,AC97,PCM)以及该平台的任何音频DSP驱动程序。
cpu dai:在嵌入式系统里面通常指CPU的I2S、PCM总线控制器,负责将音频数据从I2S tx FIFO搬运到CODEC(回放的情形,录制则方向相反)。cpu_dai通过snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的缩写,分为cpu_dai和codec_dai,这两者通过I2S/PCM/pdm总线连接;
       AIF是Audio Interface的缩写,一般分为I2S和PCM接口。
2  pcm dma:负责将dmabuffer中的音频数据搬运到I2S tx FIFO,这部分的逻辑比较复杂,以下几篇会对它详细阐述。音频dma驱动通过snd_soc_register_platform()来注册。值得留意的是:某些情形下是不需要dma操作的,比如Modem和CODEC直连,因为Modem本身已经把数据送到PCM FIFO了,这时只需启动codec_dai接收数据即可;该情形下,Machine驱动dai_link中需要指定.platform_name = “snd-soc-dummy”, 这是虚拟出来的platform驱动,实现见sound/soc/soc-utils.c。

Codec:Codec驱动程序独立于平台,包含音频控件,音频接口功能,编解码器DAPM定义和编解码器IO功能。如果需要,该类可扩展至BT,FM和MODEM IC。Codec类驱动程序应该是可以在任何体系结构和机器上运行的通用代码。
对于Playback来说,userspace送过来的PCM数据是经过抽样量化出来的数字信号,在codec经过DAC转换成模拟信号送到外放耳机输出,这样我们就可以听到声音了。Codec字面意思是编解码器,但芯片里面的功能部件很多,常见的有AIF、DAC、ADC、Mixer、PGA、Line-in、Line-out,有些高端的codec芯片还有EQ、DSP、SRC、DRC、AGC、Echo canceller、Noise suppression等部件。


Machine:Machine 驱动程序充当描述和绑定其他组件驱动程序以形成ALSA“声卡设备”的粘合剂。它可以处理任何机器特定的控制和机器级音频事件(例如,在播放开始时打开放大器)。
Machine 可以理解为对开发板的抽象,开发板可能包括多个声卡,对应Machine部分包含多个link。
Machine 指某一款机器,它把cpu_dai、codec_dai、modem_dai各个音频接口通过定义dai_link链结起来,然后注册snd_soc_card。和上面两个不一样,Platform和CODEC驱动一般是可以重用的,而Machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:DAIs之间的链结;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External OSC作为I2S、CODEC模块的基准时钟源等等。
dai_link:machine驱动中定义的音频数据链路,它指定用到的cpu_dai、codec_dai、platform、codec分别是什么。
PCM数据流
对于回放(Playback)的情形,PCM数据流向大致如下:

ユーザー空間---[copy_from_user()]--->DMA バッファ(カーネル空間)---DMA-->i2s TX FIFO--i2s-->CODEC--DAC->PGA/Mixer-->SPK/
Linux の場合、HP/Earpはユーザー空間とカーネル空間に分かれており、両者が気軽にアクセスすることはできません。したがって、ユーザーがオーディオを再生する場合、copy_from_user() を呼び出して、ユーザー データをユーザー空間からカーネル空間 (DMA バッファー) にコピーする必要があります。
DMA は、DMA バッファ内のオーディオ データを I2S TX FIFO に移動する役割を果たします。
I2S バスを介して音声データをコーデックに送信します。
Codec は DAC により内部変換され、アナログ信号はスピーカー SPK (ヘッドフォン HP、イヤフォン Earp) に送信されます.
録音 (キャプチャ) の場合、PCM データの流れの方向は次のとおりです:
ユーザー スペース<-- -[copy_from_user() ]--DMA バッファ(カーネル空間)<---DMA--i2s RX FIFO<--i2s--CODEC<--ADC--MIC

プラットフォーム ドライバー: プラットフォーム ドライバーは、snd_soc_dai_driver と snd_soc_platform_driver の 2 つの構造を抽象化する cpu 部分の制御コードです。

コーデック ドライバー: コーデック ドライバーは、snd_soc_dai_driver と snd_soc_codec_driver という 2 つの構造を抽象化するコーデック部分の制御コードです。

マシン ドライバー: マシン ドライバーは、プラットフォームとコーデック間の接続の一致を制御および管理し、コントロール、ウィジェット、およびルートを管理し、その抽象構造は snd_soc_card です。

 ASoC オーディオ ドライバーの登録プロセス:
 PCM DMA の登録 snd_register_platform() の呼び出し、snd_soc_platform_driver の登録、pcm_new() の構成、snd_pcm_ops. |
      CPU_dai
 の登録 snd_soc_register_component() の呼び出し、snd_soc_dai_driver の構造体の登録
      |
 codec と codec_dai の登録 snd_soc _register_codec() の登録、構造体 snd_s の登録oc_dai_driver and struct snd_soc_codec_driver
      |
  サウンドカード登録 snd_soc_register_card()を呼び出し、struct snd_soc_card を登録 dai_link 配下の cpu_dai\pcm dma\codec\codec_dai を初期化し、pcm 論理デバイスを作成します。          


プラットフォーム ドライバーには、オーディオ DMA エンジン ドライバー (PCM DMA)、デジタル オーディオ インターフェイス (CPU DAI) ドライバー (I2S、AC97、PCM など)、およびプラットフォーム用のオーディオ DSP ドライバーが含まれます。一般的に使用されるのは、CPU DAI および PCM DMA ドライバーです。

CPU DAI: 組み込みシステムでは、通常、CPU の I2S および PCM バス コントローラーを指します。再生の場合、オーディオ データを I2S TX FIFO から CODEC に移動します (キャプチャは反対方向です)。cpu_dai は snd_soc_register_dai() によって登録されます。

PCM DMA: 再生の場合、DMA バッファ内のオーディオ データを I2S TX FIFO に移動します (キャプチャは反対方向です)。オーディオ dma ドライバーは、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 ドライバーのコールバック */
        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);
        /* dai を圧縮 */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* pcm 作成時に使用されるオプションのコールバック*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);
        /* DAI は制御バスにも使用されます */
        bool bus_control;

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

        /* DAI 機能 */
        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_sample bits: 1;

        /* プローブの順序付け - ランタイム依存関係を持つコンポーネント用 */
        int probe_order;
        int remove_order;
};


name:cpu_dai的名称标识,machine中的dai_link通过cpu_dai_name来匹配cpu_dai;
probe:cpu_dai的probe函数,由snd_soc_instantiate_card()回调;
playback:回放数据流性能描述信息,如所支持的声道数、采样率、音频格式;
capture:录制数据流性能描述信息,如所支持声道数、采样率、音频格式;
ops:指向cpu_dai的操作函数集,这些函数集非常重要,它定义了DAI的时钟配置、格式配置、数字静音、PCM音频接口、FIFO延迟报告等回调。
snd_soc_register_dai()
严格来说,注册cpu_dai是通过snd_soc_register_component()->snd_soc_register_dai()。
当注册多个cpu_dai时,通过snd_soc_register_component()->snd_soc_register_dais()
PCM DMA
2.2.1. 数据结构struct snd_soc_platform_driver
ASoC音频驱动中,用struct snd_soc_platform_driver结构体来描述 PCM DMA。

控件(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();

snd_pcm_ops

open:打开pcm逻辑设备时,会回调该函数,用于为runtime设定硬件约束,为runtime的private_data申请一个私有结构,保存dma资源如通道号、传输单元、缓冲区信息、IO设备信息等。
close:close函数,和open操作相反;
ioctl:这个可以不用自己写,使用内核的snd_pcm_lib_ioctl()函数;
hw_params:设置pcm硬件参数时(cmd:SNDRV_PCM_IOCTL_HW_PARAMS),会回调该函数,一般用于初始化dma资源,包括通道号、传输单元、缓冲区信息、IO设备信息等。
prepare:当数据已准备好时(cmd:SNDRV_PCM_IOCTL_PREPARE),会回调该函数告知dma数据已就绪。
trigger:pcm数据传送开始、停止、暂停、恢复时,会回调该函数启动或停止dma传输(补充:当上层第一次调用pcm_write()时,触发trigger启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger停止dma传输)。trigger函数里面的操作必须是原子的,不能有可能引起睡眠的操作,并且应尽量简单。
pointer:该回调函数返回传输数据的当前位置。当dma每完成一次传输后,都会调用该函数获得传输数据的当前位置,这样pcm native可根据它来计算dma buffer指针位置及可用空间。该函数也是原子的。


注册PCM DMA:snd_soc_register_platform()
音频dma驱动通过snd_soc_register_platform()来注册。(和snd_soc_register_dai()函数非常相似)

 DMA Buffer Allocation
のセクション 2.2.1 では、dma バッファーについて何度か言及されています。つまり、dma データ バッファーは、上位レイヤーからコピーされ、マイクによって録音されたオーディオ データを保存するために使用されます。
struct snd_dma_buffer 構造体を使用して、dma バッファーを記述します。

struct snd_dma_buffer {     struct snd_dma_device dev; /* デバイス タイプ */     unsigned char *area; /* 仮想ポインター */     dma_addr_t addr; /* 物理アドレス */     size_t バイト; /* バッファー サイズ (バイト単位) */     void *private_data; /* private for allocator; don't touch */ };通常、dma バッファの割り当ては、pcm_dma ドライバ (struct snd_soc_platform_driver) 初期化フェーズ (プローブ) または pcm 論理デバイス作成フェーズ (pcm_new) で発生します。probe() および pcm_new() コールバック関数







对于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设备设计,使得音频系统任何时候都工作在最低功耗状态。

コントロール、ウィジェット、およびルート:
コントロールはコントロールであり、その抽象構造は struct snd_kcontrol_new です。これは、サウンド カードのサウンドの再生など、サウンド カードの特定の機能を制御する単一のコントロールにすることも、2 つのコンポーネントを接続するために使用することもできます。 (widgets)でルート(route)を作る、コントロールの登録関数snd_soc_add_codec_controls();
widgetsはコンポーネントで、その抽象構造はstruct snd_soc_dapm_widget.コンポーネントとはサウンドカード内のノードを抽象化したものだと個人的には感じており、これは特定のノードの抽象化でもあります. いくつかのコントロールは 1 つのレイヤーにパッケージ化されています. たとえば, 入力ピンの録音, 出力ピン, マルチチャンネルサウンドのミキシングを中央で制御するミキサー. ウィジェットレジスタ関数 snd_soc_dapm_new_controls; ルートはルーティングを表します.であり、その抽象構造は struct snd_soc_dapm_route です
2 つのウィジェットがコントロールを介して接続されてルートを形成し (コントロールは空にできるようです)、ルート登録関数 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 ドライバーのコールバック */
        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);
        /* dai を圧縮 */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* pcm 作成時に使用されるオプションのコールバック*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);
        /* DAI は制御バスにも使用されます */
        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:codec_dai的名称标识,machine中的dai_link通过codec_dai_name来匹配codec_dai;
probe:codec_dai的probe函数,由snd_soc_instantiate_card()回调;
playback:回放数据流性能描述信息,如所支持的声道数、采样率、音频格式;
capture:录制数据流性能描述信息,如所支持声道数、采样率、音频格式;
ops:指向codec_dai的操作函数集,这些函数集非常重要,它定义了DAI的时钟配置、格式配置、数字静音、PCM音频接口、FIFO延迟报告等回调。struct snd_soc_dai_ops 结构体注释非常详细,这里不再赘述。

struct snd_soc_dai_ops {         /*          * DAI クロッキング構成、すべてオプション。          * 通常は hw_params で、soc_card ドライバーによって呼び出されます。          */         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 オーディオ操作 - すべてオプション。
         * オーディオ PCM 操作中に soc-core によって呼び出されます。
         */
        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 ストリーム情報 */
struct snd_soc_pcm_stream {         const char *stream_name;         u64 フォーマット; /* SNDRV_PCM_FMTBIT_* */         unsigned int レート; /* SNDRV_PCM_RATE_* */         unsigned int rate_min; /* 最小レート */         unsigned int rate_max; /* 最大レート */         unsigned int channels_min; /* 最小チャネル数 */         unsigned int channels_max; /* 最大チャネル数 */         unsigned int sig_bits; /* コンテンツのビット数 */         const char *aif_name; /* DAPM AIF ウィジェット名 */ };










/* 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);
};

マシンの
Linux オーディオ ドライバー (2) ASoC オーディオ ドライバーのプラットフォーム ドライバーと Linux オーディオ ドライバー (3) ASoC オーディオ ドライバーのコーデック ドライバーには、それぞれプラットフォーム ドライバーとコーデック ドライバーが導入されていますが、プラットフォーム ドライバーとコーデック ドライバーだけでは機能せず、その役割はcodec、codec_dai、cpu_dai、およびプラットフォームをリンクして完全なオーディオ ループを形成します。この役割はマシンが引き受けます。
マシンは開発ボードの抽象化として理解できます. 開発ボードには複数のサウンドカードが含まれている場合があり, 対応するマシン部分には複数のリンクが含まれています.
マシン ドライバー コントロールは、プラットフォームとコーデック間の接続の一致を管理し、その抽象構造は struct snd_soc_dai_link です。
struct snd_soc_dai_link {         /* config - マシン ドライバーで設定する必要があります */         const char *name; /* コーデック名 */         const char *stream_name; /* ストリーム名 */         /*          * リンクの CPU 側デバイスを指定してもよい (MAY)。デバイス名、          * または DT/OF ノードのいずれか (両方ではない) この情報が省略された場合、






         * 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:音频链路需要绑定的codec名称标识,soc-core中会遍历codec_list,找到同名的codec并绑定;
platform_name:音频链路需要绑定的platform名称标识,soc-core中会遍历platform_list,找到同名的platform并绑定;
cpu_dai_name:音频链路需要绑定的cpu_dai名称标识,soc-core中会遍历dai_list,找到同名的dai并绑定;
codec_dai_name:音频链路需要绑定的codec_dai名称标识,soc-core中会遍历dai_list,找到同名的dai并绑定;
ops:machine数据流操作函数集。重点留意hw_params回调,一般来说这个回调是要实现的,用于配置codec、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:声卡控件指针;


 注册声卡 snd_soc_register_card()
在我们学习音频驱动时,多次碰到了platform。但是,每次提platform时都要结合上下文,才能确定其真正指代什么。要注意区分:
1. 当我们谈ASoC驱动包含platform driver、codec driver、machine driver时,此时说的platform特指某款SoC平台,如exynos、omap、qcom、mtk等等;
2. 当我们谈machine driver时,即snd_soc_dai_link时,此时说的platform特指PCM DMA,即snd_soc_platform_driver;
3. 当我们谈注册声卡时,声卡被抽象为挂在platform bus上的platform device。因此,才会有创建名字为 “soc-audio” 的标准Linux平台设备(platform_device),注册标准的Linux平台驱动(platform_driver),此时说的platform特指虚拟的platform bus。

PCM
ASoC音频驱动中Codec、Platform、Machine驱动的组成部分及其注册过程,这三者都是物理设备相关的,大家应该对音频物理链路有了一定的认知。接着分析音频驱动的中间层,由于这些并不是真正的物理设备,故我们称之为逻辑设备。

PCM中間層またはpcmネイティブと呼ばれるPCM論理デバイスは、接続する役割を果たします。1.上向きは、
ユーザーモードインターフェイスとの相互作用であり、ユーザーモードとカーネル間のオーディオデータのコピーを実現しますモード、すなわちユーザー空間 <-> カーネル空間;
2. 次のステップは、コーデック、プラットフォーム、およびマシンの操作関数をトリガーして、dma_buffer<->cpu_dai<-> コーデック間のオーディオ データの伝送を実現することです。

PCM 論理デバイスの作成 snd_soc_instantiate_card この関数は以下で実行されます。ここに論理図がありますが、自由に理解できます

snd_register_device_for_dev  

pcmCxDxp を作成するための snd_register_device_for_dev() のプロセス (現在、カーネルはこの関数を使用して sound_insert_unit を作成しません) pcmCxDxc デバイス ノードは次のとおりです。 static int sound_insert_unit(struct sound_unit **list,
const struct file_operations *fops, int index, int low, int top, const 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;
}

首先,分配并初始化一个snd_minor结构中的各字段;
type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
card:card的编号
device:pcm实例的编号,大多数情况为0
f_ops:pcmCxDxp、pcmCxDxc设备节点的文件操作函数集,为snd_pcm_f_ops[*]
private_data:指向该snd_pcm的实例对象
根据type,card和pcm的编号,确定数组的索引值minor,minor也作为pcm设备的次设备号;
把该snd_minor结构的地址放入全局数组snd_minors[minor]中;
最后,调用device_create创建设备节点。

注1:C0D0代表的是Card 0 Device 0,即声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。
注2:snd_minors[]非常重要,用于保存声卡下某个逻辑设备的上下文信息,它在逻辑设备建立阶段被填充,在逻辑设备被使用时就可以从该结构体中得到相应的信息。
注3:sound_class 是sysfs文件系统中的声卡类,在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 デバイス                                                                                                                                  
  1: : シーケンサー
  2: [ 0- 0]: デジタル オーディオ再生
  3: [ 0- 0]: デジタル オーディオ キャプチャ
  4: [ 0] : コントロール
  5: [ 1- 0]:デジタル オーディオ キャプチャ
  6: [ 1] : コントロール
 33: : タイマー

これらのデバイス ノードの Major=116 と Minor は、システムの論理デバイス情報ファイル /proc/asound/devices にリストされているものに対応し、これらはすべてキャラクター デバイスであることがわかります。上位層は、他のキャラクター デバイスと同様に、open/close/read/write/ioctl などのシステム コールを通じてサウンド カード デバイスを操作できます。ただし、一般的には、tinyalsa や alsa-lib などのパッケージ化されたユーザー インターフェイス ライブラリを使用します。

以下、tinyslsaをもとに詳しく紹介します。Tinyalsa は、pcm_open()、pcm_start()、pcm_prepare()、pcm_read()、pcm_write() などの pcm 論理デバイス ユーザー インターフェイスの完全なセットを提供します。


 PCM 論理デバイス ファイル操作関数セット: snd_pcm_f_ops[]
PCM 論理デバイス ファイル操作関数セットは、Playback と Capture で個別に定義され、操作関数セットは次のとおりです。

const struct file_operations snd_pcm_f_ops[2] = {         {                 .owner = THIS_MODULE,                 .write = snd_pcm_write,//シングルチャンネルオーディオ信号書き込み用.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,//マルチチャンネルオーディオ信号書き込み用.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,//用于单通道音频信号读
                .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,//用于多通道音频信号读
                .compat_ioctl =         snd_pcm_ioctl_compat,
                .mmap =                 snd_pcm_mmap,
                .fasync =               snd_pcm_fasync,
                .get_unmapped_area =    snd_pcm_get_unmapped_area,
        }
};

样本长度(sample):样本是记录音频数据最基本的单位,常见的有8位和16位。

通道数(channel):该参数为1表示单声道,2则是立体声。

桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。
采样率(rate):每秒钟采样次数,该次数是针对桢而言。
周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。

交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。

period(周期):硬件中中断间的间隔时间。它表示输入延时。

サウンド カード インターフェイスには、サウンド カード ハードウェア バッファ内の現在の読み取りおよび書き込み位置を示すポインタがあります。インターフェイスが実行されている限り、このポインターはバッファー内の場所を再帰的に指します。
フレーム サイズ = sizeof(1 サンプル) * nChannels
alsa で構成されたバッファーとサイクル サイズは、ランタイムにフレームの形式で格納されます。
period_bytes = frames_to_bytes(runtime, runtime-> period_size);
bytes_to_frames()

サウンドキャッシングとデータ転送

每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断。内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。类似地,对于回放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。
这样硬件缓存区是环缓存。也就是说当数据到达缓存区末尾时将重新回到缓存区的起始位置。ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置。从内核外部看,我们只对应用程序的缓存区感兴趣,所以本文只讨论应用程序缓存区。
应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments).ALSA以period为单元来传送数据。
一个周期(period)存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。分解过程:一个缓存区分解成周期,然后是帧,然后是样本。左右信道信息被交替地存储在一个帧内。这称为交错 (interleaved)模式。在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。

Over and Under Run
当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间传输。但是也有例外。在录音例子中,如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖。这种数据的丢失被称为over run.在回放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。这样的错误被称为"under run"。在ALSA文档中,有时将这两种情形统称为"XRUN"。适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。
snd_pcm_read() //      interleaved 是否是交错模式取决与该参数
    |--->snd_pcm_lib_read(substream, buf, count);
         |--->__snd_pcm_lib_xfer(substream, (void __force *)buf, \ //(交错模式)  runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED  //判断存储模式
         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(サブストリーム);
             |--->snd_pcm_action(&snd_pcm_action_start, サブストリーム,
                  SNDRV_PCM_STATE_RUNNING);
                  |--->snd_pcm_stream_linked(サブストリーム)
                  |--->snd_pcm_action_group(ops, サブストリーム, 状態, 1);/ /この関数はリンクストリーム処理の中核
                      |--->ops->pre_action(s, state); = snd_pcm_pre_start(struct snd_pcm_substream *substream, int state) // コールバック関数
                      |--->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)中赋值
                                     |--->codec_dai->driver->ops->trigger(substream,cmd, codec_dai); //codec 驱动中赋值
                                     |--->component->driver->ops->trigger(substream, cmd);
                                     |--->cpu_dai->driver->ops->trigger(substream, cmd, cpu_dai); // codec 驱动中赋值 \
                                     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()类似
        

 PCM 論理デバイス ユーザー空間を開く
 : アプリケーションは、pcm_open() を介してシステム コール open() を直接呼び出し、pcmCxDxp または pcmCxDxc を開き
 ます
                     。 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: ここでデフォルト設定をサポートできます */     }     pcm = calloc(1, sizeof(struct pcm));     if (!pcm)         return &bad_pcm; /* TODO: ここでデフォルト設定をサポートできます */




    pcm->config = *config;

    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", カード, デバイス, フラグ
             & PCM_IN ? 'c' : 'p');

    pcm->flags = フラグ;
    pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
…………
}

カーネル空間: アプリケーション層がカーネル空間に呼び出すシステム コール open() は、PCM 論理デバイスの snd_pcm_f_ops.open ファイル操作関数 sound
/core/pcm_native.cを呼び出すことです。

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{         struct snd_pcm *pcm;         int err = nonseekable_open(inode, ファイル);         (エラー < 0)                 エラーを返す場合。         pcm = snd_lookup_minor_data(iminor(inode),                                     SNDRV_DEVICE_TYPE_PCM_PLAYBACK);         err = snd_pcm_open(ファイル、pcm、SNDRV_PCM_STREAM_PLAYBACK);         if (pcm)                 snd_card_unref(pcm->カード);         エラーを返します。}










static int snd_pcm_capture_open(struct inode *inode, struct file *file)
{         struct snd_pcm *pcm;         int err = nonseekable_open(inode, ファイル);         (エラー < 0)                 エラーを返す場合。         pcm = snd_lookup_minor_data(iminor(inode),                                     SNDRV_DEVICE_TYPE_PCM_CAPTURE);         err = snd_pcm_open(ファイル、pcm、SNDRV_PCM_STREAM_CAPTURE);         if (pcm)                 snd_card_unref(pcm->カード);         エラーを返します。} static int snd_pcm_playback_open(struct inode *inode, struct file *file) {     | } snd_pcm_open(ファイル、pcm、SNDRV_PCM_STREAM_PLAYBACK);














        | | snd_pcm_open_file(ファイル、pcm、ストリーム);
            | | snd_pcm_open_substream(pcm, ストリーム, ファイル, &substream);
                | | substream->ops->open(サブストリーム); //即座に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 類似

PCM 論理デバイスの書き込み/読み取り
私のソース コード パッケージでは、tinyalsa PCM 論理デバイスの書き込みは ioctl() 関数を介して完了します。つまり、アプリケーション プログラムは、再生するオーディオ データを pcm_write() を介してカーネルに渡します --> ioctl ().

User Space:应用程序write PCM逻辑设备是通过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:应用层呼叫的系统调用ioctl()到内核空间就是调用PCM逻辑设备的snd_pcm_f_ops.ioctl文件操作函数
.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 记录:
在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值。cmd除了可区别数字外,还包含有助于处理的几种相应信息。 cmd的大小为 32位,共分 4 个域:
     bit31~bit30 2位为 “区别读写” 区,作用是区分是读取命令还是写入命令。
     bit29~bit15 14位为 "数据大小" 区,表示 ioctl() 中的 arg 变量传送的内存大小。
     bit20~bit08  8位为 “魔数"(也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
     bit07~bit00   8位为 "区别序号" 区,是区分命令的命令顺序序号。
像 命令码中的 “区分读写区” 里的值可能是 _IOC_NONE (0值)表示无数据传输,_IOC_READ (读), _IOC_WRITE (写) , _IOC_READ|_IOC_WRITE (双向)。
内核定义了 _IO() , _IOR() , IOW() 和 _IOWR() 这 4 个宏来辅助生成上面的 cmd 。下面分析 _IO() 的实现,其它的类似:
这几个宏的使用格式为:

·         _IO (魔数, 基数);

_IOR (マジック ナンバー、カーディナリティ、変数の型)

_IOW (マジックナンバー、カーディナリティ、変数の型)

_IOWR (マジック ナンバー、カーディナリティ、変数の型)

魔数 (magic number)
      魔数范围为 0~255 。通常,用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示。设备驱动程序从传递进来的命令获取魔数,然后与自身处理的魔数想比较,如果相同则处理,不同则不处理。魔数是拒绝误使用的初步辅助状态。设备驱动程序可以通过 _IOC_TYPE (cmd) 来获取魔数。不同的设备驱动程序最好设置不同的魔数,但并不是要求绝对,也是可以使用其他设备驱动程序已用过的魔数。
基(序列号)数
      基数用于区别各种命令。通常,从 0开始递增,相同设备驱动程序上可以重复使用该值。例如,读取和写入命令中使用了相同的基数,设备驱动程序也能分辨出来,原因在于设备驱动程序区分命令时 使用 switch ,且直接使用命令变量 cmd值。创建命令的宏生成的值由多个域组合而成,所以即使是相同的基数,也会判断为不同的命令。设备驱动程序想要从命令中获取该基数,就使用下面的宏:
_IOC_NR (cmd)
通常,switch 中的 case 值使用的是命令的本身。
变量型
   变量型使用 arg 变量指定传送的数据大小,但是不直接代入输入,而是代入变量或者是变量的类型,原因是在使用宏创建命令,已经包含了 sizeof() 编译命令
   
#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ビット

        
        
        
        
// カーネルの前のエントリは snd_pcm_playback_ioctl であり、現在のエントリは snd_pcm_ioctl に続く 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); |
                    //メモリと DMA バッファ間のデータ転送、再生が完了するまで循環転送
                    |--> copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames))
                |--> //./kernel-3.10/sound/core/pcm_native.c, 904 行目
                | snd_pcm_start() //DMA 転送を開始する (最初のみ、1 回呼び出す)
                    |--> //. /kernel-3.10/sound/core/pcm_native.c、785 行目
                    | snd_pcm_action(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
                        |--> //./kernel-3.10/sound/core/pcm_native.c、716 行目または 765 行目
                        | snd_pcm_action_group()/snd_pcm_action_single()
                            |--> //ops为snd_pcm_action()函数第一个入参
                            | 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(); //即soc_pcm_trigger(), 在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)
snd_pcm_action()的第一个入参很重要,write PCM逻辑设备时,snd_pcm_action()函数的第一个入参为struct action_ops snd_pcm_action_start。
细心读者可能已经注意到,PCM逻辑设备提供了snd_pcm_f_ops[0].write()文件操作函数,那为什么应用程序不是通过系统调用 write()来写音频数据给内核呢 ?
这个问题,我自己的思考:
比较一下PCM逻辑设备的write()和ioctl(),可以发现snd_pcm_write()函数是snd_pcm_playback_ioctl()函数的简化版。
对于单声道音频数据,应用程序使用系统调用write()就可以满足需求。
对于多声道音频数据,应用程序要使用系统调用ioctl()。在多声道时,snd_pcm_playback_ioctl()会调用snd_pcm_lib_writev()。
关于read PCM逻辑设备,应用程序也是通过系统调用 ioctl() 完成的,到内核空间后调用snd_pcm_capture_ioctl()。code flow 和 write非常相似.

PCM 論理デバイスの
ユーザー空間を閉じる: アプリケーションは、pcm_close() --> pcm_stop() --> ioctl() を通じて pcmCxDxp または pcmCxDxc を閉じます。

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

    pcm_hw_munmap_status(pcm);

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




カーネル空間: アプリケーション層によってカーネル空間に呼び出されるシステム コール ioctl() は、PCM 論理デバイスの snd_pcm_f_ops.ioctl ファイル操作関数を呼び出すことです。

 .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);
}
//snd_pcm_playback_ioctl に前のカーネルのエントリがあり、現在のエントリは snd_pcm_ioctl にあり、自分でフォローする

 static long snd_pcm_playback_ioctl(構造体ファイル *file, unsigned int cmd, unsigned long arg)
{     | snd_pcm_playback_ioctl1(ファイル, pcm, SNDRV_PCM_STREAM_PLAYBACK);         | | snd_pcm_common_ioctl1(ファイル、サブストリーム、コマンド、引数);             | | snd_pcm_drop(サブストリーム);                           | | snd_pcm_stop(サブストリーム, SNDRV_PCM_STATE_SETUP);                     | | snd_pcm_action(&snd_pcm_action_stop, サブストリーム, 状態);                         | | snd_pcm_action_group()/snd_pcm_action_single()                             |--> //ops は snd_pcm_action() 関数の最初の入参                             | res = ops->do_action(サブストリーム、状態); //snd_pcm_action_stop.do_action()、snd_pcm_do_stop()








                                | substream->ops->trigger(); //つまり、soc_new_pcm() 関数で構成された soc_pcm_trigger()
                                    |--> codec_dai->driver->ops->trigger()
                                    |--> platform->driver - >ops->trigger()
                                    |--> cpu_dai->driver->ops->trigger()
}                                    

 snd_pcm_action() の最初の入力パラメーターは非常に重要です. PCM 論理デバイスを閉じるとき、snd_pcm_action() 関数の最初の入力パラメーターは struct action_ops snd_pcm_action_stop です。
注: PCM 論理デバイスの書き込み/クローズを区別するときは、snd_pcm_action() の最初の入力パラメーターに注意してください。

ALSA音频驱动之PCM Write数据传递过程
本文,我们将以回放(Playback,播放音频)为例,讲解PCM Data是如何从用户空间到内核空间,最后传递到Codec。
流程: [User Space]---copy_from_user()-->[DMA Buffer(kernel space)]----DMA--->[i2s TX FIFO]---i2s---->[Codec]---DAC->PGA/Mixer---> [SPK/HP/Earp]

对于Linux来说,由于分为 user space 和kernel space,而且两者之间不能随便互相访问。因此用户如果播放音频,则需要调用copy_from_user()将用户数据从user space拷贝到kernel space (DMA Buffer)。
DMA 负责将DMA Buffer中的音频数据搬运到I2S TX FIFO。
通过I2S总线,将音频数据传送到Codec。
Codec内部经过DAC转换,将模拟信号传到扬声器SPK(头戴式耳机HP、耳塞式耳机Earp)。


PCM Data Flow

ユーザー空間 ユーザー
空間アプリケーションは、tinyalsa が提供するインターフェース write PCM Data を使用して、オーディオ ファイルを再生します。
PCM 論理デバイスへの書き込みは、ioctl() 関数を介して行われます。つまり、アプリケーション プログラムは、再生するオーディオ データを pcm_write() --> ioctl() を介してカーネルに渡します。
pcm_write() この関数の紹介で上に貼り付けます。
オーディオ データのいくつかの重要な概念:
フォーマット: サンプル長 (サンプリング精度またはサンプリング深度)、オーディオ データの最も基本的な単位、通常は 8 ビットと 16 ビット; チャネル: モノとモノに分割されたチャネルの数 ステレオ ステレオ
;
Frame: 完全なサウンド ユニットを構成するフレーム、Frame = Format * Channel;
Rate: 別名サンプル レート: サンプリング レート、つまり、フレームの 1 秒あたりのサンプル数;
Period size: サイクル、各ハードウェア割り込みオーディオ データを処理するためのフレーム数. オーディオ機器のデータの読み取りと書き込みの場合、これは単位です;
バッファ サイズ: データ バッファのサイズです。ここでは、ランタイムのバッファ サイズを指します。 structure snd_pcm_hardware; 一般に、buffer_size = period_size * period_count、period_count は、バッファ データを処理するために必要なハードウェア割り込みの数に相当します。
システムコール ioctl() で音声データを転送するために、struct snd_xferi x を定義し、x.buf は今回再生する音声データを指し、x.frames は音声の総フレーム (フレーム) 数を示します。今回のデータ。


Kernel Space
通过系统调用ioctl()传递数据到内核,在内核空间是PCM逻辑设备对应的snd_pcm_f_ops[0].unlocked_ioctl()。上文以经提及过了。
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とはDynamic Audio Power Managementの略で、直訳するとDynamic Audio Power Managementの意味で、DAPMとはLinuxベースのモバイル機器のオーディオサブシステムを常に最小の消費電力状態で動作させることです。DAPM はユーザー空間アプリケーションに対して透過的であり、すべての電源関連の切り替えは ASoc コアで行われます。The application in user space does not need to modify the code or recompile. DAPM は、現在アクティブなオーディオ ストリーム (再生/キャプチャ) とミキサーの構成に従って、これらのオーディオ コントロールの電源スイッチをオンまたはオフにするかどうかを決定します。サウンドカード。


snd_kcontrol_new 構造体

在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:kcontrol,不熟悉的读者需要浏览一下我之前的文章:Linux ALSA声卡驱动之四:Control设备的创建。通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等
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;
};
nd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。
snd_kcontrol_new结构中,几个主要的字段是get,put,private_value,get回调函数用于获取该控件当前的状态值,而put回调函数则用于设置控件的状态值,而private_value字段则根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息。值得庆幸的是,ASoc系统已经为我们准备了大量的宏定义,用于定义常用的控件,这些宏定义位于include/sound/soc.h中


SOC_SINGLE SOC_SINGLE は、最も単純なコントロールと見なす必要があります. このコントロールには、スイッチや数値変数 (Codec の特定の周波数、FIFO サイズなど) などの 1 つの制御量しかありません。 #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) }


ミキサー コントロール
ミキサー コントロールは、オーディオ チャンネルのルーティング コントロールに使用されます. 複数の入力と出力で構成されます. 複数の入力を自由に混合して、混合出力を形成できます:

ミキサー コントロールの場合, 複数の単純なコントロールの組み合わせと考えることができます. 通常, 入力の開閉を制御するためにミキサーの各入力に対して単純なコントロールを定義します. コード内の反応は次のように定義されます. soc_kcontrol_new 配列:

    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),
    };

以上这个mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位来分别控制4个输入端的开启和关闭。

Mux控件

mux控件与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:

    /* 列挙された kcontrol */
    struct soc_enum {             unsigned short reg;             符号なしの短い reg2;             unsigned char shift_l;             unsigned char shift_r;             unsigned int max;             unsigned int マスク。             const char * const *texts;             const unsigned int *values;     };








2 つのレジスタ アドレスおよび変位フィールド: reg、reg2、shift_l、shift_r は、左右のチャネルの制御レジスタ情報を記述するために使用されます。文字列配列ポインターは、各入力に対応する名前を記述するために使用され、値フィールドは、レジスターによって選択できる値を定義する配列を指します.各値は入力に対応します.値が連続値のセットです。通常、values パラメータは無視できます。


Widget- kcontrol とパスおよび電源管理情報
kcontrol を使用して、オーディオ システムのミキサー、マルチプレクサ、ボリューム コントロール、サウンド コントロール、およびさまざまなスイッチング値の制御を完了できます. さまざまな kcontrols の制御を通じて、オーディオ ハードウェア エイブル思い描いた結果に沿って働くこと。同時に、kcontrol にはまだ次の欠点があることがわかります。

    只能描述自身,无法描述各个kcontrol之间的连接关系;
    没有相应的电源管理机制;
    没有相应的时间处理机制来响应播放、停止、上电、下电等音频事件;
    为了防止pop-pop声,需要用户程序关注各个kcontrol上电和下电的顺序;
    当一个音频路径不再有效时,不能自动关闭该路径上的所有的kcontrol;

 DAPM的基本单元:widget

目前kcontrol的一些不足,而DAPM框架为了解决这些问题,引入了widget这一概念,所谓widget,其实可以理解为是kcontrol的进一步升级和封装,她同样是指音频系统中的某个部件,比如mixer,mux,输入输出引脚,电源供应器等等,甚至,我们可以定义虚拟的widget,例如playback stream widget。widget把kcontrol和动态电源管理进行了有机的结合,同时还具备音频路径的连结功能,一个widget可以与它相邻的widget有某种动态的连结关系。在DAPM框架中,widget用结构体snd_soc_dapm_widget来描述

/* dapm widget */
struct snd_soc_dapm_widget {
        enum snd_soc_dapm_type id;  //该widget的类型值,比如snd_soc_dapm_output,snd_soc_dapm_mixer等等。
        const char *name;               /* widget name */ 该widget的名字
        const char *sname;      /* stream name */ 代表该widget所在stream的名字,比如对于snd_soc_dapm_dai_in类型的widget,会使用该字段
        struct list_head list; // 所有注册到系统中的widget都会通过该list,链接到代表声卡的snd_soc_card结构的widgets链表头字段中。
        struct snd_soc_dapm_context *dapm; //snd_soc_dapm_context结构指针,ASoc把系统划分为多个dapm域,每个widget属于某个dapm域,同一个域代表着同样的偏置电压供电策略,比如,同一个codec中的widget通常位于同一个dapm域,而平台上的widget可能又会位于另外一个platform域中。
        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; /* 負の reg = no direct dapm */reg shift mask これらの 3 つのフィールドは、ウィジェットの電源状態を制御するために使用され、\それぞれ
                                                        レジスタ アドレス、シフト値、およびマスク値に対応します。制御情報が配置されています。
        unsigned char shift; /* シフトするビット数 */
        unsigned int mask; /* シフトされていないマスク */
        unsigned int on_val; /* オン状態の値 */
        unsigned int off_val; /* オフ状態の値 */
        unsigned char power:1 ; /* ブロックの電源ステータス */
        unsigned char active:1; /* DAC、ADC のアクティブ ストリーム */
        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    电源状态的当前只,开启时和关闭时所对应的值。
power invert    用于指示该widget当前是否处于上电状态,invert则用于表明power字段是否需要逻辑反转。
active connected    分别表示该widget是否处于激活状态和连接状态,当和相邻的widget有连接关系时,connected位会被置1,否则置0。
new   我们定义好的widget(snd_soc_dapm_widget结构),在注册到声卡中时需要进行实例化,该字段用来表示该widget是否已经被实例化。
ext    表示该widget当前是否有外部连接,比如连接mic,耳机,喇叭等等。
force    该位被设置后,将会不管widget当前的状态,强制更新至新的电源状态。
ignore_suspend new_power power_checked    这些电源管理相关的字段。
subseq    该widget目前在上电或下电队列中的排序编号,为了防止在上下电的过程中出现pop-pop声,DAPM会给每个widget分配合理的上下电顺序。
*power_check    用于检查该widget是否应该上电或下电的回调函数指针。
event_flags    该字段是一个位或字段,每个位代表该widget会关注某个DAPM事件通知。只有被关注的通知事件会被发送到widget的事件处理回调函数中。
*event    DAPM事件处理回调函数指针。
num_kcontrols *kcontrol_news **kcontrols    这3个字段用来描述与该widget所包含的kcontrol控件,例如一个mixer控件或者是一个mux控件。
sources sinks    两个链表字段,两个widget如果有连接关系,会通过一个snd_soc_dapm_path结构进行连接,sources链表用于链接所有的输入path,sinks链表用于链接所有的输出path。
power_list    每次更新整个dapm的电源状态时,会根据一定的算法扫描所有的widget,然后把需要变更电源状态的widget利用该字段链接到一个上电或下电的链表中,扫描完毕后,dapm系统会遍历这两个链表执行相应的上电或下电操作。
dirty    链表字段,widget的状态变更后,dapm系统会利用该字段,把该widget加入到一个dirty链表中,稍后会对dirty链表进行扫描,以执行整个路径的更新。
inputs    该widget的所有有效路径中,连接到输入端的路径数量。
outputs    该widget的所有有效路径中,连接到输出端的路径数量。
*clk    对于snd_soc_dapm_clock_supply类型的widget,指向相关联的clk结构指针。

widget的种类

在DAPM框架中,把各种不同的widget划分为不同的种类,snd_soc_dapm_widget结构中的id字段用来表示该widget的种类,可选的种类都定义在一个枚举中:

/* dapm ウィジェット タイプ */
enum snd_soc_dapm_type {         snd_soc_dapm_input = 0, /* 入力ピン */         snd_soc_dapm_output, /* 出力ピン */         snd_soc_dapm_mux, /* 多くの入力から 1 つのアナログ信号を選択する */ snd_soc_dapm_demux         , /* 入力を 1 つに接続する複数の出力 */         snd_soc_dapm_mixer, /* いくつかのアナログ信号を一緒にミックス */         snd_soc_dapm_mixer_named_ctl, /* 名前付きコントロールを持つミキサー */         snd_soc_dapm_pga, /* プログラム可能なゲイン/減衰 (ボリューム) */         snd_soc_dapm_out_drv, /* 出力ドライバー */








        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, /* DAI 構造へのリンク */
        snd_soc_dapm_dai_out,
        snd_soc_dapm_dai_link, /* 2 つの DAI 構造間のリンク */
        snd_soc_dapm_kcontrol, /* kcontrol の自動無効化 */ snd_soc_dapm_buffer
        , /* DSP/CODEC 内部バッファ */
        snd_soc_dapm_scheduler 、/* DSP/ CODEC 内部スケジューラ */
        snd_soc_dapm_effect, /* DSP/CODEC エフェクト コンポーネント */
        snd_soc_dapm_src, /* DSP/CODEC SRC コンポーネント */
        snd_soc_dapm_asrc, /* DSP/CODEC ASRC コンポーネント */
        snd_soc_dapm_encoder, /* FW/SW オーディオ エンコーダ コンポーネント */
        snd_soc_dapm_decoder,           /* FW/SW audio decoder component */
};

snd_soc_dapm_input     该widget对应一个输入引脚。
snd_soc_dapm_output    该widget对应一个输出引脚。
snd_soc_dapm_mux    该widget对应一个mux控件。
snd_soc_dapm_virt_mux    该widget对应一个虚拟的mux控件。
snd_soc_dapm_value_mux    该widget对应一个value类型的mux控件。
snd_soc_dapm_mixer    该widget对应一个mixer控件。
snd_soc_dapm_mixer_named_ctl    该widget对应一个mixer控件,但是对应的kcontrol的名字不会加入widget的名字作为前缀。
snd_soc_dapm_pga    该widget对应一个pga控件(可编程增益控件)。
snd_soc_dapm_out_drv    该widget对应一个输出驱动控件
snd_soc_dapm_adc    该widget对应一个ADC
snd_soc_dapm_dac    该widget对应一个DAC
snd_soc_dapm_micbias    该widget对应一个麦克风偏置电压控件
snd_soc_dapm_mic    该widget对应一个麦克风。
snd_soc_dapm_hp    该widget对应一个耳机。
snd_soc_dapm_spk    该widget对应一个扬声器。
snd_soc_dapm_line     该widget对应一个线路输入。
snd_soc_dapm_switch       该widget对应一个模拟开关。
snd_soc_dapm_vmid      该widget对应一个codec的vmid偏置电压。
snd_soc_dapm_pre      machine级别的专用widget,会先于其它widget执行检查操作。
snd_soc_dapm_post    machine级别的专用widget,会后于其它widget执行检查操作。
snd_soc_dapm_supply           对应一个电源或是时钟源。
snd_soc_dapm_regulator_supply  对应一个外部regulator稳压器。
snd_soc_dapm_clock_supply      对应一个外部时钟源。
snd_soc_dapm_aif_in            对应一个数字音频输入接口,比如I2S接口的输入端。
snd_soc_dapm_aif_out          对应一个数字音频输出接口,比如I2S接口的输出端。
snd_soc_dapm_siggen            对应一个信号发生器。
snd_soc_dapm_dai_in           对应一个platform或codec域的输入DAI结构。
snd_soc_dapm_dai_out        对应一个platform或codec域的输出DAI结构。
snd_soc_dapm_dai_link         用于链接一对输入/输出DAI结构。

//
ウィジェット間のコネクタ: パス

前述のように、ウィジェットには入力と出力があり、ウィジェットは動的に接続できます。では、2 つのウィジェットを接続するために何を使用するのでしょうか? DAPM は私たちにパスの概念を提案します. パスは回路のジャンパーに相当します. あるウィジェットの出力端子を別のウィジェットの入力端子に接続します. パスは snd_soc_dapm_path 構造体で記述されます: /* dapm audio path
between 2 つのウィジェット */
struct snd_soc_dapm_path {         const char *name;

        /*
         * ソース (入力) およびシンク (出力) ウィジェット
         * 結合は利便性のためです。入力する方がはるかに優れているためです
         * p->node[SND_SOC_DAPM_DIR_IN] ではなく、p->source
         */
        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
};


ウィジェット間で接続関係が発生すると、snd_soc_dapm_path がコネクタとして機能し、そのソース フィールドは接続の開始端ウィジェットを指し、そのシンク フィールドは接続の到着端ウィジェットを指します。前の snd_soc_dapm_widget の 2 つを思い出してください。構造 リンク リスト ヘッダー フィールド: ソースとシンク? ウィジェットの入力端子と出力端子は複数のパスに接続できます. すべての入力端子の snd_soc_dapm_path 構造は list_sink フィールドを通じてウィジェットのソース リンク リストにリンクされます. 同様に, すべての出力端子の snd_soc_dapm_path 構造はウィジェットのシンクにリンクされますlist_source フィールドを介してリンクされたリスト。ここでめまいがするかもしれませんが、しばらくの間ソースになり、しばらくシンクしますが、関係ありません。覚えておいてください。接続パスは次のようになります。開始ウィジェットの出力 --> パスの入力 -->パスの出力 --> 到着ウィジェットの入力。
さらに、snd_soc_dapm_path 構造体のリスト フィールドは、実際には snd_soc_card 構造体のパス リスト ヘッダー フィールドにぶら下がっているサウンド カードへのすべてのパスを登録するために使用されます。パスの現在の接続状態を確認する独自のメソッドを定義する場合は、独自の接続されたコールバック関数ポインターを提供できます。

connect、walking、walking、weak はいくつかの補助フィールドであり、すべてのパスの横断を支援するために使用されます。

widget的连接关系:route  路由
一个路径的连接至少包含以下几个元素:起始端widget,跳线path,到达端widget,在DAPM中,用snd_soc_dapm_route结构来描述这样一个连接关系:
/*
 * 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指向到达端widget的名字字符串,source指向起始端widget的名字字符串,control指向负责控制该连接所对应的kcontrol名字字符串,connected回调则定义了上一节所提到的自定义连接检查回调函数。该结构的意义很明显就是:source通过一个kcontrol,和sink连接在一起,现在是否处于连接状态,请调用connected回调函数检查。

这里直接使用名字字符串来描述连接关系,所有定义好的route,最后都要注册到dapm系统中,dapm会根据这些名字找出相应的widget,并动态地生成所需要的snd_soc_dapm_path结构,正确地处理各个链表和指针的关系,实现两个widget之间的连接,具体的连接代码分析,我们留到以后的章节中讨论。


如何定义各种widget
snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route。其中snd_soc_dapm_path无需我们自己定义,它会在注册snd_soc_dapm_route时动态地生成,但是对于系统中的widget和route,我们是需要自己进行定义的,另外,widget所包含的kcontrol与普通的kcontrol有所不同,它们的定义方法与标准的kcontrol也有所不同。本节的内容我将会介绍如何使用DAPM系统提供的一些辅助宏定义来定义各种类型的widget和它所用到的kcontrol。

定义widget

和普通的kcontrol一样,DAPM框架为我们提供了大量的辅助宏用来定义各种各样的widget控件,这些宏定义根据widget的类型,按照它们的电源所在的域,被分为了几个域,他们分别是:

    コーデック ドメインは、VREF や VMID などの基準電圧ウィジェットを提供します. これらのウィジェットは通常、コーデックのプローブ/削除コールバックで制御されます. もちろん、作業中にオーディオ ストリームがない場合は、適切にオン/オフすることもできます.
    プラットフォーム ドメイン このドメインにあるウィジェットは、通常、イヤホン、スピーカー、マイクなどの物理的な接続を必要とするプラットフォームまたはボード用の入力/出力インターフェイスです. これらのインターフェイスはボードごとに異なる可能性があるため、通常は.マシン ドライバーで定義および制御され、何らかの方法でユーザー空間アプリケーションによってオンまたはオフにすることもできます。
    オーディオ パス ドメインとは、一般に、オーディオ パスを制御するコーデック内のミキサーやマルチプレクサなどのウィジェットを指し、これらのウィジェットは、ユーザー空間で設定された接続関係に従って、電力ステータスを自動的に設定できます。
    オーディオ データ ストリーム ドメインは、ADC、DAC などのオーディオ データ ストリームを処理する必要があるウィジェットを指します。

コーデック ドメイン ウィジェットの定義

現在、DAPM フレームワークは、
コーデックドメイン
ウィジェットを定義するための補助マクロのみを提供しています         。 0 }


platform域widget的定义
DAPM框架为我们提供了多种platform域widget的辅助定义宏:
/* 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 SND_SOC_DAPM_INPUT(wname) \
{       .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_OUTPUT(wname) \
{       .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM }
#define SND_SOC_DAPM_MIC(wname, wevent) \
{       .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \
        .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \
        .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD}
#define SND_SOC_DAPM_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, \
        .nu​​m_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_P OST_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

以上这些widget分别对应信号发生器,输入引脚,输出引脚,麦克风,耳机,扬声器,线路输入接口。其中的reg字段被设置为SND_SOC_NOPM(-1),表明这些widget是没有寄存器控制位来控制widget的电源状态的。麦克风,耳机,扬声器,线路输入接口这几种widget,还可以定义一个dapm事件回调函数wevent,从event_flags字段的设置可以看出,他们只会响应SND_SOC_DAPM_POST_PMU(上电后)和SND_SOC_DAPM_PMD(下电前)事件,这几个widget通常会在machine驱动中定义,而SND_SOC_DAPM_INPUT和SND_SOC_DAPM_OUTPUT则用来定义codec芯片的输出输入脚,通常在codec驱动中定义,最后,在machine驱动中增加相应的route,把麦克风和耳机等widget与相应的codec输入输出引脚的widget连接起来。


音频路径(path)域widget的定义
这种widget通常是对普通kcontrols控件的再封装,增加音频路径和电源管理功能,所以这种widget会包含一个或多个kcontrol,普通kcontrol的定义方法我们在 ALSA声卡驱动中的DAPM详解之一:kcontrol中已经介绍过,不过这些被包含的kcontrol不能使用这种方法定义,它们需要使用dapm框架提供的定义宏来定义,详细的讨论我们后面有介绍。这里先列出这些widget的定义宏:
/* 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}

可以看出,这些widget的reg和shift字段是需要赋值的,说明这些widget是有相应的电源控制寄存器的,DAPM框架在扫描和更新音频路径时,会利用这些寄存器来控制widget的电源状态,使得它们的供电状态是按需分配的,需要的时候(在有效的音频路径上)上电,不需要的时候(不再有效的音频路径上)下电。这些widget需要完成和之前介绍的mixer、mux等控件同样的功能,实际上,这是通过它们包含的kcontrol控件来完成的,这些kcontrol我们需要在定义widget前先定义好,然后通过wcontrols和num_kcontrols参数传递给这些辅助定义宏。


音频数据流(stream)域widget的定义
这些widget主要包含音频输入/输出接口,ADC/DAC等等:

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


每个codec有多个dai,而cpu(通常就是指某个soc cpu芯片)也会有多个dai,dai注册时,dapm系统会为每个dai创建一个snd_soc_dapm_dai_in或snd_soc_dapm_dai_out类型的widget,通常,这两种widget会和codec中具有相同的stream name的widget进行连接。另外一种情况,当系统中具有多个音频处理器(比如多个codec)时,他们之间可能会通过某两个dai进行连接,当machine驱动确认有这种配置时(通过判断dai_links结构中的param字段),会为他们建立一个dai link把他们绑定在一起,因为有连接关系,两个音频处理器之间的widget的电源状态就可以互相传递。


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


定义dapm kcontrol
对于音频路径上的mixer或mux类型的widget,它们包含了若干个kcontrol,这些被包含的kcontrol实际上就是我们之前讨论的mixer和mux等,dapm利用这些kcontrol完成音频路径的控制。不过,对于widget来说,它的任务还不止这些,dapm还要动态地管理这些音频路径的连结关系,以便可以根据这些连接关系来控制这些widget的电源状态,如果按照普通的方法定义这些kcontrol,是无法达到这个目的的,因此,dapm为我们提供了另外一套定义宏,由它们完成这些被widget包含的kcontrol的定义。

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 タイプ */
#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, \
        アクセス = 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、最大、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_en um_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 }

SOC_DAPM_SINGLE は通常のコントロールの SOC_SINGLE に対応し、SOC_DAPM_SINGLE_TLV は SOC_SINGLE_TLV に対応していることがわかります。通常の kcontrol コントロールと比較すると、dapm の kcontrol コントロールは、info、get、および put コールバック関数を置き換えるだけです。dapm kcontrol の put コールバック関数は、コントロール自体の状態を更新するだけでなく、この変更を隣接する dapm kcontrol に渡し、隣接する dapm kcontrol はこの変更を自身の隣接する dapm kcontrol に渡します。このメカニズムを通じて、ウィジェットの 1 つの接続状態が変更されている限り、それに関連するすべてのウィジェットがスキャンされ、テストされて、それらがまだ有効なオーディオ パスにあるかどうかが確認されるため、電力を動的に変更できます。状態、これが dapm のエッセンスです。


ウィジェットとルートを構築する最初のステップ
は、補助マクロを使用して、ウィジェットに必要な dapm kcontrol を定義することです。

    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[] = {             "左", "右"     };     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);


     


     
     


     
     


     
     

上記では、wm8993 で左右のチャンネルのスピーカー ミキサー コントロールを定義しました: left_speaker_mixer と right_speaker_mixer、また左右のチャンネルに対して AIFINL Mux と AIFINR Mux と呼ばれる入力選択マルチプレクサ コントロールを定義しました。


2 番目のステップは、最初のステップで定義された dapm コントロールを含む、実際のウィジェットを定義することです。

    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_soc_soc_dapm_aif_in         、 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)),         ......






     





    };

In this step, a mux widget is defined for the left and right channels: DACL Mux and DACR Mux. 実際のマルチチャンネルの選択は、dapm kcontrol によって行われます: aifinl_mux と aifinr_mux. SND_SOC_NOPM パラメータが渡されるため、これら 2 つのウィジェットは電源プロパティはありませんが、mux の切り替えは、それに接続されている他の電源プロパティの電源ステータスに影響します。また、左右のチャネル スピーカー用のミキサー ウィジェットを定義します: SPKL と SPKR. 特定のミキサー コントロールは、前の手順で定義した left_speaker_mixer と right_speaker_mixer によって完成されます. 2 つのウィジェットには電力属性があるため、これら 2 つのウィジェットが有効なオーディオ パス上にある場合、dapm フレームワークはレジスタ WM8993_POWER_MANAGEMENT_3 のビット 8 と 9 を介して電源状態を制御できます。

3 番目のステップは、これらのウィジェットの接続パスを定義することです。

    static const struct snd_soc_dapm_route routes[] = {             ......             { "DACL Mux", "Left", "AIFINL" },             { "DACL Mux", "Right", "AIFINR" },             { "DACR Mux" , "左", "AIFINL" },             { "DACR Mux", "右", "AIFINR" },             ......             { "SPKL", "DAC スイッチ", "DACL" },             { "SPKL" , NULL, "CLK_SYS" },             { "SPKR", "DAC スイッチ", "DACR" },             { "SPKR", NULL, "CLK_SYS" },     };

     




     

     


     


最初のステップの定義から、DACL Mux と DACR Mux には 2 つの入力ピンがあることがわかります。

    左
    右

SPKL と SPKR には、次の 4 つの入力選択ピンがあります。

    入力スイッチ
    IN1LP スイッチ/IN1RP スイッチ
    出力スイッチ
    DAC スイッチ

したがって、明らかに、上記のパス定義は次のことを意味します。

    AIFINL を DACL の左入力ピンに
    接続 Mux AIFINR を DACL Mux の右入力ピン
    に接続 AIFINL を DACR の左入力ピンに接続 Mux
    AIFINR を DACR の右入力ピンに接続 Mux
    DACL を DAC に接続
    SPKL DAC のスイッチ入力ピンスイッチ入力端子

4 番目のステップは、これらのウィジェットとパスをコーデック駆動型プローブ コールバックに登録することです。

    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));             ……     }




     



マシン ドライバーでは、ボード固有のウィジェットとパス情報を同じ方法で定義および登録できます。


    ウィジェットを登録する方法
    2 つのウィジェットを接続する方法
    ウィジェットのステータスをオーディオ パス全体に転送する方法

dapm コンテキスト
dapm コンテキスト、直訳すると dapm コンテキストを意味します。これは理解しにくいようですが、実際には次のように理解できます。dapm はオーディオ システム全体を機能とバイアス電圧レベルに応じていくつかの電源ドメインに分割します。それぞれのウィジェット、各ドメイン内のすべてのウィジェットは通常、同じバイアス電圧レベルにあり、電源ドメインは dapm コンテキストです。通常、次の dapm コンテキストがあります。

    属于codec中的widget位于一个dapm context中
    属于platform的widget位于一个dapm context中
    属于整个声卡的widget位于一个dapm context中
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个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
};

snd_soc_bias_level の値の範囲は次のとおりです。

    SND_SOC_BIAS_OFF
    SND_SOC_BIAS_STANDBY
    SND_SOC_BIAS_PREPARE
    SND_SOC_BIAS_ON

snd_soc_dapm_context は、コーデック、プラットフォーム、カード、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;
        ……
};

ウィジェット構造体 snd_soc_dapm_widget には、snd_soc_dapm_context 構造体ポインターがあり、それが属するコーデック、プラットフォーム、カード、または dai の dapm 構造体を指します。同時に、すべての dapm 構造体は、そのリスト フィールドを通じて、サウンド カードを表す snd_soc_card 構造体の dapm_list リンク リスト ヘッダー フィールドにリンクされます。

ウィジェットが snd_soc_dapm_widget 構造体で記述されることは既にわかっています. 通常、オーディオ ハードウェアの構成に応じて、サウンド カードのコーデック ドライバー、プラットフォーム ドライバー、マシン ドライバーにウィジェットのグループを定義します. これらのウィジェットは編成されていますin arrays. We general A large number ofauxiliary macros provided by the DAPM Framework will be used to define these widget arrays. 補助マクロの説明については、以前の記事を参照してください: ALSA サウンド カード ドライバー部分の DAPM の詳細な説明3: 各種ウィジェットの定義方法

コーデック ドライバーへの登録 ASoc が提供する api 関数 snd_soc_register_codec を介してコーデック ドライバーを登録することがわかっています. この関数の 2 番目のパラメーターは snd_soc_component_driver 構造体のポインターです. この snd_soc_component_driver 構造体は、コーデック ドライバーで明示的に定義する必要があります. dapm フレームワークに関連するいくつかのフィールドがあります。

/* 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);         …… }




上記の登録方法には欠点があります.コードをわかりやすくするために、機能に応じて異なるウィジェットを複数の配列に定義することがあります.ただし、snd_soc_codec_driverにはdapm_widgetsフィールドが1つしかないため、複数のウィジェット配列を設定することはできません.これらのウィジェットを作成するには、コーデックのプローブ コールバックで dapm フレームワークによって提供される API をアクティブに呼び出す必要があります。

    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_register_codec内部其实也是调用snd_soc_dapm_new_controls来完成的。后面会有关于这个函数的详细分析。


platform驱动中注册    和codec驱动一样,我们会通过ASoc提供的api函数snd_soc_register_platform来注册一个platform驱动,该函数的第二个参数是一个snd_soc_platform_driver结构指针,snd_soc_platform_driver结构中同样也包含了与dapm相关的字段:

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

        /* デフォルトの制御と設定、probe() の実行後に追加 */
        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. snd_soc_register_platform 関数が paltform を登録した後, マシン ドライバがプラットフォームに一致するとき、システムが自動的に作成および登録作業を完了します。同様に、プラットフォーム主導のプローブ コールバック関数で snd_soc_dapm_new_controls を積極的に使用して、ウィジェットの作成を完了することもできます。具体的なコードはコーデック ドライバーと似ているため、ここには掲載しません。


machine驱动中注册    有些widget可能不是位于codec中,例如一个独立的耳机放大器,或者是喇叭功放等,这种widget通常需要在machine驱动中注册,通常他们的dapm context也从属于声卡(snd_soc_card)域。做法依然和codec驱动类似,通过代表声卡的snd_soc_card结构中的几个dapm字段完成:

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

        /* pre および post PM 関数は、
         コーデックと DAI が PM 作業を行う前と * 後に PM 作業を行うために使用されます。*/
        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 <--> コーデック DAI リンク */
        struct snd_soc_dai_link *dai_link; /* 定義済みリンクのみ */
        int num_links; /* 定義済みリンクのみ */
        struct list_head dai_link_list; /* すべてのリンク */
        int num_dai_links;

        struct list_head rtd_list;
        int num_rtd;

        /* オプションのコーデック固有の構成 */
        struct snd_soc_codec_conf *codec_conf;
        int num_configs;

        /*
         * DAI を備えたアンプやコーデックなどのオプションの補助デバイス
         * リンク未使用
         */
        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;

        /*
         * カード固有のルートとウィジェット。
         * 注: デバイス ツリーの of_dapm_xxx; それ以外の場合は、ドライバーの組み込み用です。
         */
        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;

        /* このカードに属するプローブされたデバイスのリスト */
        struct list_head component_dev_list;

        struct list_head ウィジェット;
        struct list_head パス;
        struct list_head dapm_list;
        struct list_head dapm_dirty;

        /* アタッチされた動的オブジェクト */
        struct list_head dobj_list;

        /* カードの汎用 DAPM コンテキスト */
        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;
};
    
只要把定义好的widget数组和数量赋值给dapm_widgets指针和num_dapm_widgets即可,注册声卡使用的api:snd_soc_register_card(),也会通过snd_soc_dapm_new_controls来完成widget的创建工作。


注册音频路径
系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,请参考: ALSA声卡驱动中的DAPM详解之三:如何定义各种widget中的"建立widget和route"一节的内容。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:


通过snd_soc_component_driver(原 snd_soc_codec_driver/snd_soc_platform_driver)    /snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;
两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。以下的代码片段是omap的pandora板子的machine驱动,使用第二种方法注册路径信息:


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;
        //注册機音频路径
        return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,
                ARRAY_SIZE(omap3pandora_in_map));
}
 
/* デジタル オーディオ インターフェイス グルー - コーデック <--> CPU を接続 */
static struct snd_soc_dai_link omap3pandora_dai[] = {         {                 .name = "PCM1773",                 ......                 .init = omap3pandora_out_init,         }, {                 .name = "TWL4030"、






                .stream_name = "ライン/マイク入力",
                ......
                .init = omap3pandora_in_init, }
        }
;

上記のdai ウィジェット
のセクションでは、コーデック、プラットフォーム、およびマシン レベルのウィジェットとルートの登録方法を紹介しています. dapm フレームワークには、dai (デジタル オーディオ インターフェイス) を表す別のウィジェットがあります. dai の説明については、以下を参照してください: Linux ALSA サウンド カード ドライバー 7: ASoC アーキテクチャのコーデック。Dai はその位置によって cpu dai と codec dai に分けられます. ハードウェアでは, cpu dai は通常 codec dai に接続されます. マシン ドライバでは, snd_soc_card 構造体に snd_soc_dai_link という構造体を指定する必要があります. この構造体はどちらを定義しますか?サウンドカードが接続に使用する cpu dai と codec dai。Asoc では、dai は snd_soc_dai 構造体で表され、いくつかのフィールドが dapm フレームワークに関連しています。

/*
 * デジタル オーディオ インターフェイスのランタイム データ。
 *
 * DAI のランタイム データを保持します。
 */
struct snd_soc_dai {         const char *name;         整数 ID;         構造体デバイス *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;

        /* 親プラットフォーム/コーデック */
        struct snd_soc_component *component;

        /* CODEC TDM スロット マスクとパラメーター (フィックスアップ用) */
        unsigned int tx_mask;
        unsigned int rx_mask;

        struct list_head リスト;
};

Dai はプラットフォーム コード内のコーデック ドライバーと iis または pcm インターフェイス ドライバーによって登録されます. マシン ドライバーは、snd_soc_dai_link で指定された cpu/コーデック dai のペアを見つけてバインドする役割を果たします. CPU dai であれコーデック dai であれ、通常はオーディオ ストリームを同時に再生および録音する機能を送信するため、snd_soc_dai には再生ストリームと録音ストリームをそれぞれ表す 2 つのウィジェット ポインターがあることがわかります。


コーデック dai ウィジェット    
まず、コーデック ドライバーがコーデックを登録するときに、コーデックがサポートする dai の数と、dai 情報を記録する snd_soc_dai_driver 構造体ポインターを渡します。


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);         …… }





今回は、ASoc にコーデックの dai をシステムに登録させ、これらの dai をグローバル リンク リスト変数 dai_list に吊るします。次に、コーデックがマシン ドライバーによって照合された後、soc_probe_component 関数が呼び出され、彼は検索します。グローバル リンク リスト変数 dai_list を通じて、コーデックに属するすべての dai に対して、snd_soc_dapm_new_dai_widgets 関数を呼び出して、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(コンポーネント);

        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,                                 "新しいコントロールの作成に失敗しました %d\n", ret);                         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;
}


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 ウィジェット  
        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: %s ウィジェットを追加\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);

            /* プローブの延期についてうるさく言わないでください */
            if (ret != -EPROBE_DEFER)
                dev_err(dapm->dev,
                "ASoC: %s ウィジェット (%d) の作成に失敗しました\n",
                dai->driver->playback. stream_name, ret);
            retを返します。
        }
        if (!w) {             dev_err(dapm->dev, "ASoC: %s ウィジェットの作成に失敗しました\n",                 dai->driver->capture.stream_name);             -ENOMEM を返します。         }



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

    return 0;
}
    
Playback と Capture のウィジェットをそれぞれ作成し、ウィジェットの priv フィールドが dai を指すようにすることで、対応する dai をウィジェットから見つけることができ、ウィジェットの名前は snd_soc_dai_driver 構造体の stream_name になります。

エンドポイント ウィジェット

完全な dapm オーディオ パスには開始点と終了点が必要です. これらの開始点と終了点にあるウィジェットをエンドポイント ウィジェットと呼びます. 次のタイプのウィジェットをエンドポイント ウィジェットにすることができます。

コーデックの入力および出力ピン:

    snd_soc_dapm_output
    snd_soc_dapm_input

外部オーディオ機器:

    snd_soc_dapm_hp
    snd_soc_dapm_spk
    snd_soc_dapm_line

オーディオ ストリーム (ストリーム ドメイン):

    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

電源、クロック、その他:

    snd_soc_dapm_supply
    snd_soc_dapm_regulator_supply
    snd_soc_dapm_clock_supply
    snd_soc_dapm_kcontrol


ウィジェット間の接続関係を確立する

これまでは主に、コーデック、プラットフォーム、およびマシン ドライバーで dapm が必要とするウィジェットとルートを使用および構築する方法に焦点を当てていました. これらは、オーディオ ドライバーの開発者が理解しなければならない内容です. 前の章の紹介の後、知っておくべきことalsa の使用方法 オーディオ ドライバーの 3 つの部分 (コーデック、プラットフォーム、マシン) では、使用されるオーディオ ハードウェア構造に従って、対応するウィジェット、kcontrol、および必要なオーディオ パスが定義されます。 dapm のコア部分。各ウィジェット間で接続関係がどのように確立され、完全なオーディオ パスが形成されるかを確認します。

前に簡単に紹介したように、ドライバーは次の API 関数を使用してウィジェットを作成する必要があります。

    int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
        const struct snd_soc_dapm_widget *widget,
        int num);
实际上,这个函数只是创建widget的第一步,它为每个widget分配内存,初始化必要的字段,然后把这些widget挂在代表声卡的snd_soc_card的widgets链表字段中。要使widget之间具备连接能力,我们还需要第二个函数:
    /* dapm path setup */
int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
这个函数会根据widget的信息,创建widget所需要的dapm kcontrol,这些dapm kcontol的状态变化,代表着音频路径的变化,从而影响着各个widget的电源状态。看到函数的名称可能会迷惑一下, 实际上, snd_soc_dapm_new_controls的作用更多地是创建widget,而 snd_soc_dapm_new_widget的作用则更多地是创建widget所包含的kcontrol,所以 在我看来,这两个函数名称应该换过来叫更好

创建widget:snd_soc_dapm_new_controls

snd_soc_dapm_new_controls函数完成widget的创建工作,并把这些创建好的widget注册在声卡的widgets链表中

/**
 * 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, ウィジェット);                 if (IS_ERR(w)) {                         ret = PTR_ERR(w);                         /* プローブの延期についてしつこく言わないでください */                         if (ret == -EPROBE_DEFER)                                 break;                         dev_err(dapm->dev,                                 "ASoC: DAPM コントロール %s (%d) の作成に失敗しました\n",                                 widget->name, ret);                         壊す;                 }                 もし (!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);

该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control函数,实际的工作由snd_soc_dapm_new_control完成,继续进入该函数,看看它做了那些工作。
我们之前已经说过,驱动中定义的snd_soc_dapm_widget数组,只是作为一个模板,所以,snd_soc_dapm_new_control_unlocked 所做的第一件事,就是为该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)
{
        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 はメモリの適用を完了し、テンプレートのコピー アクションは
                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: %s の要求に失敗しました: %d\n",                                 w->name, ret);                         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);
                                                     

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


snd_soc_dapm_regulator_supply タイプのウィジェットの場合、ウィジェット名に従って対応するレギュレーター構造体を取得し、snd_soc_dapm_clock_supply タイプのウィジェットの場合、ウィジェット名に従って対応するクロック構造体を取得します。次に、必要に応じてウィジェットの名前に必要なプレフィックスを追加します。

            if (dapm->codec && dapm->codec->name_prefix)
                    w->name = kasprintf(GFP_KERNEL, "%s %s",
                            dapm->codec->name_prefix, widget->name);
            そうでなければ
                    w->name = kasprintf(GFP_KERNEL, "%s", widget->name);

次に、さまざまな種類のウィジェットに適切な power_check 電源状態コールバック関数を設定します. ウィジェットの種類と対応する power_check コールバック関数は、次の表に示すように設定されます: ウィジェットの power_check コールバック関数 (特定の解析コード
                     )

オーディオ パスが変更されると、power_check コールバックが呼び出され、ウィジェットの電源ステータスを更新する必要があるかどうかがチェックされます。power_check の設定が完了したら、ウィジェットが属するコーデック、プラットフォーム、および dapm コンテキストを設定する必要があります. オーディオ パスのいくつかのリンクされたリストも初期化する必要があります. 次に、サウンドのウィジェットのリンクされたリストにウィジェットを追加します.カード:

            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);

几个链表的作用如下:

    sources    用于链接所有连接到该widget输入端的snd_soc_path结构
    sinks    用于链接所有连接到该widget输出端的snd_soc_path结构
    list    用于链接到声卡的widgets链表
    dirty    用于链接到声卡的dapm_dirty链表

最后,把widget设置为connect状态:

            /* マシン層が接続されていないピンと挿入をセットアップします */
            w->connected = 1;
            wを返します。
    }

connected フィールドは、ピンの接続ステータスを表します。現在、次のウィジェットのみが connected フィールドを使用しています。

    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

ドライバーは、次の API を使用して、ピンの接続状態を設定できます。

    snd_soc_dapm_enable_pin
    snd_soc_dapm_force_enable_pin
    snd_soc_dapm_disable_pin
    snd_soc_dapm_nc_pin

到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后我们就可以通过声卡的widgets链表来遍历所有的widget,再次强调一下snd_soc_dapm_new_controls函数所完成的主要功能:
                                                                      //snd_soc_dapm_new_control_unlocked
     为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板
    设置power_check回调函数
    把widget挂在声卡的widgets链表中

为widget建立dapm kcontrol
定义一个widget,我们需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。前一节的内容中,我们只是创建了widget的实例,并把它们注册到声卡的widgts链表中,但是到目前为止,包含在widget中的dapm kcontrol并没有建立起来,dapm框架在声卡的初始化阶段,等所有的widget(包括machine、platform、codec)都创建好之后,通过snd_soc_dapm_new_widgets函数,创建widget内包含的dapm kcontrol,并初始化widget的初始电源状态和音频路径的初始连接状态。我们看看声卡的初始化函数,都有那些初始化与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) /* 创建machine级别的widget  */
                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) /*  建立machine级别的普通kcontrol控件  */
                snd_soc_add_card_controls(card, card->controls, card->num_controls);

        if (card->dapm_routes) /*  注册machine级别的路径连接信息  */
                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);/*初始化widget包含的dapm kcontrol、电源状态和连接状态*/
 


      ..........


}
正如我添加的注释中所示,在完成machine级别的widget和route处理之后,调用的snd_soc_dapm_new_widgets函数,来为所有已经注册的widget初始化他们所包含的dapm kcontrol,并初始化widget的电源状态和路径连接状态。下面我们看看snd_soc_dapm_new_widgets函数的工作过程。
该函数通过声卡的widgets链表,遍历所有已经注册了的widget,其中的new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数,如果num_kcontrols字段有数值,表明该widget包含有若干个dapm kcontrol,那么就需要为这些kcontrol分配一个指针数组,并把数组的首地址赋值给widget的kcontrols字段,该数组存放着指向这些kcontrol的指针,当然现在这些都是空指针,因为实际的kcontrol现在还没有被创建:

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 ;             }         }         //次に、オーディオ パス         に影響             を与える可能             性のあるいくつかのウィジェットについて、         ウィジェット                  に含まれる dapm kcontrol を作成して初期化します         :         ケース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;
        }

        /* デバイスから初期電力状態を読み取ります */
        if (w->reg >= 0) {//ウィジェット レジスタの現在の値に従って、ウィジェットの電力状態を初期化し、電力フィールドに設定します 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;//新しいフィールドを設定して、ウィジェットが初期化されたことを示し、サウンド カードの dapm_dirty リストにウィジェットを追加する必要があり、ウィジェットの状態が変更されたことを示します。適切な時点で、dapm フレームワークは dapm_dirty リンク リストをスキャンし、変更されたすべてのウィジェットを均一に処理します。なぜ一律に扱う必要があるのでしょうか?dapm は、さまざまなウィジェットの電源投入と電源投入のシーケンスを制御する必要があり、レジスタの読み取りと書き込みの回数を減らす必要があるため (複数のウィジェットが同じレジスタを使用する場合があります):

        dapm_mark_dirty(w, "新しいウィジェット");
        dapm_debugfs_add_widget(w);
    }

    dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);//通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变
    mutex_unlock(&card->dapm_mutex);
    return 0;
}

需要用到的创建函数分别是:

    dapm_new_mixer()    对于mixer类型,用该函数创建dapm kcontrol;
    dapm_new_mux()   对于mux类型,用该函数创建dapm kcontrol;
    dapm_new_pga()   对于pga类型,用该函数创建dapm kcontrol;


dapm mixer kcontrol
上一节中,我们提到,对于mixer类型的dapm kcontrol,我们会使用dapm_new_mixer来完成具体的创建工作,先看代码后分析:

/* 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)  因为一个mixer是由多个kcontrol组成的,每个kcontrol控制着mixer的一个输入端的开启和关闭,所以,该函数会根据kcontrol的数量做循环,逐个建立对应的kcontrol。
(2)(3)  之前多次提到,widget之间使用snd_soc_path进行连接,widget的sources链表保存着所有和输入端连接的snd_soc_path结构,所以我们可以用kcontrol模板中指定的名字来匹配对应的snd_soc_path结构。
(4)  因为一个输入脚可能会连接多个输入源,所以可能在上一个输入源的path关联时已经创建了这个kcontrol,所以这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了,所以我们只要简单地 把连接该输入端的path加入到kcontrol的path_list链表中,并且 增加一个虚拟的影子widget,该影子widget连接和输入端对应的源widget,因为使用了kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关,这个影子widget只有在kcontrol的autodisable字段被设置的情况下才会被创建,该特性使得source的关闭时,与之连接的mixer的输入端也可以自动关闭,这个特性通过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);             /* kcontrol で接続されたパスをパス リストに追加 */         /* path s がリンクされた dapm_kcontrol_data 構造体リストは kcontrol の private_data フィールドに保存されます */     list_add_tail(&path->list_kcontrol, &data->paths); }




(5) kcontrol が作成されていない場合は、dapm_create_or_share_mixmux_kcontrol でこの入力端子の kcontrol を作成し、同様に kcontrol に対応するシャドウウィジェットも dapm_kcontrol_add_path で作成の要否を判断します。


dapm mux kcontrol

ウィジェットにはマルチプレクサ タイプの湿った kcontrol を 1 つしか含めることができないため、その作成方法はわずかに異なります. dapm フレームワークは、dapm_new_mux 関数を使用して、マルチプレクサ タイプの dapm kcontrol を作成します: /* 新しい dapm mux コントロールを作成する */ 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);         -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) muxタイプのwidgetの場合、kcontrolが1つしかないので、ここで判断してください。
(2) 同様に、ミキサー タイプと同様に、dapm_create_or_share_mixmux_kcontrol を使用して、この kcontrol を作成します。
(3) 各入力に接続されたパスを dapm_kcontrol_data 構造体のパス リンク リストに追加し、自動無効化機能をサポートするシャドウ ウィジェットを作成します。


dapm_create_or_share_kcontrol
上記のミキサー タイプとマルチプレクサ タイプのウィジェットは、含まれる dapm kcontrol を作成するときに、実際に dapm_create_or_share_kcontrol 関数を使用して作成作業を完了するため、ここでこの関数の動作原理を分析する必要があります。この関数のコードの大部分は, 実際には kcontrol の名前に codec を付けるかどうかを扱っています. コードのこの部分は無視します. 興味のある読者は自分でカーネルコードをチェックしてください. パスは: sound/soc/ soc-dapm. c では、簡略化されたコードは次のようになります。

/*
 * kcontrol が共有されているかどうかを判断します。もしそうなら、それを見てください。そうでない場合は、
 * 作成します。いずれにしても、ウィジェットをコントロールのウィジェット リストに追加します
 */
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 *プレフィックス;     size_t prefix_len;     int 共有;     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) {             /*              * コントロールはコントロール              作成プロセスから * プレフィックスを取得しますが、 * ウィジェットにも同じプレフィックスを使用しているため、              * ウィジェット名の先頭から              プレフィックスを削除します。              */             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);             exit_free に移動します。         }


(5) ret = snd_ctl_add(カード、kcontrol);
        if (ret < 0) {             dev_err(dapm->dev,                 "ASoC: ウィジェット %s dapm kcontrol %s の追加に失敗しました: %d\n",                 w->name, name, ret);             exit_free に移動します。         }     }





(6) 右 = dapm_kcontrol_add_widget(kcontrol, w);
    もし (右 == 0)
(7) w->kcontrols[kci] = kcontrol;

exit_free:
    kfree(long_name);

    return ret;
}
(1)  为了节省内存,通过kcontrol名字的匹配查找,如果这个kcontrol已经在其他widget中已经创建好了,那我们不再创建,dapm_is_shared_kcontrol的参数kcontrol会返回已经创建好的kcontrol的指针。
(2)  如果kcontrol指针被赋值,说明在(1)中查找到了其他widget中同名的kcontrol,我们不用再次创建,只要共享该kcontrol即可。
(3)  标准的kcontrol创建函数,请参看: Linux ALSA声卡驱动之四:Control设备的创建中的“创建control“一节的内容。
(4)  如果widget支持autodisable特性,创建与该kcontrol所对应的影子widget,该影子widget的类型是:snd_soc_dapm_kcontrol。
(5)  标准的kcontrol创建函数,请参看: Linux ALSA声卡驱动之四:Control设备的创建中的“创建control“一节的内容。
(6)  把所有共享该kcontrol的影子widget(snd_soc_dapm_kcontrol),加入到kcontrol的private_data字段所指向的dapm_kcontrol_data结构中。
(7)  把创建好的kcontrol指针赋值到widget的kcontrols数组中。
需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由于source的关闭而被自动关闭,则用户空间只能操作该kcontrol的cache值,只有该kcontrol再次打开时,该cache值才会被真正地更新到寄存器中。
现在。我们总结一下,创建一个widget所包含的kcontrol所做的工作:

    循环每一个输入端,为每个输入端依次执行下面的一系列操作
    为每个输入端创建一个kcontrol,能共享的则直接使用创建好的kcontrol
    kcontrol的private_data字段保存着这些共享widget的信息
    如果支持autodisable特性,每个输入端还要额外地创建一个虚拟的snd_soc_dapm_kcontrol类型的影子widget,该影子widget也记录在private_data字段中
    创建好的kcontrol会依次存放在widget的kcontrols数组中,供路径的控制和匹配之用。

为widget建立连接关系
如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,前面我们已经知道,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来我们就是要分析该函数的具体实现方式

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

该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。我们进入snd_soc_dapm_add_route函数看看:


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 *ソース;     char prefixed_sink[80];     char prefixed_source[80];     const char *プレフィックス;     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, //接続情報を追加するために使用:
        route->connected);
    if (ret)
        goto err;

    0 を返します。
err:
    dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
         source, route->control, sink);
    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 - リストから最初の要素を取得する
 * @ptr: 要素を取得するリストの先頭。
 * @type: これが埋め込まれている構造体の型。
 * @member: 構造体内の list_head の名前。
 *
 * リストは空ではないことが予想されることに注意してください。
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

list_for_each_entry(w, &dapm->カード->ウィジェット, リスト)


#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), \
             "container_of() のポインター型が一致しません"); \
    ((type *)(__mptr - offsetof(type, member))); })

/**
 * BUILD_BUG_ON_MSG - 条件が真の場合にコンパイルを中断し、提供された
 * エラー メッセージを出力します。
 * @condition: コンパイラが認識すべき条件は false です。
 *
 * 説明は BUILD_BUG_ON を参照してください。
 */
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)


snd_soc_dapm_add_path 関数は呼び出しチェーン全体の鍵です。分析してみましょう。

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 *sインク))
{     struct snd_soc_dapm_widget *widgets[2];     enum snd_soc_dapm_direction dir;     struct snd_soc_dapm_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;
}


函数的一开始,首先为这个连接分配了一个snd_soc_path结构,path的source和sink字段分别指向源widget和目的widget,connected字段保存connected回调函数,初始化几个snd_soc_path结构中的几个链表。

    /* 静的パスを接続する */
    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;             壊す;         デフォルト:             ブレーク;         }










        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;
        }
    }

按照目的widget来判断,如果属于以上这些类型,直接把它们连接在一起即可,目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作,这两个函数我们后面再讲。

/* ウィジェットには PM レジスタ ビットがありません */
#define SND_SOC_NOPM -1

/* mux ウィジェットを相互接続オーディオ パスに接続する */
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 {         /* 仮想マルチプレクサには、接続するパスを決定するバッキング レジスタがないため、          * 最初の列挙と          一致させようとします。これは          * デフォルトのマルチプレクサの選択 (最初の) が          * 初期化中に正しくパワーアップされることを保証するためです。          */         項目 = 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;

}

声卡创建并初始化好了所需的widget,各个widget也通过path连接在了一起,接下来,dapm等待用户的指令,一旦某个dapm kcontrol被用户空间改变,利用这些连接关系,dapm会重新创建音频路径,脱离音频路径的widget会被下电,加入音频路径的widget会被上电,所有的上下电动作都会自动完成,用户空间的应用程序无需关注这些变化,它只管按需要改变某个dapm kcontrol即可。

设计dapm的主要目的之一,就是希望声卡上的各种部件的电源按需分配,需要的就上电,不需要的就下电,使得整个音频系统总是处于最小的耗电状态,最主要的就是,这一切对用户空间的应用程序是透明的,也就是说,用户空间的应用程序无需关心那个部件何时需要电源,它只要按需要设定好音频路径,播放音频数据,暂停或停止,dapm框架会根据音频路径,完美地对各种部件的电源进行控制,而且精确地按某种顺序进行,防止上下电过程中产生不必要的pop-pop声。

统计widget连接至端点widget的路径个数
端点widget位于音频路径的起始端或者末端,所以通常它们就是指codec的输入输出引脚所对应的widget,或者是外部器件对应的widget,这些widget的类型有以下这些

                コーデックの                 入力ピン                 出力ピンsnd_soc_dapm_aif_in
                snd_soc_dapm_dai_out                 snd_soc_dapm_dai_in                 電源
クロック                 snd_soc_dapm_supply snd_soc_dapm_regulator_supply                 snd_soc_dapm_clock_supply                 シャドウ                 ウィジェット                 snd_soc_dapm_kcontrol












dapm がウィジェットの電源をオンにするための前提条件の 1 つは、ウィジェットが完全なオーディオ パス上にあり、完全なオーディオ パスの両端が入力/出力ピンまたは外部オーディオ デバイスである必要があること、またはアクティブなオーディオであることです。ストリーム ウィジェット, つまり、上の表の最初の 3 つのアイテムと上の表の最後の 2 つのアイテム. これらはパスの最後に配置できますが、オーディオ パスを完了するための必須条件ではありません.パスのスキャンの終了条件を決定するためにのみ使用してください。dapm は、ウィジェットが出力ピン、入力ピン、およびアクティブ化されたオーディオ ストリーム ウィジェットに接続する有効なパスの数をカウントする 2 つの内部関数を提供します。

is_connected_output_ep 出力ピンまたはアクティブ状態に接続された出力オーディオ ストリームのパスの数を返します
is_connected_input_ep 入力ピンまたはアクティブ状態に接続された入力オーディオ ストリームのパスの数を返します

dapm_dirty リスト

サウンド カードを表す snd_soc_card 構造体には、連結リスト フィールド dapm_dirty があります。ステータスが変更されたすべてのウィジェットに対して、dapm はその電源ステータスをすぐには処理しませんが、最初に連結リストの下でハングアップし、その後の追加を待機する必要があります。処理: または 電源オンまたは電源オフのいずれか。dapm は、このアクションを完了するための API 関数を提供します。

    void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason)
    {             if (!dapm_dirty_widget(w)) {                     dev_vdbg(w->dapm->dev, "%s により %s をダーティとしてマーク\n",                              w->name 、 理由);                     list_add_tail(&w->dirty, &w->dapm->card->dapm_dirty);             }     }





power_check コールバック関数

ウィジェット間の接続関係を確立する際に、ウィジェットを作成するときに、ウィジェットの power_check コールバック関数がウィジェットのタイプに応じて異なるコールバック関数を設定することがわかっています。ウィジェットのステータスが変化すると、dapm は dapm_dirty リンク リストを走査し、power_check コールバック関数を使用して、ウィジェットの電源をオンにする必要があるかどうかを判断します。ほとんどのウィジェットの power_check コールバックは次のように設定されています: 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;
}

很简单,分别用is_connected_output_ep和is_connected_input_ep得到该widget是否有同时连接到一个输入端和一个输出端,如果是,返回1来表示该widget需要上电。
对于snd_soc_dapm_dai_out和snd_soc_dapm_dai_in类型,power_check回调是dapm_generic_check_power

widget的上电和下电顺序

在扫描dapm_dirty链表时,dapm使用两个链表来分别保存需要上电和需要下电的widget:

    up_list           保存需要上电的widget
    down_list     保存需要下电的widget

dapm内部使用dapm_seq_insert函数把一个widget加入到上述两个链表中的其中一个:

/* 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);
}

上述函数会按照一定的顺序把widget加入到链表中,从而保证正确的上下电顺序:

        上電顺序           
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
        [ 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的上下电过程
dapm_power_widgets

当一个widget的状态改变后,该widget会被加入dapm_dirty链表,然后通过dapm_power_widgets函数来改变整个音频路径上的电源状态,

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

    /* どのウィジェットに電力を供給する必要があるかを確認し、それらを
     * リストに格納して、電力をオンにするかオフにするかを示します。
     * ダーティとしてフラグが立てられたウィジェットのみをチェックしますが、* 反復中に新しいウィジェットが * ダーティ リストに追加される可能性があることに注意し
     ください
     。
     */
    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;

            /* サプライとマイクバイアスは、
             * コンテキストを * STANDBY にするだけです。
             他の * 何かがアクティブで * オーディオを渡している場合を除き、
             * 通常はフルパワーを必要としません。*信号
             発生器は仮想ピンであり、
             * それ自体に電力の影響はありません。
             */
            switch (w->id) {             case snd_soc_dapm_siggen:             case snd_soc_dapm_vmid:                 break;             ケース snd_soc_dapm_supply:             ケース snd_soc_dapm_regulator_supply:             ケース snd_soc_dapm_pinctrl:             ケース 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;
            }
        }

    }

    /* カード内のすべてのコンテキストを強制的に同じバイアス状態に
     * それらが地面参照されていない場合。
     */
    バイアス = SND_SOC_BIAS_OFF;
    list_for_each_entry(d, &card->dapm_list, list)
        if (d->target_bias_level > bias)
            バイアス = d->target_bias_level;
    list_for_each_entry(d, &card->dapm_list, list)
        if (!dapm_idle_bias_off(d))
            d->target_bias_level = バイアス;

    trace_snd_soc_dapm_walk_done(カード);

    /* 最初にカード バイアスの変更を実行します */
    dapm_pre_sequence_async(&card->dapm, 0);
    /* 他のバイアス変更を並行して実行します */
    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(カード, w, SND_SOC_DAPM_WILL_PMD);     }

    list_for_each_entry(w, &up_list, power_list) {         dapm_seq_check_event(カード, 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);

    /* DAPM イベントが完了したことをクライアントに通知する必要がありますか */
    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 シーケンスが終了しました。%dms を待っています\n", card->pop_time);
    pop_wait(card->pop_time);
    mutex_unlock(&card->dapm_power_mutex);

    trace_snd_soc_dapm_done(カード);

    0 を返します。
}

可见,该函数通过遍历dapm_dirty链表,对每个链表中的widget调用dapm_power_one_widget,dapm_power_one_widget函数除了处理自身的状态改变外,还把自身的变化传递到和它相连的邻居widget中,结果就是,所有需要上电的widget会被放在up_list链表中,而所有需要下电的widget会被放在down_list链表中,这个函数我们稍后再讨论。
遍历down_list链表,向其中的widget发出SND_SOC_DAPM_WILL_PMD事件,感兴趣该事件的widget的event回调会被调用。
遍历up_list链表,向其中的widget发出SND_SOC_DAPM_WILL_PMU事件,感兴趣该事件的widget的event回调会被调用。
通过dapm_seq_run函数,处理down_list中的widget,使它们按定义好的顺序依次下电。
通过dapm_widget_update函数,切换触发该次状态变化的widget的kcontrol中的寄存器值,对应的结果就是:改变音频路径。
通过dapm_seq_run函数,处理up_list中的widget,使它们按定义好的顺序依次上电。
对每个dapm context发出状态改变回调。
适当的延时,防止pop-pop声。

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;
    }
}


dapm_power_widgets の最初のステップは、dapm_dirty リンク リストをトラバースし、リンク リスト内の各ウィジェットに対して dapm_power_one_widget を呼び出し、電源をオンにする必要があるウィジェットと電源をオフにする必要があるウィジェットを up_list および down_list リンク リストに追加することです。ウィジェットは dapm_dirty リンク リストの最後に再度追加されます. このアクションにより、サウンド カード内の影響を受けるすべてのウィジェットが「感染」し、dapm_dirty リンク リストに追加されます。次に、dapm_power_one_widget 関数が順番に実行されます。

dapm_widget_power_check を通じて、ウィジェットの power_check コールバック関数を呼び出して、ウィジェットの新しい電源状態を取得します。
dapm_widget_set_power を呼び出して、それに接続されている隣接ウィジェットに「感染」させます。
ソース ウィジェットをトラバースし、dapm_widget_set_peer_power 関数を使用して、接続された状態のソース ウィジェットを dapm_dirty リンク リストに追加します。
シンク ウィジェットをトラバースし、dapm_widget_set_peer_power 関数を使用して、接続されたシンク ウィジェットを dapm_dirty リンク リストに追加します。
最初のステップで取得した新しい電源状態に従って、ウィジェットを up_list または down_list リンク リストに追加します。

この関数により、ウィジェットの状態が変化すると、隣接するウィジェットが「感染」し、dapm_dirty リンク リストの末尾に追加されることがわかります。そのため、リンク リストの末尾がスキャンされると、隣接ウィジェットはも同じ操作を実行するため、dapm_dirty リンク リストに新しいウィジェットが追加されなくなるまで、隣接するネイバーに「感染」します。この時点で、影響を受けるすべてのウィジェットが up_list または down_li リンク リストに追加され、その後の電源投入を待機します。電源オフ操作。


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(保留中);     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 *ソート;








    if (power_up)
        sort = dapm_up_seq;
    そうでなければ
        ソート= dapm_down_seq;

    list_for_each_entry_safe(w, n, リスト, 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);
    }
}

電源をオンまたはオフにする必要があるすべてのウィジェットが dapm_dirty リンク リストに追加されると、down_list リンク リストのウィジェットは dapm_seq_run によって処理され、リンク リストのウィジェットは順番に電源がオフになり、その後、ウィジェット内の kcontrol は dapm_widget_update によって更新されます (この kcontrol は通常、この状態変更をトリガーするトリガー ソースです)。次に、up_list リンク リスト上のウィジェットが apm_seq_run によって処理され、リンク リスト上のウィジェットが順番にパワーオンされます。 . 最終的な電源オンまたは電源オフ操作は、コーデック レジスタを介して実装する必要があります。これは、ウィジェットを定義するときに、それが電源制御を備えたウィジェットである場合、reg/shift などのフィールドの設定値を提供する必要があるためです。 、ウィジェットが電源状態を制御するためにレジスタを必要としない場合、reg フィールドに次の値を割り当てる必要があります。

    SND_SOC_NOPM (このマクロで定義される実際の値は -1 です)

特定の実装に関して、dapm フレームワークはトリックを使用します。同じ電源オンおよび電源オン シーケンス内の複数のウィジェットが同じレジスタ アドレスを使用する場合 (レジスタは、異なるウィジェットの電源ステータスを制御するために異なるビットを使用する場合があります)、dapm_seq_run dapm_seq_run_coalesced 関数を渡します これらのウィジェットの変更をマージし、マージされた値を一度だけレジスタに書き込む必要があります。

おすすめ

転載: blog.csdn.net/qq_48709036/article/details/124293771