在系统/dev/snd下可查看注册成功的声卡信息,如下所示。
ubuntu@ubuntu:~$ ls -l /dev/snd
total 0
drwxr-xr-x 2 root root 60 4月 7 09:22 by-path
crw-rw----+ 1 root audio 116, 2 4月 7 09:22 controlC0 // 通路控制
crw-rw----+ 1 root audio 116, 4 4月 7 09:23 pcmC0D0c // 录音设备0
crw-rw----+ 1 root audio 116, 3 4月 7 09:23 pcmC0D0p // 播放设备
crw-rw----+ 1 root audio 116, 5 4月 7 09:22 pcmC0D1c // 录音设备1
crw-rw----+ 1 root audio 116, 1 4月 7 09:22 seq
crw-rw----+ 1 root audio 116, 33 4月 7 09:22 timer
下面分别分析machine, platform, codec驱动注册过程。
2.1、Machine驱动注册
参考sound\soc\pxa\mioa701_wm9713.c,注册snd_soc_card同时会填充dai_link,这里还注册了widgets和routes(下面章节分析)。
static struct snd_soc_card mioa701 = {
.name = "MioA701",
.owner = THIS_MODULE,
.dai_link = mioa701_dai,
.num_links = ARRAY_SIZE(mioa701_dai),
.dapm_widgets = mioa701_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(mioa701_dapm_widgets),
.dapm_routes = audio_map,
.num_dapm_routes = ARRAY_SIZE(audio_map),
};
snd_soc_dai_link定义如下,这里用到了SND_SOC_DAILINK_DEFS宏和SND_SOC_DAILINK_REG。
SND_SOC_DAILINK_DEFS(ac97,
DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-hifi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
SND_SOC_DAILINK_DEFS(ac97_aux,
DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-aux")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
static struct snd_soc_dai_link mioa701_dai[] = {
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.init = mioa701_wm9713_init,
.ops = &mioa701_ops,
SND_SOC_DAILINK_REG(ac97),
},
{
.name = "AC97 Aux",
.stream_name = "AC97 Aux",
.ops = &mioa701_ops,
SND_SOC_DAILINK_REG(ac97_aux),
},
};
SND_SOC_DAILINK_DEFS用于定义compoment,而SND_SOC_DAILINK_REG则是引用compoment,宏展开后如下。
static struct snd_soc_dai_link_component ac97_cpus[] = { { .dai_name = "pxa2xx-ac97", } };
static struct snd_soc_dai_link_component ac97_codecs[] = { { .name = "wm9713-codec", .dai_name = "wm9713-hifi", } };
static struct snd_soc_dai_link_component ac97_platforms[] = { { .name = "pxa-pcm-audio" } };
static struct snd_soc_dai_link_component ac97_aux_cpus[] = { { .dai_name = "pxa2xx-ac97-aux", } };
static struct snd_soc_dai_link_component ac97_aux_codecs[] = { { .name = "wm9713-codec", .dai_name = "wm9713-aux", } };
static struct snd_soc_dai_link_component ac97_aux_platforms[] = { { .name = "pxa-pcm-audio" } };
static struct snd_soc_dai_link mioa701_dai[] = {
{
.name = "AC97",
.stream_name = "AC97 HiFi",
.init = mioa701_wm9713_init,
.ops = &mioa701_ops,
.cpus = ac97_cpus, \
.num_cpus = ARRAY_SIZE(ac97_cpus), \
.codecs = ac97_codecs, \
.num_codecs = ARRAY_SIZE(ac97_codecs), \
.platforms = ac97_platforms, \
.num_platforms = ARRAY_SIZE(ac97_platforms)
},
{
.name = "AC97 Aux",
.stream_name = "AC97 Aux",
.ops = &mioa701_ops,
.cpus = ac97_aux_cpus, \
.num_cpus = ARRAY_SIZE(ac97_aux_cpus), \
.codecs = ac97_aux_codecs, \
.num_codecs = ARRAY_SIZE(ac97_aux_codecs), \
.platforms = ac97_aux_platforms, \
.num_platforms = ARRAY_SIZE(ac97_aux_platforms)
},
};
这里指定codec_name为"wm9713-codec",因此挂载的是wm9713声卡。
// sound\soc\codecs\wm9713.c
static struct platform_driver wm9713_codec_driver = {
.driver = {
.name = "wm9713-codec",
},
.probe = wm9713_probe,
};
module_platform_driver(wm9713_codec_driver);
platform_name为"pxa-pcm-audio",指定platform驱动为pxa2xx-pcm.c。
// sound\soc\pxa\pxa2xx-pcm.c
static struct platform_driver pxa_pcm_driver = {
.driver = {
.name = "pxa-pcm-audio",
},
.probe = pxa2xx_soc_platform_probe,
};
module_platform_driver(pxa_pcm_driver);
Machine驱动注册通过devm_snd_soc_register_card实现,PCM创建部分查看下图蓝色字体,红色字体为DAPM部分后面分析。
图2.1 devm_snd_soc_register_card注册snd_soc_card流程
snd_soc_bind_card会为每个dai_link调用snd_soc_add_pcm_runtime完成dai_link的初始化。
for_each_card_prelinks(card, i, dai_link) {
ret = snd_soc_add_pcm_runtime(card, dai_link);
if (ret < 0)
goto probe_end;
}
int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link_component *codec, *platform, *cpu;
struct snd_soc_component *component;
……
if (dai_link->ignore)
return 0;
dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);
ret = soc_dai_link_sanity_check(card, dai_link);
if (ret < 0)
return ret;
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
for_each_link_cpus(dai_link, i, cpu) {
asoc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);
if (!asoc_rtd_to_cpu(rtd, i)) {
dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
cpu->dai_name);
goto _err_defer;
}
snd_soc_rtd_add_component(rtd, asoc_rtd_to_cpu(rtd, i)->component);
}
/* Find CODEC from registered CODECs */
for_each_link_codecs(dai_link, i, codec) {
asoc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);
if (!asoc_rtd_to_codec(rtd, i)) {
dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n",
codec->dai_name);
goto _err_defer;
}
snd_soc_rtd_add_component(rtd, asoc_rtd_to_codec(rtd, i)->component);
}
/* Find PLATFORM from registered PLATFORMs */
for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
if (!snd_soc_is_matching_component(platform, component))
continue;
snd_soc_rtd_add_component(rtd, component);
}
}
……
}
snd_soc_add_pcm_runtime主要完成以下几项工作
- 检查dai_link是否有匹配的设备;
- 创建runtime;
- 绑定component及dai;
static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_component *component;
/*
* for rtd
*/
rtd = devm_kzalloc(dev,
sizeof(*rtd) +
sizeof(*component) * (dai_link->num_cpus +
dai_link->num_codecs +
dai_link->num_platforms),
GFP_KERNEL);
if (!rtd)
goto free_rtd;
rtd->dev = dev;
INIT_LIST_HEAD(&rtd->list);
for_each_pcm_streams(stream) {
INIT_LIST_HEAD(&rtd->dpcm[stream].be_clients);
INIT_LIST_HEAD(&rtd->dpcm[stream].fe_clients);
}
dev_set_drvdata(dev, rtd);
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
/*
* for rtd->dais
*/
rtd->dais = devm_kcalloc(dev, dai_link->num_cpus + dai_link->num_codecs,
sizeof(struct snd_soc_dai *),
GFP_KERNEL);
if (!rtd->dais)
goto free_rtd;
rtd->num_cpus = dai_link->num_cpus;
rtd->num_codecs = dai_link->num_codecs;
rtd->card = card;
rtd->dai_link = dai_link;
rtd->num = card->num_rtd++;
/* see for_each_card_rtds */
list_add_tail(&rtd->list, &card->rtd_list);
……
}
在驱动匹配时会用到snd_soc_find_dai,该函数会遍历component_list,并比较name字答串完成匹配。
struct snd_soc_dai *snd_soc_find_dai(
const struct snd_soc_dai_link_component *dlc)
{
struct snd_soc_component *component;
struct snd_soc_dai *dai;
lockdep_assert_held(&client_mutex);
/* Find CPU DAI from registered DAIs */
for_each_component(component) {
if (!snd_soc_is_matching_component(dlc, component))
continue;
for_each_component_dais(component, dai) {
if (dlc->dai_name && strcmp(dai->name, dlc->dai_name)
&& (!dai->driver->name
|| strcmp(dai->driver->name, dlc->dai_name)))
continue;
return dai;
}
}
return NULL;
}
for_each_component即遍历component_list。
static LIST_HEAD(component_list);
#define for_each_component(component) \
list_for_each_entry(component, &component_list, list)
component_list填充的操作则是在codec驱动,platform驱动里完成的。
soc_init_pcm_runtime函数实现PCM设备的创建与初始化,生成的设备用于PCM数据流控制,分析如图2.2.
图2.2 soc_init_pcm_runtime流程分析
2.2、CODEC驱动注册
Codec驱动负责声卡通路管理,电源控制等。需要实现snd_soc_component_driver结构体, 并在probe初始化时通过devm_snd_soc_register_component向ALSA注册自己的能力。
static const struct snd_soc_component_driver soc_component_dev_wm9713 = {
.probe = wm9713_soc_probe,
.remove = wm9713_soc_remove,
.suspend = wm9713_soc_suspend,
.resume = wm9713_soc_resume,
.set_bias_level = wm9713_set_bias_level,
.controls = wm9713_snd_ac97_controls,
.num_controls = ARRAY_SIZE(wm9713_snd_ac97_controls),
.dapm_widgets = wm9713_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm9713_dapm_widgets),
.dapm_routes = wm9713_audio_map,
.num_dapm_routes = ARRAY_SIZE(wm9713_audio_map),
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.non_legacy_dai_naming = 1,
};
static int wm9713_probe(struct platform_device *pdev)
{
……
return devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_wm9713, wm9713_dai, ARRAY_SIZE(wm9713_dai));
}
devm_snd_soc_register_component第三个参数是snd_soc_dai_driver结构,主要描述codec支持的通路声道数据,采样率等信息,其snd_soc_dai_ops成员可感知音频播放事件,如startup, prepare, trigger等。
还有一个很重要的地方是snd_soc_dai_driver的stream_name字段,ALSA会为每个dai创建一个widget,类型为snd_soc_dapm_dai_in或snd_soc_dapm_dai_out,用于音频流控制,后面dapm章节会分析。
soc_component_dev_wm9713也会填充上自己的kcontrol, widget以及routes等信息。
图2.3 snd_soc_register_component函数流程
上面只是将codec注册到全局链表,后面还要完成匹配的工作参见Machine驱动注册部分。
ALSA涉及很多结构体,大致梳理了其中的关系如图2.4所示。Machine驱动起总领作用,通过搜索component_list并比较name/dai_name与codec/platform驱动进行匹配。
图2.4 ALSA结构体关系图
图2.5 PCM流数据结构