您当前的位置: 首页 >  linux
  • 3浏览

    0关注

    417博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

08.音频系统:第003课_Linux音频驱动程序:第005节_DAPM_widget_route_path

江南才尽,年少无知! 发布时间:2019-04-17 11:37:02 ,浏览量:3

DAPM是Dynamic Audio PowerManagement的缩写,译过来就是动态音频电源管理的意思。DAPM是为了使基于linux的移动设备上的音频驱动子系统,在任何时候都工作在最小功耗状态下。 DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc Core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译。 如果没有改系统,我们驱动程序是非常复杂了: 在这里插入图片描述 如上图,是一个编码器录音的流程,从LINPUT2进入,然后经过多个部件,才能进行录音,其中红色的圈为我们需要操作的寄存器,可以看到,多达4个之多。仅仅是为了录制声音,却如此复杂。但是如果当我们打开LINPUT2的时候,后面的开关,系统帮我们完成,这样我们不就比较方便了吗?以后得应用程序,只要设置三个输入通道任意一个,其就顺带的把接下来要设置(启动)的也完成,如下规则: 在这里插入图片描述 1.Miser有多个输入源,只要其中的某个开关使能,顺便其他的都打开了。 2.其上的3个开关我们与Mixer可以使用共同的一个结构体widget表示,则widget中含有3个开关,以及一个Mixer,每个开关使用kontrol来表示。当我们打开开关的时候,Mixer也会被设置。kontrol中有3个函数,info与get,put。之前讲解kontrol的只会设置自己的寄存器,现在除了设置自己的寄存器,还会去设置Mixer的寄存器。

在这里,我们引入了一个新的概念widget,在我们现在查看源代码之前,先看看我们的ALC5651音频编码器:

ALC5651硬件

开发板原理图 在这里插入图片描述 下面是ALC5651编码器的声音混合路径: 在这里插入图片描述 初步看起来复杂无比(后面我会把上图一一截图,把上图分成多个部分,方便大家观看,否则将不清晰,大家可以进行对比,然后知道为上图的那个部分),不过没关系,后续为大家慢慢讲解。

源码分析

下面我们打开rt5651文件,我们可以找到如下数组(为了博客看起来不过于臃肿,只粘贴部分):

static const struct snd_soc_dapm_widget rt5651_dapm_widgets[] = {
	......
	SND_SOC_DAPM_MIXER("Stereo2 ADC MIXL", SND_SOC_NOPM, 0, 0,rt5651_sto2_adc_l_mix,ARRAY_SIZE(rt5651_sto2_adc_l_mix)),
	SND_SOC_DAPM_MIXER("Stereo2 ADC MIXR", SND_SOC_NOPM, 0, 0,rt5651_sto2_adc_r_mix,ARRAY_SIZE(rt5651_sto2_adc_r_mix)),
	......
}

可以知道这是snd_soc_dapm_widget数组,其中使用各种宏,去定义各种widget。如我们的录音部分,根据原理图,可以知道其输入通道为IN2P,IN2N,分别为左右声道。其对应核心widgets为上面的粘贴部分。其中填写SND_SOC_NOPM则代表没有电源开关的寄存器,否则应该填写寄存器的地址。

可以看到他们分别包含了rt5651_sto2_adc_l_mix,rt5651_sto2_adc_r_mix,他们包含的snd_kcontrol_new如下:

static const struct snd_kcontrol_new rt5651_sto1_adc_l_mix[] = {
	SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO1_ADC_MIXER,
			RT5651_M_STO1_ADC_L1_SFT, 1, 1),
	SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO1_ADC_MIXER,
			RT5651_M_STO1_ADC_L2_SFT, 1, 1),
};

static const struct snd_kcontrol_new rt5651_sto1_adc_r_mix[] = {
	SOC_DAPM_SINGLE("ADC1 Switch", RT5651_STO1_ADC_MIXER,
			RT5651_M_STO1_ADC_R1_SFT, 1, 1),
	SOC_DAPM_SINGLE("ADC2 Switch", RT5651_STO1_ADC_MIXER,
			RT5651_M_STO1_ADC_R2_SFT, 1, 1),
};

每个snd_kcontrol_new都填写了自己的寄存器,以及相应的位。可以看到其使用的红也不一样了,前面都带有了SOC_DAPM。他们的不一样的,主要来自于put函数的不一样,除了设置自己的,还会去设置widgets(这里为混合器)的。

下面我们看看,还有哪些需要改进的地方: 在这里插入图片描述 如上图,如果每个蓝色的框都代表一个widgets,那么他怎么知道前后的widgets,或者左右的widgets是那一个呢?其中用红色长方形的框,框起来的,表示的就是一个route,这个就是接下来要讲解的。

从上面我们可以分析出,route包括3个部分:sink(获得数据),kontrol(控制寄存器),source(发出数据)。在文件中搜索route,在结合下图: 在这里插入图片描述

struct snd_soc_dapm_route {
	const char *sink;
	const char *control; //
	const char *source;

