程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - ASoC Machine驱动基础

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC :16GB
LPDDR3 :4GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot :2023.04
linux    :6.3
----------------------------------------------------------------------------------------------------------------------------

Rockchip RK3399 - ALSA子系统我们介绍了ALSA子系统的软件架构,同时介绍了ALSA CORE核心数据结构和相关API。本节我们将会介绍ASoC软件体系中音频三大驱动模块(Codec、Platform 和Machine)中的Machine。

Machine driver描述了如何控制platform、codec、cpu dai(Digital Audio Interface,数字音频接口)和codec dai,使得互相配合在一起工作。单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

一、核心数据结构

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,描述Machine 的最主要的几个数据结构分别是:snd_soc_card,snd_soc_dai,snd_soc_dai_driver、snd_soc_dai_link;当然了此外还有一些操作集相关的数据结构,比如snd_soc_ops、snd_soc_dai_ops;

  • snd_soc_card:ASoC中的核心数据结构,和ASLA CORE中的snd_card地位一样;用于对ASocC中的声卡设备进行统一抽象;可以认为snd_soc_card是整个 ASoc 数据结构的根本,由它开始,引出一系列的数据结构用于表述音频的各个特性和功能;snd_soc_card数据结构中引出了snd_soc_dai_link结构;
  • snd_soc_dai和snd_soc_dai_driver:用于描述dai以及dai驱动,根据codec端和soc端,分为codec_dai 和cpu_dai,在ASoC的Platform驱动和Codec驱动中也会使用到;所以这个我们单独拎出来说;
  • snd_soc_dai_link:用来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字;

1.1 struct snd_soc_card

ASoC中使用struct snd_soc_card数据结构来描述SoC声卡的所有信息,需要将该数据结构与我们上一节介绍的ALSA CORE中的struct snd_card区分开来;struct snd_soc_card定义在include/sound/soc.h;

/* SoC card */
struct snd_soc_card {
        const char *name;
        const char *long_name;
        const char *driver_name;
        const char *components;
#ifdef CONFIG_DMI
        char dmi_longname[80];
#endif /* CONFIG_DMI */
        char topology_shortname[32];

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

        struct mutex mutex;
        struct mutex dapm_mutex;

        /* Mutex for PCM operations */
        struct mutex pcm_mutex;
        enum snd_soc_pcm_subclass pcm_subclass;

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

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

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

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

        long pmdown_time;
        /* CPU <--> Codec DAI links  */
        struct snd_soc_dai_link *dai_link;  /* predefined links only */
        int num_links;  /* predefined links only */

        struct list_head rtd_list;
        int num_rtd;

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

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

        const struct snd_kcontrol_new *controls;
        int num_controls;

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

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

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

        /* attached dynamic objects */
        struct list_head dobj_list;

        /* Generic DAPM context for the card */
        struct snd_soc_dapm_context dapm;
        struct snd_soc_dapm_stats dapm_stats;
        struct snd_soc_dapm_update *update;
#ifdef CONFIG_DEBUG_FS
        struct dentry *debugfs_card_root;
#endif
#ifdef CONFIG_PM_SLEEP
        struct work_struct deferred_resume_work;
#endif
        u32 pop_time;

        /* bit field */
        unsigned int instantiated:1;
        unsigned int topology_shortname_created:1;
        unsigned int fully_routed:1;
        unsigned int disable_route_checks:1;
        unsigned int probed:1;
        unsigned int component_chaining:1;

        void *drvdata;
};

这个数据结构的内容比较多,我们只挑一些重点说一下:

  • name:声卡的名称;如果使用设备树,保存解析设备节点label或者simple-audio-card,name得到的信息;
  • long_name:更详细的名称;
  • driver_name:驱动程序的名称;
  • components:component标识符;
  • dev:分配给此声卡的设备;一般设置为平台设备的device;
  • snd_card:ALSA CORE中的声卡设备;
  • owner:指向驱动程序拥有者模块的指针;
  • pcm_subclass:PCM子类的枚举类型;
  • probe:probe是可选的函数,注册当前声卡设备时会回调该函数;
  • late_probe:late_probe是可选的函数,注册当前声卡设备时会回调该函数;
  • remove:remove是可选的函数,用于在设备被移除时执行特定的操作;
  • add_dai_link:snd_soc_add_pcm_runtime函数执行时会回调该函数;
  • remove_dai_link:
  • dai_link:指向动态分配得到的数组,每个元素都一个struct snd_soc_dai_link,即每一元素描述了一条音频数据链路(如果使用设备树,保存解析设备节点simple-audio-card,cpu、simple-audio-card,codec得到的dai_link);
  • num_links:dai_link指向数组的长度;
  • rtd_list:保存pcm runtime的链表;链表中存储的是struct snd_soc_pcm_runtime;
  • codec_conf:指向动态分配得到的数组,每个元素都是一个struct snd_soc_codec_conf,即每个元素描述一个codec_conf;
  • num_configs:codec_conf指向数组的长度;
  • aux_dev:指向动态分配得到的数组,每个元素都是一个struct snd_soc_aux_dev;如果使用设备树,保存解析设备节点simple-audio-card,aux-devs得到的aux_dev;
  • num_aux_devs:aux_dev指向的数组的长度;
  • controls:指向动态分配得到的数组,每个元素都是一个struct snd_kcontrol;如果使用设备树,保存解析设备节点simple-audio-card,pin-switches得到的kcontrol信息;
  • num_controls:controls指向的数组的长度;
  • dapm_widgets:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_widget ,即每个元素描述了一个widget控件;
  • num_dapm_widgets:dapm_widgets指向的数组的长度;
  • dapm_routes:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_route;
  • num_dapm_routes:dapm_routes指向的数组的长度;
  • of_dapm_widgets:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_widget ;如果使用设备树,保存解析设备节点simple-audio-card,widgets得到的音频控件信息;
  • num_of_dapm_widgets:of_dapm_widgets指向的数组的长度;
  • of_dapm_routes:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dapm_route;如果使用设备树,保存解析设备节点simple-audio-card,routing得到的音频路由信息;
  • num_of_dapm_routes:of_dapm_routes指向的数组的长度;
  • component_dev_list:保存component的链表;链表中存放的数据类型为struct snd_soc_component;
  • widgets:保存widget的链表;链表中存放的数据类型为struct snd_soc_dapm_widget;
  • paths:保存path的链表;链表中存放的数据类型为struct snd_soc_dapm_path;
  • dapm_list:保存dapm域的链表;链表中存放的数据类型为struct snd_soc_dapm_context;
  • dapm_dirty:保存状态(包括电源状态、连接状态)发生改变的widget;链表中存放的数据类型为struct snd_soc_dapm_widget;
  • dapm:dapm上下文,struct snd_soc_dapm_context类型;
  • deferred_resume_work:用于在系统挂起(suspend)后执行延迟的恢复操作,func函数被设置为soc_resume_deferred;
  • drvdata:驱动程序的私有数据结构;

1.2 struct snd_soc_dai_link

ASoC使用struct snd_soc_dai_link数据结构来描述音频数据链路以及板级操作函数,在snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字,Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的Platform驱动和Codec驱动的代码文件中定义的。

struct snd_soc_dai_link定义在include/sound/soc.h:

struct snd_soc_dai_link {
        /* config - must be set by machine driver */
        const char *name;                       /* Codec name */
        const char *stream_name;                /* Stream name */

        /*
         * You MAY specify the link's CPU-side device, either by device name,
         * or by DT/OF node, but not both. If this information is omitted,
         * the CPU-side DAI is matched using .cpu_dai_name only, which hence
         * must be globally unique. These fields are currently typically used
         * only for codec to codec links, or systems using device tree.
         */
        /*
         * 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.
         */
        struct snd_soc_dai_link_component *cpus;
        unsigned int num_cpus;

        /*
         * You MUST specify the link's codec, either by device name, or by
         * DT/OF node, but not both.
         */
        /* You MUST specify the DAI name within the codec */
        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. In such case, platforms are not mandatory.
         */
        struct snd_soc_dai_link_component *platforms;
        unsigned int num_platforms;

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

        /* codec/machine specific exit - dual of init() */
        void (*exit)(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 */
        unsigned int nonatomic:1;

        /* 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_rate:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_sample_bits: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;

        /* 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;
        /* DPCM used FE & BE merged rate */
        unsigned int dpcm_merged_rate:1;

        /* pmdown_time is ignored at stop */
        unsigned int ignore_pmdown_time:1;

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

        /* This flag will reorder stop sequence. By enabling this flag
         * DMA controller stop sequence will be invoked first followed by
         * CPU DAI driver stop sequence
         */
        unsigned int stop_dma_first:1;

#ifdef CONFIG_SND_SOC_TOPOLOGY
        struct snd_soc_dobj dobj; /* For topology */
#endif
};

这个数据结构的内容比较多,我们只挑一些重点说一下:

  • name:指定名称,必须配置;
  • stream_name:指定Stream名称,必须配置;
  • cpus:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(名称/设备树节点(二选一)以及dai的名称),即保存当前音频数据链路上的所有cpu设备
  • num_cpus:cpus指向的数组长度,一般来说1条音频数据链路只有1个CPU、1个Codec;
  • codecs:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(名称/设备树节点(二选一)以及dai名称),即保存当前音频数据链路上的所有codec设备;
  • num_codecs:codec指向的数组长度,一般来说1条音频数据链路只有1个CPU、1个Codec;
  • platforms:指向动态分配得到的数组,每个元素都是一个struct snd_soc_dai_link_component(通过名称/设备树节点字段指定cpu测的platform驱动名称,通常都是DMA驱动,用于音频数据传输);
  • num_platforms:platforms指向的数组长度;
  • id:可选的链接 ID,用于识别Machine driver link;
  • params:指定PCM流参数;
  • num_params:PCM流参数数量;
  • dai_fmt:数字音频接口格式;
  • trigger:DPCM(Direct Pulse Code Modulation)触发类型;
  • init:初始化函数,例如添加Machine controls;
  • exit:退出函数;
  • be_hw_params_fixup:可选的硬件参数重写函数;
  • ops:音频相关的操作集;重点留意 hw_params回调,一般来说这个回调是要实现的,用于配置 codec、codec_dai、cpu_dai 的数据格式和系统时钟;
  • compr_ops:数据压缩操作函数;
  • nonatomic:标记 PCM 是否使用非原子操作;
  • playback_only:标记 PCM 流是否只支持播放;
  • capture_only:标记 PCM 流是否只支持捕获;
  • ignore_suspend:标记 PCM 是否在挂起时保持 DAI 活动状态;
  • symmetric_rate:标记 PCM 采样率是否对称;
  • symmetric_channels:标记 PCM 通道数是否对称;
  • symmetric_sample_bits:标记 PCM 采样位数是否对称;
  • no_pcm:标记 PCM 流是否需要创建;
  • dynamic:标记该 dai link是否可以在运行时路由到其他 dai link,具体可以参考DPCM相关内容;
  • dpcm_capture:标记是否支持 DPCM 捕获;
  • dpcm_playback:标记是否支持 DPCM 播放;
  • dpcm_merged_format:标记是否使用合并格式的 DPCM;
  • dpcm_merged_chan:标记是否使用合并通道的 DPCM;
  • dpcm_merged_rate:标记是否使用合并采样率的 DPCM;
  • ignore_pmdown_time:标记是否忽略 pmdown_time 停止时间;pmdown_time 是一种 PCM 的停止时间戳,用于控制 PCM 流在空闲一段时间后自动停止以降低功耗;
  • ignore:标记该dai link是否需要创建 PCM;
  • stop_dma_first:标记是否对停止序列进行排序;
1.3.1 snd_soc_dai_link_component 

ASoC使用struct snd_soc_dai_link_component来描述音频数据链路中的component,主要目的就是为了定位到snd_soc_component、snd_soc_dai,定义在include/sound/soc.h;

struct snd_soc_dai_link_component {
        const char *name;
        struct device_node *of_node;
        const char *dai_name;
};

其中:

  • name:名称,在非设备树场景使用;如果指定了这个,音频数据链路可以通过这个字段到全局链表component_list中查找与之匹配的component;
  • of_node:设备树节点,在设备树场景使用;如果指定了这个,音频数据链路可以通过这个字段到全局链表component_list中查找与之匹配的component;
  • dai_name:dai的名称,音频数据链路就是通过这个字段到匹配的component的dai_list链表中查找与之匹配的dai;
1.3.2 struct snd_soc_ops

ASoC使用struct snd_soc_ops来描述ALSA PCM操作集,定义在include/sound/soc.h;

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

其中:

