前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path。之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开始,我们要从代码入手,分析dapm的详细工作原理:
- 如何注册widget
- 如何连接两个widget
- 一个widget的状态裱画如何传递到整个音频路径中
声明:本博内容均由 http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
dapm context
在讨论widget的注册之前,我们先了解另一个概念:dapm context,直译过来的意思是dapm上下文,这个好像不好理解,其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:
- 属于codec中的widget位于一个dapm context中
- 属于platform的widget位于一个dapm context中
- 属于整个声卡的widget位于一个dapm context中
- struct snd_soc_dapm_context {
- enum snd_soc_bias_level bias_level;
- enum snd_soc_bias_level suspend_bias_level;
- struct delayed_work delayed_work;
- unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
- struct snd_soc_dapm_update *update;
- void (*seq_notifier)(struct snd_soc_dapm_context *,
- enum snd_soc_dapm_type, int);
- struct device *dev; /* from parent - for debug */
- struct snd_soc_codec *codec; /* parent codec */
- struct snd_soc_platform *platform; /* parent platform */
- struct snd_soc_card *card; /* parent card */
- /* used during DAPM updates */
- enum snd_soc_bias_level target_bias_level;
- struct list_head list;
- int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);
- #ifdef CONFIG_DEBUG_FS
- struct dentry *debugfs_dapm;
- #endif
- };
- SND_SOC_BIAS_OFF
- SND_SOC_BIAS_STANDBY
- SND_SOC_BIAS_PREPARE
- SND_SOC_BIAS_ON
- struct snd_soc_codec {
- ......
- /* dapm */
- struct snd_soc_dapm_context dapm;
- ......
- };
- struct snd_soc_platform {
- ......
- /* dapm */
- struct snd_soc_dapm_context dapm;
- ......
- };
- struct snd_soc_card {
- ......
- /* dapm */
- struct snd_soc_dapm_context dapm;
- ......
- };
- :
- struct snd_soc_dai {
- ......
- /* dapm */
- struct snd_soc_dapm_widget *playback_widget;
- struct snd_soc_dapm_widget *capture_widget;
- struct snd_soc_dapm_context dapm;
- ......
- };
创建和注册widget
我们已经知道,一个widget用snd_soc_dapm_widget结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的codec驱动、platform驱动和machine驱动中定义一组widget,这些widget用数组进行组织,我们一般会使用dapm框架提供的大量的辅助宏来定义这些widget数组,辅助宏的说明请参考前一偏文章:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget。
codec驱动中注册 我们知道,我们会通过ASoc提供的api函数snd_soc_register_codec来注册一个codec驱动,该函数的第二个参数是一个snd_soc_codec_driver结构指针,这个snd_soc_codec_driver结构需要我们在codec驱动中显式地进行定义,其中有几个与dapm框架有关的字段:
- struct snd_soc_codec_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;
- ......
- }
- static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
- ......
- SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),
- SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),
- ......
- };
- static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {
- .probe = codec_xxx_probe,
- ......
- .dapm_widgets = &wm8993_dapm_widgets[0],
- .num_dapm_widgets = ARRAY_SIZE(wm8993_dapm_widgets),
- ......
- };
- static int codec_wm8993_i2c_probe(struct i2c_client *i2c,
- const struct i2c_device_id *id)
- {
- ......
- ret = snd_soc_register_codec(&i2c->dev,
- &soc_codec_dev_wm8993, &wm8993_dai, 1);
- ......
- }
- static int wm8993_probe(struct snd_soc_codec *codec)
- {
- ......
- snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
- ARRAY_SIZE(wm8993_dapm_widgets));
- ......
- }
platform驱动中注册 和codec驱动一样,我们会通过ASoc提供的api函数snd_soc_register_platform来注册一个platform驱动,该函数的第二个参数是一个snd_soc_platform_driver结构指针,snd_soc_platform_driver结构中同样也包含了与dapm相关的字段:
- 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;
- ......
- }
machine驱动中注册 有些widget可能不是位于codec中,例如一个独立的耳机放大器,或者是喇叭功放等,这种widget通常需要在machine驱动中注册,通常他们的dapm context也从属于声卡(snd_soc_card)域。做法依然和codec驱动类似,通过代表声卡的snd_soc_card结构中的几个dapm字段完成:
- 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需要互相连接在一起才能协调工作,连接关系通过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回调函数来注册音频路径;
- static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {
- SND_SOC_DAPM_MIC("Mic (internal)", NULL),
- SND_SOC_DAPM_MIC("Mic (external)", NULL),
- SND_SOC_DAPM_LINE("Line In", NULL),
- };
- static const struct snd_soc_dapm_route omap3pandora_out_map[] = {
- {"PCM DAC", NULL, "APLL Enable"},
- {"Headphone Amplifier", NULL, "PCM DAC"},
- {"Line Out", NULL, "PCM DAC"},
- {"Headphone Jack", NULL, "Headphone Amplifier"},
- };
- static const struct snd_soc_dapm_route omap3pandora_in_map[] = {
- {"AUXL", NULL, "Line In"},
- {"AUXR", NULL, "Line In"},
- {"MAINMIC", NULL, "Mic (internal)"},
- {"Mic (internal)", NULL, "Mic Bias 1"},
- {"SUBMIC", NULL, "Mic (external)"},
- {"Mic (external)", NULL, "Mic Bias 2"},
- };
- static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)
- {
- struct snd_soc_codec *codec = rtd->codec;
- struct snd_soc_dapm_context *dapm = &codec->dapm;
- int ret;
- /* All TWL4030 output pins are floating */
- snd_soc_dapm_nc_pin(dapm, "EARPIECE");
- ......
- //注册kcontrol控件
- ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,
- ARRAY_SIZE(omap3pandora_out_dapm_widgets));
- if (ret < 0)
- return ret;
- //注册machine的音频路径
- return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,
- ARRAY_SIZE(omap3pandora_out_map));
- }
- static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)
- {
- struct snd_soc_codec *codec = rtd->codec;
- struct snd_soc_dapm_context *dapm = &codec->dapm;
- int ret;
- /* Not comnnected */
- snd_soc_dapm_nc_pin(dapm, "HSMIC");
- ......
- //注册kcontrol控件
- ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,
- ARRAY_SIZE(omap3pandora_in_dapm_widgets));
- if (ret < 0)
- return ret;
- //注册machine音频路径
- return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,
- ARRAY_SIZE(omap3pandora_in_map));
- }
- /* Digital audio interface glue - connects codec <--> CPU */
- static struct snd_soc_dai_link omap3pandora_dai[] = {
- {
- .name = "PCM1773",
- ......
- .init = omap3pandora_out_init,
- }, {
- .name = "TWL4030",
- .stream_name = "Line/Mic In",
- ......
- .init = omap3pandora_in_init,
- }
- };
dai widget
上面几节的内容介绍了codec、platform以及machine级别的widget和route的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),关于dai的描述,请参考:Linux ALSA声卡驱动之七:ASoC架构中的Codec。dai按所在的位置,又分为cpu dai和codec dai,在硬件上,通常一个cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。在Asoc中,一个dai用snd_soc_dai结构来表述,其中有几个字段和dapm框架有关:
- struct snd_soc_dai {
- ......
- struct snd_soc_dapm_widget *playback_widget;
- struct snd_soc_dapm_widget *capture_widget;
- struct snd_soc_dapm_context dapm;
- ......
- }
codec dai widget
首先,codec驱动在注册codec时,会传入该codec所支持的dai个数和记录dai信息的snd_soc_dai_driver结构指针:
- static struct snd_soc_dai_driver wm8993_dai = {
- .name = "wm8993-hifi",
- .playback = {
- .stream_name = "Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8993_RATES,
- .formats = WM8993_FORMATS,
- .sig_bits = 24,
- },
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8993_RATES,
- .formats = WM8993_FORMATS,
- .sig_bits = 24,
- },
- .ops = &wm8993_ops,
- .symmetric_rates = 1,
- };
- static int wm8993_i2c_probe(struct i2c_client *i2c,
- const struct i2c_device_id *id)
- {
- ......
- ret = snd_soc_register_codec(&i2c->dev,
- &soc_codec_dev_wm8993, &wm8993_dai, 1);
- ......
- }
- static int soc_probe_codec(struct snd_soc_card *card,
- struct snd_soc_codec *codec)
- {
- ......
- /* Create DAPM widgets for each DAI stream */
- list_for_each_entry(dai, &dai_list, list) {
- if (dai->dev != codec->dev)
- continue;
- snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);
- }
- ......
- }
- 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;
- template.sname = dai->driver->playback.stream_name;
- w = snd_soc_dapm_new_control(dapm, &template);
- w->priv = dai;
- dai->playback_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;
- w = snd_soc_dapm_new_control(dapm, &template);
- w->priv = dai;
- dai->capture_widget = w;
- }
- return 0;
- }
cpu dai widget
这里顺便说一个小意外,昨天晚上手贱,执行了一下git pull,版本升级到了3.12 rc7,结果发现ASoc的代码有所变化,于是稍稍纠结了一下,用新的代码继续还是恢复之前的3.10 rc5?经过查看了一些变化后,发现还是新的版本改进得更合理,现在决定,后面的内容都是基于3.12 rc7了。如果大家发现后面贴的代码和之前贴的有差异的地方,自己比较一下这两个版本的代码吧!
回到cpu dai,以前的内核版本由驱动通过snd_soc_register_dais注册,新的版本中,这个函数变为了soc-core的内部函数,驱动改为使用snd_soc_register_component注册,snd_soc_register_component函数再通过调用snd_soc_register_dai/snd_soc_register_dais来完成实际的注册工作。和codec dai widget一样,cpu dai widget也发生在machine驱动匹配上相应的platform驱动之后,soc_probe_platform会被调用,在soc_probe_platform函数中,通过比较dai->dev和platform->dev,挑选出属于该platform的dai,然后通过snd_soc_dapm_new_dai_widgets为cpu dai创建相应的widget:
- static int soc_probe_platform(struct snd_soc_card *card,
- struct snd_soc_platform *platform)
- {
- int ret = 0;
- const struct snd_soc_platform_driver *driver = platform->driver;
- struct snd_soc_dai *dai;
- ......
- if (driver->dapm_widgets)
- snd_soc_dapm_new_controls(&platform->dapm,
- driver->dapm_widgets, driver->num_dapm_widgets);
- /* Create DAPM widgets for each DAI stream */
- list_for_each_entry(dai, &dai_list, list) {
- if (dai->dev != platform->dev)
- continue;
- snd_soc_dapm_new_dai_widgets(&platform->dapm, dai);
- }
- platform->dapm.idle_bias_off = 1;
- ......
- if (driver->controls)
- snd_soc_add_platform_controls(platform, driver->controls,
- driver->num_controls);
- if (driver->dapm_routes)
- snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,
- driver->num_dapm_routes);
- ......
- return 0;
- }
花了这么多篇幅来讲解dai widget,好像现在看来它还没有什么用处。嗯,不要着急,实际上dai widget是一条完整dapm音频路径的重要元素,没有她,我们无法完成dapm的动态电源管理工作,因为它是音频流和其他widget的纽带,细节我们要留到下一篇文章中来阐述了。
端点widget
一条完整的dapm音频路径,必然有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。以下这些类型的widget可以成为端点widget:
codec的输入输出引脚:
- snd_soc_dapm_output
- snd_soc_dapm_input
- snd_soc_dapm_hp
- snd_soc_dapm_spk
- snd_soc_dapm_line
- snd_soc_dapm_adc
- snd_soc_dapm_dac
- snd_soc_dapm_aif_out
- snd_soc_dapm_aif_in
- snd_soc_dapm_dai_out
- snd_soc_dapm_dai_in
- snd_soc_dapm_supply
- snd_soc_dapm_regulator_supply
- snd_soc_dapm_clock_supply
- snd_soc_dapm_kcontrol