	/* Note: currently only supported for links where source is a supply */
	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink);
};
static const struct snd_soc_dapm_route rt5651_dapm_routes[] = {
	......
	{"RECMIXL", "INL1 Switch", "INL1 VOL"},
	{"RECMIXL", "BST3 Switch", "BST3"},
	{"RECMIXL", "BST2 Switch", "BST2"},
	{"RECMIXL", "BST1 Switch", "BST1"},

	{"RECMIXR", "INR1 Switch", "INR1 VOL"},
	{"RECMIXR", "BST3 Switch", "BST3"},
	{"RECMIXR", "BST2 Switch", "BST2"},
	{"RECMIXR", "BST1 Switch", "BST1"},
	......
}

可以看到类似于{“RECMIXL”, “BST3 Switch”, “BST3”},表示的是一个路径,"RECMIXR"表示: 在这里插入图片描述 "BST3 Switch"表示他对应的kcontrol。那么他是怎么把一个route转化为一个path,一一去设置这些开关的呢?

static struct snd_soc_codec_driver soc_codec_dev_rt5651 = {
	.dapm_routes = rt5651_dapm_routes,
}
snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5651,rt5651_dai, ARRAY_SIZE(rt5651_dai));
	/*machin之后最终导致该函数被调用*/
	static int snd_soc_instantiate_card(struct snd_soc_card *card)
		soc_probe_link_components(card, i, order);
			soc_probe_component(card, component);
				snd_soc_dapm_add_routes(dapm, component->dapm_routes,component->num_dapm_routes);
					snd_soc_dapm_add_route(dapm, route);	
						/*创建一个path*/
						snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,route->connected);				

这样就根据route创建了path,其中path的定义如下:

struct snd_soc_dapm_path {
	const char *name;

	/*
	 * source (input) and sink (output) widgets
	 * The union is for convience, since it is a lot nicer to type
	 * p->source, rather than p->node[SND_SOC_DAPM_DIR_IN]
	 */
	union {
		struct {
			struct snd_soc_dapm_widget *source;
			struct snd_soc_dapm_widget *sink;
		};
		struct snd_soc_dapm_widget *node[2];
	};

	/* status */
	u32 connect:1;	/* source and sink widgets are connected */
	u32 walking:1;  /* path is in the process of being walked */
	u32 weak:1;	/* path ignored for power management */
	u32 is_supply:1;	/* At least one of the connected widgets is a supply */

	int (*connected)(struct snd_soc_dapm_widget *source,
			 struct snd_soc_dapm_widget *sink);

	struct list_head list_node[2];
	struct list_head list_kcontrol;
	struct list_head list;
};

我们已经基本了解了这样概念,那么我们这么使用DAPM呢?不要忘记DAPM的初衷是为了省电的电源管理。根据前面的分析: 在这里插入图片描述 我们可以知道,在选择一个BST的时候,RECMIXL都会被打开,那是不是这样呢?显然不是: 在这里插入图片描述 如上,我们先看其中的序号1: LINPUT1需要经过一个开关才能到达mixer,其中的开关对应一个konctrol,前面提到konctrol中的put函数会把开关打开,实际不是这样,因为他要省电啊,不会去设置寄存寄存器,那么put函数会做什么?如下:

#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) 
	int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_value *ucontrol)
	connect = !!val;
	ret = soc_dapm_mixer_update_power(card, kcontrol, connect);
		dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);

1.设置path-connect为1或者0 2.最终调用dapm_power_widgets。 dapm_power_widgets:a,无人使用声卡,则不设置, 不启动任何寄存器。b,有APP使用声卡,也许会设置寄存器。

为什么说也许呢?下面为大家讲解: 我们先引入一个概念,comlete path,还是结合上一副图,我们观看序列2. 如图,我们可以看到分开了很多个path,一条路线所有的path,则为一个comlete path,在源码我们开始查找:

static const struct snd_soc_dapm_route rt5651_dapm_routes[] = {
	{"IN2P", NULL, "LDO"},
	{"BST2", NULL, "IN2P"}
	{"RECMIXL", "BST2 Switch", "BST2"},
	{"ADC L", NULL, "RECMIXL"}
	{"Stereo1 ADC L1 Mux", "ADC", "ADC L"},
	{"Stereo1 ADC MIXL", "ADC1 Switch", "Stereo1 ADC L1 Mux"},
	{"IF1 ADC1", NULL, "Stereo1 ADC MIXL"},
	{"AIF1TX", NULL, "IF1 ADC1"},
}

如上,从"LDO"到"AIF1TX"可以看做一条comlete path,基于省电的设置,只有每个一条comlete path每个path的connet都标记为connect=1,并且存在APP使用声卡,其才会去设置所有的寄存器。

下面是该小节的总结:

a. tinymix设置普通的kcontrol: 会直接设置寄存器
b. tinymix设置DAPM的kcontrol: 
设置所在path的connect,
调用dapm_power_widgets(widget->dapm, SND_SOC_DAPM_STREAM_NOP);
c. tinyplay, tinycap在传输数据之前:
调用 dapm_power_widgets(dapm, event);
d. dapm_power_widgets在有APP使用声卡的前提下, 
   会找出complete path,设置上面的所有widget

下小节我们在去学习判断一个widget是否在一个comlete path上面。

关注
打赏
1592542134
查看更多评论
立即登录/注册

微信扫码登录

0.0601s