ALSA ASOC Machine Driver
- 一、Machine 简介
- 二、ASoC Machine Driver
- 2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型
- 2.2 在 Probe() 中注册声卡
- 三、snd_soc_register_card 函数
- 3.1 bind DAIs
- 3.2 New a sound card
- 3.3 Create card new widgets
- 3.4 Probe all component used by DAI links
- 3.5 Probe all DAI links on this card
- 3.6 DAPM 相关操作
- 3.7 Register Sound Card
- 四、创建 Pcm Device 节点
一、Machine 简介
在前面的章节中已经有提到,ASoC 被分为 Machine、Platform 和 Codec 三大部分,其中 Machine 负责 Platform 和 Codec 之间的耦合以及部分和设备或板子特定的代码。Machine 驱动负责处理机器特有的一些控件和音频事件,单独的 Platform 和 Codec 驱动是不能工作的,它必须由 Machine 驱动把它们结合在一起才能完成整个设备的音频处理工作。
二、ASoC Machine Driver
# Note下面均以 mt2701-wm8960.c
为例进行讲解
2.1 Machine Driver 的 Platform Driver & Platform Device 驱动模型
该部分其实就是 /sound/soc/mediate/mt2701/mt2701-wm8960.c
中的 platform driver 与 /arch/arm/boot/dts/mt7623a-rfb-emmc.dts
中的 platform device 进行匹配,匹配成功后调用 mt2701_wm8960_machine_probe()
函数。
1)mt2701-wm8960.c
#ifdef CONFIG_OF
static const struct of_device_id mt2701_wm8960_machine_dt_match[] = {
{.compatible = "mediatek,mt2701-wm8960-machine",},
{}
};
#endif
static struct platform_driver mt2701_wm8960_machine = {
.driver = {
.name = "mt2701-wm8960",
#ifdef CONFIG_OF
.of_match_table = mt2701_wm8960_machine_dt_match,
#endif
},
.probe = mt2701_wm8960_machine_probe,
};
module_platform_driver(mt2701_wm8960_machine);
在 platform driver
中有注册名为 "ediatek,mt2701-wm8960-machine"
;
2)mt7623a-rfb-emmc.dts
...
sound {
compatible = "mediatek,mt2701-wm8960-machine";
mediatek,platform = <&afe>;
audio-routing =
"Headphone", "HP_L",
"Headphone", "HP_R",
"LINPUT1", "AMIC",
"RINPUT1", "AMIC";
mediatek,audio-codec = <&wm8960>;
pinctrl-names = "default";
pinctrl-0 = <&i2s0_pins_a>;
};
...
在设备树文件中有注册名为 "mediatek,mt2701-wm8960-machine"
的 platform device
,当 platform driver & platform device 匹配之后则会调用 platform_driver 下的 probe() 函数。
2.2 在 Probe() 中注册声卡
devm_snd_soc_register_card(&pdev->dev, card); // 注册声卡
ASoC 的一切都从 Machine 驱动开始,包括声卡的注册,绑定 Platform 和 Codec 驱动等。在 mt2701_wm8960_machine__probe() 函数会调用 devm_snd_soc_register_card() 函数在 ASoC Core 中注册一个 card,即 snd_soc_card,代码如下:
static struct snd_soc_card mt2701_wm8960_card = {
.name = "mt2701-wm8960",
.owner = THIS_MODULE,
.dai_link = mt2701_wm8960_dai_links,
.num_links = ARRAY_SIZE(mt2701_wm8960_dai_links),
.controls = mt2701_wm8960_controls,
.num_controls = ARRAY_SIZE(mt2701_wm8960_controls),
.dapm_widgets = mt2701_wm8960_widgets,
.num_dapm_widgets = ARRAY_SIZE(mt2701_wm8960_widgets),
};
static int mt2701_wm8960_machine_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &mt2701_wm8960_card;
...
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n",
__func__, ret);
...
return ret;
}
如上代码所示,在 snd_soc_card 中会定义 num_links 个 snd_soc_dai_link 实例 dai_link
,其中 dai_link 根据 DPCM 可以分为 Front End & Back End(当然也还有在两者之外的)
,并且会指定 cpu_name/cpu_of_node/cpu_dai_name、codec_name/codec_of_node/codec_dai_name、platform_name/platform_of_node
等,稍后 Machine 驱动将会利用这些属性去匹配系统中已经注册的 platform、cpu_dai、codec component。
三、snd_soc_register_card 函数
snd_soc_register_card() 函数中大部分工作都是在 snd_soc_instantiate_card()
函数中实现,其主要实现的内容如下:(涉及 DAPM 此处省略,待后面 DAPM 部分再解析)
3.1 bind DAIs
在 snd_soc_instantiate_card() 函数首先会进行 bind dais 操作
,代码如下:
/* bind DAIs */
// Machine:
// For FE dai_link: bind platform FE dai
// For BE dai_link: bind platform BE dai & Codec dai
//
// FE -------- BE -------
// _| |_ _| |_ HP
// _|platform|_ | Codec |
// _| |_ _| |_ SPK
// | | | |
// -------- -------
//
for_each_card_prelinks(card, i, dai_link) {
ret = soc_bind_dai_link(card, dai_link);
if (ret != 0)
goto base_error;
}
如前面所述,在定义 snd_soc_card 时有定义 dai_links
,如上述代码则会循环为每个 dai_link 均调用 soc_bind_dai_link()
函数进行匹配绑定 DAI 操作
,其中 soc_bind_dai_link() 函数定义如下:
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_component *component;
struct snd_soc_dai **codec_dais;
int i;
if (dai_link->ignore)
return 0;
dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);
//3.1.1 Check the dai_link 是否已经被绑定
if (soc_is_dai_link_bound(card, dai_link)) {
dev_dbg(card->dev, "ASoC: dai link %s already bound\n",
dai_link->name);
return 0;
}
//3.1.2 为该 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtd
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
//3.1.3 Find cpu_dai from dai_link cpu params & component_list cpu params
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) {
dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
goto _err_defer;
}
snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
//3.1.4 FInd codec_dais[] from dai_link codec params & component_list codec params
rtd->num_codecs = dai_link->num_codecs;
/* Find CODEC from registered CODECs */
/* we can use for_each_rtd_codec_dai() after this */
codec_dais = rtd->codec_dais;
for (i = 0; i < rtd->num_codecs; i++) {
codec_dais[i] = snd_soc_find_dai(&codecs[i]);
if (!codec_dais[i]) {
dev_err(card->dev, "ASoC: CODEC DAI %s not registered\n",
codecs[i].dai_name);
goto _err_defer;
}
snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
}
/* Single codec links expect codec and codec_dai in runtime data */
rtd->codec_dai = codec_dais[0];
//3.1.5 Find platform component from dai_link platform params & component_list platform params
/* find one from the set of registered platforms */
for_each_component(component) {
if (!snd_soc_is_matching_component(dai_link->platform,
component))
continue;
snd_soc_rtdcom_add(rtd, component);
}
//3.1.6 Add rtd -> card->rtd_list
soc_add_pcm_runtime(card, rtd);
return 0;
_err_defer:
soc_free_pcm_runtime(rtd);
return -EPROBE_DEFER;
}
如代码所示,该函数首先会为 dai_link 分配一个 snd_soc_pcm_runtime 实例 rtd
,在前面小节中已经介绍 platform & codec 驱动最终都会创建相应的 component
实例并插入到全局链表 component_list
中,此处则会根据 dai_link 参数匹配相应的 platform & codec,
① 根据 cpu_name/cpu_of_node/cpu_dai_name
从 component(cpu_dai_component) 中找到匹配的 snd_soc_dai cpu_dai(rtd->cpu_dai),并将该 cpu_dai 对应的 component 插入到 rtd->component_list
链表中;
② 根据 codec_name/codec_of_node/codec_dai_name
从 component(codec_dai component) 中找到匹配的 snd_soc_dai codec_dais(rtd->codec_dais[]),并将该 codec_dais 对应的 component 插入到 rtd->component_list
链表中;
③ 根据 platform_name/platform_of_node 从 component(platform driver component)
【例如前面的 mtk_afe_pcm_platform】中找到匹配的 component,并将该 component 插入到 rtd->component_list
链表中。
最后调用 soc_add_pcm_runtime() 函数将该 rtd 插入到 card->rtd_list
中。
3.2 New a sound card
紧接着调用标准 alsa core 函数创建声卡实例,代码如下:
/* 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: %d\n",
card->name, ret);
goto base_error;
}
3.3 Create card new widgets
紧接着调用 snd_soc_dapm_new_controls() 函数创建 card->dapm_widgets
,如下代码可知,定义该 widget 可以通知直接在代码中定义 dapm_widgets,也可以通过在 dts 文件中创建(该函数详解解析属于 DAPM,见后面章节)
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);
3.4 Probe all component used by DAI links
紧接着遍历 card->rtd_list 中的所有 rtd
调用 soc_probe_link_components() 函数,代码如下:
/* probe all components used by DAI links on this card */
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) {
ret = soc_probe_link_components(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
其中 soc_probe_link_components() 函数定义如下:
static int soc_probe_link_components(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd, int order)
{
struct snd_soc_component *component;
struct snd_soc_rtdcom_list *rtdcom;
int ret;
// 3.4.1 遍历该 rtd->component_list, 为所有用到的 components 调用 soc_probe_component() 函数
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
if (component->driver->probe_order == order) {
ret = soc_probe_component(card, component);
if (ret < 0)
return ret;
}
}
return 0;
}
如上代码所示,该函数会遍历该 rtd->component_list
, 为所有用到的 components 调用 soc_probe_component()
函数,定义如下:
static int soc_probe_component(struct snd_soc_card *card,
struct snd_soc_component *component)
{
// dapm context 详见后面章节
struct snd_soc_dapm_context *dapm =
snd_soc_component_get_dapm(component);
struct snd_soc_dai *dai;
int ret;
if (!strcmp(component->name, "snd-soc-dummy"))
return 0;
...
if (!try_module_get(component->dev->driver->owner))
return -ENODEV;
component->card = card;
dapm->card = card;
soc_set_name_prefix(card, component);
soc_init_component_debugfs(component);
// 3.4.1.1 创建 component->driver->dai_widgets
if (component->driver->dapm_widgets) {
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;
}
}
// 3.4.1.2 遍历 component->dai_list 创建 dai widgets
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;
}
}
// 3.4.1.3 调用 component->driver->probe() 函数
if (component->driver->probe) {
ret = component->driver->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);
}
...
// 3.4.1.4 创建 component->driver->controls & dapm_routes
if (component->driver->controls)
snd_soc_add_component_controls(component,
component->driver->controls,
component->driver->num_controls);
if (component->driver->dapm_routes)
snd_soc_dapm_add_routes(dapm,
component->driver->dapm_routes,
component->driver->num_dapm_routes);
// 3.4.1.5 将该 dapm text(即 component dapm text) 插入到 card->dapm_list 中
list_add(&dapm->list, &card->dapm_list);
/* see for_each_card_components */
list_add(&component->card_list, &card->component_dev_list);
return 0;
...
return ret;
}
如代码所示,该函数主要是实现对匹配的 platform/codec component 涉及的 DAPM、kcontrol 相关操作(详见后面 DAPM 章节),并调用 component->driver->probe()
,该 probe() 函数主要也是涉及 DAPM、kcontrol 操作等。
3.5 Probe all DAI links on this card
紧接着遍历 card->rtd_list 中的所有 rtd
调用 soc_probe_link_dais() 函数,代码如下:
/* probe all DAI links on this card */
for_each_comp_order(order) {
for_each_card_rtds(card, rtd) {
ret = soc_probe_link_dais(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
其中 soc_probe_link_dais()
函数定义如下:
static int soc_probe_link_dais(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd, int order)
{
struct snd_soc_dai_link *dai_link = rtd->dai_link;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_rtdcom_list *rtdcom;
struct snd_soc_component *component;
struct snd_soc_dai *codec_dai;
int i, ret, num;
...
// 3.5.1 调用 rtd->cpu_dai->driver->probe() 函数
ret = soc_probe_dai(cpu_dai, order);
if (ret)
return ret;
// 3.5.2 调用 rtd->codec_dais[]->driver->probe() 函数
/* probe the CODEC DAI */
for_each_rtd_codec_dai(rtd, i, codec_dai) {
ret = soc_probe_dai(codec_dai, order);
if (ret)
return ret;
}
...
// 3.5.3 创建 pcm device
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
// 3.5.4 调用 cpu_dai/codec_dais[]->driver->pcm_new() 函数
ret = soc_link_dai_pcm_new(&cpu_dai, 1, rtd);
if (ret < 0)
return ret;
ret = soc_link_dai_pcm_new(rtd->codec_dais,
rtd->num_codecs, rtd);
if (ret < 0)
return ret;
} else {
INIT_DELAYED_WORK(&rtd->delayed_work,
codec2codec_close_delayed_work);
}
return 0;
}
如上述函数定义所示,该函数首先会调用各个 rtd->cpu_dai/codec_dais[]->driver->probe()
函数,接着调用 soc_new_pcm()
函数用于 create the pcm,该函数定义如下:
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_dai *codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_component *component;
struct snd_soc_rtdcom_list *rtdcom;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
int i;
// 3.5.3.1 Check rtd 是否支持 playback & capture
// DPCM:
// FE: rtd->dai_link->dynamic
// BE: rtd->dai_link->no_pcm
// No DPCM:
// codec_dai&cpu_dai
if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) {
playback = rtd->dai_link->dpcm_playback;
capture = rtd->dai_link->dpcm_capture;
} else {
for_each_rtd_codec_dai(rtd, i, codec_dai) {
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;
}
// 3.5.3.2 create the pcm
// 由于 FE->BE ==> BE 调用 snd_pcm_new_internal() 不会创建 user device, 自然后面也就不需要填充 ops 操作集给到 user
/* 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 %s\n",
rtd->dai_link->name);
return ret;
}
...
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;
}
// 3.5.1.3 for BE/ No DPCM 设置 ops 操作集
/* 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;
}
for_each_rtdcom(rtd, rtdcom) {
const struct snd_pcm_ops *ops = rtdcom->component->driver->ops;
if (!ops)
continue;
if (ops->ack)
rtd->ops.ack = soc_rtdcom_ack;
if (ops->copy_user)
rtd->ops.copy_user = soc_rtdcom_copy_user;
if (ops->copy_kernel)
rtd->ops.copy_kernel = soc_rtdcom_copy_kernel;
if (ops->fill_silence)
rtd->ops.fill_silence = soc_rtdcom_fill_silence;
if (ops->page)
rtd->ops.page = soc_rtdcom_page;
if (ops->mmap)
rtd->ops.mmap = soc_rtdcom_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);
//3.5.1.4 调用 rtd 下所有 component->driver_pcm_new()
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
if (!component->driver->pcm_new)
continue;
ret = component->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(component->dev,
"ASoC: pcm constructor failed: %d\n",
ret);
return ret;
}
}
pcm->private_free = soc_pcm_private_free;
out:
dev_info(rtd->card->dev, "%s <-> %s mapping ok\n",
(rtd->num_codecs > 1) ? "multicodec" : rtd->codec_dai->name,
cpu_dai->name);
return ret;
}
如上所示该函数首先会调用标准 alsa core 函数,如下:
DPCM BE
:由于 FE->BE,在操作 FE 时会同时操作与之 connect 的 BE,故不会对 BE 创建 user pcm device,即调用 snd_pcm_new_internal() 函数;DPCM FE
:调用 snd_pcm_new() 创建声卡 pcm device,并且FE Device Name:("%s(*)", dai_link->stream_name)
(故有 '*'
代表为 FE 可以动态连接 BE);No DPCM
:调用 snd_pcm_new() 创建声卡 pcm device,并且No DPCM Device Name:("%s %s-%d", dai_link->stream, "multicodec"/codec_dai->name, num)
(故无 '*'
代表 No NDPCM)
从上述代码中可以看出,对于 DPCM FE 和 No DPCM 对应 rtd->ops(snd_soc_ops) 字段赋值也不一样,由于 FE 的操作函数中需要涉及对 Connect’s BE 的操作;
在初始化 rtd->ops 时还会根据 platform driver 中的 snd_pcm_ops
填充相应的字段,如 ack、copy_user 等,最终会调用标准 alsa core 函数 snd_pcm_set_ops()
将 rtd->ops 设置为 substream->ops.
# Note:在 rtd->ops 字段中,如 open、hw_params、prepare 等回调函数中最终都会调用到 platform_driver & cpu_dai driver 以及 connect BE 对应的 codec driver 对应的 ops 字段相应的回调函数。
最后,该函数还会调用 rtd 下 component->driver->pcm_new()
(主要是 platform driver pcm_new())函数,如前面 platform driver 小节讲解,此函数主要是预分配 DMA Memory,最后真正分配 DMA Memory 是在 Platform Driver/Cpu Dai Driver hw_params 中。
3.6 DAPM 相关操作
接着会调用 card dapm、route 相关操作(详细见后面章节)
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);
...
snd_soc_dapm_new_widgets(card);
3.7 Register Sound Card
snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s", card->name);
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->long_name ? card->long_name : card->name);
snprintf(card->snd_card->driver, sizeof(card->snd_card->driver),
"%s", card->driver_name ? card->driver_name : card->name);
for (i = 0; i < ARRAY_SIZE(card->snd_card->driver); i++) {
switch (card->snd_card->driver[i]) {
case '_':
case '-':
case '\0':
break;
default:
if (!isalnum(card->snd_card->driver[i]))
card->snd_card->driver[i] = '_';
break;
}
}
...
ret = snd_card_register(card->snd_card);
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
ret);
goto probe_aux_dev_err;
}
card->instantiated = 1;
如上代码所示,在该函数最后先填充 snd_card 的重要参数,如 shortname、longname、driver 等,最后调用标准 alsa core 函数 snd_card_register()
来注册声卡以及注册该声卡下的所有 snd_devices.
至此,整个 Machine 驱动的初始化已经完成,通过各个子结构 probe 调用等,实际上,也完成了部分 Platform 驱动和 Codec 驱动的初始化工作,整个过程可以用以下的序列图表示:
四、创建 Pcm Device 节点
snd_soc_dai_link mt2701_wm8960_dai_link 定义如下:
static struct snd_soc_dai_link mt2701_wm8960_dai_links[] = {
/* FE */
{
.name = "wm8960-playback",
.stream_name = "wm8960-playback",
.cpu_dai_name = "PCMO0",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_playback = 1,
},
{
.name = "wm8960-capture",
.stream_name = "wm8960-capture",
.cpu_dai_name = "PCM0",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST},
.dynamic = 1,
.dpcm_capture = 1,
},
/* BE */
{
.name = "wm8960-codec",
.cpu_dai_name = "I2S0",
.no_pcm = 1,
.codec_dai_name = "wm8960-hifi",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS
| SND_SOC_DAIFMT_GATED,
.ops = &mt2701_wm8960_be_ops,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
};
根据上面 Machine 驱动中调用 soc_new_pcm()
创建 pcm device 节点后如下:
参考链接:
linux-alsa详解7 ASOC-machine