  • startup:在 PCM子流上启动音频传输期间调用;
  • shutdown:在PCM子流上关闭音频传输期间调用;
  • hw_params:更改PCM子流硬件参数期间调用;
  • hw_free:释放PCM子流资源期间调用;
  • prepare:准备PCM子流传输期间调用;
  • trigger:在PCM子流上触发执行操作期间调用;

1.3 DAI

DAI全称数字音频接口,根据codec端和soc端,分为codec_dai、cpu_dai,实际上dai也是一种widget。

描述dai的最主要的几个数据结构分别是:snd_soc_dai、snd_soc_dai_driver、snd_soc_dai_ops;

以cpu dai driver为例,每个cpu dai driver必须提供以下功能:

  • DAI描述信息;
  • DAI配置信息;
  • PCM描述信息;
  • 系统时钟(SYSCLK)配置;
  • 挂起和恢复(可选);

以codec dai driver为例,每个codec dai driver必须提供以下功能:

  • Codec DAI和PCM的配置信息;
  • Codec的控制接口,如I2C/SPI;
  • Mixer和其它音频控件;
  • Codec的音频操作;
1.3.1 struct snd_soc_dai

ALSA中使用struct snd_soc_dai数据结构来描述dai,定义在include/sound/soc-dai.h:

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

        /* driver ops */
        struct snd_soc_dai_driver *driver;

        /* DAI runtime info */
        struct snd_soc_dai_stream stream[SNDRV_PCM_STREAM_LAST + 1];

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

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

        struct list_head list;

        /* function mark */
        struct snd_pcm_substream *mark_startup;
        struct snd_pcm_substream *mark_hw_params;
        struct snd_pcm_substream *mark_trigger;
        struct snd_compr_stream  *mark_compr_startup;

        /* bit field */
        unsigned int probed:1;
};
 

该数据结构包含以下字段:

  • name:dai的名称,音频数据链路在component的dai_list链表中查找dai时,就是通过该字段匹配来完成的;snd_soc_dai一般是在注册component时动态创建的,name字段值取自与之关联的snd_soc_dai_driver的name;
  • id:dai的标识符;
  • dev:指向包含该dai的设备的指针;
  • driver:指向dai驱动结构的指针;
  • stream:录音流和播放流的数组,其中包含有关流的信息;
  • rate:如果强制对称,则为采样率;
  • channels:如果强制对称,则为通道数;
  • sample_bits:如果强制对称,则为采样位数;
  • component:指向component(通常是 platform或codec)的指针;
  • list:用于构建链表节点,用于将该dai添加到到component的dai_list链表中;
  • mark_startup:用于标记PCM启动事件的指针;
  • mark_hw_params:用于标记PCM硬件参数变化事件的指针;
  • mark_trigger:用于标记PCM触发事件的指针;
  • mark_compr_startup:用于标记压缩流启动事件的指针;
  • probed:标记dai是否已经探测完成;
1.3.2  struct snd_soc_dai_stream 

ALSA中使用struct snd_soc_dai_stream数据结构来表示音频数据流,定义在include/sound/soc-dai.h:

/* for Playback/Capture */
struct snd_soc_dai_stream {
        struct snd_soc_dapm_widget *widget;

        unsigned int active;    /* usage count */
        unsigned int tdm_mask;  /* CODEC TDM slot masks and params (for fixup) */

        void *dma_data;         /* DAI DMA data */
};

数据结构中有一个widget指针,可以用来代表播放流/录音流。

1.3.3 struct snd_soc_dai_driver

ALSA中使用struct snd_soc_dai_driver数据结构来描述DAI驱动,包括DAI和PCM的能力和操作,定义在include/sound/soc-dai.h:

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

        /* DAI driver callbacks */
        int (*probe)(struct snd_soc_dai *dai);
        int (*remove)(struct snd_soc_dai *dai);
        /* compress dai */
        int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
        /* Optional Callback used at pcm creation*/
        int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
                       struct snd_soc_dai *dai);

        /* 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_rate:1;
        unsigned int symmetric_channels:1;
        unsigned int symmetric_sample_bits:1;

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

该数据结构包含了以下字段:

  • name:指定dai的名称;
  • id:可选的dai标识符,用于在注册期间区分多个dai;
  • base:可选的dai寄存器基地址;
  • dobj:dai对象,包含dai及其父组件的句柄;
  • probe:可选的dai探测回调函数;注册声卡时回调;
  • remove:可选的dai卸载回调函数;
  • compress_new:可选的压缩dai创建回调函数;
  • pcm_new:可选的 PCM 创建回调函数;
  • ops:指向本dai的snd_soc_dai_ops结构,即dai的操作集,这个操作集非常重要,用于dai的时钟配置、格式配置、硬件参数配置;
  • cops:dai压缩操作函数指针表;
  • capture:描述capture的能力;如回放设备所支持的声道数、采样率、音频格式;非常重要的字段;
  • playbackk:描述playback的能力;如录制设备所支持声道数、采样率、音频格式;非常重要的字段;
  • symmetric_rate:标记dai采样率是否对称;
  • symmetric_channels:标记dai通道数是否对称;
  • symmetric_sample_bits:标记dai采样位数是否对称;
  • probe_order:probe函数执行顺序;
  • remove_order:remove函数执行顺序;
1.3.4 struct snd_soc_dai_ops
ASoC使用struct snd_soc_dai_ops数据结构描述dai的控制和参数配置,这些函数包括对dai时钟配置、对 dai格式配置、对 TDM(时分复用)通道配置以及对 ALSA PCM音频操作的配置等。定义在include/sound/soc-dai.h:
struct snd_soc_dai_ops {
        /*
         * DAI clocking configuration, all optional.
         * Called by soc_card drivers, normally in their hw_params.
         */
        int (*set_sysclk)(struct snd_soc_dai *dai,
                int clk_id, unsigned int freq, int dir);
        int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
                unsigned int freq_in, unsigned int freq_out);
        int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
        int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

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

        int (*set_stream)(struct snd_soc_dai *dai,
                          void *stream, int direction);
        void *(*get_stream)(struct snd_soc_dai *dai, int direction);

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

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

        /*
         * Format list for auto selection.
         * Format will be increased if priority format was
         * not selected.
         * see
         *      snd_soc_dai_get_fmt()
         */
        u64 *auto_selectable_formats;
        int num_auto_selectable_formats;

        /* bit field */
        unsigned int no_capture_mute:1;
};

具体函数成员包括:

  • set_sysclk: 配置dai的时钟源;
  • set_pll: 配置dai的PLL(锁相环);
  • set_clkdiv: 配置dai的时钟分频;
  • set_bclk_ratio: 配置dai的BCLK(Bit Clock)比率;
  • set_fmt: 配置dai的数据格式;
  • xlate_tdm_slot_mask: 将TDM槽位映射为掩码;
  • set_tdm_slot: 配置dai的TDM槽位;
  • set_channel_map: 配置dai的通道映射;
  • get_channel_map: 获取dai的通道映射;
  • set_tristate: 配置dai的三态(tri-state)设置;
  • set_stream / get_stream: 设置/获取dai的数据流;
  • mute_stream: 静音dai的数据流;
  • startup / shutdown: 在PCM音频操作期间启动/关闭dai;
  • hw_params: 配置PCM音频的硬件参数;
  • hw_free: 释放PCM音频的硬件资源;
  • prepare: 准备PCM音频操作;
  • trigger: 触发PCM音频操作;
  • bespoke_trigger: 自定义触发PCM音频操作;
  • delay: 返回基于硬件FIFO导致的延迟;
  • auto_selectable_formats: 自动选择的格式列表;
  • num_auto_selectable_formats: 自动选择的格式数量;
  • no_capture_mute: 捕获静音标志位;

这些函数指针在dai driver中实现,并通过structsnd_soc_dai_driver的ops成员导出,供上层音频驱动程序使用。

二、 DAPM Widget&Route&Path

在介绍注册声卡设备之前,有必要先了解Rockchip RK3399 - DAPM Widget&Route&Path

三、注册声卡设备

Machine驱动的注册是通过devm_snd_soc_register_card函数实现的,函数内部调用snd_soc_register_card,该函数用于向ASoC CORE注册一个声卡设备,函数定义在sound/soc/soc-core.c:

/**
 * snd_soc_register_card - Register a card with the ASoC core
 *
 * @card: Card to register
 *
 */
int snd_soc_register_card(struct snd_soc_card *card)
{
        if (!card->name || !card->dev)
                return -EINVAL;

        dev_set_drvdata(card->dev, card);

        INIT_LIST_HEAD(&card->widgets);
        INIT_LIST_HEAD(&card->paths);
        INIT_LIST_HEAD(&card->dapm_list);
        INIT_LIST_HEAD(&card->aux_comp_list);
        INIT_LIST_HEAD(&card->component_dev_list);
        INIT_LIST_HEAD(&card->list);
        INIT_LIST_HEAD(&card->rtd_list);
        INIT_LIST_HEAD(&card->dapm_dirty);
        INIT_LIST_HEAD(&card->dobj_list);

        card->instantiated = 0;
        mutex_init(&card->mutex);
        mutex_init(&card->dapm_mutex);
        mutex_init(&card->pcm_mutex);

        return snd_soc_bind_card(card);
}
View Code

该函数接受一个 struct snd_soc_card 结构体作为参数,函数指向流程如下:

  • 首先检查传入的 card 结构体是否具有合法的 name 和 dev 成员,如果缺少其中任何一个成员,则返回 -EINVAL 错误代码;
  • 接着,函数使用dev_set_drvdata 函数设置card->dev 的driver_data为card。这是方便在后续的操作中可以通过设备句柄来获取到对应的card 结构体;
  • 然后,函数使用 INIT_LIST_HEAD 宏初始化card结构体中的各个链表成员,包括 widgets、paths、dapm_list、aux_comp_list、component_dev_list、list、rtd_list、dapm_dirty 和 dobj_list;
  • 接下来,函数将 card->instantiated 初始化为 0,表示该声卡尚未实例化。然后,初始化card中的互斥锁:mutex、dapm_mutex 和 pcm_mutex;
  • 最后,函数调用 snd_soc_bind_card 函数来绑定声卡,并返回其结果;

snd_soc_bind_card函数定义在sound/soc/soc-core.c:

