由前面可知,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,Machine为CPU、Codec、输入输出设备提供了一个载体。单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等。
Machine关键数据结构如下
//代表 Machine
struct snd_soc_card
//rtd 记录单个完整链路的组成信息(codec,platform,dais)
struct snd_soc_pcm_runtime *rtd;
//用于创建CPU DAI 和 Codec DAI的连接
struct snd_soc_dai_link
以WM8994的一款Samsung的开发板SMDK为例子做说明,WM8994是一颗Wolfson生产的多功能Codec芯片。
kernel\sound\soc\samsung\smdk_wm8994.c
/*
* SMDK WM8994 DAI operations.
*/
static struct snd_soc_ops smdk_ops = {
.hw_params = smdk_hw_params,
};
/*
指定了Platform、Codec、codec_dai、cpu_dai的名字
稍后Machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai
*/
static struct snd_soc_dai_link smdk_dai[] = {
{ /* Primary DAI i/f */
.name = "WM8994 AIF1",
.stream_name = "Pri_Dai",
.cpu_dai_name = "samsung-i2s.0",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s.0",
.codec_name = "wm8994-codec",
.init = smdk_wm8994_init_paiftx,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops,
}, { /* Sec_Fifo Playback i/f */
.name = "Sec_FIFO TX",
.stream_name = "Sec_Dai",
.cpu_dai_name = "samsung-i2s-sec",
.codec_dai_name = "wm8994-aif1",
.platform_name = "samsung-i2s-sec",
.codec_name = "wm8994-codec",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM,
.ops = &smdk_ops, //!!!
},
};
//machine
static struct snd_soc_card smdk = {
.name = "SMDK-I2S",
.owner = THIS_MODULE,
.dai_link = smdk_dai, //snd_soc_dai_link
.num_links = ARRAY_SIZE(smdk_dai),//2
};
static int smdk_audio_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &smdk;
card->dev = &pdev->dev;
/*
struct platform_device *pdev
struct device dev;
struct snd_soc_card *card
*/
ret = devm_snd_soc_register_card(&pdev->dev, card);
}
static struct platform_driver smdk_audio_driver = {
.driver = {
.name = "smdk-audio-wm8994",
.of_match_table = of_match_ptr(samsung_wm8994_of_match),
.pm = &snd_soc_pm_ops,
},
.probe = smdk_audio_probe,
};
module_platform_driver(smdk_audio_driver);
kernel\sound\soc\soc-devres.c
/*
struct platform_device *pdev
struct device dev;
struct snd_soc_card *card
*/
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
struct snd_soc_card **ptr;
int ret;
ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = snd_soc_register_card(card);
if (ret == 0) {
*ptr = card;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
EXPORT_SYMBOL_GPL(devm_snd_soc_register_card);
/**
* snd_soc_register_card - Register a card with the ASoC core
*
* @card: Card to register
*
*/
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, ret;
struct snd_soc_pcm_runtime *rtd;
if (!card->name || !card->dev)
return -EINVAL;
...
//初始化 rtd_list链表
INIT_LIST_HEAD(&card->rtd_list);
card->num_rtd = 0;
...
ret = snd_soc_instantiate_card(card);
if (ret != 0)
return ret;
...
return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_card);
//主要工作就在这里 !!!
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai_link *dai_link;
int ret, i, order;
...
/* bind DAIs */
//1 根据指定 的 codec platform 的 dai name信息 查找已经注册的目标dai, 并得到其所属的组件(codec platform)。全部整到 snd_soc_pcm_runtime
for (i = 0; i < card->num_links; i++) {
/*
根据snd_soc_dai_link结构体中的name,到全局组件链表中找到相应的 codec_dai, cpu_dai。platform 找到之后将这些 以及 所属的 codec,platform 全部放入结构体 snd_soc_pcm_runtime 的相应位置
很明显 本例程有两个dai_link 即会填充两个 rtd
所以 rtd 就是用来记录单个链路(codec,platform,dais)的信息
*/
ret = soc_bind_dai_link(card, &card->dai_link[i]);
}
...
//2 创建声卡逻辑设备 类型:control
/* 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;
}
...
/* probe all components used by DAI links on this card */
//3 调用 platform codec 的probe()
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
//很明显 本例程rtd_list链表上有两个 rtd
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 %d\n",
ret);
goto probe_dai_err;
}
}
}
...
/* probe all DAI links on this card */
//4 调用 platform codec 的 DAIS probe()
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 %d\n",
ret);
goto probe_dai_err;
}
}
}
...
//5 注册声卡
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;
}
...
return ret;
}
这里简单说说明一下 工作1和工作2。
工作1:
根据指定 的 codec platform 的 dai name信息 查找已经注册的目标dai, 并得到其所属的组件(codec platform)。全部整到 snd_soc_pcm_runtime
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;
struct device_node *platform_of_node;
const char *platform_name;
int i;
if (dai_link->ignore)
return 0;
dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);
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;
}
//创建 snd_soc_pcm_runtime
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
/*
在已注的组件链表中 遍历所有组件。依次遍历每一个组件的 dai list 链表。查找目标名字的 cpu_dai
*/
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);
rtd->num_codecs = dai_link->num_codecs;
/* Find CODEC from registered CODECs */
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];
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name;
if (!platform_name && !dai_link->platform_of_node)
platform_name = "snd-soc-dummy";
/* find one from the set of registered platforms */
list_for_each_entry(component, &component_list, list) {
platform_of_node = component->dev->of_node;
if (!platform_of_node && component->dev->parent->of_node)
platform_of_node = component->dev->parent->of_node;
if (dai_link->platform_of_node) {
if (platform_of_node != dai_link->platform_of_node)
continue;
} else {
if (strcmp(component->name, platform_name))
continue;
}
snd_soc_rtdcom_add(rtd, component);
}
soc_add_pcm_runtime(card, rtd);
return 0;
_err_defer:
soc_free_pcm_runtime(rtd);
return -EPROBE_DEFER;
}
/** 搜索所有已注册的组件 和 DAIs
* snd_soc_find_dai - Find a registered DAI
*
* @dlc: name of the DAI or the DAI driver and optional component info to match
* 此函数将搜索所有已注册的组件及其DAIs
* This function will search all registered components and their DAIs to
* find the DAI of the same name. The component's of_node and name
* should also match if being specified.
*
* Return: pointer of DAI, or NULL if not found.
*/
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;
struct device_node *component_of_node;
lockdep_assert_held(&client_mutex);
/* Find CPU DAI from registered DAIs*/
/*
component_list 全局组件链表(包含已注册的所有组件以及DAIS)
*/
list_for_each_entry(component, &component_list, list) {
component_of_node = component->dev->of_node;
if (!component_of_node && component->dev->parent)
component_of_node = component->dev->parent->of_node;
if (dlc->of_node && component_of_node != dlc->of_node)
continue;
if (dlc->name && strcmp(component->name, dlc->name))
continue;
list_for_each_entry(dai, &component->dai_list, list) {
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;
}
EXPORT_SYMBOL_GPL(snd_soc_find_dai);
工作2 创建声卡逻辑设备 类型:control
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
//声卡设备
struct snd_card *card;
int err;
if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;
if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
if (!card)
return -ENOMEM;
if (extra_size > 0)
card->private_data = (char *)card + sizeof(struct snd_card);
...
err = snd_ctl_create(card);
err = snd_info_card_create(card);
*card_ret = card;
return 0;
__error_ctl:
snd_device_free_all(card);
__error:
put_device(&card->card_dev);
return err;
}
EXPORT_SYMBOL(snd_card_new);
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
if (snd_BUG_ON(!card))
return -ENXIO;
if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS))
return -ENXIO;
snd_device_initialize(&card->ctl_dev, card);
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
//创建类型为 SNDRV_DEV_CONTROL 的 snd_device,并挂入 snd_card 的devices链表
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
整理后
machine codec platform codec_DAI platform_DAI 以及声卡设备 声卡逻辑设备 组成如下关系:

本文深入探讨了ASoC音频子系统的架构,详细讲解了Machine、Platform和Codec的交互原理,以及声卡注册和组件绑定的过程。通过具体实例,阐述了如何在SMDK开发板上使用WM8994 Codec芯片实现音频处理。
1724

被折叠的 条评论
为什么被折叠?



