Android14音频子系统-ASoC-ALSA之DAPM电源管理子系统
文章目录
- 概述
- 1)codec对象-WM8960
- 2)ALSA下的kcontrol的构造与使用
- 3)ASOC-ALSA下的kcontrol构造与使用
- 1、通用寄存器对象 - kcontrol
- 2、DAPM下的寄存器对象-widget
- 3、如何构造widget?
- 4、抽象对象widget、route与path
- 1)route与path的关系?
- 2)widget与path的关系
- 3)一条完整的path - complete path
- 3)complete path是如何生成的?
- 4)DAPM下的数据结构
- 5)DAPM场景分析
- 1、widget什么时候会被打开?
- 2、tinymix/tinyplay/tinymix调用DAPM
概述
ALSA声卡驱动中的DAPM详解之一 :https://blog.csdn.net/DroidPhone/article/details/12793293基本概念
1、DAPM : Dynamic Audio Power Management
>>可以当做ASoc-ALSA系统下的一个子系统,构造也是相当复杂,但声卡驱动需要熟悉此框架,使用其框架的宏和API来构造(电源)寄存器对象;产生的背景:
1)ASOC框架中动态音频电源管理子系统 - 设计的目的是省电
2)同时也达到减少暴露给应用的操作(寄存器操作)效果 - 比如封装打开某个input通道、MUX、mixer操作等等;
3)
DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。
4)
在嵌入式领域中,由于数字功放内部的寄存器很多,没有DAPM之前,我们可以在init里面逐一将其设置好,即完全打开数字功放的所有功能,虽然简单,但比较费电,因此设计DAPM来控制按需打开(这套框架也相当复杂);
5)
此前在ASOC-ALSA分析中,看到很多DAPM的影子,现在来看看DAPM子系统如何嵌入到ASOC框架control中的,注意数字功放里面很多寄存器,DAPM主要控制电源相关的寄存器(Mixer、MUX、switch),音量volume则不需要。2、DAPM Widget用于表示音频路径中的物理连接点或控制点,根据一定条件(自动路由管理)触发关闭/打开 控制点以达到节能状态3、kcontrol : 代表一个控制寄存器(可能是寄存器中某几个bit),即功能控制4、coalesced : 合并
DAPM框架有较多的抽象概念,理解起来比较费劲和枯燥,我们先逐个拆解"对象",并梳理这些对象之间的关系,最后从宏观上看应用的效果,接下来使用WM8960 (有丰富的寄存器) 功放作为硬件声卡展开说明
1)codec对象-WM8960
Input Signal Path
1、PGA 是 “可编程增益放大器”(Programmable Gain Amplifier);
2、以上是Left Input/Left Output示意图,三路输入对应三条音频路径,输入到输出需要经过内部多个器件(寄存器);
1)比如:LINPUT1 -> LMN1 -> Left Input PGA -> LMICBOOST -> LMIC2B -> Left Boose Mixer -> Left ADC
2)可以看到有些部件是功能部件,有些是开关部件;
3、假如用户只适用LINPUT1,其它两路不适用,全部寄存器都设置打开,而有些模拟器件即使没有输入源信号也会耗电,而DAPM的任务是按需打开;
4、哪些寄存器对象是需要DAPM控制?
1)不是所有器件都需要DAPM控制,跟电源相关且可以通过寄存器关闭的器件,则需要DAPM参与把控,比如LIN1BOOST/LIN2BOOST/LIN3BOOST/Left Boost Mixer,而LINVOL则不需要DAPM参与把控;
2)ALSA下的kcontrol的构造与使用
1、kcontrol对应的结构体 - snd_kcontrol_new 与 snd_kcontrol
struct snd_kcontrol_new {snd_ctl_elem_iface_t iface; /* interface identifier */const unsigned char *name; /* ASCII name of item */snd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;unsigned long private_value;
};以snd_kcontrol_new为模版,构造实际的寄存器对象-snd_kcontrol
struct snd_kcontrol {struct list_head list; /* list of controls */struct snd_ctl_elem_id id;unsigned int count; /* count of same elements */ 相同元素-比如左右声道,在链表里面会占用两个idsnd_kcontrol_info_t *info;snd_kcontrol_get_t *get;snd_kcontrol_put_t *put;union {snd_kcontrol_tlv_rw_t *c;const unsigned int *p;} tlv;unsigned long private_value;void *private_data;void (*private_free)(struct snd_kcontrol *kcontrol);struct snd_kcontrol_volatile vd[0]; /* volatile data */
};info/get/put这些函数怎么使用,直接看用户层怎么使用即可一目了然2、tinymix的使用
1)查看tinymix可控的寄存器列表
root@# tinymix
Mixer name: 'audiocodec' //const unsigned char *name;
Number of controls: 16
ctl type num name value
1 INT 1 "digital volume" 0 //snd_kcontrol_info_t *info;
2 INT 1 "LINEIN to output mixer gain control" 3
3 BOOL 1 "LINEOUT Switch" On
...2)设置音量
1、根据name来操作
tinymix "LINEOUT volume" "2" //snd_kcontrol_put_t *put;
2、根据ctl id来操作 - 使用成员struct snd_ctl_elem_id id表示,数值代表加入card list里面的顺序。
tinymix 1 "2" //snd_kcontrol_put_t *put; //
3、private_value字段
可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息,供给put/get/info函数所使用!
以Bass Boost 寄存器为例:
1)一个寄存器会有多个功能,一个kcontrol代表某个功能,shift代表从第几个bit开始,max表示占多少bit;
2)snd_kcontrol_put_t *put; 会将用户传下来的值 经过转换 最终写到对应寄存器中去;
3)ASOC-ALSA下的kcontrol构造与使用
ASOC-ALSA相对ALSA,进一步封装,但最终还是会调用ALSA那套kcontrol逻辑
1、通用寄存器对象 - kcontrol
内核提供一堆宏供开发者定义kcontrol(用来定义所有寄存器对象)
1)android\kernel\fusion\4.19\include\sound\soc.h
/** Convenience kcontrol builders*/
#define SOC_SINGLE(xname, reg, shift, max, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\.put = snd_soc_put_volsw, \.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }2)android\kernel\fusion\4.19\sound\soc\soc-ops.c
通用info/put/get对应的操作函数3)开发者需要根据寄存器属于哪一种分类,每种不同分类的寄存器所对应的put/get不一样,然后使用对应的内核宏来进行构造
常用的寄存器对象 - 基本可以覆盖所有功放寄存器类型
1、SOC_SINGLE 寄存器对象 - 操作一个寄存器
2、SOC_SINGLE_TLV 寄存器对象 - 用于音量,增益寄存器
3、SOC_DOUBLE 寄存器对象 - 操作两个寄存器
4、Mixer 寄存器对象 - 多合一寄存器
5、Mux 寄存器对象 - 多选一寄存器6、如果需要自定义put/get函数,可以使用EXT后缀的内核宏
SOC_SINGLE_EXT
SOC_DOUBLE_EXT4)构造例子
static const struct snd_kcontrol_new wm8960_snd_controls[] = {
SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,6, 1, 0),
SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume",WM8960_LINPATH, 4, 3, 0, micboost_tlv),
SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
SOC_ENUM("ADC Polarity", wm8960_enum[0]),
...
}细节可以展开代码进行研究
2、DAPM下的寄存器对象-widget
1、DAMP子系统下 基本操作单元不是kcontrol,而是widget,一个widget可以包含一个kcontrol,也可以包含多个kcontrol;
2、widget是对kcontrol的进一个封装,使其具备更多的能力(连接、状态),使其与电源管理关联起来;
3、DAPM框架下 定义kcontrol(主要是电源相关的寄存器)的宏,分类很多!android\kernel\fusion\4.19\include\sound\soc-dapm.h
/** SoC dynamic audio power management** We can have up to 4 power domains* 1. Codec domain - VREF, VMID* Usually controlled at codec probe/remove, although can be set* at stream time if power is not needed for sidetone, etc.* 2. Platform/Machine domain - physically connected inputs and outputs* Is platform/machine and user action specific, is set in the machine* driver and by userspace e.g when HP are inserted* 3. Path domain - Internal codec path mixers* Are automatically set when mixer and mux settings are* changed by the user.* 4. Stream domain - DAC's and ADC's.* Enabled when stream playback/capture is started.*//* codec domain */
#define SND_SOC_DAPM_VMID(wname) \
{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \.num_kcontrols = 0}/* platform domain */
#define SND_SOC_DAPM_INPUT(wname) \
{ .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \.num_kcontrols = 0, .reg = SND_SOC_NOPM }/* path domain */
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \wcontrols, wncontrols)\
{ .id = snd_soc_dapm_mixer, .name = wname, \SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
3、如何构造widget?
widget是DAPM下的基本操作单元,widget实际上是一个抽象感念,
与kcontrol的关系,一个widget可以包含一个kcontrol,也可以包含多个kcontrol,也可不包含kcontrol
构造例子
widget的构造分为两个阶段,
第一阶段是使用内核宏进行静态构造(name、reg等赋值);
第二阶段则使用内核api snd_soc_dapm_new_controls进行动态构造1、内核宏(codc驱动)
static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("LINPUT1"), //虚拟端点,不包含kcontrolSND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0), //包含一个kcontrolSND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, //包含多个kcontrolwm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,wm8960_lin, ARRAY_SIZE(wm8960_lin)),SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,&wm8960_loutput_mixer[0],ARRAY_SIZE(wm8960_loutput_mixer)),SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),SND_SOC_DAPM_OUTPUT("SPK_LP"), //虚拟端点,不包含kcontrol
};static const struct snd_kcontrol_new wm8960_lin_boost[] = {
SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
};注意与kcontrol不同的get/put函数
.get = snd_soc_dapm_get_volsw,
.put = snd_soc_dapm_put_volsw, //调用很深!简单看看snd_soc_dapm_put_volsw的调用
--dapm_kcontrol_set_value(kcontrol, val | (rval << width));
--soc_dapm_mixer_update_power(card, kcontrol, connect, rconnect);
----soc_dapm_connect_path(path, rconnect, "mixer update");
----dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
------dapm_seq_run(card, &down_list, event, false);
------dapm_widget_update(card);
------dapm_seq_run(card, &up_list, event, true);2、内核api snd_soc_dapm_new_controls (machine驱动)
wm8960_probe
--wm8960_add_widgets
----snd_soc_dapm_new_controls
------snd_soc_dapm_new_control_unlocked(dapm, widget);//涉及很多变量设置,需要花费一定精力去梳理
//根据dapm type,挂上对应check_power方法,用于判断该widget是否需要上电
//上电条件:1、widget位于complete path上;2、有APP在使用声卡;
--------w->is_ep = SND_SOC_DAPM_EP_SOURCE; //当type=snd_soc_dapm_mic,会设置endpoint
--------w->power_check = dapm_generic_check_power;
--------w->connected = 1;
----snd_soc_dapm_link_dai_widgets(card);
----snd_soc_dapm_connect_dai_link_widgets(card);
----snd_soc_dapm_add_routes()
----snd_soc_dapm_new_widgets(card);
------dapm_new_mixer(w);
--------dapm_create_or_share_kcontrol(w, i);
----------snd_soc_cnew()
----------snd_ctl_add(card, kcontrol); //添加kcontrol到card链表里面去,kcontrol的name=(widget name + kcontrol name)
------dapm_new_mux(w);3、实际开发时,操作相近的例子进行构造
4、几点疑问:
1)通用寄存器对象与DAPM下的寄存器对象会重复吗? 不会重复
2)DAPM的kcontrol也需要给用户使用? 需要,在machine probe中会挂到card下的kcontrol链表中,在哪里触发?在machine代码probe一路下来,最终在snd_soc_dapm_new_widgets()
4、抽象对象widget、route与path
1)route与path的关系?
1、内核设计为了方便path的表示,使用route用来表示path的链接,然后在初始化代码中自动构造出path。2、比如:
struct snd_soc_dapm_route {const char *sink; //终点widget的nameconst char *control; //连接两个widget的kcontrolconst char *source; //起始widget的name
};static const struct snd_soc_dapm_route audio_paths[] = {{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
}
3、可以看出一个path只能连接相邻的两个widget4、route如何转化为path?
wm8960_probe
--wm8960_add_widgets
----snd_soc_dapm_add_routes()
------snd_soc_dapm_add_route(dapm, route);
//最终添加到snd_soc_dapm_context下path链表中
--------snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,route->connected); snd_soc_dapm_add_path()的工作
1)找到source/sink,构造path
2)设置path中的connect,当path中的kcontrol为空 (即path中间没有switch),则connect恒为1;不为空,则根据kcontrol 寄存器的值设置connect;
3)将path放进链表里面去,构造出complete path
2)widget与path的关系
1、一个path相当于一个跳线,但只能连上 物理硬件上相邻的widget;
2、complete path : 多个path连起来,完整实现一条音频流通路 即为一个complete path;
3)一条完整的path - complete path
1、多个path组成完整的一条通路,叫做完全路径,比如一个录音功能需要一条完全路径来实现
2、举个例子
static const struct snd_soc_dapm_route audio_paths[] = {{ "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, //path1{ "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, //path2{ "Left ADC", NULL, "Left Input Mixer" }, //path3
}
path1->path2->path3 组成了一条完整的complete path
链表结构图
3)complete path是如何生成的?
1、complete path其实是抽象出来的概念,不是静态定义的,而是利用链表算法动态生成,通过递归遍历算法 动态探索出一条complete path。2、怎么从一个widget,遍历所有跟此widget在同一条complete path上的widget?在构造path时,每个widget都有记录此path的变量,称为edges,区分是in还是out
snd_soc_dapm_add_path(){list_add(&path->list, &dapm->card->paths);snd_soc_dapm_for_each_direction(dir)list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);
}#define snd_soc_dapm_for_each_direction(dir) \for ((dir) = SND_SOC_DAPM_DIR_IN; (dir) <= SND_SOC_DAPM_DIR_OUT; \(dir)++)
1、举个例子
widget1的拓扑关系
完全展开widget的拓扑关系,可以包含此widget在同一条complete path上的所有widget;
最后利用递归算法(利用edges[in] 实现向后遍历,利用edges[out] 实现向前遍历)
4)DAPM下的数据结构
1、DAPM数据结构里存在大量链表;
2、DAPM结构体指向混乱,但有个总的源头 - snd_soc_card;
5)DAPM场景分析
1、widget什么时候会被打开?
上层发出请求后(指tinymix / tinyplay),会触发一次事件,判断widget的成员connect为1时,打开此widget
那connect在哪里被设置?
1)注册route,path时会设置一次;
2)后续上层发出请求后 会调用check_power进行动态更新;
1、check power的实现
android\kernel\fusion\4.19\sound\soc\soc-dapm.c
dapm_generic_check_power() //DAPM实现的关键,也是重难点
--is_connected_input_ep() //ep endpoint,如 input1/input2
--is_connected_output_ep() //ep endpoint,如 speaker/headphone
----is_connected_ep()
------snd_soc_dapm_widget_for_each_path(widget, rdir, path) //涉及内核算法,遍历每一个widget,确认该widget位于complete path,位于的话就需要被打开
2、tinymix/tinyplay/tinymix调用DAPM
1)DAPM中重点的概念和算法都已经清楚,接下来从应用层的角度看看如何使用DAMP;
2)关键的实现都集中的soc-dapm.c文件中,这里使用树状图来进行记录说明1)tinyplay,tinycap
播放/录音前都会调用 soc_pcm_prepare
android\kernel\fusion\4.19\sound\soc\soc-pcm.c
soc_pcm_prepare// stream's name = Playback or Capturesnd_soc_dapm_stream_event(rtd, stream's name, SND_SOC_DAPM_STREAM_START)soc_dapm_stream_event()// 找出每一个widget// 如果strstr(w->sname, stream) // w->sname中含有Playback or Capture// w->active = 1 dapm_power_widgets()2)tinymix调用过程:
设置寄存器,会调用其put函数
android\kernel\fusion\4.19\sound\soc\soc-dapm.c
snd_soc_dapm_put_volsw()
--dapm_kcontrol_set_value(kcontrol, val | (rval << width));
--soc_dapm_mixer_update_power(card, kcontrol, connect, rconnect);
----soc_dapm_connect_path(path, rconnect, "mixer update");
----dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);3)tinyplay,tinycap,tinymix最终都会调用dapm_power_widgets
dapm_power_widgets()// 对于每一个widget// power = w->power_check(w); // 确定是否要上电list_for_each_entry(w, &card->dapm_dirty, dirty) {dapm_power_one_widget(w, &up_list, &down_list);--dapm_widget_power_check(w);----w->power_check(w); //调用power check设置power字段--dapm_widget_set_power(w, power, up_list, down_list);----if (power) // 放入不同的链表, 以后统一上是或关闭dapm_seq_insert(w, &up_list, true); //上电widget listelsedapm_seq_insert(w, &down_list, false); //掉电widget list} //给down_list上的所有widget掉电dapm_seq_run(dapm, &down_list, event, false);//根据dapm->update设置kcontrol, // update来自tinymix的调用dapm_widget_update(dapm);//给up_list上的所有widget上电dapm_seq_run(dapm, &up_list, event, true);4)给链表中的widget上电或关闭
dapm_seq_rundapm_seq_run_coalescedsnd_soc_update_bits