static int snd_soc_bind_card(struct snd_soc_card *card)
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_component *component;
        struct snd_soc_dai_link *dai_link;
        int ret, i;

        mutex_lock(&client_mutex);       // 获取互斥锁
        mutex_lock_nested(&card->mutex, SND_SOC_CARD_CLASS_INIT);  // 获取互斥锁

        snd_soc_dapm_init(&card->dapm, card, NULL);  // dapm初始化

        /* check whether any platform is ignore machine FE and using topology */
        soc_check_tplg_fes(card);

        /* bind aux_devs too */
        ret = soc_bind_aux_dev(card);  // 忽略即可
        if (ret < 0)
                goto probe_end;

        /* add predefined DAI links to the list */
        card->num_rtd = 0;
        // 遍历声卡的所有音频数据链路,为每个dai_link创建一个snd_soc_pcm_runtime,并添加到声卡card的rtd_list链表中
        for_each_card_prelinks(card, i, dai_link) {   
                ret = snd_soc_add_pcm_runtime(card, dai_link); 
                if (ret < 0)
                        goto probe_end;
        }

        soc_init_card_debugfs(card);  // 调试信息

        soc_resume_init(card);    // 忽略即可

        // 根据card->dapm_widgets模板重新申请内存并初始化各个widget,每一个widget添加到声卡card的widgets链表中
        ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,  
                                        card->num_dapm_widgets);
        if (ret < 0)
                goto probe_end;

        // 根据card->of_dapm_widgets模板重新申请内存并初始化各个widget,每一个widget添加到声卡card的widgets链表中
        ret = snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,  
                                        card->num_of_dapm_widgets);
        if (ret < 0)
                goto probe_end;

        /* initialise the sound card only once  */
        ret = snd_soc_card_probe(card);  // 回调card->probe函数
        if (ret < 0)
                goto probe_end;

        /* probe all components used by DAI links on this card */
        // 遍历card->rtd_list中的所有rtd,然后再次遍历rtd->component_list, 为所有用到的components调用soc_probe_component函数
        ret = soc_probe_link_components(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to instantiate card %d\n", ret);
                goto probe_end;
        }

        /* probe auxiliary components */
        ret = soc_probe_aux_devices(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to probe aux component %d\n", ret);
                goto probe_end;
        }

        /* probe all DAI links on this card */
        // 遍历card->rtd_list中的所有rtd,调用soc_probe_link_dais函数
        ret = soc_probe_link_dais(card);
        if (ret < 0) {
                dev_err(card->dev,
                        "ASoC: failed to instantiate card %d\n", ret);
                goto probe_end;
        }

        for_each_card_rtds(card, rtd) {
                ret = soc_init_pcm_runtime(card, rtd);
                if (ret < 0)
                        goto probe_end;
        }

        snd_soc_dapm_link_dai_widgets(card);
        snd_soc_dapm_connect_dai_link_widgets(card);

        ret = snd_soc_add_card_controls(card, card->controls,
                                        card->num_controls);
        if (ret < 0)
                goto probe_end;

        // 遍历card->dapm_routes数组,调用snd_soc_dapm_add_routes将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构,
        // 然后将snd_soc_dapm_path注册到声卡card的paths链表
        ret = snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
                                      card->num_dapm_routes);
        if (ret < 0) {
                if (card->disable_route_checks) {
                        dev_info(card->dev,
                                 "%s: disable_route_checks set, ignoring errors on add_routes\n",
                                 __func__);
                } else {
                        dev_err(card->dev,
                                 "%s: snd_soc_dapm_add_routes failed: %d\n",
                                 __func__, ret);
                        goto probe_end;
                }
        }

        // 遍历card->of_dapm_routes数组,调用snd_soc_dapm_add_routes将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构,
        // 然后将snd_soc_dapm_path注册到声卡card的paths链表
        ret = snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
                                      card->num_of_dapm_routes);
        if (ret < 0)
                goto probe_end;

        /* try to set some sane longname if DMI is available */
        snd_soc_set_dmi_name(card, NULL);

        soc_setup_card_name(card, card->snd_card->shortname,
                            card->name, NULL);
        soc_setup_card_name(card, card->snd_card->longname,
                            card->long_name, card->name);
        soc_setup_card_name(card, card->snd_card->driver,
                            card->driver_name, card->name);

        if (card->components) {
                /* the current implementation of snd_component_add() accepts */
                /* multiple components in the string separated by space, */
                /* but the string collision (identical string) check might */
                /* not work correctly */
                ret = snd_component_add(card->snd_card, card->components);
                if (ret < 0) {
                        dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n",
                                card->name, ret);
                        goto probe_end;
                }
        }

        ret = snd_soc_card_late_probe(card);
        if (ret < 0)
                goto probe_end;

        snd_soc_dapm_new_widgets(card);
        snd_soc_card_fixup_controls(card);

        // 注册声卡
        ret = snd_card_register(card->snd_card);
        if (ret < 0) {
                dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
                                ret);
                goto probe_end;
        }

        card->instantiated = 1;
        dapm_mark_endpoints_dirty(card);
        snd_soc_dapm_sync(&card->dapm);

        /* deactivate pins to sleep state */
        for_each_card_components(card, component)
                if (!snd_soc_component_active(component))
                        pinctrl_pm_select_sleep_state(component->dev);

probe_end:
        if (ret < 0)
                soc_cleanup_card_resources(card);

        mutex_unlock(&card->mutex);    // 释放互斥锁 
        mutex_unlock(&client_mutex);   // 释放互斥锁

        return ret;

 


}

由于该函数内部流程比较繁琐,因此我们将流程以时序图显示,如下:

3.1 snd_soc_dapm_init

snd_soc_dapm_init函数定义在sound/soc/soc-dapm.c:

void snd_soc_dapm_init(struct snd_soc_dapm_context *dapm,   // dapm上下文
                       struct snd_soc_card *card,           // ASoC声卡设备
                       struct snd_soc_component *component)  // NULL
{
        dapm->card              = card;
        dapm->component         = component;
        dapm->bias_level        = SND_SOC_BIAS_OFF;

        if (component) {    // NULL
                dapm->dev               = component->dev;
                dapm->idle_bias_off     = !component->driver->idle_bias_on;
                dapm->suspend_bias_off  = component->driver->suspend_bias_off;
        } else {
                dapm->dev               = card->dev;  // 设置为Machine驱动平台设备的device
        }

        INIT_LIST_HEAD(&dapm->list);             // 初始化链表节点
        /* see for_each_card_dapms */
        list_add(&dapm->list, &card->dapm_list);  // 将dapm->list节点添加到链表card->dapm_list中
}

3.2 soc_check_tplg_fes

soc_check_tplg_fes函数定义在sound/soc/soc-core.c:

static void soc_check_tplg_fes(struct snd_soc_card *card)
{
        struct snd_soc_component *component;
        const struct snd_soc_component_driver *comp_drv;
        struct snd_soc_dai_link *dai_link;
        int i;

        for_each_component(component) {   // 遍历component_list全局列表

                /* does this component override BEs ? */
                if (!component->driver->ignore_machine)
                        continue;

                /* for this machine ? */
                if (!strcmp(component->driver->ignore_machine,
                            card->dev->driver->name))
                        goto match;
                if (strcmp(component->driver->ignore_machine,
                           dev_name(card->dev)))
                        continue;
match:
                /* machine matches, so override the rtd data */
                for_each_card_prelinks(card, i, dai_link) {

                        /* ignore this FE */
                        if (dai_link->dynamic) {
                                dai_link->ignore = true;
                                continue;
                        }

                        dev_dbg(card->dev, "info: override BE DAI link %s\n",
                                card->dai_link[i].name);

                        /* override platform component */
                        if (!dai_link->platforms) {
                                dev_err(card->dev, "init platform error");
                                continue;
                        }

                        if (component->dev->of_node)
                                dai_link->platforms->of_node = component->dev->of_node;
                        else
                                dai_link->platforms->name = component->name;

                        /* convert non BE into BE */
                        if (!dai_link->no_pcm) {
                                dai_link->no_pcm = 1;

                                if (dai_link->dpcm_playback)
                                        dev_warn(card->dev,
                                                 "invalid configuration, dailink %s has flags no_pcm=0 and dpcm_playback=1\n",
                                                 dai_link->name);
                                if (dai_link->dpcm_capture)
                                        dev_warn(card->dev,
                                                 "invalid configuration, dailink %s has flags no_pcm=0 and dpcm_capture=1\n",
                                                 dai_link->name);

                                /* convert normal link into DPCM one */
                                if (!(dai_link->dpcm_playback ||
                                      dai_link->dpcm_capture)) {
                                        dai_link->dpcm_playback = !dai_link->capture_only;
                                        dai_link->dpcm_capture = !dai_link->playback_only;
                                }
                        }

                        /*
                         * override any BE fixups
                         * see
                         *      snd_soc_link_be_hw_params_fixup()
                         */
                        dai_link->be_hw_params_fixup =
                                component->driver->be_hw_params_fixup;

                        /*
                         * most BE links don't set stream name, so set it to
                         * dai link name if it's NULL to help bind widgets.
                         */
                        if (!dai_link->stream_name)
                                dai_link->stream_name = dai_link->name;
                }

                /* Inform userspace we are using alternate topology */
                if (component->driver->topology_name_prefix) {

                        /* topology shortname created? */
                        if (!card->topology_shortname_created) {
                                comp_drv = component->driver;

                                snprintf(card->topology_shortname, 32, "%s-%s",
                                         comp_drv->topology_name_prefix,
                                         card->name);
                                card->topology_shortname_created = true;
                        }

                        /* use topology shortname */
                        card->name = card->topology_shortname;
                }
        }
}
View Code

3.3 soc_bind_aux_dev

soc_bind_aux_dev函数定义在sound/soc/soc-core.c,用于遍历card->aux_dev,由于我们未使用aux dev,忽略即可;

static int soc_bind_aux_dev(struct snd_soc_card *card)
{
        struct snd_soc_component *component;
        struct snd_soc_aux_dev *aux;
        int i;

        for_each_card_pre_auxs(card, i, aux) {
                /* codecs, usually analog devices */
                component = soc_find_component(&aux->dlc);
                if (!component)
                        return -EPROBE_DEFER;

                /* for snd_soc_component_init() */
                snd_soc_component_set_aux(component, aux);
                /* see for_each_card_auxs */
                list_add(&component->card_aux_list, &card->aux_comp_list);
        }
        return 0;
}
View Code

3.4 snd_soc_add_pcm_runtime(重点)

遍历声卡的所有音频数据链路card->dai_link,调用snd_soc_add_pcm_runtime函数为每一个dai_link分配一个snd_soc_pcm_runtime实例,并将其添加到card的rtd_list链表中。然后snd_soc_pcm_runtime 是ASoC的桥梁,初始化成员dais、components,其中:

  •  rtd->dais数组保存着当前音频链路上的所有dai;
  •  rtd->components数组保存着当前音频链路上的所有component;

snd_soc_add_pcm_runtime函数定义在sound/soc/soc-core.c;

/**
 * snd_soc_add_pcm_runtime - Add a pcm_runtime dynamically via dai_link
 * @card: The ASoC card to which the pcm_runtime is added
 * @dai_link: The DAI link to find pcm_runtime
 *
 * This function adds a pcm_runtime ASoC card by using dai_link.
 *
 * Note: Topology can use this API to add pcm_runtime when probing the
 * topology component. And machine drivers can still define static
 * DAI links in dai_link array.
 */
int snd_soc_add_pcm_runtime(struct snd_soc_card *card,          // ASoC声卡  
                            struct snd_soc_dai_link *dai_link)  // 每一个音频数据链路
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_dai_link_component *codec, *platform, *cpu;
        struct snd_soc_component *component;
        int i, ret;

        lockdep_assert_held(&client_mutex);

        /*
         * Notify the machine driver for extra initialization
         */
        ret = snd_soc_card_add_dai_link(card, dai_link);   // 如果定义了card->add_dai_link回调函数,将会执行该回调函数;
        if (ret < 0)
                return ret;

        if (dai_link->ignore)  
                return 0;

        dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);

        ret = soc_dai_link_sanity_check(card, dai_link);
        if (ret < 0)
                return ret;

        rtd = soc_new_pcm_runtime(card, dai_link);  // 创建一个新的snd_soc_pcm_runtime,并进行成员的初始化
        if (!rtd)
                return -ENOMEM;

        for_each_link_cpus(dai_link, i, cpu) {    // cpu = dai_link->cpus[i]
                asoc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);   // 根据音频数据链路组件信息查找snd_soc_dai,并赋值给rtd->dais[i]
                if (!asoc_rtd_to_cpu(rtd, i)) {
                        dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
                                 cpu->dai_name);
                        goto _err_defer;
                }
                snd_soc_rtd_add_component(rtd, asoc_rtd_to_cpu(rtd, i)->component);  // 绑定component到rtd,即rtd->components[rtd->num_components] = component
        }

        /* Find CODEC from registered CODECs */
        for_each_link_codecs(dai_link, i, codec) {   // codec = dai_link->codecs[i]
                asoc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);  // 根据音频数据链路组件信息查找snd_soc_dai,并赋值给rtd->dais[i + (rtd)->dai_link->num_cpus]
                if (!asoc_rtd_to_codec(rtd, i)) {
                        dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n",
                                 codec->dai_name);
                        goto _err_defer;
                }

                snd_soc_rtd_add_component(rtd, asoc_rtd_to_codec(rtd, i)->component);  // 绑定component到rtd,即rtd->components[rtd->num_components] = component
        }

        /* Find PLATFORM from registered PLATFORMs */
        for_each_link_platforms(dai_link, i, platform) { // platform = dai_link->platforms[i]
                for_each_component(component) { // 遍历component_list全局链表中每一个component,查找满足如下条件的component:
              // component->dev->of_node == platform->of_node
if (!snd_soc_is_matching_component(platform, component)) continue; snd_soc_rtd_add_component(rtd, component); } } return 0; _err_defer: snd_soc_remove_pcm_runtime(card, rtd); return -EPROBE_DEFER; }

 snd_soc_add_pcm_runtime主要完成以下几项工作

  • 检查dai_link是否有匹配的设备;
  • 为音频数据链路创建一个snd_soc_pcm_runtime,并进行成员的初始化,同时将snd_soc_pcm_runtime通过链表节点形式add到 snd_soc_card->rtd_list 中;
  • 绑定component及到snd_soc_pcm_runtime的components数组中;
    • 根据cpu_name/cpu_of_node/cpu_dai_name从全局链表component_list中找到匹配的snd_soc_dai cpu_dai,并将该cpu_dai对应的component插入到 rtd->components数组中;

    • 根据codec_name/codec_of_node/codevc_dai_name从全局链表component_list中找到匹配的snd_soc_dai codec_dai,并将该codec_dai对应的component插入到 rtd->components数组中;

    • 根据platform_name/platform_of_node从全局链表component_list中找到匹配的component,并将该component插入到 rtd->components数组中;

