【DAPM杂谈之三】DAPM的初始化流程
本文主要分析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_outconst 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->dapmvoid *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_REGunsigned char shift; /* bits to shift */ (add)--> .shift = 2unsigned int mask; /* non-shifted mask */ (add)--> .mask = 1unsigned int on_val; /* on state value */ (add)--> .on_val = 0unsigned int off_val; /* off state value */ (add)--> .off_val = 0unsigned 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 */widgetint (*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_PMDint (*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;}// 首先尝试从路径缓存中查找源和目的widgetwsource = 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; // 记录临时找到的目的widgetif (w->dapm == dapm) {wsink = w; // 优先使用当前上下文中的widgetif (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; // 记录临时找到的源widgetif (w->dapm == dapm) {wsource = w; // 优先使用当前上下文中的widgetif (wsink)break;}// 更新源widget引用计数source_ref++;if (source_ref > 1)dev_warn(dapm->dev,"ASoC: source widget %s overwritten\n",w->name);}}// 如果当前上下文中未找到widget,则尝试使用其他上下文中的widgetif (!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);// 添加路径到DAPMret = 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]; // 用于保存源和目的widgetenum snd_soc_dapm_direction dir; // 路径方向(输入/输出)struct snd_soc_dapm_path *path; // 要创建的路径int ret;// 检查非法的路径连接:非供电widget不能连接到供电widgetif (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必须是供电widgetif (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指针,用于遍历所有widgetunsigned int val; // 存储从硬件寄存器读取的值// 锁定 dapm_mutex 以保证线程安全,防止在操作过程中其他线程对 DAPM 小部件进行修改mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);// 遍历当前音频卡中的所有widgetfor_each_card_widgets(card, w){// 如果当前widget已经是新的(即已被初始化),则跳过该widgetif (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 类型来初始化不同类型的widgetswitch (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 和返回值 retstruct 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函数内容挺重要的,后续文章进行深入分析一下。
相关文章:
【DAPM杂谈之三】DAPM的初始化流程
本文主要分析DAPM的设计与实现 内核的版本是:linux-5.15.164,下载链接:Linux内核下载 主要讲解有关于DAPM相关的知识,会给出一些例程并分析内核如何去实现的 /**************************************************************…...
消息队列架构、选型、专有名词解释
私人博客传送门 消息队列专有名词解释 | 魔筝炼药师 MQ选型 | 魔筝炼药师 MQ架构 | 魔筝炼药师 MQ顺序消息 | 魔筝炼药师...
Scala语言的计算机基础
Scala语言的计算机基础 Scala是一种现代的编程语言,兼具面向对象和函数式编程的特性,广泛应用于大数据处理、后端开发和分布式系统等领域。本文将围绕Scala语言的基础知识,包括其语法特点、数据结构、函数式编程思想、与Java的关系以及在实际…...
爬虫基础之爬取歌曲宝歌曲批量下载
声明:本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…...
书说 MySQL 的悲观锁和乐观锁
什么是乐观锁?什么是悲观锁? 悲观锁: 悲观锁是一种基于悲观态度的控制机制(最坏的程度想,每次并发一定会造成阻塞),用于防止数据冲突。它采取预防性措施,在修改数据之前将其锁定&a…...
Linux WEB漏洞
定义:Linux Web 漏洞是指在基于 Linux 操作系统的 Web 应用程序、Web 服务器软件或者相关的网络服务配置中存在的安全弱点。这些漏洞可能导致攻击者未经授权访问敏感信息、篡改网页内容、执行恶意代码,甚至完全控制服务器。 常见类型及原理 SQL 注入漏…...
AIDD - 人工智能药物设计 -深度学习赋能脂质纳米颗粒设计,实现高效肺部基因递送
Nat. Biotechnol. | 深度学习赋能脂质纳米颗粒设计,实现高效肺部基因递送 今天为大家介绍的是来自美国麻省理工和爱荷华大学卡弗医学院团队的一篇论文。可离子化脂质(ionizable lipids)是脂质纳米颗粒(lipid nanoparticles&#…...
Selenium 进行网页自动化操作的一个示例,绕过一些网站的自动化检测。python编程
初级教程 selenium 教程和视频教程s原理与安装 - 白月黑羽 https://www.byhy.net/auto/selenium/01/#chrome%201 Selenium 自动化环境安装_哔哩哔哩_bilibili Selenium 自动化环境安装是Python Selenium Web自动化 2024版 - 自动化测试 爬虫的第2集视频,该合集共…...
力扣 岛屿数量
从某个点找,不断找相邻位置。 题目 岛屿中被“0”隔开后 ,是每一小块状的“1”,本题在问有多少块。可以用dfs进行搜索,遍历每一个点,把每一个点的上下左右做搜索检测,当检测到就标记为“0”表示已访问过&a…...
【前端动效】HTML + CSS 实现打字机效果
目录 1. 效果展示 2. 思路分析 2.1 难点 2.2 实现思路 3. 代码实现 3.1 html部分 3.2 css部分 3.3 完整代码 4. 总结 1. 效果展示 如图所示,这次带来的是一个有趣的“擦除”效果,也可以叫做打字机效果,其中一段文本从左到右逐渐从…...
期刊(中英),期刊分区,期刊所在数据库(中英),出版商区别和联系
目录 对期刊、分区、数据库、出版商整体了解期刊(中英)期刊分区期刊所在数据库总结 出版商 对期刊、分区、数据库、出版商整体了解 下图是我对这四部分的一个理解,其中期刊根据论文使用语言分为中英两种,期刊分区是用来评判论文质…...
LLM中temperature参数设置为0
LLM中 temperature参数设置为0 当模型的temperature参数设置为0时,通常有以下含义: 解码策略角度 意味着采用贪婪解码(greedy decoding)策略。在每一步生成文本时,模型会选择概率最高的词元,从而使输出具…...
Javase 基础复习 函数式接口 lambda表达式 方法应用
目录 案例1 案例2 1. 函数式接口 (Functional Interface) 特点: 示例: Java 8 引入了几个常用的函数式接口: 2. Lambda 表达式 语法解析: 示例: 3. 方法引用 (Method References) 示例: 4. 方法…...
【39. 组合总和 中等】
题目: 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…...
UE小白学习日记
Level UE中的Level(关卡)和Unity中的Scene(场景)在概念和用途上非常相似,都是用来组织和管理3D环境的基本单位。让我为您详细对比一下: 相似之处: 它们都是游戏世界的容器,可以包含游戏对象、光照、地形等元素都支持场景/关卡的切换和加载都可以用来划分游戏内容,比如不同关…...
补充之前的一篇 MySQL 的索引为什么能加快查询速度
在之前的一篇文章中写了 MySQL 的索引为什么能加快查询速度,结合这两篇文章,相信你会对 MySQL 的索引有更深一步的了解 首先我们要理解一件事,无论什么数据库,它的数据一定都是存储在硬盘中的,而硬盘和内存之间的读…...
GoLand下载安装教程
一、goland环境配置 1.下载地址 https://golang.google.cn/dl/ 2.下载安装 3.添加环境变量 4.测试环境变量 输出Hello,World! 说明环境配置成功 二、goland安装 1.下载安装 https://www.jetbrains.com/go/download/download-thanks.html 2.激活使用 SFXUSA86FM-eyJsaWNlbnNl…...
GAN的应用
5、GAN的应用 GANs是一个强大的生成模型,它可以使用随机向量生成逼真的样本。我们既不需要知道明确的真实数据分布,也不需要任何数学假设。这些优点使得GANs被广泛应用于图像处理、计算机视觉、序列数据等领域。上图是基于GANs的实际应用场景对不同G…...
[石榴翻译] 维吾尔语音识别 + TTS语音合成
API网址 丝路AI平台 获取 Access token 接口地址:https://open.xjguoyu.cn/api/auth/oauth/token,请求方式:GET,POST Access token是调用服务API的凭证,调用服务API之前需要获取 token。每次成功获取 token 以后只有…...
PHP Filesystem:深入解析与实战应用
PHP Filesystem:深入解析与实战应用 引言 PHP作为一种流行的服务器端编程语言,提供了强大的文件系统操作功能。本文将深入探讨PHP的Filesystem函数,这些函数允许开发者访问和操作服务器上的文件系统。无论是进行基本的文件操作,还是实现复杂的文件管理系统,PHP的Filesys…...
仓颉笔记——写一个简易的web服务并用浏览器打开
创建一个web服务端,同时创建一个客户端去读取这个服务端。 也满足浏览器打开web的需求。 直接上代码。 import net.http.* import std.time.* import std.sync.* import std.log.LogLevel// 1. 构建 Server 实例 let server ServerBuilder().addr("127.0.0.1&…...
yolov5+colab跑起来
教程1.先上传网盘再run 教程2.直接上传解压run 本人过程...
Windows下安装最新版的OpenSSL,并解决OpenSSL不是当前版本的问题,或者安装不正确的问题
文章目录 1. 文章引言1.1 需求描述1.2 简单介绍1.3 支持平台1.4 源码地址1.5 组件介绍2. 下载OpenSSL3. 安装OpenSSL5. 查看安装目录6. 解决OpenSSL的错误1. 文章引言 1.1 需求描述 今天接到一需求,解密php加密后的数据,由于php使用 openssl_encrypt的方式加密,java也需要使…...
HTML5 网站模板
HTML5 网站模板 参考 HTML5 Website Templates...
代码随想录算法训练营第三十二天|509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯
目录 509.斐波那契数 动态规划五部曲: 1.确定dp数组(dp table)以及下标的含义 2.确定递推公式 3.dp数组如何初始化 4.确定遍历顺序 5.举例推导dp数组 70.爬楼梯 动态规划五部曲: 1.确定dp数组(dp table)…...
<C++学习>C++ Boost 数学与科学计算教程
C Boost 数学与科学计算教程 Boost 提供了强大的数学与科学计算功能模块,包括常用数学函数、特殊函数、矩阵运算、随机数生成器、统计分析工具等。这些工具覆盖了从基本数学操作到复杂科学计算的广泛场景。 1. Boost.Math 简介 Boost.Math 提供了丰富的数学功能&a…...
Spring Framework 5.3.x源码构建 (jdk-1.8, gradle 7.5.1, idea2024.3)
1、下载jdk安装并配置环境变量(自行百度) https://www.oracle.com/java/technologies/downloads/#java8 2、下载spring-framework源码,切换分支到5.3.x https://github.com/spring-projects/spring-framework.git 备用地址 https://gitco…...
有心力场的两体问题
有心力场中的两体问题是经典力学中的重要研究对象,中心力场问题通常涉及两个相互作用的物体(例如行星与恒星、电子与原子核等)。为了简化分析,问题往往可以转化为一个等效的单体问题。这种方法大大提高了问题的可解性,是解决两体和多体问题的基础步骤之一。将两体问题简化…...
【Pandas】pandas Series rdiv
Pandas2.2 Series Binary operator functions 方法描述Series.add()用于对两个 Series 进行逐元素加法运算Series.sub()用于对两个 Series 进行逐元素减法运算Series.mul()用于对两个 Series 进行逐元素乘法运算Series.div()用于对两个 Series 进行逐元素除法运算Series.true…...
Openstack持久存储-Swift,Cinder,Manila三者之间的区别
总结不易,给个三连吧!!! 补充: 文件共享存储服务Manila 在OpenStack生态系统中,Cinder和Manila分别提供了两种不同类型的存储服务,类似于传统的SAN(存储区域网络)和NAS&…...
单片机(MCU)-简单认识
简介: 内部集成了CPU,RAM,ROM,定时器,中断系统,通讯接口等一系列电脑的常用硬件功能。 单片机的任务是信息采集(依靠传感器),处理(依靠CPU)&…...
Redis常见知识点
1、什么是缓存穿透? 缓存穿透是指查询一定某个key是否存在,每次不存在都查询DB会把DB压垮。 解决方案的话,我们通常都会用布隆过滤器来解决。 布隆过滤器:在查找一个数是否在一个集合中使用的,通过对数组长度取模&a…...
SQL进阶实战技巧:统计相同时刻多地登陆的用户?
目录 0 问题描述 1 数据准备 2 代码实现 3 问题拓展 3.1 查询每个用户登录日期的最大空档期...
【数据库初阶】表的增删改语句
🎉博主首页: 有趣的中国人 🎉专栏首页: 数据库初阶 🎉其它专栏: C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们,大家好!在这篇文章中,我们将深入浅出地为大家讲解 MySQL…...
水水水水水水
为了拿推广卷,但不想把我原本完整的文章拆成零散的多篇,只能出此下策随便发一篇,认真写的都笔记专栏里 网络技术:数字时代的基础设施 在当今社会,网络技术无疑是推动现代生活和经济发展的核心动力之一。从简单的信息传…...
基于STM32的智能电表可视化设计:ESP8266、AT指令集、python后端Flask(代码示例)
一、项目概述 随着智能家居的普及,智能电表作为家庭用电管理的重要工具,能够实时监测电流、电压及功率,并将数据传输至后台进行分析和可视化。本项目以STM32C8T6为核心,结合交流电压电流监测模块、ESP8266 Wi-Fi模块、OLED显示屏…...
SpringBoot的@Scheduled和@Schedules有什么区别
Scheduled 的详细解析 参数详解 cron: 使用Cron表达式来指定复杂的调度模式。Cron表达式的格式如下: 秒(0-59)分钟(0-59)小时(0-23)日(1-31)月(1-12 或 JAN-…...
Qiskit快速编程探索(进阶篇)
五、量子电路模拟:探索量子世界的虚拟实验室 5.1 Aer模拟器:强大的模拟引擎 在量子计算的探索旅程中,Aer模拟器作为Qiskit的核心组件之一,宛如一座功能强大的虚拟实验室,为开发者提供了在经典计算机上模拟量子电路运行的卓越能力。它打破了硬件条件的限制,使得研究者无…...
【漫话机器学习系列】043.提前停止训练(Early Stopping)
提前停止训练(Early Stopping) 提前停止(Early Stopping) 是一种在训练机器学习模型(尤其是深度学习模型)时常用的正则化技术,用于防止过拟合并提升模型的泛化能力。它通过监控验证集的性能&am…...
Linux(上):基本知识篇
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、Linux初识1 Linux简介2 Linux学习环境配置(1)安装Linux(2)FinalShell远程连接Linux服务器二、Linux基础命令1 Linux目录结构,根目录 /2 Linux命令基础(1)什么是命令、命令行?(2)…...
Python爬虫与1688图片搜索API接口:深度解析与显著收益
在电子商务的浩瀚海洋中,数据是驱动业务决策的核心引擎。阿里巴巴旗下的1688平台,作为全球领先的B2B在线市场,不仅汇聚了海量的商品信息,还提供了丰富的API接口,为开发者提供了强大的数据获取工具。本文将深入探讨1688…...
生物医学信号处理--随机信号通过线性时不变系统
本章主要讨论 (1)输出、输入间自相关函数和功率谱的关系以及两者间的互相关函数和互谱,把它们和系统的冲激响应与频率特性联系起来。这些是用于随机问题的基本关系,将分别讨论连续时间和离散时间两种情况。 (2)初步介绍一些线性系统在处理随机信号时的应用。但是,应该指出,…...
《HeadFirst设计模式》笔记(上)
设计模式的目录: 1 设计模式介绍 要不断去学习如何利用其它开发人员的智慧与经验。学习前人的正统思想。 我们认为《Head First》的读者是一位学习者。 一些Head First的学习原则: 使其可视化将文字放在相关图形内部或附近,而不是放在底部…...
Ubuntu更改内核
需求背景: 由于软件需要在较低版本或者指定版本才可以运行 版本: 配置文件: vi /etc/default/grub 启动界面: 可运行版本: 解决方案: 方案1、更改启动顺序 sudo vi /etc/default/grub 方案2、调整启动顺…...
广告公司咋找客户?怎么获取目标企业的采购部联系方式
在广告行业,获取目标企业采购部的联系方式是开展业务、拓展客户的关键一步。分享一些实用的方法,希望能帮到正在为获取联系方式而发愁的广告行业的朋友们。 一、利用官方网站和社交媒体平台 1. 官网 大多数企业的官方网站都会提供一些联系方式…...
个人在技术领导力方面的自我反思与提升
大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步! 开发领域:前端开发 | A…...
鸿蒙面试 2025-01-11
ArkTs 和TS的关系? ArkTS(方舟开发语言)与 TypeScript(TS)存在紧密联系,同时也有显著区别: 联系 语法基础:ArkTS 在语法层面大量借鉴了 TypeScript ,TypeScript 里诸如…...
Vim的使用方法
Vim的使用方法 来自Linux 日常操作与基础知识 | archlinux 简明指南 终端编辑器 vim 的使用 我们需要掌握一个能在终端中进行文本编辑的软件,这里介绍 vim。 创建并编辑名为 hello.txt 的文件: vim hello.txt此时可以看到进入了一个空的界面…...
什么是卷积网络中的平移不变性?平移shft在数据增强中的意义
今天来介绍一下数据增强中的平移shft操作和卷积网络中的平移不变性。 1、什么是平移 Shift 平移是指在数据增强(data augmentation)过程中,通过对输入图像或目标进行位置偏移(平移),让目标在图像中呈现出…...
Java基础:equals()方法与==的区别
1、超类Object的equals()底层原理: 在Object超类中已经提供了equals()方法,源码如下: public boolean equals(Object obj) { return (this obj); } 所有的对象都拥有标识(内存地址)和状态(数据&a…...