}
上面这种注册方法有个缺点,有时候我们为了代码的清晰,可能会根据功能把不同的widget定义成多个数组,但是snd_soc_codec_driver中只有一个
dapm_widgets字段,无法设定多个widget数组,这时候,我们需要主动在codec的probe回调中调用dapm框架提供的api来创建这些widget: [cpp] view plaincopy
static int wm8993_probe(struct snd_soc_codec *codec) {
......
snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets, ARRAY_SIZE(wm8993_dapm_widgets));
...... }
实际上,对于第一种方法,snd_soc_register_codec内部其实也是调用
snd_soc_dapm_new_controls来完成的。后面会有关于这个函数的详细分析。
platform驱动中注册 和codec驱动一样,我们会通过ASoc提供的api函数snd_soc_register_platform来注册一个platform驱动,该函数的第二个参数是一个snd_soc_platform_driver结构指针,snd_soc_platform_driver结构中同样也包含了与dapm相关的字段: [cpp] view plaincopy
struct snd_soc_platform_driver { ......
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls; int num_controls;
const struct snd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes; int num_dapm_routes; ...... }
要注册platform级别的widget,和codec驱动一样,只要把定义好的widget数组赋值给dapm_widgets和num_dapm_widgets字段即可,
snd_soc_register_platform函数注册paltform后,当machine驱动匹配上该
platform时,系统会自动完成创建和注册的工作。同理,我们也可以在platform驱动的probe回调函数中主动使用snd_soc_dapm_new_controls来完成widget的创建工作。具体的代码和codec驱动是类似的,这里就不贴了。
machine驱动中注册 有些widget可能不是位于codec中,例如一个独立的耳机放大器,或者是喇叭功放等,这种widget通常需要在machine驱动中注册,通常
31
他们的dapm context也从属于声卡(snd_soc_card)域。做法依然和codec驱动类似,通过代表声卡的snd_soc_card结构中的几个dapm字段完成: [cpp] view plaincopy struct snd_soc_card { ...... /*
* Card-specific routes and widgets. */
const struct snd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes; int num_dapm_routes; bool fully_routed; ...... }
只要把定义好的widget数组和数量赋值给dapm_widgets指针和
num_dapm_widgets即可,注册声卡使用的api:snd_soc_register_card(),也会通过snd_soc_dapm_new_controls来完成widget的创建工作。
注册音频路径
系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过
snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,请参考:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget中的\建立widget和route\一节的内容。通常,所有的路径信息会用一个
snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:
通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;
两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。以下的代码片段是omap的pandora板子的machine驱动,使用第二种方法注册路径信息: [cpp] view plaincopy
static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {
SND_SOC_DAPM_MIC(\, NULL), SND_SOC_DAPM_MIC(\, NULL), SND_SOC_DAPM_LINE(\, NULL),
32
};
static const struct snd_soc_dapm_route omap3pandora_out_map[] = {
{\, NULL, \},
{\, NULL, \}, {\, NULL, \},
{\, NULL, \}, };
static const struct snd_soc_dapm_route omap3pandora_in_map[] = {
{\, NULL, \}, {\, NULL, \},
{\, NULL, \}, {\, NULL, \},
{\, NULL, \},
{\, NULL, \}, };
static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd) {
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm; int ret;
/* All TWL4030 output pins are floating */ snd_soc_dapm_nc_pin(dapm, \); ......
//注册kcontrol控件
ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,
ARRAY_SIZE(omap3pandora_out_dapm_widgets));
if (ret < 0)
return ret;
//注册machine的音频路径
return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,
ARRAY_SIZE(omap3pandora_out_map)); }
33
static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd) {
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm; int ret;
/* Not comnnected */
snd_soc_dapm_nc_pin(dapm, \); ......
//注册kcontrol控件
ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,
ARRAY_SIZE(omap3pandora_in_dapm_widgets));
if (ret < 0)
return ret; //注册machine音频路径
return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,
ARRAY_SIZE(omap3pandora_in_map)); }
/* Digital audio interface glue - connects codec <--> CPU */ static struct snd_soc_dai_link omap3pandora_dai[] = { {
.name = \, ......
.init = omap3pandora_out_init, }, {
.name = \,
.stream_name = \, ......
.init = omap3pandora_in_init, } };
dai widget
上面几节的内容介绍了codec、platform以及machine级别的widget和route的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),关于dai的描述,请参考:Linux ALSA声卡驱动之七:ASoC架构中的Codec。dai按所在的位置,又分为cpu dai和codec dai,在硬件上,通常一个
34
cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。在Asoc中,一个dai用snd_soc_dai结构来表述,其中有几个字段和dapm框架有关: [cpp] view plaincopy struct snd_soc_dai { ......
struct snd_soc_dapm_widget *playback_widget; struct snd_soc_dapm_widget *capture_widget; struct snd_soc_dapm_context dapm; ...... }
dai由codec驱动和平台代码中的iis或pcm接口驱动注册,machine驱动负责找到snd_soc_dai_link中指定的一对cpu/codec dai,并把它们进行绑定。不管是cpu dai还是codec dai,通常会同时传输播放和录音的音频流的能力,所以我们可以看到,snd_soc_dai中有两个widget指针,分别代表播放流和录音流。这两个dai widget是何时创建的呢?下面我们逐一进行分析。 codec dai widget
首先,codec驱动在注册codec时,会传入该codec所支持的dai个数和记录dai信息的snd_soc_dai_driver结构指针: [cpp] view plaincopy
static struct snd_soc_dai_driver wm8993_dai = { .name = \, .playback = {
.stream_name = \, .channels_min = 1, .channels_max = 2,
.rates = WM8993_RATES,
.formats = WM8993_FORMATS, .sig_bits = 24, },
.capture = {
.stream_name = \, .channels_min = 1, .channels_max = 2,
.rates = WM8993_RATES,
.formats = WM8993_FORMATS, .sig_bits = 24, },
.ops = &wm8993_ops, .symmetric_rates = 1, };
35