3.4.1 snd_soc_card_add_dai_link

snd_soc_card_add_dai_link函数定义在sound/soc/soc-card.c,如果定义了card->add_dai_link回调函数,将会执行该回调函数,该函数忽略即可;

int snd_soc_card_add_dai_link(struct snd_soc_card *card,
                              struct snd_soc_dai_link *dai_link)
{
        int ret = 0;

        if (card->add_dai_link)
                ret = card->add_dai_link(card, dai_link);

        return soc_card_ret(card, ret);
}
View Code
3.4.2 soc_new_pcm_runtime

soc_new_pcm_runtime函数定义在sound/soc/soc-core.c,为音频数据链路dai_link创建一个新的snd_soc_pcm_runtime,并进行成员的初始化;

static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
        struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_component *component;
        struct device *dev;
        int ret;
        int stream;

        /*
         * for rtd->dev
         */
        dev = kzalloc(sizeof(struct device), GFP_KERNEL);  // 动态申请内存,数据结构类型为struct device
        if (!dev)
                return NULL;

        dev->parent     = card->dev;              // 初始化成员
        dev->release    = soc_release_rtd_dev;

        dev_set_name(dev, "%s", dai_link->name); // 设置dev名字

        ret = device_register(dev);      // 注册设备到内核
        if (ret < 0) {
                put_device(dev); /* soc_release_rtd_dev */
                return NULL;
        }

        /*
         * for rtd
         */
        rtd = devm_kzalloc(dev,
                           sizeof(*rtd) +
                           sizeof(component) * (dai_link->num_cpus +
                                                 dai_link->num_codecs +
                                                 dai_link->num_platforms),  // 动态申请内存,数据结构类型为struct snd_soc_pcm_runtime 
                           GFP_KERNEL);
        if (!rtd) {
                device_unregister(dev);
                return NULL;
        }

        rtd->dev = dev;                 // 设置设备
        INIT_LIST_HEAD(&rtd->list);     // 初始化链表节点
        for_each_pcm_streams(stream) {
                INIT_LIST_HEAD(&rtd->dpcm[stream].be_clients);
                INIT_LIST_HEAD(&rtd->dpcm[stream].fe_clients);
        }
        dev_set_drvdata(dev, rtd);       // 设置设备驱动数据为rtd
        INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);

        /*
         * for rtd->dais
         */
        rtd->dais = devm_kcalloc(dev, dai_link->num_cpus + dai_link->num_codecs,
                                        sizeof(struct snd_soc_dai *),   // 动态申请数组,即为每一个dai分配一个struct asoc_soc_dai
                                        GFP_KERNEL);
        if (!rtd->dais)
                goto free_rtd;
        /*
         * dais = [][][][][][][][][][][][][][][][][][]
         *        ^cpu_dais         ^codec_dais
         *        |--- num_cpus ---|--- num_codecs --|
         * see
         *      asoc_rtd_to_cpu()
         *      asoc_rtd_to_codec()
         */
        rtd->card       = card;                         // 初始化成员
        rtd->dai_link   = dai_link;
        rtd->num        = card->num_rtd++;
        rtd->pmdown_time = pmdown_time;                 /* default power off timeout */

        /* see for_each_card_rtds */
        list_add_tail(&rtd->list, &card->rtd_list);   // 将rtd->list节点添加到链表card->rtd_list中

        ret = device_add_groups(dev, soc_dev_attr_groups);
        if (ret < 0)
                goto free_rtd;

        return rtd;

free_rtd:
        soc_free_pcm_runtime(rtd);
        return NULL;
}
View Code
3.4.3 snd_soc_find_dai

snd_soc_find_dai函数定义在sound/soc/soc-core.c,该函数会遍历component_list(platform & codec 驱动最终都会创建相应的component实例并插入到全局链表component_list),根据dlc查找匹配的dai;

/**
 * snd_soc_find_dai - Find a registered DAI
 *
 * @dlc: name of the DAI or the DAI driver and optional component info to match
 *
 * This function will search all registered components and their DAIs to
 * find the DAI of the same name. The component's of_node and name
 * should also match if being specified.
 *
 * Return: pointer of DAI, or NULL if not found.
 */
struct snd_soc_dai *snd_soc_find_dai(
        const struct snd_soc_dai_link_component *dlc)  // 音频数据链路组件,里面存储了name、of_node、dam name;
{
        struct snd_soc_component *component;
        struct snd_soc_dai *dai;

        lockdep_assert_held(&client_mutex);

        /* Find CPU DAI from registered DAIs */
        for_each_component(component) {            // 遍历遍历全局链表component_list
                if (!snd_soc_is_matching_component(dlc, component))  // 通过of_node、name进行匹配
                        continue;
                for_each_component_dais(component, dai) {  // 对于匹配的snd_soc_component,遍历其所有dai,对于每个dai,检查其名称是否与dlc中的dai_name匹配
                        if (dlc->dai_name && strcmp(dai->name, dlc->dai_name) // 如果dlc如果没有定义dai_name,就不会走着匹配流程
                            && (!dai->driver->name
                                || strcmp(dai->driver->name, dlc->dai_name)))
                                continue;

                        return dai;
                }
        }

        return NULL;
}

snd_soc_is_matching_component定义如下:

static int snd_soc_is_matching_component(
        const struct snd_soc_dai_link_component *dlc,
        struct snd_soc_component *component)
{
        struct device_node *component_of_node;

        if (!dlc)
                return 0;

        component_of_node = soc_component_to_node(component);

        if (dlc->of_node && component_of_node != dlc->of_node)
                return 0;
        if (dlc->name && strcmp(component->name, dlc->name))
                return 0;

        return 1;
}
View Code
3.4.4 snd_soc_rtd_add_component

snd_soc_rtd_add_component函数定义在sound/soc/soc-core.c,该函数将成功匹配的snd_soc_component填充到 snd_soc_pcm_runtime;

static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd,
                                     struct snd_soc_component *component)
{
        struct snd_soc_component *comp;
        int i;

        for_each_rtd_components(rtd, i, comp) {  // 遍历rtd->components,如果已经绑定,退出
                /* already connected */
                if (comp == component)
                        return 0;
        }

        /* see for_each_rtd_components */
        rtd->components[rtd->num_components] = component; // 绑定
        rtd->num_components++; // 计数

        return 0;
}
View Code

3.5 soc_init_card_debugfs

debug文件系统相关内容,我们不关系,忽略。

3.6 soc_resume_init

soc_resume_init函数定义在sound/soc/soc-core.c,作用就是创建工作,设置工作func为soc_resume_deferred;

static void soc_resume_init(struct snd_soc_card *card)
{
        /* deferred resume work */
        INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
}

3.7 snd_soc_card_probe

snd_soc_card_probe函数定义在sound/soc/soc-card.c,实际上就是回调card->probe函数;

int snd_soc_card_probe(struct snd_soc_card *card)
{
        if (card->probe) {
                int ret = card->probe(card);

                if (ret < 0)
                        return soc_card_ret(card, ret);

                /*
                 * It has "card->probe" and "card->late_probe" callbacks.
                 * So, set "probed" flag here, because it needs to care
                 * about "late_probe".
                 *
                 * see
                 *      snd_soc_bind_card()
                 *      snd_soc_card_late_probe()
                 */
                card->probed = 1;
        }

        return 0;
}
View Code

Rockchip RK3399 - Machine驱动(simple-card)中介绍的simple-card驱动为例,probe会被设置为simple_soc_probe,

static int simple_soc_probe(struct snd_soc_card *card)
{
        struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card);
        int ret;

        ret = asoc_simple_init_hp(card, &priv->hp_jack, PREFIX); // 创建带有pin的jack,用于耳机检测
        if (ret < 0)
                return ret;

        ret = asoc_simple_init_mic(card, &priv->mic_jack, PREFIX);  // 创建带有pin的jack,用于麦克风检测
        if (ret < 0)
                return ret;

        ret = asoc_simple_init_aux_jacks(priv, PREFIX);
        if (ret < 0)
                return ret;

        return 0;
}

其中asoc_simple_init_hp是对耳机的初始化,asoc_simple_init_mic是对麦克风的初始化,其均定义在include/sound/simple_card_utils.h:

#define asoc_simple_init_hp(card, sjack, prefix) \
        asoc_simple_init_jack(card, sjack, 1, prefix, NULL)
#define asoc_simple_init_mic(card, sjack, prefix) \
        asoc_simple_init_jack(card, sjack, 0, prefix, NULL)

这里都调用asoc_simple_init_jack,只是传入参数不一样。

3.7.1 asoc_simple_init_jack

asoc_simple_init_jack函数位于sound/soc/generic/simple-card-utils.c,下面我们以HP为例进行介绍;

