本文主要分析DAPM的设计与实现
内核的版本是:linux-5.15.164,下载链接:Linux内核下载
主要讲解有关于DAPM相关的知识,会给出一些例程并分析内核如何去实现的
/*****************************************************************************************************************/
声明: 本博客内容均由芯心智库-CSDN博客原创,转载or引用请注明出处,谢谢!
创作不易,如果文章对你有帮助,麻烦点赞 收藏支持~感谢
/*****************************************************************************************************************/
目录
零、本文主要内容
一、注册DAPM的另外一种写法
二、dapm widget的初始化:snd_soc_dapm_new_controls
三、route的添加:snd_soc_dapm_add_routes
四、更新 DAPM 系统的状态:snd_soc_dapm_new_widgets
零、本文主要内容
- After all the widgets have been defined, they can then be added to the DAPM subsystem individually with a call to snd_soc_dapm_new_control().
- Interconnections are created with a call to: snd_soc_dapm_connect_input(codec, sink, path, source);
- Finally, snd_soc_dapm_new_widgets(codec) must be called after all widgets and interconnections have been registered with the core. This causes the core to scan the codec and machine so that the internal DAPM state matches the physical state of the machine.
- snd_soc_dapm_new_widgets函数需要在我们驱动中调用的进行widgets初始化,这里仅是进行简单初始化,例如申请一块内存去存储我们定义的widgets和简单的赋值,其并没有进行更深层次的操作,比如始化与硬件对应的 DAPM 状态
- snd_soc_dapm_connect_input函数在内核文档的描述中是用作route的创建和链接的,但是实际上,我们用更多的函数应该是:snd_soc_dapm_add_routes
- snd_soc_dapm_new_widgets函数的作用是进行更深层次的初始化,通过调用这个函数,系统会扫描已注册的部件和连接,并且初始化与硬件对应的 DAPM 状态
本文所有提交的代码可以见平台:https://github.com/xiaoWEN7/ASOC_DAPM/tree/master
一、注册DAPM的另外一种写法
.controls = my_controls,
.num_controls = ARRAY_SIZE(my_controls),
.dapm_widgets = vcodec_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(vcodec_dapm_widgets),
.dapm_routes = vcodec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(vcodec_dapm_routes),
};
这种方式是我们经常使用的,但是其实我们也可以按照内核文档给我们的方式进行注册,例如:
--- a/codec.c
+++ b/codec.c
@@ -128,9 +128,15 @@ static const struct snd_kcontrol_new my_controls[] = {
static int my_codec_probe(struct snd_soc_component *codec)
{
- //int ret;
+ //Changes to DAPM registration method^M
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec);
+ snd_soc_dapm_new_controls(dapm, vcodec_dapm_widgets,
+ ARRAY_SIZE(vcodec_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, vcodec_dapm_routes,
+ ARRAY_SIZE(vcodec_dapm_routes));
+
+ //int ret;
printk("-----%s----\n",__func__);
-
return 0;
}
@@ -146,10 +152,10 @@ static struct snd_soc_component_driver soc_my_codec_drv = {
.write = virtual_reg_write,
.controls = my_controls,
.num_controls = ARRAY_SIZE(my_controls),
- .dapm_widgets = vcodec_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(vcodec_dapm_widgets),
- .dapm_routes = vcodec_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(vcodec_dapm_routes),
+// .dapm_widgets = vcodec_dapm_widgets,
+// .num_dapm_widgets = ARRAY_SIZE(vcodec_dapm_widgets),
+// .dapm_routes = vcodec_dapm_routes,
+// .num_dapm_routes = ARRAY_SIZE(vcodec_dapm_routes),
};
这种方式在内核源码中也有相关codec在使用:
那么这两种注册方式有何差异呢?
这要从声卡的注册流程说起了,对于不直接调用API而是定义在结构体struct snd_soc_component_driver中这种方式最终是由声卡的注册流程调用API去注册,写法参考我的这篇文章:【ASOC全解析(三)】machine原理和实战-CSDN博客。我们使用的注册声卡的函数:snd_soc_register_card(),里面就会调用到我们上面的三个API,声卡注册这里还是比较多内容的,后续我再出文章说明一下注册的流程,大致流程如下:
snd_soc_register_card()
--> snd_soc_bind_card()
--> snd_soc_dapm_new_controls()
--> snd_soc_dapm_add_routes()
--> snd_soc_dapm_new_widgets()
而如果在snd_soc_component_driver结构体的probe中调用的话流程是这样:
snd_soc_register_card()
--> snd_soc_bind_card()
--> snd_soc_card_probe()
--> card->probe(card)
--> snd_soc_dapm_new_controls()
--> snd_soc_dapm_add_routes()
--> snd_soc_dapm_new_widgets()
从上面我们不难看出,其实两种写法基本是差不多的,一个是依赖于声卡注册中的调用,一个则是自行在probe上加载,后面均会调用“snd_soc_dapm_new_widgets”进行进一步的初始化。
二、dapm widget的初始化:snd_soc_dapm_new_controls
这个函数的作用主要是进行widget的简单初始化,主要包括:
1、为结构体申请空间
2、根据结构体控件id设置相关的属性
整体的流程图如下:
左下角的图可能看不清,这里放大一下:
内容其实不难,可以直接看源码,我举个例子,在上个文章中的源码:
SND_SOC_DAPM_AIF_OUT_E("ADCL", "Capture", 0, VCODEC_ADCL_REG,
2, 0, vcodec_capture_event,
SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_WILL_PMD),
经过snd_soc_dapm_new_controls函数的处理后,结构体snd_soc_dapm_widget的内容会变成下面:
struct snd_soc_dapm_widget {
enum snd_soc_dapm_type id; (add)--> .id = snd_soc_dapm_aif_out
const char *name; /* widget name */ (add)--> .name = "ADCL"
const char *sname; /* stream name */ (add)--> .sname = "Capture"
struct list_head list; (add)--> list_add_tail(&w->list, &dapm->card->widgets);
struct snd_soc_dapm_context *dapm; (add)--> .dapm = &component->dapm
void *priv; /* widget specific data */
struct regulator *regulator; /* attached regulator */
struct pinctrl *pinctrl; /* attached pinctrl */
/* dapm control */
int reg; /* negative reg = no direct dapm */ (add)--> .reg = VCODEC_ADCL_REG
unsigned char shift; /* bits to shift */ (add)--> .shift = 2
unsigned int mask; /* non-shifted mask */ (add)--> .mask = 1
unsigned int on_val; /* on state value */ (add)--> .on_val = 0
unsigned int off_val; /* off state value */ (add)--> .off_val = 0
unsigned char power:1; /* block power status */
unsigned char active:1; /* active stream on DAC, ADC's */
unsigned char connected:1; /* connected codec pin */
unsigned char new:1; /* cnew complete */
unsigned char force:1; /* force state */
unsigned char ignore_suspend:1; /* kept enabled over suspend */
unsigned char new_power:1; /* power from this run */
unsigned char power_checked:1; /* power checked this run */
unsigned char is_supply:1; /* Widget is a supply type widget */
unsigned char is_ep:2; /* Widget is a endpoint type */
int subseq; /* sort within widget type */widget
int (*power_check)(struct snd_soc_dapm_widget *w); (add)--> dapm_generic_check_power
/* external events */
unsigned short event_flags; /* flags to specify event types */ (add)--> .event_flags = SND_SOC_DAPM_WILL_PMU | SND_SOC_DAPM_WILL_PMD
int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int); (add)--> .event = vcodec_capture_event
/* kcontrols that relate to this widget */
int num_kcontrols;
const struct snd_kcontrol_new *kcontrol_news;
struct snd_kcontrol **kcontrols;
struct snd_soc_dobj dobj;
/* widget input and output edges */
struct list_head edges[2];
/* used during DAPM updates */
struct list_head work_list;
struct list_head power_list;
struct list_head dirty;
int endpoints[2];
struct clk *clk;
int channel; (add)--> .channel=0
};
这每个结构体其实就是代表一个dapm_widget,代码中注册的dapm最终都会加入到“component->dapm->card->widgets”这个list中。
struct list_head list; (add)--> list_add_tail(&w->list, &dapm->card->widgets);
struct snd_soc_dapm_context *dapm; (add)--> .dapm = &component->dapm
三、route的添加:snd_soc_dapm_add_routes
route的作用是描述各个DAPM widget的关系,比如上一个章节中,我们给出的连接关系是:
/* 定义音频路径(路由) */
static const struct snd_soc_dapm_route vcodec_dapm_routes[] = {
/* MIC 输入路径 */
{"ADCL Input", "MIC1 Boost Switch", "MIC1"},
{"ADCL", NULL, "ADCL Input"},
{"Output Switch", "Output_mixer", "ADCL"},
/* 如果有其他输入输出路径,在此添加 */
{"ADC Output", NULL, "Output Switch"},
};
这里首先看两个结构体:
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);
struct snd_soc_dobj dobj;
};
结构体snd_soc_dapm_route是我们在定义route的时候所使用的,它比较简洁,仅仅是把我们定义的route进行简单的赋值,但是在音频的内核调度的时候,如果要用这个结构体直接去找相应的widget势必会造成大量重复的代码,在内核中,结构体snd_soc_dapm_route通过函数snd_soc_dapm_add_routes生成了一个带有更多详细信息的结构体snd_soc_dapm_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;
};
因此函数snd_soc_dapm_add_routes的作用其实可以归纳如下:
1、主要将简单的结构体snd_soc_dapm_route构建出具备更多信息的结构体snd_soc_dapm_path,以方便控制和减少非必要的代码
2、进行一些简单的初始化,如读取寄存器的数值进行初始化widget的状态
这个函数的流程如下:
可以看出,其实并不是非常的复杂,这里直接附上分析的代码:
/**
* snd_soc_dapm_add_route - 添加一条DAPM路径
* @dapm: DAPM上下文
* @route: 描述路径的路由信息
*
* 此函数用于在音频系统中添加一条DAPM路径(从一个源widget到一个目的widget)。
* DAPM(动态音频功耗管理)是ALSA系统的一个子系统,用于动态管理音频部件的功耗。
*/
static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route)
{
// 定义widget指针,用于保存源和目的widget以及临时变量
struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
const char *sink; // 目的widget名称
const char *source; // 源widget名称
char prefixed_sink[80]; // 带前缀的目的widget名称
char prefixed_source[80]; // 带前缀的源widget名称
const char *prefix; // 前缀字符串
unsigned int sink_ref = 0; // 目的widget引用计数
unsigned int source_ref = 0; // 源widget引用计数
int ret;
// 获取DAPM前缀(如果存在)
prefix = soc_dapm_prefix(dapm);
if (prefix) {
// 为源和目的widget添加前缀
snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
prefix, route->sink);
sink = prefixed_sink;
snprintf(prefixed_source, sizeof(prefixed_source), "%s %s",
prefix, route->source);
source = prefixed_source;
} else {
// 如果没有前缀,直接使用路由中提供的名称
sink = route->sink;
source = route->source;
}
// 首先尝试从路径缓存中查找源和目的widget
wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);
// 如果源和目的widget都已找到,则跳过查找
if (wsink && wsource)
goto skip_search;
/*
* 遍历整个声卡的widget列表,寻找源和目的widget。
* 优先使用当前DAPM上下文中的widget。
*/
for_each_card_widgets(dapm->card, w) {
if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w; // 记录临时找到的目的widget
if (w->dapm == dapm) {
wsink = w; // 优先使用当前上下文中的widget
if (wsource)
break;
}
// 更新目的widget引用计数
sink_ref++;
if (sink_ref > 1)
dev_warn(dapm->dev,
"ASoC: sink widget %s overwritten\n",
w->name);
continue;
}
if (!wsource && !(strcmp(w->name, source))) {
wtsource = w; // 记录临时找到的源widget
if (w->dapm == dapm) {
wsource = w; // 优先使用当前上下文中的widget
if (wsink)
break;
}
// 更新源widget引用计数
source_ref++;
if (source_ref > 1)
dev_warn(dapm->dev,
"ASoC: source widget %s overwritten\n",
w->name);
}
}
// 如果当前上下文中未找到widget,则尝试使用其他上下文中的widget
if (!wsink)
wsink = wtsink;
if (!wsource)
wsource = wtsource;
// 如果仍未找到源widget,返回错误
if (wsource == NULL) {
dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
route->source);
return -ENODEV;
}
// 如果仍未找到目的widget,返回错误
if (wsink == NULL) {
dev_err(dapm->dev, "ASoC: no sink widget found for %s\n",
route->sink);
return -ENODEV;
}
skip_search:
// 更新路径缓存,以加快后续查找
dapm_wcache_update(&dapm->path_sink_cache, wsink);
dapm_wcache_update(&dapm->path_source_cache, wsource);
// 添加路径到DAPM
ret = snd_soc_dapm_add_path(dapm, wsource, wsink, route->control,
route->connected);
if (ret)
goto err;
return 0;
err:
// 如果添加路径失败,记录警告日志
dev_warn(dapm->dev, "ASoC: no dapm match for %s --> %s --> %s\n",
source, route->control, sink);
return ret;
}
- 解析路径源和目的: 根据提供的route结构体确定源和目的widget的名称,并处理可能的前缀。
- 查找widget: 优先从路径缓存中查找,如果未找到,则在整个声卡的widget列表中查找。
- 路径添加: 调用dapm_add_path函数将路径添加到DAPM系统中。
/**
* snd_soc_dapm_add_path - 添加一条DAPM路径
* @dapm: DAPM上下文
* @wsource: 源widget
* @wsink: 目的widget
* @control: 控制路径的名称(可选)
* @connected: 回调函数,用于动态判断路径是否连接
*
* 此函数用于在DAPM系统中添加路径,并更新相关widget和路径的状态。
*/
static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *wsource, struct snd_soc_dapm_widget *wsink,
const char *control,
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink))
{
struct snd_soc_dapm_widget *widgets[2]; // 用于保存源和目的widget
enum snd_soc_dapm_direction dir; // 路径方向(输入/输出)
struct snd_soc_dapm_path *path; // 要创建的路径
int ret;
// 检查非法的路径连接:非供电widget不能连接到供电widget
if (wsink->is_supply && !wsource->is_supply) {
dev_err(dapm->dev,
"Connecting non-supply widget to supply widget is not supported (%s -> %s)\n",
wsource->name, wsink->name);
return -EINVAL;
}
// 如果提供了connected回调,则源widget必须是供电widget
if (connected && !wsource->is_supply) {
dev_err(dapm->dev,
"connected() callback only supported for supply widgets (%s -> %s)\n",
wsource->name, wsink->name);
return -EINVAL;
}
// 供电widget不支持带条件的路径
if (wsource->is_supply && control) {
dev_err(dapm->dev,
"Conditional paths are not supported for supply widgets (%s -> [%s] -> %s)\n",
wsource->name, control, wsink->name);
return -EINVAL;
}
// 检查是否有动态路径冲突
ret = snd_soc_dapm_check_dynamic_path(dapm, wsource, wsink, control);
if (ret)
return ret;
// 为路径分配内存
path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM;
// 初始化路径结构
path->node[SND_SOC_DAPM_DIR_IN] = wsource; // 源节点
path->node[SND_SOC_DAPM_DIR_OUT] = wsink; // 目的节点
widgets[SND_SOC_DAPM_DIR_IN] = wsource;
widgets[SND_SOC_DAPM_DIR_OUT] = wsink;
path->connected = connected; // 设置动态连接回调
INIT_LIST_HEAD(&path->list); // 初始化路径链表
INIT_LIST_HEAD(&path->list_kcontrol); // 初始化控制链表
// 如果源或目的widget是供电widget,标记路径为供电路径
if (wsource->is_supply || wsink->is_supply)
path->is_supply = 1;
/* 处理静态路径 */
if (control == NULL) {
// 如果路径没有条件控制,直接标记为连接状态
path->connect = 1;
} else {
// 根据widget的类型处理条件控制
switch (wsource->id) {
case snd_soc_dapm_demux: // 处理多路分解器
ret = dapm_connect_mux(dapm, path, control, wsource);
if (ret)
goto err;
break;
default:
break;
}
switch (wsink->id) {
case snd_soc_dapm_mux: // 处理多路复用器
ret = dapm_connect_mux(dapm, path, control, wsink);
if (ret != 0)
goto err;
break;
case snd_soc_dapm_switch: // 处理开关
case snd_soc_dapm_mixer: // 处理混音器
case snd_soc_dapm_mixer_named_ctl: // 处理带名称的混音器
ret = dapm_connect_mixer(dapm, path, control);
if (ret != 0)
goto err;
break;
default:
break;
}
}
// 将路径添加到DAPM的全局路径列表
list_add(&path->list, &dapm->card->paths);
// 将路径添加到源和目的widget的边缘列表
snd_soc_dapm_for_each_direction(dir)
list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);
// 更新每个widget的标志,并标记为脏以触发更新
snd_soc_dapm_for_each_direction(dir) {
dapm_update_widget_flags(widgets[dir]);
dapm_mark_dirty(widgets[dir], "Route added");
}
// 如果声卡已实例化并且路径已连接,则使路径失效以触发重新评估
if (dapm->card->instantiated && path->connect)
dapm_path_invalidate(path);
return 0;
err:
// 如果添加路径失败,释放分配的内存
kfree(path);
return ret;
}
- mux 是一种选择器,允许从多个输入源中选择一个作为输出,比如在音频路径中选择使用麦克风输入还是线路输入。
- mixer 用于将多个音频信号混合成一个信号,比如在耳机输出上同时播放来自麦克风和音乐播放器的音频。
/* connect mixer widget to its interconnecting audio paths */
static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_path *path, const char *control_name)
{
int i, nth_path = 0;
/* search for mixer kcontrol */
for (i = 0; i < path->sink->num_kcontrols; i++) {
if (!strcmp(control_name, path->sink->kcontrol_news[i].name)) {
path->name = path->sink->kcontrol_news[i].name;
dapm_set_mixer_path_status(path, i, nth_path++);
return 0;
}
}
return -ENODEV;
}
也就是以名字进行匹配的,比如上一小节的源码:
{"Output Switch", "Output_mixer", "ADCL"},
control_name变量的数值就是“Output_mixer”,其先遍历path->sink->num_kcontrols,找到相同名字的widgets,把这个名字赋值给path->name,然后调用dapm_set_mixer_path_status函数再去填充和初始化结构体snd_soc_dapm_route。
Tip :这个path->sink其实就是path->node[1],仔细看这个成员的实现:
union {
struct {
struct snd_soc_dapm_widget *source;
struct snd_soc_dapm_widget *sink;
};
struct snd_soc_dapm_widget *node[2];
};
再翻一下代码,其来源就是:
path->sink 即 dapm->card->widgets
前面第二章节分析过,这个list中有我们所有的DAPM widgets信息:
struct list_head list; (add)--> list_add_tail(&w->list, &dapm->card->widgets);
以route: {"ADC Output", NULL, "Output Switch"}为例:最终被填充的结构体snd_soc_dapm_route的内容如下(注意不同的route填充的结果是不太一样的):
struct snd_soc_dapm_path {
const char *name; 如果是mixer则path->name = path->sink->kcontrol_news[i].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]; --> path->node[SND_SOC_DAPM_DIR_IN] = wsource;path->node[SND_SOC_DAPM_DIR_OUT] = wsink;
};
/* status */
u32 connect:1; /* source and sink widgets are connected */ --> if (control == NULL) {path->connect = 1;}
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, --> path->connected = connected;(初始为空)
struct snd_soc_dapm_widget *sink);
struct list_head list_node[2]; --> list_add(&path->list_node[0], &widgets[0]->edges[0]); list_add(&path->list_node[1], &widgets[1]->edges[1]);
struct list_head list_kcontrol;
struct list_head list; --> list_add(&path->list, &dapm->card->paths);
};
以上个章节的route为例,经过了snd_soc_dapm_new_controls函数和snd_soc_dapm_new_controls函数,最终我们会得到是下面的结构体组织(图是自制的,可能会不够美观,但是你看连接和代码应该能悟到我意思):
四、更新 DAPM 系统的状态:snd_soc_dapm_new_widgets
直接贴出含函数的内容并附上分析:
int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *w; // widget指针,用于遍历所有widget
unsigned int val; // 存储从硬件寄存器读取的值
// 锁定 dapm_mutex 以保证线程安全,防止在操作过程中其他线程对 DAPM 小部件进行修改
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
// 遍历当前音频卡中的所有widget
for_each_card_widgets(card, w)
{
// 如果当前widget已经是新的(即已被初始化),则跳过该widget
if (w->new)
continue;
// 如果当前widget有控制项(kcontrol),为其分配内存
if (w->num_kcontrols) {
// 为 kcontrols 数组分配内存,用于存储与该小部件相关的所有控制
w->kcontrols = kcalloc(w->num_kcontrols,
sizeof(struct snd_kcontrol *),
GFP_KERNEL);
// 如果内存分配失败,则释放锁并返回错误代码
if (!w->kcontrols) {
mutex_unlock(&card->dapm_mutex);
return -ENOMEM;
}
}
// 根据widget的 ID 类型来初始化不同类型的widget
switch (w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
// 如果是开关或混音器widget,调用 dapm_new_mixer 来初始化它
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
// 如果是复用器或解复用器,调用 dapm_new_mux 来初始化它
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
case snd_soc_dapm_effect:
case snd_soc_dapm_out_drv:
// 如果是放大器或效果驱动widget,调用 dapm_new_pga 来初始化它
dapm_new_pga(w);
break;
case snd_soc_dapm_dai_link:
// 如果是 DAI 链接widget,调用 dapm_new_dai_link 来初始化它
dapm_new_dai_link(w);
break;
default:
// 对于其他类型的widget,默认不做任何处理
break;
}
// 如果widget有寄存器设置(即 reg >= 0),则读取初始的电源状态
if (w->reg >= 0) {
// 读取widget所在的寄存器值
val = soc_dapm_read(w->dapm, w->reg);
// 将寄存器值右移到适当的位,按掩码过滤得到有效值
val = val >> w->shift;
val &= w->mask;
// 如果读取的值与 on_val 相等,则将widget的电源状态设置为开启
if (val == w->on_val)
w->power = 1;
}
// 将widget标记为已初始化
w->new = 1;
// 标记当前widget为“dirty”,表示它已被修改,需要重新处理
dapm_mark_dirty(w, "new widget");
// 将该widget添加到 DAPM 的调试文件系统(需要启用debugfs)
dapm_debugfs_add_widget(w);
}
// 调用 dapm_power_widgets 来处理widget的电源管理
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
// 解锁 dapm_mutex,允许其他线程访问 DAPM 资源
mutex_unlock(&card->dapm_mutex);
// 返回 0,表示函数执行成功
return 0;
}
也就是对于个别widget进行详细的初始化,例如上一章的mixier:
/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
int i, ret; // 定义循环计数器 i 和返回值 ret
struct snd_soc_dapm_path *path; // 定义 dapm 路径结构体指针,用于遍历widget的路径
struct dapm_kcontrol_data *data; // 定义控制数据结构体指针,用于存储控制项的信息
/* add kcontrol */
// 遍历当前widget的所有控制项(kcontrols)
for (i = 0; i < w->num_kcontrols; i++) {
/* match name */
// 遍历当前widget的所有源路径(通过宏 snd_soc_dapm_widget_for_each_source_path)
snd_soc_dapm_widget_for_each_source_path(w, path) {
// 如果路径的名称与控制项的名称不匹配,则跳过
if (path->name != (char *)w->kcontrol_news[i].name)
continue;
// 如果当前控制项未创建,则调用 dapm_create_or_share_kcontrol 创建该控制项
if (!w->kcontrols[i]) {
ret = dapm_create_or_share_kcontrol(w, i);
// 如果控制项创建失败,返回错误码
if (ret < 0)
return ret;
}
// 将当前路径添加到对应控制项的路径列表中
dapm_kcontrol_add_path(w->kcontrols[i], path);
// 获取当前控制项的私有数据(用于控制的硬件数据)
data = snd_kcontrol_chip(w->kcontrols[i]);
// 如果控制项的数据中有widget,则为该widget添加新的路径
if (data->widget)
snd_soc_dapm_add_path(data->widget->dapm,
data->widget,
path->source,
NULL, NULL);
}
}
// 返回 0,表示函数执行成功
return 0;
}
即创建一个widget控制项了,这个会生成一个mixer的控制项,随后使用amixer或者tinymix工具就可以看到这个控制项了。
接下来函数snd_soc_dapm_new_widgets会调用dapm_power_widgets函数去触发一次DAPM!
这个dapm_power_widgets函数内容挺重要的,后续文章进行深入分析一下。