Linux ALSA架构:DAPM初始化(六)
一、dapm context
dapm context,直译过来的意思是dapm上下文。可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:
1)属于codec中的widget位于一个dapm context中;
2)属于platform的widget位于一个dapm context中;
3)属于整个声卡的widget位于一个dapm context中。
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个dapm context:
/* DAPM context */
struct snd_soc_dapm_context {
enum snd_soc_bias_level bias_level;
unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
/* Go to BIAS_OFF in suspend if the DAPM context is idle */
unsigned int suspend_bias_off:1;
struct device *dev; /* from parent - for debug */
struct snd_soc_component *component; /* parent component */
struct snd_soc_card *card; /* parent card */
/* used during DAPM updates */
enum snd_soc_bias_level target_bias_level;
struct list_head list;
struct snd_soc_dapm_wcache path_sink_cache;
struct snd_soc_dapm_wcache path_source_cache;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs_dapm;
#endif
};
枚举变量snd_soc_bias_level:
enum snd_soc_bias_level {
SND_SOC_BIAS_OFF = 0,
SND_SOC_BIAS_STANDBY = 1,
SND_SOC_BIAS_PREPARE = 2,
SND_SOC_BIAS_ON = 3,
};
snd_soc_dapm_context被内嵌到代表codec、platform和cpu中成员结构体snd_soc_component中 。card(machine)结构体中是直接内嵌的snd_soc_dapm_context。代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。
二、创建和注册widget
2.1 dapm初始化
snd_soc_dapm_init函数初始化dapm结构体,并将其加入到card->dapm_list链表。
void snd_soc_dapm_init(struct snd_soc_dapm_context *dapm,
struct snd_soc_card *card,
struct snd_soc_component *component)
{
dapm->card = card;
dapm->component = component;
dapm->bias_level = SND_SOC_BIAS_OFF;
if (component) {
dapm->dev = component->dev;
dapm->idle_bias_off = !component->driver->idle_bias_on,
dapm->suspend_bias_off = component->driver->suspend_bias_off;
} else {
dapm->dev = card->dev;
}
INIT_LIST_HEAD(&dapm->list);
/* see for_each_card_dapms */
list_add(&dapm->list, &card->dapm_list);
}
2.2 注册widget
widget注册最终都调用了snd_soc_dapm_new_controls函数。
int snd_soc_dapm_new_controls(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget,
int num)
{
struct snd_soc_dapm_widget *w;
int i;
int ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for (i = 0; i < num; i++) {
w = snd_soc_dapm_new_control_unlocked(dapm, widget);
if (IS_ERR(w)) {
ret = PTR_ERR(w);
break;
}
widget++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
该函数只是简单的一个循环,为传入的widget模板数组依次调用snd_soc_dapm_new_control_unlocked函数,实际的工作由snd_soc_dapm_new_control_unlocked完成。
struct snd_soc_dapm_widget *
snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_widget *widget)
{
enum snd_soc_dapm_direction dir;
struct snd_soc_dapm_widget *w;
const char *prefix;
int ret;
/* dapm_cnew_widget完成内存申请和拷贝模板的动作 */
if ((w = dapm_cnew_widget(widget)) == NULL)
return ERR_PTR(-ENOMEM);
/* 根据widget的不同类型做不同的处理 */
switch (w->id) {
case snd_soc_dapm_regulator_supply:
w->regulator = devm_regulator_get(dapm->dev, w->name);
if (IS_ERR(w->regulator)) {
ret = PTR_ERR(w->regulator);
goto request_failed;
}
if (w->on_val & SND_SOC_DAPM_REGULATOR_BYPASS) {
ret = regulator_allow_bypass(w->regulator, true);
if (ret != 0)
dev_warn(dapm->dev,
"ASoC: Failed to bypass %s: %d\n",
w->name, ret);
}
break;
case snd_soc_dapm_pinctrl:
w->pinctrl = devm_pinctrl_get(dapm->dev);
if (IS_ERR(w->pinctrl)) {
ret = PTR_ERR(w->pinctrl);
goto request_failed;
}
/* set to sleep_state when initializing */
dapm_pinctrl_event(w, NULL, SND_SOC_DAPM_POST_PMD);
break;
case snd_soc_dapm_clock_supply:
w->clk = devm_clk_get(dapm->dev, w->name);
if (IS_ERR(w->clk)) {
ret = PTR_ERR(w->clk);
goto request_failed;
}
break;
default:
break;
}
/* 根据需要,在widget的名称前加入必要的前缀 */
prefix = soc_dapm_prefix(dapm);
if (prefix)
w->name = kasprintf(GFP_KERNEL, "%s %s", prefix, widget->name);
else
w->name = kstrdup_const(widget->name, GFP_KERNEL);
if (w->name == NULL) {
kfree_const(w->sname);
kfree(w);
return ERR_PTR(-ENOMEM);
}
/* 为不同类型的widget设置合适的power_check电源状态回调函数 */
switch (w->id) {
case snd_soc_dapm_mic:
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
case snd_soc_dapm_input:
if (!dapm->card->fully_routed)
w->is_ep = SND_SOC_DAPM_EP_SOURCE;
w->power_check = dapm_generic_check_power;
break;
...
default:
w->power_check = dapm_always_on_check_power;
break;
}
w->dapm = dapm;
INIT_LIST_HEAD(&w->list);
INIT_LIST_HEAD(&w->dirty);
/* see for_each_card_widgets */
list_add_tail(&w->list, &dapm->card->widgets);
snd_soc_dapm_for_each_direction(dir) {
INIT_LIST_HEAD(&w->edges[dir]);
w->endpoints[dir] = -1;
}
/* machine layer sets up unconnected pins and insertions */
w->connected = 1; /* 把widget设置为connect状态 */
return w;
request_failed:
if (ret != -EPROBE_DEFER)
dev_err(dapm->dev, "ASoC: Failed to request %s: %d\n",
w->name, ret);
kfree_const(w->sname);
kfree(w);
return ERR_PTR(ret);
}
connected字段代表着引脚的连接状态,只有以下这些widget使用connected字段:
snd_soc_dapm_output
snd_soc_dapm_input
snd_soc_dapm_hp
snd_soc_dapm_spk
snd_soc_dapm_line
snd_soc_dapm_vmid
snd_soc_dapm_mic
snd_soc_dapm_siggen
驱动程序可以使用以下这些api来设置引脚的连接状态:
snd_soc_dapm_enable_pin
snd_soc_dapm_force_enable_pin
snd_soc_dapm_disable_pin
snd_soc_dapm_nc_pin
到此,widget已经被正确地创建并初始化,而且被挂在声卡的widgets链表中,以后就可以通过声卡的widgets链表来遍历所有的widget,snd_soc_dapm_new_controls函数所完成的主要功能:
1)为widget分配内存,并拷贝参数中传入的在驱动中定义好的模板;
2)设置power_check回调函数,当音频路径发生变化时,power_check回调会被调用,用于检查该widget的电源状态是否需要更新;
3)把widget挂在声卡的widgets链表中。
2.3 Platform和Codec下widget注册
soc_probe_link_components函数依次调用soc_probe_component完成相关注册。
static int soc_probe_component(struct snd_soc_card *card,
struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(component);
struct snd_soc_dai *dai;
int probed = 0;
int ret;
if (!strcmp(component->name, "snd-soc-dummy"))
return 0;
if (component->card) {
if (component->card != card) {
dev_err(component->dev,
"Trying to bind component to card \"%s\" but is already bound to card \"%s\"\n",
card->name, component->card->name);
return -ENODEV;
}
return 0;
}
ret = snd_soc_component_module_get_when_probe(component);
if (ret < 0)
return ret;
component->card = card;
soc_set_name_prefix(card, component);
soc_init_component_debugfs(component);
snd_soc_dapm_init(dapm, card, component);
ret = snd_soc_dapm_new_controls(dapm,
component->driver->dapm_widgets,
component->driver->num_dapm_widgets);
if (ret != 0) {
dev_err(component->dev,
"Failed to create new controls %d\n", ret);
goto err_probe;
}
for_each_component_dais(component, dai) {
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
if (ret != 0) {
dev_err(component->dev,
"Failed to create DAI widgets %d\n", ret);
goto err_probe;
}
}
ret = snd_soc_component_probe(component);
if (ret < 0) {
dev_err(component->dev,
"ASoC: failed to probe component %d\n", ret);
goto err_probe;
}
WARN(dapm->idle_bias_off &&
dapm->bias_level != SND_SOC_BIAS_OFF,
"codec %s can not start from non-off bias with idle_bias_off==1\n",
component->name);
probed = 1;
/*
* machine specific init
* see
* snd_soc_component_set_aux()
*/
ret = snd_soc_component_init(component);
if (ret < 0)
goto err_probe;
ret = snd_soc_add_component_controls(component,
component->driver->controls,
component->driver->num_controls);
if (ret < 0)
goto err_probe;
ret = snd_soc_dapm_add_routes(dapm,
component->driver->dapm_routes,
component->driver->num_dapm_routes);
if (ret < 0) {
if (card->disable_route_checks) {
dev_info(card->dev,
"%s: disable_route_checks set, ignoring errors on add_routes\n",
__func__);
} else {
dev_err(card->dev,
"%s: snd_soc_dapm_add_routes failed: %d\n",
__func__, ret);
goto err_probe;
}
}
/* see for_each_card_components */
list_add(&component->card_list, &card->component_dev_list);
err_probe:
if (ret < 0)
soc_remove_component(component, probed);
return ret;
}
2.4 dai注册widget
上面的内容介绍了codec、platform以及machine级别的widget的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),dai按所在的位置,又分为cpu dai和codec dai。在硬件上,通常一个cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。
soc_probe_component调用snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:
int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
struct snd_soc_dai *dai)
{
struct snd_soc_dapm_widget template;
struct snd_soc_dapm_widget *w;
WARN_ON(dapm->dev != dai->dev);
memset(&template, 0, sizeof(template));
template.reg = SND_SOC_NOPM;
if (dai->driver->playback.stream_name) {
template.id = snd_soc_dapm_dai_in;
template.name = dai->driver->playback.stream_name;
template.sname = dai->driver->playback.stream_name;
dev_dbg(dai->dev, "ASoC: adding %s widget\n",
template.name);
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->playback_widget = w;
}
if (dai->driver->capture.stream_name) {
template.id = snd_soc_dapm_dai_out;
template.name = dai->driver->capture.stream_name;
template.sname = dai->driver->capture.stream_name;
dev_dbg(dai->dev, "ASoC: adding %s widget\n",
template.name);
w = snd_soc_dapm_new_control_unlocked(dapm, &template);
if (IS_ERR(w))
return PTR_ERR(w);
w->priv = dai;
dai->capture_widget = w;
}
return 0;
}
分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。
snd_soc_dapm_link_dai_widgets函数针对同一component下相同stream的dai widget连接:
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *dai_w, *w;
struct snd_soc_dapm_widget *src, *sink;
struct snd_soc_dai *dai;
/* For each DAI widget... */
for_each_card_widgets(card, dai_w) {
switch (dai_w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
break;
default:
continue;
}
/* let users know there is no DAI to link */
if (!dai_w->priv) {
dev_dbg(card->dev, "dai widget %s has no DAI\n",
dai_w->name);
continue;
}
dai = dai_w->priv;
/* ...find all widgets with the same stream and link them */
for_each_card_widgets(card, w) {
if (w->dapm != dai_w->dapm)
continue;
switch (w->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
continue;
default:
break;
}
if (!w->sname || !strstr(w->sname, dai_w->sname))
continue;
if (dai_w->id == snd_soc_dapm_dai_in) {
src = dai_w;
sink = w;
} else {
src = w;
sink = dai_w;
}
dev_dbg(dai->dev, "%s -> %s\n", src->name, sink->name);
snd_soc_dapm_add_path(w->dapm, src, sink, NULL, NULL);
}
}
return 0;
}
snd_soc_dapm_connect_dai_link_widgets是针对BE DAI与Codec dai连接
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *codec_dai;
int i;
/* for each BE DAI link... */
for_each_card_rtds(card, rtd) {
/*
* dynamic FE links have no fixed DAI mapping.
* CODEC<->CODEC links have no direct connection.
*/
if (rtd->dai_link->dynamic)
continue;
if (rtd->num_cpus == 1) {
for_each_rtd_codec_dais(rtd, i, codec_dai)
dapm_connect_dai_pair(card, rtd, codec_dai,
asoc_rtd_to_cpu(rtd, 0));
} else if (rtd->num_codecs == rtd->num_cpus) {
for_each_rtd_codec_dais(rtd, i, codec_dai)
dapm_connect_dai_pair(card, rtd, codec_dai,
asoc_rtd_to_cpu(rtd, i));
} else {
dev_err(card->dev,
"N cpus to M codecs link is not supported yet\n");
}
}
}
dapm_connect_dai_pair函数调用如下:
static void dapm_connect_dai_pair(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd,
struct snd_soc_dai *codec_dai,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_dai_link *dai_link = rtd->dai_link;
struct snd_soc_dapm_widget *dai, *codec, *playback_cpu, *capture_cpu;
struct snd_pcm_substream *substream;
struct snd_pcm_str *streams = rtd->pcm->streams;
if (dai_link->params) {
playback_cpu = cpu_dai->capture_widget;
capture_cpu = cpu_dai->playback_widget;
} else {
playback_cpu = cpu_dai->playback_widget;
capture_cpu = cpu_dai->capture_widget;
}
/* connect BE DAI playback if widgets are valid */
codec = codec_dai->playback_widget;
if (playback_cpu && codec) {
if (dai_link->params && !rtd->playback_widget) {
substream = streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
dai = snd_soc_dapm_new_dai(card, substream, "playback");
if (IS_ERR(dai))
goto capture;
rtd->playback_widget = dai;
}
dapm_connect_dai_routes(&card->dapm, cpu_dai, playback_cpu,
rtd->playback_widget,
codec_dai, codec);
}
capture:
/* connect BE DAI capture if widgets are valid */
codec = codec_dai->capture_widget;
if (codec && capture_cpu) {
if (dai_link->params && !rtd->capture_widget) {
substream = streams[SNDRV_PCM_STREAM_CAPTURE].substream;
dai = snd_soc_dapm_new_dai(card, substream, "capture");
if (IS_ERR(dai))
return;
rtd->capture_widget = dai;
}
dapm_connect_dai_routes(&card->dapm, codec_dai, codec,
rtd->capture_widget,
cpu_dai, capture_cpu);
}
}
dapm_connect_dai_routes函数最终调用snd_soc_dapm_add_path连接音频路径。
static void dapm_connect_dai_routes(struct snd_soc_dapm_context *dapm,
struct snd_soc_dai *src_dai,
struct snd_soc_dapm_widget *src,
struct snd_soc_dapm_widget *dai,
struct snd_soc_dai *sink_dai,
struct snd_soc_dapm_widget *sink)
{
dev_dbg(dapm->dev, "connected DAI link %s:%s -> %s:%s\n",
src_dai->component->name, src->name,
sink_dai->component->name, sink->name);
if (dai) {
snd_soc_dapm_add_path(dapm, src, dai, NULL, NULL);
src = dai;
}
snd_soc_dapm_add_path(dapm, src, sink, NULL, NULL);
}
三、注册音频路径
如果widget之间没有连接关系,dapm就无法实现动态的电源管理工作,正是widget之间有了连结关系,这些连接关系形成了一条所谓的完成的音频路径,dapm可以顺着这条路径,统一控制路径上所有widget的电源状态,widget之间是使用snd_soc_path结构进行连接的,驱动要做的是定义一个snd_soc_route结构数组,该数组的每个条目描述了目的widget的和源widget的名称,以及控制这个连接的kcontrol的名称,最终,驱动程序使用api函数snd_soc_dapm_add_routes来注册这些连接信息,接下来要分析该函数的具体实现方式:
int snd_soc_dapm_add_routes(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route, int num)
{
int i, r, ret = 0;
mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME);
for (i = 0; i < num; i++) {
r = snd_soc_dapm_add_route(dapm, route);
if (r < 0) {
dev_err(dapm->dev, "ASoC: Failed to add route %s -> %s -> %s\n",
route->source,
route->control ? route->control : "direct",
route->sink);
ret = r;
}
route++;
}
mutex_unlock(&dapm->card->dapm_mutex);
return ret;
}
该函数只是一个循环,依次对参数传入的数组调用snd_soc_dapm_add_route,主要的工作由snd_soc_dapm_add_route完成。
static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
const struct snd_soc_dapm_route *route)
{
struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
struct snd_soc_dapm_widget *wtsource = NULL, *wtsink = NULL;
const char *sink;
const char *source;
char prefixed_sink[80];
char prefixed_source[80];
const char *prefix;
unsigned int sink_ref = 0;
unsigned int source_ref = 0;
int ret;
/* 名称前缀的处理部分 */
prefix = soc_dapm_prefix(dapm);
if (prefix) {
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;
}
wsource = dapm_wcache_lookup(&dapm->path_source_cache, source);
wsink = dapm_wcache_lookup(&dapm->path_sink_cache, sink);
if (wsink && wsource)
goto skip_search;
/*
* find src and dest widgets over all widgets but favor a widget from
* current DAPM context
*/
for_each_card_widgets(dapm->card, w) {
if (!wsink && !(strcmp(w->name, sink))) {
wtsink = w;
if (w->dapm == dapm) {
wsink = w;
if (wsource)
break;
}
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;
if (w->dapm == dapm) {
wsource = w;
if (wsink)
break;
}
source_ref++;
if (source_ref > 1)
dev_warn(dapm->dev,
"ASoC: source widget %s overwritten\n",
w->name);
}
}
/* use widget from another DAPM context if not found from this */
if (!wsink)
wsink = wtsink;
if (!wsource)
wsource = wtsource;
if (wsource == NULL) {
dev_err(dapm->dev, "ASoC: no source widget found for %s\n",
route->source);
return -ENODEV;
}
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);
/* 增加一条连接信息 */
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;
}
snd_soc_dapm_add_path函数是整个调用链条中的关键:
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];
enum snd_soc_dapm_direction dir;
struct snd_soc_dapm_path *path;
int ret;
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;
}
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;
}
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;
/* 为这个连接分配了一个snd_soc_path结构 */
path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
if (!path)
return -ENOMEM;
/* path的source和sink字段分别指向源widget和目的widget */
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;
/* connected字段保存connected回调函数 */
path->connected = connected;
INIT_LIST_HEAD(&path->list);
INIT_LIST_HEAD(&path->list_kcontrol);
if (wsource->is_supply || wsink->is_supply)
path->is_supply = 1;
/* connect static paths */
if (control == NULL) {
path->connect = 1;
} else {
switch (wsource->id) {
case snd_soc_dapm_demux:
ret = dapm_connect_mux(dapm, path, control, wsource);
if (ret)
goto err;
break;
default:
break;
}
/* 目的widget如果是mixer和mux类型,分别用dapm_connect_mixer和dapm_connect_mux函数完成连接工作 */
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;
}
}
list_add(&path->list, &dapm->card->paths);
snd_soc_dapm_for_each_direction(dir)
list_add(&path->list_node[dir], &widgets[dir]->edges[dir]);
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;
}
2.3.1 函数dapm_connect_mixer
连接一个目的widget为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;
}
用需要用来连接的kcontrol的名字,和目的widget中的kcontrol模板数组中的名字相比较,找出该kcontrol在widget中的编号,path的名字设置为该kcontrol的名字,然后用dapm_set_path_status函数来初始化该输入端的连接状态。连接两个widget的链表操作和其他widget是一样的。
2.3.2 函数dapm_set_mixer_path_status
根据传入widget中的kcontrol编号,读取实际寄存器的值,根据寄存器的值来初始化这个path是否处于连接状态。
/* set up initial codec paths */
static void dapm_set_mixer_path_status(struct snd_soc_dapm_path *p, int i,
int nth_path)
{
struct soc_mixer_control *mc = (struct soc_mixer_control *)
p->sink->kcontrol_news[i].private_value;
unsigned int reg = mc->reg;
unsigned int shift = mc->shift;
unsigned int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
unsigned int val;
if (reg != SND_SOC_NOPM) {
val = soc_dapm_read(p->sink->dapm, reg);
/*
* The nth_path argument allows this function to know
* which path of a kcontrol it is setting the initial
* status for. Ideally this would support any number
* of paths and channels. But since kcontrols only come
* in mono and stereo variants, we are limited to 2
* channels.
*
* The following code assumes for stereo controls the
* first path is the left channel, and all remaining
* paths are the right channel.
*/
if (snd_soc_volsw_is_stereo(mc) && nth_path > 0) {
if (reg != mc->rreg)
val = soc_dapm_read(p->sink->dapm, mc->rreg);
val = (val >> mc->rshift) & mask;
} else {
val = (val >> shift) & mask;
}
if (invert)
val = max - val;
p->connect = !!val;
} else {
/* since a virtual mixer has no backing registers to
* decide which path to connect, it will try to match
* with initial state. This is to ensure
* that the default mixer choice will be
* correctly powered up during initialization.
*/
p->connect = invert;
}
}
2.3.3 函数dapm_connect_mux
用该函数连接一个目的widget是mux类型的所有输入端。
/* connect mux widget to its interconnecting audio paths */
static int dapm_connect_mux(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_path *path, const char *control_name,
struct snd_soc_dapm_widget *w)
{
const struct snd_kcontrol_new *kcontrol = &w->kcontrol_news[0];
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int val, item;
int i;
if (e->reg != SND_SOC_NOPM) {
val = soc_dapm_read(dapm, e->reg);
val = (val >> e->shift_l) & e->mask;
item = snd_soc_enum_val_to_item(e, val);
} else {
/* since a virtual mux has no backing registers to
* decide which path to connect, it will try to match
* with the first enumeration. This is to ensure
* that the default mux choice (the first) will be
* correctly powered up during initialization.
*/
item = 0;
}
i = match_string(e->texts, e->items, control_name);
if (i < 0)
return -ENODEV;
path->name = e->texts[i];
path->connect = (i == item);
return 0;
}
和mixer类型一样用名字进行匹配,只不过mux类型的kcontrol只需一个,所以要通过private_value字段所指向的soc_enum结构找出匹配的输入脚编号。初始化该输入端的连接状态。
当widget之间通过path进行连接之后,他们之间的关系就如下图所示:
四、注册dapm kcontrol
定义一个widget,我需要指定两个很重要的内容:一个是用于控制widget的电源状态的reg/shift等寄存器信息,另一个是用于控制音频路径切换的dapm kcontrol信息,这些dapm kcontrol有它们自己的reg/shift寄存器信息用于切换widget的路径连接方式。通过snd_soc_dapm_new_widgets函数,创建widget内包含的dapm kcontrol。
int snd_soc_dapm_new_widgets(struct snd_soc_card *card)
{
struct snd_soc_dapm_widget *w;
unsigned int val;
mutex_lock_nested(&card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);
for_each_card_widgets(card, w)
{ /* new字段用于判断该widget是否已经执行过snd_soc_dapm_new_widgets函数 */
if (w->new)
continue;
if (w->num_kcontrols) {
w->kcontrols = kcalloc(w->num_kcontrols,
sizeof(struct snd_kcontrol *),
GFP_KERNEL);
if (!w->kcontrols) {
mutex_unlock(&card->dapm_mutex);
return -ENOMEM;
}
}
/* 对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol */
switch(w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_mixer_named_ctl:
dapm_new_mixer(w);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
dapm_new_mux(w);
break;
case snd_soc_dapm_pga:
case snd_soc_dapm_effect:
case snd_soc_dapm_out_drv:
dapm_new_pga(w);
break;
case snd_soc_dapm_dai_link:
dapm_new_dai_link(w);
break;
default:
break;
}
/* 根据widget寄存器的当前值,初始化widget的电源状态,并设置到power字段 */
/* Read the initial power state from the device */
if (w->reg >= 0) {
val = soc_dapm_read(w->dapm, w->reg);
val = val >> w->shift;
val &= w->mask;
if (val == w->on_val)
w->power = 1;
}
/*接着,设置new字段,表明该widget已经初始化完成,我们还要吧该widget加入到声卡的dapm_dirty链表中,表明该widget的状态发生了变化,稍后在合适的时刻,dapm框架会扫描dapm_dirty链表,
统一处理所有已经变化的widget。为什么要统一处理?因为dapm要控制各种widget的上下电顺序,同时也是为了减少寄存器的读写次数(多个widget可能使用同一个寄存器)*/
w->new = 1;
dapm_mark_dirty(w, "new widget");
dapm_debugfs_add_widget(w);
}
/*最后,通过dapm_power_widgets函数,统一处理所有位于dapm_dirty链表上的widget的状态改变*/
dapm_power_widgets(card, SND_SOC_DAPM_STREAM_NOP);
mutex_unlock(&card->dapm_mutex);
return 0;
}
对几种能影响音频路径的widget,创建并初始化它们所包含的dapm kcontrol,需要用到的创建函数分别是:
dapm mixer kcontrol
对于mixer类型的dapm kcontrol,我们会使用dapm_new_mixer来完成具体的创建工作,代码如下
/* create new dapm mixer control */
static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
{
int i, ret;
struct snd_soc_dapm_path *path;
struct dapm_kcontrol_data *data;
/* add kcontrol */
for (i = 0; i < w->num_kcontrols; i++) {
/* match name */
snd_soc_dapm_widget_for_each_source_path(w, path) {
/* mixer/mux paths name must match control name */
if (path->name != (char *)w->kcontrol_news[i].name)
continue;
if (!w->kcontrols[i]) {
ret = dapm_create_or_share_kcontrol(w, i);
if (ret < 0)
return ret;
}
/*说明kcontrol已经在之前创建好了,增加一个虚拟的影子widget。则通过dapm_create_or_share_mixmux_kcontrol创建这个输入端的kcontrol,同理,kcontrol对应的影子widget也会通过dapm_kcontrol_add_path判断是否需要创建*/
dapm_kcontrol_add_path(w->kcontrols[i], path);
data = snd_kcontrol_chip(w->kcontrols[i]);
if (data->widget)
snd_soc_dapm_add_path(data->widget->dapm,
data->widget,
path->source,
NULL, NULL);
}
}
return 0;
}
因为一个输入脚可能会连接多个输入源,所以可能在上一个输入源的path关联时已经创建了这个kcontrol,所以这里判断kcontrols指针数组中对应索引中的指针值,如果已经赋值,说明kcontrol已经在之前创建好了,所以我们只要简单地把连接该输入端的path加入到kcontrol的path_list链表中,并且增加一个虚拟的影子widget,该影子widget连接和输入端对应的源widget,因为使用了kcontrol本身的reg/shift等寄存器信息,所以实际上控制的是该kcontrol的开和关,这个影子widget只有在kcontrol的autodisable字段被设置的情况下才会被创建,该特性使得source的关闭时,与之连接的mixer的输入端也可以自动关闭,这个特性通过dapm_kcontrol_add_path来实现这一点:
static void dapm_kcontrol_add_path(const struct snd_kcontrol *kcontrol,
struct snd_soc_dapm_path *path)
{
struct dapm_kcontrol_data *data = snd_kcontrol_chip(kcontrol);
list_add_tail(&path->list_kcontrol, &data->paths);
}
dapm mux kcontrol
因为一个widget最多只会包含一个mux类型的damp kcontrol,所以他的创建方法稍有不同,dapm框架使用dapm_new_mux函数来创建mux类型的dapm kcontrol:
/* create new dapm mux control */
static int dapm_new_mux(struct snd_soc_dapm_widget *w)
{
struct snd_soc_dapm_context *dapm = w->dapm;
enum snd_soc_dapm_direction dir;
struct snd_soc_dapm_path *path;
const char *type;
int ret;
switch (w->id) {
case snd_soc_dapm_mux:
dir = SND_SOC_DAPM_DIR_OUT;
type = "mux";
break;
case snd_soc_dapm_demux:
dir = SND_SOC_DAPM_DIR_IN;
type = "demux";
break;
default:
return -EINVAL;
}
if (w->num_kcontrols != 1) {
dev_err(dapm->dev,
"ASoC: %s %s has incorrect number of controls\n", type,
w->name);
return -EINVAL;
}
if (list_empty(&w->edges[dir])) {
dev_err(dapm->dev, "ASoC: %s %s has no paths\n", type, w->name);
return -EINVAL;
}
ret = dapm_create_or_share_kcontrol(w, 0);
if (ret < 0)
return ret;
snd_soc_dapm_widget_for_each_path(w, dir, path) {
if (path->name)
dapm_kcontrol_add_path(w->kcontrols[0], path);
}
return 0;
}
dapm pga kcontrol
/* create new dapm volume control */
static int dapm_new_pga(struct snd_soc_dapm_widget *w)
{
int i, ret;
for (i = 0; i < w->num_kcontrols; i++) {
ret = dapm_create_or_share_kcontrol(w, i);
if (ret < 0)
return ret;
}
return 0;
}
dapm_create_or_share_kcontrol
以上创建kcontrol 最终都call此函数,完成最后的创建,下面来看此函数定义:
static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w,
int kci)
{
struct snd_soc_dapm_context *dapm = w->dapm;
struct snd_card *card = dapm->card->snd_card;
const char *prefix;
size_t prefix_len;
int shared;
struct snd_kcontrol *kcontrol;
bool wname_in_long_name, kcname_in_long_name;
char *long_name = NULL;
const char *name;
int ret = 0;
prefix = soc_dapm_prefix(dapm);
if (prefix)
prefix_len = strlen(prefix) + 1;
else
prefix_len = 0;
shared = dapm_is_shared_kcontrol(dapm, w, &w->kcontrol_news[kci],
&kcontrol);
if (!kcontrol) {
if (shared) {
wname_in_long_name = false;
kcname_in_long_name = true;
} else {
switch (w->id) {
case snd_soc_dapm_switch:
case snd_soc_dapm_mixer:
case snd_soc_dapm_pga:
case snd_soc_dapm_effect:
case snd_soc_dapm_out_drv:
wname_in_long_name = true;
kcname_in_long_name = true;
break;
case snd_soc_dapm_mixer_named_ctl:
wname_in_long_name = false;
kcname_in_long_name = true;
break;
case snd_soc_dapm_demux:
case snd_soc_dapm_mux:
wname_in_long_name = true;
kcname_in_long_name = false;
break;
default:
return -EINVAL;
}
}
if (wname_in_long_name && kcname_in_long_name) {
/*
* The control will get a prefix from the control
* creation process but we're also using the same
* prefix for widgets so cut the prefix off the
* front of the widget name.
*/
long_name = kasprintf(GFP_KERNEL, "%s %s",
w->name + prefix_len,
w->kcontrol_news[kci].name);
if (long_name == NULL)
return -ENOMEM;
name = long_name;
} else if (wname_in_long_name) {
long_name = NULL;
name = w->name + prefix_len;
} else {
long_name = NULL;
name = w->kcontrol_news[kci].name;
}
kcontrol = snd_soc_cnew(&w->kcontrol_news[kci], NULL, name,
prefix);
if (!kcontrol) {
ret = -ENOMEM;
goto exit_free;
}
kcontrol->private_free = dapm_kcontrol_free;
ret = dapm_kcontrol_data_alloc(w, kcontrol, name);
if (ret) {
snd_ctl_free_one(kcontrol);
goto exit_free;
}
ret = snd_ctl_add(card, kcontrol);
if (ret < 0) {
dev_err(dapm->dev,
"ASoC: failed to add widget %s dapm kcontrol %s: %d\n",
w->name, name, ret);
goto exit_free;
}
}
ret = dapm_kcontrol_add_widget(kcontrol, w);
if (ret == 0)
w->kcontrols[kci] = kcontrol;
exit_free:
kfree(long_name);
return ret;
}
需要注意的是,如果kcontol支持autodisable特性,一旦kcontrol由于source的关闭而被自动关闭,则用户空间只能操作该kcontrol的cache值,只有该kcontrol再次打开时,该cache值才会被真正地更新到寄存器中。
总结一下,创建一个widget所包含的kcontrol所做的工作:
1)循环每一个输入端,为每个输入端依次执行下面的一系列操作;
2)为每个输入端创建一个kcontrol,能共享的则直接使用创建好的kcontrol;
3)kcontrol的private_data字段保存着这些共享widget的信息;
4)如果支持autodisable特性,每个输入端还要额外地创建一个虚拟的snd_soc_dapm_kcontrol类型的影子widget,该影子widget也记录在private_data字段中;
5)创建好的kcontrol会依次存放在widget的kcontrols数组中,供路径的控制和匹配之用。