int asoc_simple_init_jack(struct snd_soc_card *card, // 传入ASoC声卡设备
                          struct asoc_simple_jack *sjack, // 插孔,该函数会进行其成员的初始化
                          int is_hp, char *prefix,  // is_hp为1表示耳机,为0表示麦克风 prefix="simple-audio-card,"
                          char *pin) // 传入NULL
{
        struct device *dev = card->dev;  // 获取平台设备的设备
        struct gpio_desc *desc;
        char prop[128];
        char *pin_name;
        char *gpio_name;
        int mask;
        int error;

        if (!prefix)
                prefix = "";

        sjack->gpio.gpio = -ENOENT;
     // 下面通过参数来判断是hp还是mic
        if (is_hp) {
          // 这里做了字符串拼接,所以dts中要按照这样设置:simple-audio-card,hp-det-gpio snprintf(prop,
sizeof(prop), "%shp-det", prefix); // prop="simple-audio-card,hp-det" pin_name = pin ? pin : "Headphones"; // "Headphones" gpio_name = "Headphone detection"; // GPIO口的名称 mask = SND_JACK_HEADPHONE; // 0x0001 } else { snprintf(prop, sizeof(prop), "%smic-det", prefix); pin_name = pin ? pin : "Mic Jack"; gpio_name = "Mic detection"; mask = SND_JACK_MICROPHONE; } // 根据dts中dev设备节点属性名prop获取属性中描述的GPIO信息,同时配置GPIO口为输入 optional表示可选的,也就是说如果没有指定prop属性,也不会报错 desc = gpiod_get_optional(dev, prop, GPIOD_IN); // 第三个参数表示配置GPIO口为输入 error = PTR_ERR_OR_ZERO(desc); if (error) return error;    if (desc) { error = gpiod_set_consumer_name(desc, gpio_name); // 设置GPIO口的label为"Headphone detection" if (error) return error; sjack->pin.pin = pin_name; // "Headphones",根据该名称定为widget sjack->pin.mask = mask; // jack pin可以上报的类型,SND_JACK_HEADPHONE sjack->gpio.name = gpio_name;// GPIO的名称,也是GPIO中断的名称"Headphone detection" sjack->gpio.report = mask; // GPIO可以上报的类型,SND_JACK_HEADPHONE sjack->gpio.desc = desc; sjack->gpio.debounce_time = 150; // 延迟时间           // 创建一个带有pin的jack snd_soc_card_jack_new_pins(card, pin_name, mask, &sjack->jack, // jack可以检测的类型为SND_JACK_HEADPHONE &sjack->pin, 1);           // 通过request irq申请耳机插拔中断,在中断处理函数中通过检测上升沿/下降沿判断耳机是插入还是拔出,根据判断结果调用snd_soc_jack_report发送输入事件 snd_soc_jack_add_gpios(&sjack->jack, 1, &sjack->gpio); } return 0; }

这里调用了gpiod_get_optional、snd_soc_card_jack_new_pins、snd_soc_jack_add_gpios等函数,关于jack设备我们在声卡之Jack设备已经介绍过了,这里就只聊一聊

gpiod_get_optional函数。

3.7.2 gpiod_get_optional

gpiod_get_optional定义在drivers/gpio/gpiolib.c:该函数通过解析设备节点属性simple-audio-card,hp-det获取GPIO描述符;比如在rt5651-sound设备节点中有属性:

simple-audio-card,hp-det-gpio = <&gpio4 28 GPIO_ACTIVE_HIGH>;

那么gpiod_get_index_optional获取到的就是GPIO4_D4的描述信息;

/**
 * gpiod_get_optional - obtain an optional GPIO for a given GPIO function
 * @dev: GPIO consumer, can be NULL for system-global GPIOs
 * @con_id: function within the GPIO consumer
 * @flags: optional GPIO initialization flags
 *
 * This is equivalent to gpiod_get(), except that when no GPIO was assigned to
 * the requested function it will return NULL. This is convenient for drivers
 * that need to handle optional GPIOs.
 */
struct gpio_desc *__must_check gpiod_get_optional(struct device *dev,  // 平台设备的设备
                                                  const char *con_id,  // 传入"simple-audio-card,hp-det"
                                                  enum gpiod_flags flags) 
{
        return gpiod_get_index_optional(dev, con_id, 0, flags);
}

可以看到函数内部调用了gpiod_get_index_optional,定义如下:

/**
 * gpiod_get_index_optional - obtain an optional GPIO from a multi-index GPIO
 *                            function
 * @dev: GPIO consumer, can be NULL for system-global GPIOs
 * @con_id: function within the GPIO consumer
 * @index: index of the GPIO to obtain in the consumer
 * @flags: optional GPIO initialization flags
 *
 * This is equivalent to gpiod_get_index(), except that when no GPIO with the
 * specified index was assigned to the requested function it will return NULL.
 * This is convenient for drivers that need to handle optional GPIOs.
 */
struct gpio_desc *__must_check gpiod_get_index_optional(struct device *dev,  // 平台设备的设备
                                                        const char *con_id,  // 传入"simple-audio-card,hp-det"
                                                        unsigned int index,  // 0
                                                        enum gpiod_flags flags) // 传入GPIOD_FLAGS_BIT_DIR_SET
{
        struct gpio_desc *desc;

        desc = gpiod_get_index(dev, con_id, index, flags);
        if (gpiod_not_found(desc))
                return NULL;

        return desc;
}
View Code

函数内部又调用了gpiod_get_index,定义如下:

/**
 * gpiod_get_index - obtain a GPIO from a multi-index GPIO function
 * @dev:        GPIO consumer, can be NULL for system-global GPIOs
 * @con_id:     function within the GPIO consumer
 * @idx:        index of the GPIO to obtain in the consumer
 * @flags:      optional GPIO initialization flags
 *
 * This variant of gpiod_get() allows to access GPIOs other than the first
 * defined one for functions that define several GPIOs.
 *
 * Return a valid GPIO descriptor, -ENOENT if no GPIO has been assigned to the
 * requested function and/or index, or another IS_ERR() code if an error
 * occurred while trying to acquire the GPIO.
 */
struct gpio_desc *__must_check gpiod_get_index(struct device *dev,  // 平台设备的设备
                                               const char *con_id,  // 传入"simple-audio-card,hp-det"
                                               unsigned int idx, // 0 
                                               enum gpiod_flags flags)  // 传入GPIOD_FLAGS_BIT_DIR_SET
{
        struct fwnode_handle *fwnode = dev ? dev_fwnode(dev) : NULL;  // 获取dev设备节点,即设备节点rt5651-sound
        const char *devname = dev ? dev_name(dev) : "?";  // 获取节点名称
        const char *label = con_id ?: devname; // 赋值为"simple-audio-card,hp-det"

        return gpiod_find_and_request(dev, fwnode, con_id, idx, flags, label, true);
}
View Code

函数内部又调用了gpiod_find_and_request,定义如下:

static struct gpio_desc *gpiod_find_and_request(struct device *consumer,  // 平台设备的设备
                                                struct fwnode_handle *fwnode, // 设备节点 ,即设备节点rt5651-sound
                                                const char *con_id,  // "simple-audio-card,hp-det"
                                                unsigned int idx,  // 0 
                                                enum gpiod_flags flags,  const char *label,  // "simple-audio-card,hp-det"
                                                bool platform_lookup_allowed) // true
{
        unsigned long lookupflags = GPIO_LOOKUP_FLAGS_DEFAULT;   // GPIO_ACTIVE_HIGH | GPIO_PERSISTENT
        struct gpio_desc *desc = ERR_PTR(-ENOENT);
        int ret;

        if (!IS_ERR_OR_NULL(fwnode))
                desc = gpiod_find_by_fwnode(fwnode, consumer, con_id, idx,
                                            &flags, &lookupflags); // 根据设备节点获取GPIO描述符,同时保存有效电平信息到lookupflags

        if (gpiod_not_found(desc) && platform_lookup_allowed) {  // 如果没有找到desc
                /*
                 * Either we are not using DT or ACPI, or their lookup did not
                 * return a result. In that case, use platform lookup as a
                 * fallback.
                 */
                dev_dbg(consumer, "using lookup tables for GPIO lookup\n");
                desc = gpiod_find(consumer, con_id, idx, &lookupflags);  
        }

        if (IS_ERR(desc)) {  // 错误处理
                dev_dbg(consumer, "No GPIO consumer %s found\n", con_id);
                return desc;
        }

        /*
         * If a connection label was passed use that, else attempt to use
         * the device name as label
         */
        ret = gpiod_request(desc, label); // 向gpiolib进行申请GPIO
        if (ret) {
                if (!(ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE))
                        return ERR_PTR(ret);

                /*
                 * This happens when there are several consumers for
                 * the same GPIO line: we just return here without
                 * further initialization. It is a bit of a hack.
                 * This is necessary to support fixed regulators.
                 *
                 * FIXME: Make this more sane and safe.
                 */
                dev_info(consumer,
                         "nonexclusive access to GPIO for %s\n", con_id);
                return desc;
        }
// 根据传入flag设置方向,配置GPIO引脚方向,这里我们配置为输入 ret
= gpiod_configure_flags(desc, con_id, lookupflags, flags); if (ret < 0) { dev_dbg(consumer, "setup of GPIO %s failed\n", con_id); gpiod_put(desc); return ERR_PTR(ret); } blocking_notifier_call_chain(&desc->gdev->notifier, GPIOLINE_CHANGED_REQUESTED, desc); return desc; }

函数内部又调用了gpiod_find_by_fwnode,定义如下:

static struct gpio_desc *gpiod_find_by_fwnode(struct fwnode_handle *fwnode,
                                              struct device *consumer,
                                              const char *con_id,  // "simple-audio-card,hp-det"
                                              unsigned int idx, // 0 
                                              enum gpiod_flags *flags, 
                                              unsigned long *lookupflags) // 用于返回GPIO有效电平信息
{
        struct gpio_desc *desc = ERR_PTR(-ENOENT);

        if (is_of_node(fwnode)) {  // 走这里
                dev_dbg(consumer, "using DT '%pfw' for '%s' GPIO lookup\n",
                        fwnode, con_id);
                desc = of_find_gpio(to_of_node(fwnode), con_id, idx, lookupflags);
        } else if (is_acpi_node(fwnode)) {
                dev_dbg(consumer, "using ACPI '%pfw' for '%s' GPIO lookup\n",
                        fwnode, con_id);
                desc = acpi_find_gpio(fwnode, con_id, idx, flags, lookupflags);
        } else if (is_software_node(fwnode)) {
                dev_dbg(consumer, "using swnode '%pfw' for '%s' GPIO lookup\n",
                        fwnode, con_id);
                desc = swnode_find_gpio(fwnode, con_id, idx, lookupflags);
        }

        return desc;
}

函数内部又调用了of_find_gpio,这个函数是比较常见的函数,比较重要。

3.7.3 of_find_gpio

of_find_gpio函数第一个参数为设备节点,第二个参数和第三个参数参数con_id和idx,确认要使用“xxx-gpios”、或者“xxx-gpio”的第几个pin;主要流程如下:

  • 解析dts属性,获取使用了哪个gpio-controller,使用了哪个引脚号;
  • 找到gpio-controller节点,获取已配置好的gpio_device;
  • 根据引脚号,找到对应挂在gpio_device的gpio_desc以及gpio的有效电平信息,并返回;

函数定义在drivers/gpio/gpiolib-of.c:

struct gpio_desc *of_find_gpio(struct device_node *np, const char *con_id, // con_id="simple-audio-card,hp-det"
                               unsigned int idx, unsigned long *flags)
{
        char prop_name[32]; /* 32 is max size of property name */
        enum of_gpio_flags of_flags;
        const of_find_gpio_quirk *q;
        struct gpio_desc *desc;
        unsigned int i;

        /* Try GPIO property "foo-gpios" and "foo-gpio" */
        for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) { // gpio_suffixes[] = { "gpios", "gpio" };
                if (con_id) // 走这里
                        snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, // "simple-audio-card,hp-det-gpios"、或"simple-audio-card,hp-det-gpio"
                                 gpio_suffixes[i]);
                else
                        snprintf(prop_name, sizeof(prop_name), "%s",
                                 gpio_suffixes[i]);

              /* 
               * 1. 根据属性名"simple-audio-card,hp-det-gpio"查找匹配,保存内容在gpiospec
               * 2. 根据gpiospec的phandle找到chip,即:有gpio-controller的节点
               * 3. 根据gpiospec的引脚号,找到在chip中的desc数组偏移位置, 即对应的引脚号的desc
               * 4. 并返回了flags "GPIO_ACTIVE_HIGH"(高电平有效)
               */
                desc = of_get_named_gpiod_flags(np, prop_name, idx, &of_flags);

                if (!gpiod_not_found(desc)) // 没有在dts中找到GPIO描述符信息
                        break;
        }

        /* Properly named GPIO was not found, try workarounds */
        for (q = of_find_gpio_quirks; gpiod_not_found(desc) && *q; q++)
                desc = (*q)(np, con_id, idx, &of_flags);

        if (IS_ERR(desc))
                return desc;

        *flags = of_convert_gpio_flags(of_flags); // 枚举转换为数字类型

        return desc;
}

函数内部又调用了of_get_named_gpiod_flags,定义在drivers/gpio/gpiolib-of.c:

/**
 * of_get_named_gpiod_flags() - Get a GPIO descriptor and flags for GPIO API
 * @np:         device node to get GPIO from
 * @propname:   property name containing gpio specifier(s)
 * @index:      index of the GPIO
 * @flags:      a flags pointer to fill in
 *
 * Returns GPIO descriptor to use with Linux GPIO API, or one of the errno
 * value on the error condition. If @flags is not NULL the function also fills
 * in flags for the GPIO.
 */
static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np,
                     const char *propname, int index, enum of_gpio_flags *flags)
{
        struct of_phandle_args gpiospec;
        struct gpio_chip *chip;
        struct gpio_desc *desc;
        int ret;

        /*
         * propname:当前node的属性名
         * "#gpio-cells":父node的属性名
         * index:第index个phandle
         * 举例:propname = <&phandle1 1 2 &phandle2 3>;
         *       1. 当#gpio-cells为2,index为0时,
         *       2. gpiospec->arg[0] = 1 、gpsiospec->arg[1] = 2;
         *          gpiospec->args_count = 2;
         *          gpiospec->np = phandle1;
         */  
        ret = of_parse_phandle_with_args_map(np, propname, "gpio", index,
                                             &gpiospec);
        if (ret) {
                pr_debug("%s: can't parse '%s' property of node '%pOF[%d]'\n",
                        __func__, propname, np, index);
                return ERR_PTR(ret);
        }

       /* 根据gpiospec,遍历gpio_devices,找到匹配的gpiochip */
        chip = of_find_gpiochip_by_xlate(&gpiospec);
        if (!chip) {
                desc = ERR_PTR(-EPROBE_DEFER);
                goto out;
        }

        /* 根据dts中属性提供的引脚号,找到对应引脚的gpio_desc */
        desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags);
        if (IS_ERR(desc))
                goto out;

        if (flags)
                of_gpio_flags_quirks(np, propname, flags, index);

        pr_debug("%s: parsed '%s' property of node '%pOF[%d]' - status (%d)\n",
                 __func__, propname, np, index,
                 PTR_ERR_OR_ZERO(desc));

