left_speaker_mixer和right_speaker_mixer来完成,两个widget具备电源属性,所以,当这两个widget在一条有效的音频路径上时,dapm框架可以通过寄存器WM8993_POWER_MANAGEMENT_3的第8位和第9位控制它的电源状态。
第三步,定义这些widget的连接路径: [cpp] view plaincopy
static const struct snd_soc_dapm_route routes[] = { ......
{ \, \, \ }, { \, \, \ }, { \, \, \ }, { \, \, \ },
......
{ \, \, \ }, { \, NULL, \ },
{ \, \, \ }, { \, NULL, \ }, };
通过第一步的定义,我们知道DACL Mux和DACR Mux有两个输入引脚,分别是 Left Right
而SPKL和SPKR有四个输入选择引脚,分别是: Input Switch
IN1LP Switch/IN1RP Switch Output Switch DAC Switch
所以,很显然,上面的路径定义的意思就是: AIFINL连接到DACL Mux的Left输入脚 AIFINR连接到DACL Mux的Right输入脚 AIFINL连接到DACR Mux的Left输入脚 AIFINR连接到DACR Mux的Right输入脚 DACL连接到SPKL的DAC Switch输入脚 DACR连接到SPKR的DAC Switch输入脚
第四步,在codec驱动的probe回调中注册这些widget和路径: [cpp] view plaincopy
static int wm8993_probe(struct snd_soc_codec *codec) {
......
snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
26
ARRAY_SIZE(wm8993_dapm_widgets));
......
snd_soc_dapm_add_routes(dapm, routes, ARRAY_SIZE(routes));
...... }
在machine驱动中,我们可以用同样的方式定义和注册板子特有的widget和路径信息。
1.ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route
前几篇文章我们从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中
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个dapm context: [cpp] view plaincopy
27
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_level的取值范围是以下几种: SND_SOC_BIAS_OFF
SND_SOC_BIAS_STANDBY SND_SOC_BIAS_PREPARE SND_SOC_BIAS_ON
snd_soc_dapm_context被内嵌到代表codec、platform、card、dai的结构体中: [cpp] view plaincopy struct snd_soc_codec { ......
/* dapm */
struct snd_soc_dapm_context dapm; ...... };
struct snd_soc_platform {
28
......
/* 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结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。 创建和注册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框架有关的字段: [cpp] view plaincopy
struct snd_soc_codec_driver { ......
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls; int num_controls;
29
const struct snd_soc_dapm_widget *dapm_widgets; int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes; int num_dapm_routes; ...... }
我们只要把我们定义好的snd_soc_dapm_widget结构数组的地址和widget的数量赋值到dapm_widgets和num_dapm_widgets字段即可,这样,经过
snd_soc_register_codec注册codec后,在machine驱动匹配上该codec时,系统会判断这两个字段是否被赋值,如果有,它会调佣dapm框架提供的api来创建和注册widget,注意这里我说还要创建这个词,你可能比较奇怪,既然代表
widget的snd_soc_dapm_widget结构数组已经在codec驱动中定义好了,为什么还要在创建?事实上,我们在codec驱动中定义的widget数组只是作为一个模板,dapm框架会根据该模板重新申请内存并初始化各个widget。我们看看实际的例子可能是这样的: [cpp] view plaincopy
static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {
......
SND_SOC_DAPM_SUPPLY(\, SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_AIF_IN(\, \, 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_AIF_IN(\, \, 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); ......
30