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
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); }
该函数接受一个 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; } } }
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; }
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); }
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; }
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; }
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; }
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; }
以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; }
函数内部又调用了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); }
函数内部又调用了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); }
这一步主要原因是因为我们使用的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;
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); }
参考文章
[2] Linux ALSA声卡驱动之六:ASoC架构中的Machine
[4] ALSA SoC Layer