out:
        of_node_put(gpiospec.np);

        return desc;
}

3.8 soc_probe_link_components

soc_probe_link_components函数实际上就是遍历所有的snd_soc_pcm_runtime ,由于每个snd_soc_pcm_runtime保存着音频数据链路上的所有component。这里相当于按照顺序执行soc_probe_component(card, component),soc_probe_component函数会进行component的探测工作,主要包括:

  • 注册component->driver->dapm_widgets到声卡card的widgets链表中;
  • 执行component->driver->probe(component);
  • 根据component->driver->controls数组创建并添加多个kcontrol到声卡card的controls链表;
  • 遍历component->driver->dapm_routes数组,调用snd_soc_dapm_add_routes将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构,然后将snd_soc_dapm_path注册到声卡card的paths链表;
  • 将component添加到声卡component_dev_list链表;

函数定义在sound/soc/soc-core.c:

static int soc_probe_link_components(struct snd_soc_card *card)
{
        struct snd_soc_component *component;
        struct snd_soc_pcm_runtime *rtd;
        int i, ret, order;

        for_each_comp_order(order) {   // order=-2,-1,0,1,2
                for_each_card_rtds(card, rtd) {  // 遍历card->rtd_list链表,得到每一个pcm runtime
                        for_each_rtd_components(rtd, i, component) { // 遍历rtd->components数组,为所有用到的components调用soc_probe_component函数
                                if (component->driver->probe_order != order)
                                        continue;

                                ret = soc_probe_component(card, component); 
                                if (ret < 0)
                                        return ret;
                        }
                }
        }

        return 0;
}

如上代码所示,该函数会遍历该rtd->component_list, 为所有用到的component调用soc_probe_component函数:

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);  // 获取dapm域
        struct snd_soc_dai *dai;
        int probed = 0;
        int ret;

        if (snd_soc_component_is_dummy(component))
                return 0;

        if (component->card) {  // 未设置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;
        }

        ret = snd_soc_component_module_get_when_probe(component);
        if (ret < 0)
                return ret;

        component->card = card;     // 初始化card
        soc_set_name_prefix(card, component); // 设置component->name_prefix,取自component设备节点属性"sound-name-prefix"

        soc_init_component_debugfs(component);

        snd_soc_dapm_init(dapm, card, component);

     // 根据component->driver->dapm_widgets模板重新申请内存并初始化各个widget,每一个widget添加到声卡card的widgets链表中
        ret = snd_soc_dapm_new_controls(dapm,
                                        component->driver->dapm_widgets,
                                        component->driver->num_dapm_widgets);

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

     // 遍历component->dai_list创建dai widget
        for_each_component_dais(component, dai) { 
                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;
                }
        }

     // 调用component->driver->probe函数
        ret = snd_soc_component_probe(component);
        if (ret < 0)
                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);
        probed = 1;

        /*
         * machine specific init
         * see
         *      snd_soc_component_set_aux()
         */
        ret = snd_soc_component_init(component);
        if (ret < 0)
                goto err_probe;

     // 根据component->driver->controls数组分别创建kcontrol并将其添加到声卡card的controls链表;
        ret = snd_soc_add_component_controls(component,
                                             component->driver->controls,
                                             component->driver->num_controls);
        if (ret < 0)
                goto err_probe;
// 遍历component->driver->dapm_routes数组,调用snd_soc_dapm_add_routes将snd_soc_dapm_route动态地生成所需要的snd_soc_dapm_path结构, // 然后将snd_soc_dapm_path注册到声卡card的paths链表 ret = snd_soc_dapm_add_routes(dapm, component->driver->dapm_routes, component->driver->num_dapm_routes); if (ret < 0) { if (card->disable_route_checks) { dev_info(card->dev, "%s: disable_route_checks set, ignoring errors on add_routes\n", __func__); } else { dev_err(card->dev, "%s: snd_soc_dapm_add_routes failed: %d\n", __func__, ret); goto err_probe; } } /* see for_each_card_components */ list_add(&component->card_list, &card->component_dev_list); // 将component添加到声卡component_dev_list链表 err_probe: if (ret < 0) soc_remove_component(component, probed); return ret; }
3.8.1 snd_soc_dapm_new_dai_widgets

snd_soc_dapm_new_dai_widgets函数用来生成dai的播放流widget和录音流widget,并保存到dai->stream[i].widget,函数定义在sound/soc/soc-dapm.c:

/**
 * snd_soc_dapm_new_dai_widgets - Create new DAPM widgets
 * @dapm: DAPM context
 * @dai: parent DAI
 *
 * Returns 0 on success, error code otherwise.
 */
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;
        // 创建播放dai widget
        if (dai->driver->playback.stream_name) {
                template.id = snd_soc_dapm_dai_in;
                template.name = dai->driver->playback.stream_name; // name
                template.sname = dai->driver->playback.stream_name; // snane

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

                w = snd_soc_dapm_new_control_unlocked(dapm, &template); // 根据widget模板创建一个新的widget,并添加到声卡的widgets链表
                if (IS_ERR(w))
                        return PTR_ERR(w);

                w->priv = dai;     // 对于snd_soc_dapm_dai_in类型的widget,priv字段保存的是之相关联的snd_soc_dai结构指针
                snd_soc_dai_set_widget_playback(dai, w);  // dai->stream[0].widget=w
        }
     // 创建录音dai widget 
        if (dai->driver->capture.stream_name) {
                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))
                        return PTR_ERR(w);

                w->priv = dai;    // 对于snd_soc_dapm_dai_out类型的widget,priv字段保存的是之相关联的snd_soc_dai结构指针
                snd_soc_dai_set_widget_capture(dai, w);  // dai->stream[1].widget=w
        }

        return 0;
}

分别为playback和capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的name以及sname就是snd_soc_dai_driver结构的stream_name。

3.8.2 snd_soc_component_probe

snd_soc_component_probe函数回调component->driver->probe函数,定义在sound/soc/soc-component.c:

int snd_soc_component_probe(struct snd_soc_component *component)
{
        int ret = 0;

        if (component->driver->probe)
                ret = component->driver->probe(component);

        return soc_component_ret(component, ret);
}

3.9 soc_probe_link_dais

soc_probe_link_dais函数定义在sound/soc/soc-core.c,这段代码实际上就是遍历所有的snd_soc_pcm_runtime ,由于每个snd_soc_pcm_runtime保存着音频数据链路上的所有dai。这里相当于遍历rtd的dais数组,按照顺序执行这些dai->driver->probe(dai);

static int soc_probe_link_dais(struct snd_soc_card *card)
{
        struct snd_soc_pcm_runtime *rtd;
        int order, ret;

        for_each_comp_order(order) { // order=-2,-1,0,1,2
                for_each_card_rtds(card, rtd) {   // 遍历card->rtd_list链表,得到每一个pcm runtime

                        dev_dbg(card->dev,
                                "ASoC: probe %s dai link %d late %d\n",
                                card->name, rtd->num, order);

                        /* probe all rtd connected DAIs in good order */
                        ret = snd_soc_pcm_dai_probe(rtd, order);  // 遍历rtd中的dais数组,按顺序执行dai->driver->probe(dai)
                        if (ret)
                                return ret;
                }
        }

        return 0;
}

snd_soc_pcm_dai_probe函数定义在sound/soc/soc-dai.c:

int snd_soc_pcm_dai_probe(struct snd_soc_pcm_runtime *rtd, int order)
{
        struct snd_soc_dai *dai;
        int i;

        for_each_rtd_dais(rtd, i, dai) {  // dai = (rtd)->dais[i]
                if (dai->driver->probe_order != order)
                        continue;

                if (dai->driver->probe) {
                        int ret = dai->driver->probe(dai);  // 回调dai->driver->probe函数

                        if (ret < 0)
                                return soc_dai_ret(dai, ret);
                }

                dai->probed = 1;
        }

        return 0;
}

3.10 soc_init_pcm_runtime(重点)

soc_init_pcm_runtime函数定义在sound/soc/soc-core.c,用来初始化pcm runtime;该函数接收两个参数,第一个为ASoC声卡的指针,第二个为pcm runtime的指针;

static int soc_init_pcm_runtime(struct snd_soc_card *card,
                                struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_dai_link *dai_link = rtd->dai_link;        // 获取音频数据链路
        struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);    // 获取cpu dai 
        struct snd_soc_component *component;
        int ret, num, i;

        /* do machine specific initialization */
        ret = snd_soc_link_init(rtd);               // 调用dai_link的init回调函数,进行dai_link初始化工作,设置每个dai的系统时钟频率(底层寄存器配置)
        if (ret < 0)
                return ret;

        snd_soc_runtime_get_dai_fmt(rtd);  // 获取数字音频接口格式
        ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);  // 设置每个dai的数字音频接口格式(底层寄存器配置)
        if (ret)
                return ret;

        /* add DPCM sysfs entries */
        soc_dpcm_debugfs_add(rtd);

        num = rtd->num;

        /*
         * most drivers will register their PCMs using DAI link ordering but
         * topology based drivers can use the DAI link id field to set PCM
         * device number and then use rtd + a base offset of the BEs.
         */
        for_each_rtd_components(rtd, i, component) {
                if (!component->driver->use_dai_pcm_id)
                        continue;

                if (rtd->dai_link->no_pcm)
                        num += component->driver->be_pcm_base;
                else
                        num = rtd->dai_link->id;
        }

        /* create compress_device if possible */
        ret = snd_soc_dai_compress_new(cpu_dai, rtd, num);  // 忽略
        if (ret != -ENOTSUPP)
                return ret;

        /* create the pcm */
        ret = soc_new_pcm(rtd, num);
        if (ret < 0) {
                dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
                        dai_link->stream_name, ret);
                return ret;
        }

        return snd_soc_pcm_dai_new(rtd);
}

soc_init_pcm_runtime函数实现PCM设备的创建与初始化,生成的设备用于PCM数据流控制,分析如图;

3.10.1 snd_soc_link_init

snd_soc_link_init函数定义在sound/soc/soc-link.c,回调音频数据链路的init方法来进行音频数据链路的初始化工作;

int snd_soc_link_init(struct snd_soc_pcm_runtime *rtd)
{
        int ret = 0;

        if (rtd->dai_link->init)
                ret = rtd->dai_link->init(rtd);

        return soc_link_ret(rtd, ret);
}
3.10.2 snd_soc_runtime_get_dai_fmt

snd_soc_runtime_get_dai_fmt函数定义在sound/soc/soc-core.c,这段代码应该是获取pcm runtime的数字音频接口(dai)格式;函数首先遍历每dai,获取可选的格式,并进行优先级排序。然后,根据可选的格式,转换为实际的dai格式;并保存到dai_link->dai_fmt中;

static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_dai_link *dai_link = rtd->dai_link;
        struct snd_soc_dai *dai, *not_used;
        struct device *dev = rtd->dev;
        u64 pos, possible_fmt;
        unsigned int mask = 0, dai_fmt = 0;
        int i, j, priority, pri, until;

        /*
         * Get selectable format from each DAIs.
         *
         ****************************
         *            NOTE
         * Using .auto_selectable_formats is not mandatory,
         * we can select format manually from Sound Card.
         * When use it, driver should list well tested format only.
         ****************************
         *
         * ex)
         *      auto_selectable_formats (= SND_SOC_POSSIBLE_xxx)
         *               (A)     (B)     (C)
         *      DAI0_: { 0x000F, 0x00F0, 0x0F00 };
         *      DAI1 : { 0xF000, 0x0F00 };
         *               (X)     (Y)
         *
         * "until" will be 3 in this case (MAX array size from DAI0 and DAI1)
         * Here is dev_dbg() message and comments
         *
         * priority = 1
         * DAI0: (pri, fmt) = (1, 000000000000000F) // 1st check (A) DAI1 is not selected
         * DAI1: (pri, fmt) = (0, 0000000000000000) //               Necessary Waste
         * DAI0: (pri, fmt) = (1, 000000000000000F) // 2nd check (A)
         * DAI1: (pri, fmt) = (1, 000000000000F000) //           (X)
         * priority = 2
         * DAI0: (pri, fmt) = (2, 00000000000000FF) // 3rd check (A) + (B)
         * DAI1: (pri, fmt) = (1, 000000000000F000) //           (X)
         * DAI0: (pri, fmt) = (2, 00000000000000FF) // 4th check (A) + (B)
         * DAI1: (pri, fmt) = (2, 000000000000FF00) //           (X) + (Y)
         * priority = 3
         * DAI0: (pri, fmt) = (3, 0000000000000FFF) // 5th check (A) + (B) + (C)
         * DAI1: (pri, fmt) = (2, 000000000000FF00) //           (X) + (Y)
         * found auto selected format: 0000000000000F00
         */
        until = snd_soc_dai_get_fmt_max_priority(rtd);
        for (priority = 1; priority <= until; priority++) {

                dev_dbg(dev, "priority = %d\n", priority);
                for_each_rtd_dais(rtd, j, not_used) {

                        possible_fmt = ULLONG_MAX;
                        for_each_rtd_dais(rtd, i, dai) {
                                u64 fmt = 0;

                                pri = (j >= i) ? priority : priority - 1;
                                fmt = snd_soc_dai_get_fmt(dai, pri);
                                dev_dbg(dev, "%s: (pri, fmt) = (%d, %016llX)\n", dai->name, pri, fmt);
                                possible_fmt &= fmt;
                        }
                        if (possible_fmt)
                                goto found;
                }
        }
        /* Not Found */
        return;
