概述
上篇说到Machine驱动中的snd_soc_register_card函数初始化到了最核心的snd_soc_instantiate_card函数,可以说这才是重中之重,本篇分三部分来讲这个内容。
Linux 4.9.123 可从以下地址获得
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
本文Codec基于wm8994。
1 soc_bind_dai_link
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link *dai_link;
int ret, i, order;
...
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, &card->dai_link[i]); //绑定
if (ret != 0)
goto base_error;
}
...
}
其函数定义如下:
static int soc_bind_dai_link(struct snd_soc_card *card,
struct snd_soc_dai_link *dai_link)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link_component *codecs = dai_link->codecs;
struct snd_soc_dai_link_component cpu_dai_component;
struct snd_soc_dai **codec_dais;
struct snd_soc_platform *platform;
const char *platform_name;
int i;
dev_dbg(card->dev, "ASoC: binding %sn", dai_link->name);
if (soc_is_dai_link_bound(card, dai_link)) { //判断是否已经绑定
dev_dbg(card->dev, "ASoC: dai link %s already boundn",
dai_link->name);
return 0;
}
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
cpu_dai_component.name = dai_link->cpu_name;
cpu_dai_component.of_node = dai_link->cpu_of_node;
cpu_dai_component.dai_name = dai_link->cpu_dai_name;
rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
if (!rtd->cpu_dai) { //如果cpu_dai没有初始化过,其实就是dai_link的cpu_*几个成员
dev_err(card->dev, "ASoC: CPU DAI %s not registeredn",
if (dai_link->platform_of_node) {
if (platform->dev->of_node != dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->component.name, platform_name))
continue;
}
rtd->platform = platform;
}
if (!rtd->platform) { //如果platform没有初始化过
dev_err(card->dev, "ASoC: platform %s not registeredn",
dai_link->platform_name);
goto _err_defer;
}
soc_add_pcm_runtime(card, rtd);
return 0;
_err_defer:
soc_free_pcm_runtime(rtd);
return -EPROBE_DEFER;
}
首先,绑定每一个dai_link到card: ASoC定义了三个全局的链表头变量:codec_list、dai_list、platform_list,系统中所有的Codec、DAI、Platform都在注册时连接到这三个全局链表上。soc_bind_dai_link函数逐个扫描这三个链表,根据card->dai_link[]中的名称进行匹配,匹配后把相应的codec,dai和platform实例赋值到card->rtd[]中(snd_soc_pcm_runtime)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
/* bind aux_devs too */
for (i = 0; i < card->num_aux_devs; i++) {
ret = soc_bind_aux_dev(card, i);
if (ret != 0)
goto base_error;
}
/* add predefined DAI links to the list */
for (i = 0; i < card->num_links; i++)
snd_soc_add_dai_link(card, card->dai_link+i);
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
ret = snd_soc_init_codec_cache(codec);
if (ret < 0)
goto base_error;
}
/* card bind complete so register a sound card */
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card);
if (ret < 0) {
dev_err(card->dev,
"ASoC: can't create sound card for card %s: %dn",
card->name, ret);
goto base_error;
}
然后绑定aux_dev;动态地添加dai_link到链表里,这样,就可以从链表里找到这些dai_link;为每个codec初始化寄存器缓存,基本上该绑定地都已经绑定到struct snd_soc_card card;
里了,现在可以添加这个声卡了。
<2> 创建一个card专用的debugfs,关于debugfs可以看这篇文章: [Linux]使用debugfs文件系统,但是这里只是留了一个接口,追进去以后可以看到,这是一个空函数
soc_init_card_debugfs(card);
<3> 声卡创建好了,下面开始进行dapm的工作,毕竟这部分工作堪称alsa的点睛之笔。
if (card->dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
card->num_dapm_widgets);
if (card->of_dapm_widgets)
snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
card->num_of_dapm_widgets);
添加snd_soc_dapm_new_controls,调用probe函数初始化card,仅一次
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
对于之前维护的rtd_list链表,probe 所有 这个card上DAI links用到的components。
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {
ret = soc_probe_link_components(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %dn",
ret);
goto probe_dai_err;
}
}
}
/* 对于每个card->aux_comp_list链表,probe 辅助 components */
ret = soc_probe_aux_devices(card);
if (ret < 0)
goto probe_dai_err;
查找在探测components期间添加的新DAI链接并绑定它们,因为具有拓扑结构的组件可能会带来新的DAI和DAI链接。
list_for_each_entry(dai_link, &card->dai_link_list, list) {
if (soc_is_dai_link_bound(card, dai_link))
continue;
ret = soc_init_dai_link(card, dai_link);
if (ret)
goto probe_dai_err;
ret = soc_bind_dai_link(card, dai_link);
if (ret)
goto probe_dai_err;
}
/* probe the platform */
接下来soc_probe_link_dais,在soc_probe_link_dais函数里挨个调用了cpu-dai, codec-dai(可能有n个)的probe函数,然后调用了dai_link的init函数,在这里我们看到一条注释/* do machine specific initialization */
,也就是说dai_link就是machine。在snd_soc_runtime_set_dai_fmt
中更新连接到指定运行时的DAI link的所有DAI的DAI链接格式。然后使用snd_soc_pcm_runtime
和name
再次进行component的初始化,然后创建cpu-dai端的compress_device,最后调用soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
list_for_each_entry(rtd, &card->rtd_list, list) {
ret = soc_probe_link_dais(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %dn",
ret);
goto probe_dai_err;
}
}
}
soc_new_pcm
函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
int i;
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
playback = rtd->dai_link->dpcm_playback;
capture = rtd->dai_link->dpcm_capture;
} else {
for (i = 0; i < rtd->num_codecs; i++) {
codec_dai = rtd->codec_dais[i];
if (codec_dai->driver->playback.channels_min)
playback = 1;
if (codec_dai->driver->capture.channels_min)
capture = 1;
}
capture = capture && cpu_dai->driver->capture.channels_min;
playback = playback && cpu_dai->driver->playback.channels_min;
}
if (rtd->dai_link->playback_only) {
playback = 1;
capture = 0;
}
if (rtd->dai_link->capture_only) {
playback = 0;
capture = 1;
}
/* create the PCM */
if (rtd->dai_link->no_pcm) {
snprintf(new_name, sizeof(new_name), "(%s)",
rtd->dai_link->stream_name);
ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num,
playback, capture, &pcm);
} else {
if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
else
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name,
(rtd->num_codecs > 1) ?
"multicodec" : rtd->codec_dai->name, num);
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback,
capture, &pcm);
}
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: can't create pcm for %sn",
rtd->dai_link->name);
return ret;
}
dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %sn",num, new_name);
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
pcm->nonatomic = rtd->dai_link->nonatomic;
rtd->pcm = pcm;
pcm->private_data = rtd;
if (rtd->dai_link->no_pcm) {
if (playback)
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd;
if (capture)
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd;
goto out;
}
/* ASoC PCM operations */
if (rtd->dai_link->dynamic) {
rtd->ops.open = dpcm_fe_dai_open;
rtd->ops.hw_params = dpcm_fe_dai_hw_params;
rtd->ops.prepare = dpcm_fe_dai_prepare;
rtd->ops.trigger = dpcm_fe_dai_trigger;
rtd->ops.hw_free = dpcm_fe_dai_hw_free;
rtd->ops.close = dpcm_fe_dai_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
} else {
rtd->ops.open = soc_pcm_open;
rtd->ops.hw_params = soc_pcm_hw_params;
rtd->ops.prepare = soc_pcm_prepare;
rtd->ops.trigger = soc_pcm_trigger;
rtd->ops.hw_free = soc_pcm_hw_free;
rtd->ops.close = soc_pcm_close;
rtd->ops.pointer = soc_pcm_pointer;
rtd->ops.ioctl = soc_pcm_ioctl;
}
if (platform->driver->ops) {
rtd->ops.ack = platform->driver->ops->ack;
rtd->ops.copy = platform->driver->ops->copy;
rtd->ops.silence = platform->driver->ops->silence;
rtd->ops.page = platform->driver->ops->page;
rtd->ops.mmap = platform->driver->ops->mmap;
}
if (playback)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
if (platform->driver->pcm_new) {
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(platform->dev,
"ASoC: pcm constructor failed: %dn",
ret);
return ret;
}
}
pcm->private_free = platform->driver->pcm_free;
out:
dev_info(rtd->card->dev, "%s <-> %s mapping okn",
(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
cpu_dai->name);
return ret;
}
接下来snd_soc_dapm_link_dai_widgets
找到具有相同stream的所有widgets并链接它们
snd_soc_dapm_link_dai_widgets(card);
snd_soc_dapm_connect_dai_link_widgets(card);
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
card->num_dapm_routes);
if (card->of_dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
card->num_of_dapm_routes);
执行late_probe函数,这个函数对应于在imx-wm8524.c
中定义的imx_wm8524_late_probe
if (card->late_probe) {
ret = card->late_probe(card);
if (ret < 0) {
dev_err(card->dev, "ASoC: %s late_probe() failed: %dn",
card->name, ret);
goto probe_aux_dev_err;
}
}
检查codec是否有任何新的dapm widget,如果找到则创建它们。
snd_soc_dapm_new_widgets(card);
最后,调用snd_card_register函数,注册分配给声卡的所有设备。在调用此函数之前,ALSA控制接口将被阻止从外部访问。 因此,应该在声卡初始化结束时调用此函数。
int snd_card_register(struct snd_card *card)
{
int err;
if (snd_BUG_ON(!card))
return -EINVAL;
if (!card->registered) {
err = device_add(&card->card_dev);
if (err < 0)
return err;
card->registered = true;
}
if ((err = snd_device_register_all(card)) < 0)
return err;
mutex_lock(&snd_card_mutex);
if (snd_cards[card->number]) {
/* already registered */
mutex_unlock(&snd_card_mutex);
return snd_info_card_register(card); /* register pending info */
}
if (*card->id) {
/* make a unique id name from the given string */
char tmpid[sizeof(card->id)];
memcpy(tmpid, card->id, sizeof(card->id));
snd_card_set_id_no_lock(card, tmpid, tmpid);
} else {
/* create an id from either shortname or longname */
const char *src;
src = *card->shortname ? card->shortname : card->longname;
snd_card_set_id_no_lock(card, src,
retrieve_id_from_card_name(src));
}
snd_cards[card->number] = card;
mutex_unlock(&snd_card_mutex);
init_info_for_card(card);
#if IS_ENABLED(CONFIG_SND_MIXER_OSS)
if (snd_mixer_oss_notify_callback)
snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
return 0;
}
EXPORT_SYMBOL(snd_card_register);
最后
以上就是迷人热狗为你收集整理的[Alsa]9, Machine驱动的编写(2)1 soc_bind_dai_link的全部内容,希望文章能够帮你解决[Alsa]9, Machine驱动的编写(2)1 soc_bind_dai_link所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复