Linux内核4.14版本——alsa框架分析(6)-ASoC(Machine)
alsa架构分析,ASoCf分析、Machine
3.1 检查struct snd_soc_dai_link结构体,是否符合要求。
3.3 重点snd_soc_instantiate_card
3.3.1 soc_bind_dai_link 根据num_links的值,进行DAIs的bind工作。
3.3.4 创建声卡的一些其他部件,如dapm_widgets等
3.3.5 调用各个子部件的probe函数、创建PCM、Control设备等、调用到platform->driver->pcm_new的函数
1. 概述
在ASOC小节中描述了整个ASOC的架构,其中Machine是ASOC架构中的关键部件,没有Machine部件,单独的Codec和Platform是无法工作的。因此本节则先从Machine部分开始,那应该如何开始呢? 答案当然是从代码入手,先进入ASOC在kernel中的位置: kernel/sound/soc下。
分析例子:
machine: sound\soc\samsung\s3c24xx_uda134x.c
platform: sound\soc\samsung\s3c24xx-i2s.c
codec: sound\soc\codecs\uda134x.c
2. soc-core.c代码分析
const struct dev_pm_ops snd_soc_pm_ops = {
.suspend = snd_soc_suspend,
.resume = snd_soc_resume,
.freeze = snd_soc_suspend,
.thaw = snd_soc_resume,
.poweroff = snd_soc_poweroff,
.restore = snd_soc_resume,
};
EXPORT_SYMBOL_GPL(snd_soc_pm_ops);
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
static int __init snd_soc_init(void)
{
snd_soc_debugfs_init();
snd_soc_util_init();
return platform_driver_register(&soc_driver);
}
module_init(snd_soc_init);
static void __exit snd_soc_exit(void)
{
snd_soc_util_exit();
snd_soc_debugfs_exit();
platform_driver_unregister(&soc_driver);
}
注册平台驱动soc_driver,当有同名的“soc-audio”平台设备注册时,soc_probe函数就会被调用。
static struct snd_soc_card myalsa_card = {
.name = "tx511_UDA1341",
.owner = THIS_MODULE,
.dai_link = &s3c2440_uda1341_dai_link,
.num_links = 1,
};
static void asoc_release(struct device * dev)
{
}
static struct platform_device asoc_dev = {
.name = "soc-audio",
.id = -1,
.dev = {
.release = asoc_release,
},
};
static int s3c2440_uda1341_init(void)
{
platform_set_drvdata(&asoc_dev, &myalsa_card);
platform_device_register(&asoc_dev);
return 0;
}
static void s3c2440_uda1341_exit(void)
{
platform_device_unregister(&asoc_dev);
}
平台设备又是怎么注册的呢, s3c24xx_uda134x.c中没给出常用的用法,上面代码给出了一个简单的范例,注册“soc-audio”的平台设备,并且在初始化函数中,把要注册的声卡设置到平台设备的dev->driver_data中。当platform_device和platform_driver相匹配的话,就会调用soc_probe函数。
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
/*
* no card, so machine driver should be registering card
* we should not be here in that case so ret error
*/
if (!card)
return -EINVAL;
dev_warn(&pdev->dev,
"ASoC: machine %s should use snd_soc_register_card()\n",
card->name);
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
return snd_soc_register_card(card);
}
最终调用snd_soc_register_card注册声卡。
3. Machine代码分析
samsung平台的machine代码选择为: s3c24xx_uda134x.c
此代码先注册平台驱动s3c24xx_uda134x_driver, 当平台驱动和平台设备(以前在arch下,目前在dt中配置)的名字相匹配的时候,就会调用平台驱动中的probe函数s3c24xx_uda134x_probe。
static int s3c24xx_uda134x_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x;
struct s3c24xx_uda134x *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->clk_lock);
card->dev = &pdev->dev;
snd_soc_card_set_drvdata(card, priv);
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "failed to register card: %d\n", ret);
return ret;
}
上面的文章介绍s3c24xx_uda134x.c没有注册“soc-audio”的平台设备,而是通过“s3c24xx_uda134x”平台驱动来完成的,当有同名的设备注册时,上面的s3c24xx_uda134x_probe就会被调用。声卡snd_soc_s3c24xx_uda134x直接赋值给了card的私有数据。
snd_soc_card_set_drvdata(card, priv);
static struct platform_driver s3c24xx_uda134x_driver = {
.probe = s3c24xx_uda134x_probe,
.driver = {
.name = "s3c24xx_uda134x",
},
};
module_platform_driver(s3c24xx_uda134x_driver);
s3c24xx_uda134x_probe最终也会调用snd_soc_register_card。
int devm_snd_soc_register_card(struct device *dev, struct snd_soc_card *card)
{
....................
ptr = devres_alloc(devm_card_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = snd_soc_register_card(card);
..................
}
此处的card就是snd_soc_s3c24xx_uda134x结构。接下来谈论此结构的作用。
static const struct snd_soc_ops s3c24xx_uda134x_ops = {
.startup = s3c24xx_uda134x_startup,
.shutdown = s3c24xx_uda134x_shutdown,
.hw_params = s3c24xx_uda134x_hw_params,
};
static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
.name = "UDA134X",
.stream_name = "UDA134X",
.codec_name = "uda134x-codec",
.codec_dai_name = "uda134x-hifi",
.cpu_dai_name = "s3c24xx-iis",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.ops = &s3c24xx_uda134x_ops,
.platform_name = "s3c24xx-iis",
};
static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
.name = "S3C24XX_UDA134X",
.owner = THIS_MODULE,
.dai_link = &s3c24xx_uda134x_dai_link,
.num_links = 1,
};
其中dai_link结构就是用作连接platform和codec的,指明到底用那个codec,那个platfrom。那是通过什么指定的? 如果有兴趣可以详细看snd_soc_dai_link的注释,此注释写的非常清楚。
struct snd_soc_dai_link {
/* config - must be set by machine driver */
const char *name; /* Codec name */
const char *stream_name; /* Stream name */
/*
* You MAY specify the link's CPU-side device, either by device name,
* or by DT/OF node, but not both. If this information is omitted,
* the CPU-side DAI is matched using .cpu_dai_name only, which hence
* must be globally unique. These fields are currently typically used
* only for codec to codec links, or systems using device tree.
*/
const char *cpu_name;
struct device_node *cpu_of_node;
/*
* You MAY specify the DAI name of the CPU DAI. If this information is
* omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
* only, which only works well when that device exposes a single DAI.
*/
const char *cpu_dai_name;
/*
* You MUST specify the link's codec, either by device name, or by
* DT/OF node, but not both.
*/
const char *codec_name;
struct device_node *codec_of_node;
/* You MUST specify the DAI name within the codec */
const char *codec_dai_name;
struct snd_soc_dai_link_component *codecs;
unsigned int num_codecs;
/*
* You MAY specify the link's platform/PCM/DMA driver, either by
* device name, or by DT/OF node, but not both. Some forms of link
* do not need a platform.
*/
const char *platform_name;
struct device_node *platform_of_node;
int id; /* optional ID for machine driver link identification */
const struct snd_soc_pcm_stream *params;
unsigned int num_params;
unsigned int dai_fmt; /* format to set on init */
enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */
/* codec/machine specific init - e.g. add machine controls */
int (*init)(struct snd_soc_pcm_runtime *rtd);
/* optional hw_params re-writing for BE and FE sync */
int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params);
/* machine stream operations */
const struct snd_soc_ops *ops;
const struct snd_soc_compr_ops *compr_ops;
/* Mark this pcm with non atomic ops */
bool nonatomic;
/* For unidirectional dai links */
unsigned int playback_only:1;
unsigned int capture_only:1;
/* Keep DAI active over suspend */
unsigned int ignore_suspend:1;
/* Symmetry requirements */
unsigned int symmetric_rates:1;
unsigned int symmetric_channels:1;
unsigned int symmetric_samplebits:1;
/* Do not create a PCM for this DAI link (Backend link) */
unsigned int no_pcm:1;
/* This DAI link can route to other DAI links at runtime (Frontend)*/
unsigned int dynamic:1;
/* DPCM capture and Playback support */
unsigned int dpcm_capture:1;
unsigned int dpcm_playback:1;
/* DPCM used FE & BE merged format */
unsigned int dpcm_merged_format:1;
/* pmdown_time is ignored at stop */
unsigned int ignore_pmdown_time:1;
struct list_head list; /* DAI link list of the soc card */
struct snd_soc_dobj dobj; /* For topology */
};
.cpu_dai_name: 用于指定cpu侧的dai名字,也就是所谓的cpu侧的数字音频接口,一般都是i2S接口。如果省略则会使用cpu_name/cou_of_name。
.codec_dai_name: 用于codec侧的dai名字,不可以省略。
.codec_name: 用于指定codec芯片。不可以省略。
.platform_name: 用于指定cpu侧平台驱动,通常都是DMA驱动,用于传输。
.ops: audio的相关操作函数集合。
再次回到 snd_soc_register_card函数中,继续分析Machine的作用。
/**
* 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;
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = soc_init_dai_link(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init link %s\n",
link->name);
return ret;
}
}
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
INIT_LIST_HEAD(&card->dai_link_list);
card->num_dai_links = 0;
INIT_LIST_HEAD(&card->rtd_list);
card->num_rtd = 0;
INIT_LIST_HEAD(&card->dapm_dirty);
INIT_LIST_HEAD(&card->dobj_list);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
ret = snd_soc_instantiate_card(card);
if (ret != 0)
return ret;
/* deactivate pins to sleep state */
list_for_each_entry(rtd, &card->rtd_list, list) {
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int j;
for (j = 0; j < rtd->num_codecs; j++) {
struct snd_soc_dai *codec_dai = rtd->codec_dais[j];
if (!codec_dai->active)
pinctrl_pm_select_sleep_state(codec_dai->dev);
}
if (!cpu_dai->active)
pinctrl_pm_select_sleep_state(cpu_dai->dev);
}
return ret;
}
我们分几个部分来讲解这个函数。
3.1 检查struct snd_soc_dai_link结构体,是否符合要求。
for (i = 0; i < card->num_links; i++) {
struct snd_soc_dai_link *link = &card->dai_link[i];
ret = soc_init_dai_link(card, link);
if (ret) {
dev_err(card->dev, "ASoC: failed to init link %s\n",
link->name);
return ret;
}
}
dev_set_drvdata(card->dev, card);
3.2 初始化card结构体
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
INIT_LIST_HEAD(&card->dai_link_list);
card->num_dai_links = 0;
INIT_LIST_HEAD(&card->rtd_list);
card->num_rtd = 0;
INIT_LIST_HEAD(&card->dapm_dirty);
INIT_LIST_HEAD(&card->dobj_list);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
3.3 重点snd_soc_instantiate_card
ret = snd_soc_instantiate_card(card);
if (ret != 0)
return ret;
分析 snd_soc_instantiate_card函数的实际操作:
3.3.1 soc_bind_dai_link 根据num_links的值,进行DAIs的bind工作。
/* 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;
}
(1)检测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;
}
检测dai link是否已经绑定了。
(2)分配一个struct snd_soc_pcm_runtime结构
rtd = soc_new_pcm_runtime(card, dai_link);
if (!rtd)
return -ENOMEM;
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;
rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime), GFP_KERNEL);
if (!rtd)
return NULL;
INIT_LIST_HEAD(&rtd->component_list);
rtd->card = card;
rtd->dai_link = dai_link;
rtd->codec_dais = kzalloc(sizeof(struct snd_soc_dai *) *
dai_link->num_codecs,
GFP_KERNEL);
if (!rtd->codec_dais) {
kfree(rtd);
return NULL;
}
return rtd;
}
分配一个struct snd_soc_pcm_runtime结构,然后根据num_links,设置card,复制dai_link等。
(3)bind 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_err(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);
此处dai_link就是在machine中注册的struct snd_soc_dai_link结构体,cpu_dai_name也就是注册的name,最后通过snd_soc_find_dai接口出查找。
/**
* snd_soc_find_dai - Find a registered DAI
*
* @dlc: name of the DAI or the DAI driver and optional component info to match
*
* 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*/
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;
}
此函数会在component_list链表中先找到相同的name,然后在component->dai_list中查找是否有相同的dai_name。此处的component_list是在注册codec和platform中的时候设置的。会在codec和platform的时候会详细介绍。在此处找到注册的cpu_dai之后,存在snd_soc_pcm_runtime中的cpu_dai中。
(4)然后根据codec的数据,寻找codec侧的dai
/* 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);
}
然后将找到的codec侧的dai也同样赋值给snd_soc_pcm_runtime中的codec_dai中。
(5)在platform_list链表中查找platfrom,根据dai_link中的platform_name域。如果没有platform_name,则设置为"snd-soc-dummy"
/* 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);
}
这样查找完毕之后,snd_soc_pcm_runtime中存储了查找到的codec, dai, platform。
soc_add_pcm_runtime(card, rtd);
3.3.2 其他一些初始化,如cache等
/* 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;
}
3.3.3 snd_card_new创建一个card
/* 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.4 创建声卡的一些其他部件,如dapm_widgets等
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.3.5 调用各个子部件的probe函数、创建PCM、Control设备等、调用到platform->driver->pcm_new的函数
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
/* 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 %d\n",
ret);
goto probe_dai_err;
}
}
}
/* probe auxiliary components */
ret = soc_probe_aux_devices(card);
if (ret < 0)
goto probe_dai_err;
/* Find new DAI links added during probing components and bind them.
* Components with topology may bring new DAIs and DAI links.
*/
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 all 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_dais(card, rtd, order);
if (ret < 0) {
dev_err(card->dev,
"ASoC: failed to instantiate card %d\n",
ret);
goto probe_dai_err;
}
}
}
(1) 在soc_probe_link_dais函数中依次调用了cpu_dai, codec_dai侧的probe函数
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;
int i, ret;
dev_dbg(card->dev, "ASoC: probe %s dai link %d late %d\n",
card->name, rtd->num, order);
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;
ret = soc_probe_dai(cpu_dai, order);
if (ret)
return ret;
/* probe the CODEC DAI */
for (i = 0; i < rtd->num_codecs; i++) {
ret = soc_probe_dai(rtd->codec_dais[i], order);
if (ret)
return ret;
}
/* complete DAI probe during last probe */
if (order != SND_SOC_COMP_ORDER_LAST)
return 0;
/* do machine specific initialization */
if (dai_link->init) {
ret = dai_link->init(rtd);
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to init %s: %d\n",
dai_link->name, ret);
return ret;
}
}
if (dai_link->dai_fmt)
snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);
ret = soc_post_component_init(rtd, dai_link->name);
if (ret)
return ret;
#ifdef CONFIG_DEBUG_FS
/* add DPCM sysfs entries */
if (dai_link->dynamic)
soc_dpcm_debugfs_add(rtd);
#endif
if (cpu_dai->driver->compress_new) {
/*create compress_device"*/
ret = cpu_dai->driver->compress_new(rtd, rtd->num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create compress %s\n",
dai_link->stream_name);
return ret;
}
} else {
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, rtd->num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
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);
/* link the DAI widgets */
ret = soc_link_dai_widgets(card, dai_link, rtd);
if (ret)
return ret;
}
}
return 0;
}
(2)最终调用到soc_new_pcm函数创建pcm设备
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, rtd->num);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
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;
(3)调用到platform->driver->pcm_new的函数
static int soc_link_dai_pcm_new(struct snd_soc_dai **dais, int num_dais,
struct snd_soc_pcm_runtime *rtd)
{
int i, ret = 0;
for (i = 0; i < num_dais; ++i) {
struct snd_soc_dai_driver *drv = dais[i]->driver;
if (!rtd->dai_link->no_pcm && drv->pcm_new)
ret = drv->pcm_new(rtd, dais[i]);
if (ret < 0) {
dev_err(dais[i]->dev,
"ASoC: Failed to bind %s with pcm device\n",
dais[i]->name);
return ret;
}
}
return 0;
}
最中此函数会调用ALSA的标准创建pcm设备的接口: snd_pcm_new,然后会设置pcm相应的ops操作函数集合。然后调用到platform->driver->pcm_new的函数。此处不帖函数了。
3.3.6 dapm和dai widget做相应的操作
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);
/* try to set some sane longname if DMI is available */
snd_soc_set_dmi_name(card, NULL);
3.3.7 snd_card_register
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;
}
4. 总结
总结: 经过Machine的驱动的注册,Machine会根据注册以"soc_audio"为名字的平台设备,然后在同名的平台的驱动的probe函数中,会根据snd_soc_dai_link结构体中的name,进行匹配查找相应的codec, codec_dai,platform, cpu_dai。找到之后将这些值全部放入结构体snd_soc_pcm_runtime的相应位置,然后注册card,依次调用codec, platform,cpu_dai侧相应的probe函数进行初始化,接着创建pcm设备,注册card到系统中。其实ASOC也就是在ALSA的基础上又再次封装了一次,让写驱动更方便,简便。
这样封装之后,就可以大大简化驱动的编写,关于Machine驱动需要做的:
1. 注册名为"soc-audio"的平台设备。
2. 分配一个struct snd_soc_card结构体,然后设置其中的dai_link。对其中的dai_link再次设置。
3. 将struct snd_soc_card结构放入到平台设备的dev的私有数据中。
4. 注册平台设备。
更多推荐
所有评论(0)