found:
        dev_dbg(dev, "found auto selected format: %016llX\n", possible_fmt);

        /*
         * convert POSSIBLE_DAIFMT to DAIFMT
         *
         * Some basic/default settings on each is defined as 0.
         * see
         *      SND_SOC_DAIFMT_NB_NF
         *      SND_SOC_DAIFMT_GATED
         *
         * SND_SOC_DAIFMT_xxx_MASK can't notice it if Sound Card specify
         * these value, and will be overwrite to auto selected value.
         *
         * To avoid such issue, loop from 63 to 0 here.
         * Small number of SND_SOC_POSSIBLE_xxx will be Hi priority.
         * Basic/Default settings of each part and aboves are defined
         * as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
         */
        for (i = 63; i >= 0; i--) {
                pos = 1ULL << i;
                switch (possible_fmt & pos) {
                /*
                 * for format
                 */
                case SND_SOC_POSSIBLE_DAIFMT_I2S:
                case SND_SOC_POSSIBLE_DAIFMT_RIGHT_J:
                case SND_SOC_POSSIBLE_DAIFMT_LEFT_J:
                case SND_SOC_POSSIBLE_DAIFMT_DSP_A:
                case SND_SOC_POSSIBLE_DAIFMT_DSP_B:
                case SND_SOC_POSSIBLE_DAIFMT_AC97:
                case SND_SOC_POSSIBLE_DAIFMT_PDM:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i;
                        break;
                /*
                 * for clock
                 */
                case SND_SOC_POSSIBLE_DAIFMT_CONT:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_GATED:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED;
                        break;
                /*
                 * for clock invert
                 */
                case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF;
                        break;
                /*
                 * for clock provider / consumer
                 */
                case SND_SOC_POSSIBLE_DAIFMT_CBP_CFP:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFP;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_CBC_CFP:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFP;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_CBP_CFC:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFC;
                        break;
                case SND_SOC_POSSIBLE_DAIFMT_CBC_CFC:
                        dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFC;
                        break;
                }
        }
        /*
         * Some driver might have very complex limitation.
         * In such case, user want to auto-select non-limitation part,
         * and want to manually specify complex part.
         *
         * Or for example, if both CPU and Codec can be clock provider,
         * but because of its quality, user want to specify it manually.
         *
         * Use manually specified settings if sound card did.
         */
        if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
                mask |= SND_SOC_DAIFMT_FORMAT_MASK;
        if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
                mask |= SND_SOC_DAIFMT_CLOCK_MASK;
        if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_INV_MASK))
                mask |= SND_SOC_DAIFMT_INV_MASK;
        if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
                mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;

        dai_link->dai_fmt |= (dai_fmt & mask);
}
View Code

这一步主要原因是因为我们使用的SoC和Codec芯片支持的数字音频接口各不相同,这里实际上就是使用一定算法,选取一种两者都可以支持的格式。

3.10.3 snd_soc_runtime_set_dai_fmt

snd_soc_runtime_set_dai_fmt函数定义在sound/soc/soc-core.c,用于设置pcm runtime的数字音频接口(dai)格式

/**
 * snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime
 * @rtd: The runtime for which the DAI link format should be changed
 * @dai_fmt: The new DAI link format
 *
 * This function updates the DAI link format for all DAIs connected to the DAI
 * link for the specified runtime.
 *
 * Note: For setups with a static format set the dai_fmt field in the
 * corresponding snd_dai_link struct instead of using this function.
 *
 * Returns 0 on success, otherwise a negative error code.
 */
int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
                                unsigned int dai_fmt)
{
        struct snd_soc_dai *cpu_dai;
        struct snd_soc_dai *codec_dai;
        unsigned int i;
        int ret;

        if (!dai_fmt)
                return 0;

        for_each_rtd_codec_dais(rtd, i, codec_dai) {
                ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
                if (ret != 0 && ret != -ENOTSUPP)
                        return ret;
        }

        /* Flip the polarity for the "CPU" end of link */
        dai_fmt = snd_soc_daifmt_clock_provider_flipped(dai_fmt);

        for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
                ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt);
                if (ret != 0 && ret != -ENOTSUPP)
                        return ret;
        }

        return 0;
}

在函数中,主要的步骤如下:

  • 通过 for_each_rtd_codec_dais(rtd, i, codec_dai) 循环遍历每个 codec_dai,其中 rtd是pcm runtime对象。对于每个codec_dai,调用 snd_soc_dai_set_fmt(codec_dai, dai_fmt) 函数,将新的 dai_fmt应用于该codec_dai;
  • 通过 for_each_rtd_cpu_dais(rtd, i, cpu_dai) 循环遍历每个cpu_dai。对于每个 cpu_dai,调用 snd_soc_dai_set_fmt(cpu_dai, dai_fmt) 函数,将新的 dai_fmt应用于该 cpu_dai;

snd_soc_dai_set_fmt函数是通过调用dai driver操作集的set_fmt 函数实现的,函数定义在sound/soc/soc-dai.c:

/**
 * snd_soc_dai_set_fmt - configure DAI hardware audio format.
 * @dai: DAI
 * @fmt: SND_SOC_DAIFMT_* format value.
 *
 * Configures the DAI hardware format and clocking.
 */
int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
        int ret = -ENOTSUPP;

        if (dai->driver->ops && dai->driver->ops->set_fmt)
                ret = dai->driver->ops->set_fmt(dai, fmt);

        return soc_dai_ret(dai, ret);
}
3.10.4 snd_soc_dai_compress_new

snd_soc_dai_compress_new函数定义在sound/soc/soc-dai.c,回调cpu dai driver操作集的compress_new函数;

int snd_soc_dai_compress_new(struct snd_soc_dai *dai,
                             struct snd_soc_pcm_runtime *rtd, int num)
{
        int ret = -ENOTSUPP;
        if (dai->driver->compress_new)
                ret = dai->driver->compress_new(rtd, num);
        return soc_dai_ret(dai, ret);
}
3.10.5 soc_new_pcm

soc_new_pcm用于创建pcm设备,定义在sound/soc/soc-pcm.c,该函数内部调用了soc_create_pcm,这个我们在Rockchip RK3399 - ALSA 声卡之PCM设备已经进行了详细的介绍;

/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
        struct snd_soc_component *component;
        struct snd_pcm *pcm;
        int ret = 0, playback = 0, capture = 0;
        int i;

        ret = soc_get_playback_capture(rtd, &playback, &capture); // 获取playback substream、capture substream的个数
        if (ret < 0)
                return ret;

        ret = soc_create_pcm(&pcm, rtd, playback, capture, num); // 创建pcm设备
        if (ret < 0)
                return ret;

        /* DAPM dai link stream work */
        /*
         * Currently nothing to do for c2c links
         * Since c2c links are internal nodes in the DAPM graph and
         * don't interface with the outside world or application layer
         * we don't have to do any special handling on close.
         */
        if (!rtd->dai_link->params)                      // 进入
                rtd->close_delayed_work_func = snd_soc_close_delayed_work;

        rtd->pcm = pcm;
        pcm->nonatomic = rtd->dai_link->nonatomic;
        pcm->private_data = rtd;
        pcm->no_device_suspend = true;

        if (rtd->dai_link->no_pcm || rtd->dai_link->params) {  // 不会走这里
                if (playback)
                        pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
                if (capture)
                        pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
                goto out;
        }

        /* ASoC PCM operations */
        if (rtd->dai_link->dynamic) {
                rtd->ops.open           = dpcm_fe_dai_open;
                rtd->ops.hw_params      = dpcm_fe_dai_hw_params;
                rtd->ops.prepare        = dpcm_fe_dai_prepare;
                rtd->ops.trigger        = dpcm_fe_dai_trigger;
                rtd->ops.hw_free        = dpcm_fe_dai_hw_free;
                rtd->ops.close          = dpcm_fe_dai_close;
                rtd->ops.pointer        = soc_pcm_pointer;
        } else {             // 走这里
                rtd->ops.open           = soc_pcm_open;
                rtd->ops.hw_params      = soc_pcm_hw_params;
                rtd->ops.prepare        = soc_pcm_prepare;
                rtd->ops.trigger        = soc_pcm_trigger;
                rtd->ops.hw_free        = soc_pcm_hw_free;
                rtd->ops.close          = soc_pcm_close;
                rtd->ops.pointer        = soc_pcm_pointer;
        }
        for_each_rtd_components(rtd, i, component) {
                const struct snd_soc_component_driver *drv = component->driver;

                if (drv->ioctl)
                        rtd->ops.ioctl          = snd_soc_pcm_component_ioctl;
                if (drv->sync_stop)
                        rtd->ops.sync_stop      = snd_soc_pcm_component_sync_stop;
                if (drv->copy_user)
                        rtd->ops.copy_user      = snd_soc_pcm_component_copy_user;
                if (drv->page)
                        rtd->ops.page           = snd_soc_pcm_component_page;
                if (drv->mmap)
                        rtd->ops.mmap           = snd_soc_pcm_component_mmap;
                if (drv->ack)
                        rtd->ops.ack            = snd_soc_pcm_component_ack;
        }

        if (playback)  // 走这里
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

        if (capture)   // 走这里
                snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);

        ret = snd_soc_pcm_component_new(rtd); // 遍历rtd的components数组,回调component->driver->pcm_construct(component, rtd)
        if (ret < 0)
                return ret;
out:
        dev_dbg(rtd->card->dev, "%s <-> %s mapping ok\n",
                soc_codec_dai_name(rtd), soc_cpu_dai_name(rtd));
        return ret;
}
3.10.6 snd_soc_pcm_dai_new

soc_new_pcm用于创建pcm设备,定义在sound/soc/soc-dai.c,回调dai driver的pcm_new函数;

int snd_soc_pcm_dai_new(struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_dai *dai;
        int i;

        for_each_rtd_dais(rtd, i, dai) {
                if (dai->driver->pcm_new) {
                        int ret = dai->driver->pcm_new(rtd, dai);
                        if (ret < 0)
                                return soc_dai_ret(dai, ret);
                }
        }

        return 0;
}

3.11 snd_soc_dapm_link_dai_widgets

snd_soc_dapm_link_dai_widgets函数定义在sound/soc/soc-dapm.c,该函数作用实际上就是遍历声卡card的widgets链表查找dai widget,然后根据dai widget的sname名称到widgets链表查找具有相同sname的stream widget,然后构造path连接这两个widget。

这里通俗的说,就是构造dai widget和stream widget的连接关系;

  • dai widget主要包含两种:snd_soc_dapm_dai_in以及snd_soc_dapm_dai_out;
  • stream widget包含多种:snd_soc_dapm_aif_in、snd_soc_dapm_aif_out、snd_soc_dapm_dac、snd_soc_dapm_adc;
codec dai widget和stream widget是通过stream name进行匹配的,所以我们在codec驱动中定义stream widget时,他们的sname必须要包含codec dai的sname,这样才能让ASoC自动的把codec dai widget和stream widget连接在一起,只有把它们连接在一起,ASoc中的播放、录音和停止等事件,才能通过dai widget传递到codec中,使得codec中的widget能根据目前的播放状态,动态地开启或关闭音频路径complete path上所有widget的电源。
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
        struct snd_soc_dapm_widget *dai_w, *w;
        struct snd_soc_dapm_widget *src, *sink;
        struct snd_soc_dai *dai;

        /* For each DAI widget... */
        for_each_card_widgets(card, dai_w) {            // 遍历声卡card的widgets链表,赋值给dai_w
                switch (dai_w->id) {                    // 判断widget类型,如果是dai类型,则继续, 否则进行下一次循环
                case snd_soc_dapm_dai_in:
                case snd_soc_dapm_dai_out:
                        break;
                default:
                        continue;
                }

                /* let users know there is no DAI to link */
                if (!dai_w->priv) {                      // 对于snd_soc_dapm_dai_in类型的widget,priv字段保存的是之相关联的snd_soc_dai结构指针
                        dev_dbg(card->dev, "dai widget %s has no DAI\n",
                                dai_w->name);
                        continue;
                }

                dai = dai_w->priv;  //  通过priv获取与之关联的snd_soc_dai结构指针

                /* ...find all widgets with the same stream and link them */
                for_each_card_widgets(card, w) {       // 遍历声卡card的widgets链表,赋值给w,
                        if (w->dapm != dai_w->dapm)
                                continue;

                        switch (w->id) {        // 判断widget类型,如果是dai类型,则进行下一次循环  否则继续
                        case snd_soc_dapm_dai_in:
                        case snd_soc_dapm_dai_out:
                                continue;
                        default:
                                break;
                        }
                         // 用于匹配snd_soc_dapm_aif_in、snd_soc_dapm_aif_out、snd_soc_dapm_dac、snd_soc_dapm_adc类型的widget,只有这些类型的widget才会初始化stream
                        if (!w->sname || !strstr(w->sname, dai_w->sname))  // 如果sname不匹配,则进行下一次循环  否则继续 
                                continue;

                        if (dai_w->id == snd_soc_dapm_dai_in) {
                                src = dai_w;
                                sink = w;
                        } else {
                                src = w;
                                sink = dai_w;
                        }
                        dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
                        snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);   // 创建一个snd_soc_dapm_path,并将其添加到声卡card的paths链表中
                }
        }

        return 0;
}

3.12 snd_soc_dapm_connect_dai_link_widgets

snd_soc_dapm_connect_dai_link_widgets用于构建cpu dai和codec dai之间音频播放和录音的路径;

|-----------------|                       |-----------------|  
|                 |                       |                 | 
| playback widget |---------------------->| playback widget |   playback widget为snd_soc_dapm_dai_in类型的widget
|                 |                       |                 | 
| capture widget  |<----------------------| capture widget  |   capture widget为snd_soc_dapm_dai_in类型的widget            
|                 |                       |                 | 
|-----------------|                       |-----------------|           
    cpu_dai                                   codec_dai

函数定义在sound/soc/soc-dapm.c;

void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
{
        struct snd_soc_pcm_runtime *rtd;
        struct snd_soc_dai *codec_dai;
        int i;

        /* for each BE DAI link... */
        for_each_card_rtds(card, rtd)  {    // 遍历card->rtd_list链表,得到每一个pcm runtime(对应每一个音频数据链路)
                /*
                 * dynamic FE links have no fixed DAI mapping.
                 * CODEC<->CODEC links have no direct connection.
                 */
                if (rtd->dai_link->dynamic)  // 如果是DPCM 则进入下一次循环
                        continue;

                if (rtd->dai_link->num_cpus == 1) {       // 音频数据链路中CPU数量为1,一般走这里
                        for_each_rtd_codec_dais(rtd, i, codec_dai)   // 遍历音频数据链路中的codec dai,即dai = rtd->dais[i + (rtd)->dai_link->num_cpus]
                                dapm_connect_dai_pair(card, rtd, codec_dai,
                                                      asoc_rtd_to_cpu(rtd, 0));  // rtd->dais[0]
                } else if (rtd->dai_link->num_codecs == rtd->dai_link->num_cpus) {
                        for_each_rtd_codec_dais(rtd, i, codec_dai)
                                dapm_connect_dai_pair(card, rtd, codec_dai,
                                                      asoc_rtd_to_cpu(rtd, i));
                } else {
                        dev_err(card->dev,
                                "N cpus to M codecs link is not supported yet\n");
                }
        }
}

dapm_connect_dai_pair函数定义如下 :

static void dapm_connect_dai_pair(struct snd_soc_card *card,       // 声卡
                                  struct snd_soc_pcm_runtime *rtd, // pcm runtime, 每个音频数据链路对应一个pcm runtime
                                  struct snd_soc_dai *codec_dai,   // codec dai
                                  struct snd_soc_dai *cpu_dai)     // cpu dai 
{
        struct snd_soc_dai_link *dai_link = rtd->dai_link;
        struct snd_soc_dapm_widget *dai, *codec, *playback_cpu, *capture_cpu;
        struct snd_pcm_substream *substream;
        struct snd_pcm_str *streams = rtd->pcm->streams;
        int stream;

        if (dai_link->params) {
                // 获取capture dai widget
                playback_cpu    = snd_soc_dai_get_widget_capture(cpu_dai);   // snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_CAPTURE),即cpu_dai->stream[1].widget
                // 获取playback dai widget
                capture_cpu     = snd_soc_dai_get_widget_playback(cpu_dai);  // snd_soc_dai_get_widget(dai, SNDRV_PCM_STREAM_PLAYBACK),即cpu_dai->stream[0].widget
        } else {   // 走这里
                playback_cpu    = snd_soc_dai_get_widget_playback(cpu_dai);  // 即cpu_dai->stream[0].widget
                capture_cpu     = snd_soc_dai_get_widget_capture(cpu_dai);   // 即cpu_dai->stream[1].widget
        }

        /* connect BE DAI playback if widgets are valid */
        stream = SNDRV_PCM_STREAM_PLAYBACK;     // 0
        codec = snd_soc_dai_get_widget(codec_dai, stream);  // 获取codec_dai->stream[0].widget

        if (playback_cpu && codec) {
                if (dai_link->params && !rtd->c2c_widget[stream]) { // 不会进入
                        substream = streams[stream].substream;
                        dai = snd_soc_dapm_new_dai(card, substream, "playback"); // 创建一个新的widget,类型为snd_soc_dapm_dai_link
                        if (IS_ERR(dai))
                                goto capture;
                        rtd->c2c_widget[stream] = dai;  // 保存新的widget
                }

          // 创建cpu dai到codec dai之间的音频播放的路径,并将其添加到声卡card的paths链表中
                dapm_connect_dai_routes(&card->dapm, cpu_dai, playback_cpu,  //  source为playback_cpu、sink为codec,构建path
                                        rtd->c2c_widget[stream],            
                                        codec_dai, codec);
        }

capture:
        /* connect BE DAI capture if widgets are valid */
        stream = SNDRV_PCM_STREAM_CAPTURE;        // 1 
        codec = snd_soc_dai_get_widget(codec_dai, stream);  // 获取codec_dai->stream[1].widget

        if (codec && capture_cpu) {
                if (dai_link->params && !rtd->c2c_widget[stream]) {   // 不会进入
                        substream = streams[stream].substream;
                        dai = snd_soc_dapm_new_dai(card, substream, "capture");  // 创建一个新的widget,类型为snd_soc_dapm_dai_link
                        if (IS_ERR(dai))
                                return;
                        rtd->c2c_widget[stream] = dai;      // 保存新的widget
                }
                // 创建codec dai到cpu dai之间的录音的路径,并将其添加到声卡card的paths链表中
                dapm_connect_dai_routes(&card->dapm, codec_dai, codec,         //  source为codec、sink为capture_cpu,构建path
                                        rtd->c2c_widget[stream],        
                                        cpu_dai, capture_cpu);
        }
}

dapm_connect_dai_routes定义为:

static void dapm_connect_dai_routes(struct snd_soc_dapm_context *dapm,
                                    struct snd_soc_dai *src_dai,      // codec/cpu dai
                                    struct snd_soc_dapm_widget *src,  // 输入端widget
                                    struct snd_soc_dapm_widget *dai,  // 输出端widget
                                    struct snd_soc_dai *sink_dai,     // cpu/codec dai 
                                    struct snd_soc_dapm_widget *sink)  // 输出端widget 候选
{
        dev_dbg(dapm->dev, "connected DAI link %s:%s -> %s:%s\n",
                src_dai->component->name, src->name,
                sink_dai->component->name, sink->name);

        if (dai) {        // 如果指定了dai,这里实际传入NULL
                snd_soc_dapm_add_path(dapm, src, dai, NULL, NULL); // dapm域 输入端widget 输出端widget 名称 connected回调函数
                src = dai;
        }

        snd_soc_dapm_add_path(dapm, src, sink, NULL, NULL);
}

比如当内核配置了音频驱动时,可以将该信息输出,输出内容大致如下:

[    4.114168] asoc-simple-card rt5651-sound: connected DAI link ff880000.i2s:Playback -> rt5651.1-001a:AIF1 Playback
[    4.125849] asoc-simple-card rt5651-sound: connected DAI link rt5651.1-001a:AIF1 Capture -> ff880000.i2s:Capture

3.13 snd_soc_add_card_controls

snd_soc_add_card_controls函数和snd_soc_add_component_controls函数功能一致,根据kcontrol模板数组分别创建kcontrol并将其添加到声卡card的controls链表,函数定义在sound/soc/soc-core.c:

/**
 * snd_soc_add_card_controls - add an array of controls to a SoC card.
 * Convenience function to add a list of controls.
 *
 * @soc_card: SoC card to add controls to
 * @controls: array of controls to add
 * @num_controls: number of elements in the array
 *
 * Return 0 for success, else error.
 */
int snd_soc_add_card_controls(struct snd_soc_card *soc_card,
        const struct snd_kcontrol_new *controls, int num_controls)
{
        struct snd_card *card = soc_card->snd_card;

        return snd_soc_add_controls(card, soc_card->dev, controls, num_controls,
                        NULL, soc_card);
}

3.14 snd_component_add

snd_component_add定义在sound/core/init.c,该函数用于将component标识符拼接到card->components;

/**
 *  snd_component_add - add a component string
 *  @card: soundcard structure
 *  @component: the component id string
 *
 *  This function adds the component id string to the supported list.
 *  The component can be referred from the alsa-lib.
 *
 *  Return: Zero otherwise a negative error code.
 */

int snd_component_add(struct snd_card *card, const char *component)
{
        char *ptr;
        int len = strlen(component);

        ptr = strstr(card->components, component);
        if (ptr != NULL) {
                if (ptr[len] == '\0' || ptr[len] == ' ')        /* already there */
                        return 1;
        }
        if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) {
                snd_BUG();
                return -ENOMEM;
        }
        if (card->components[0] != '\0')
                strcat(card->components, " ");
        strcat(card->components, component);
        return 0;
}

3.15 snd_soc_card_late_probe

snd_soc_card_late_probe函数定义在sound/soc/soc-card.c,实际上就是回调card->late_probe函数;

int snd_soc_card_late_probe(struct snd_soc_card *card)
{
        if (card->late_probe) {
                int ret = card->late_probe(card);

                if (ret < 0)
                        return soc_card_ret(card, ret);
        }

        /*
         * It has "card->probe" and "card->late_probe" callbacks,
         * and "late_probe" callback is called after "probe".
         * This means, we can set "card->probed" flag afer "late_probe"
         * for all cases.
         *
         * see
         *      snd_soc_bind_card()
         *      snd_soc_card_probe()
         */
        card->probed = 1;

        return 0;
}

3.16 snd_soc_card_fixup_controls

snd_soc_card_fixup_controls函数定义在sound/soc/soc-card.c,实际上就是回调card->fixup_controls函数;

void snd_soc_card_fixup_controls(struct snd_soc_card *card)
{
        if (card->fixup_controls)
                card->fixup_controls(card);
}

参考文章

[1] Linux ALSA 音频系统:物理链路篇

[2] Linux ALSA声卡驱动之六:ASoC架构中的Machine

[3] Linux音频驱动-ASOC之Machine

[4] ALSA SoC Layer

[5] Linux ALSA音频驱动二:ALSA驱动注册

[6] Linux ALSA 之十:ALSA ASOC Machine Driver

posted @ 2023-07-19 23:00  大奥特曼打小怪兽  阅读(444)  评论(1编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步