一、注册platform Driver
前面章节ALSA驱动asoc框架之machine(一)说过machine驱动的probe函数会分配一个名为“soc-audio”的平台设备,如zx297520v3_nau8810.c:
static int zx297520v3_nau8810_probe(struct platform_device *pdev)
{
... ...
zx297520v3_nau8810_snd_device = platform_device_alloc("soc-audio", -1);
if (!zx297520v3_nau8810_snd_device) {
printk(KERN_ERR "zx297520v3_nau8810 SoC Audio: Unable to register\n");
return -ENOMEM;
}
... ...
return ret;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */
有平台设备必定有平台驱动,以“soc-audio”搜索,在linux-3.4.x\sound\soc\soc-core.c中搜索到有对应platform driver驱动:
/* ASoC platform driver */
static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
};
我们知道有同名的platform device和platform driver会触发driver中的probe探测函数被调用:
/* probes a new socdev */
static int soc_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
int ret = 0;
... ...
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret != 0) {
dev_err(&pdev->dev, "Failed to register card\n");
return ret;
}
return 0;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */
soc_probe函数通过platform_get_drvdata将machine驱动platform_set_drvdata的snd_soc_card实体取出,并调用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)
{
... ...
dev_set_drvdata(card->dev, card);
snd_soc_initialize_card_lists(card);
soc_init_card_debugfs(card);
card->rtd = devm_kzalloc(card->dev,
sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
if (card->rtd == NULL)
return -ENOMEM;
card->num_rtd = 0;
card->rtd_aux = &card->rtd[card->num_links];
for (i = 0; i < card->num_links; i++)
card->rtd[i].dai_link = &card->dai_link[i];
INIT_LIST_HEAD(&card->list);
INIT_LIST_HEAD(&card->dapm_dirty);
card->instantiated = 0;
mutex_init(&card->mutex);
mutex_init(&card->dapm_mutex);
mutex_lock(&client_mutex);
list_add(&card->list, &card_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
dev_dbg(card->dev, "Registered card '%s'\n", card->name);
return 0;
}
snd_soc_register_card函数申请内存,然后将card->dai_link信息拷贝至snd_soc_pcm_runtime中,进行一些初始化,然后调用关键函数snd_soc_instantiate_cards,这个函数比较复杂,我们用<>尖括号将重要函数标注出来,然后单独说明:
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
... ...
/* bind DAIs */
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);//---------------------<1>
... ...
/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
card->owner, 0, &card->snd_card); //-------------------<2>
if (ret < 0) {
pr_err("asoc: can't create sound card for card %s: %d\n",
card->name, ret);
mutex_unlock(&card->mutex);
return;
}
card->snd_card->dev = card->dev;
card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);
... ...
/* initialise the sound card only once */
if (card->probe) {
ret = card->probe(card);//----------------<3>
if (ret < 0)
goto card_probe_error;
}
/* early DAI link probe */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST;
order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_dai_link(card, i, order);//-------------------<4>
if (ret < 0) {
pr_err("asoc: failed to instantiate card %s: %d\n",
card->name, ret);
goto probe_dai_err;
}
}
}
... ...
ret = snd_card_register(card->snd_card);//------------------<5>
if (ret < 0) {
pr_err("asoc: failed to register soundcard for %s: %d\n",
card->name, ret);
goto probe_aux_dev_err;
}
... ...
}
<1> soc_bind_dai_link函数如下:
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
... ...
/* no, then find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) {
if (dai_link->cpu_dai_of_node) {
if (cpu_dai->dev->of_node != dai_link->cpu_dai_of_node)
continue;
} else {
if (strcmp(cpu_dai->name, dai_link->cpu_dai_name))
continue;
}
rtd->cpu_dai = cpu_dai;
goto find_codec;
}
dev_dbg(card->dev, "CPU DAI %s not registered\n",
dai_link->cpu_dai_name);
find_codec:
/* do we already have the CODEC for this link ? */
if (rtd->codec) {
goto find_platform;
}
/* no, then find CODEC from registered CODECs*/
list_for_each_entry(codec, &codec_list, list) {
if (dai_link->codec_of_node) {
if (codec->dev->of_node != dai_link->codec_of_node)
continue;
} else {
if (strcmp(codec->name, dai_link->codec_name))
continue;
}
rtd->codec = codec;
/*
* CODEC found, so find CODEC DAI from registered DAIs from
* this CODEC
*/
list_for_each_entry(codec_dai, &dai_list, list) {
if (codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name,
dai_link->codec_dai_name)) {
rtd->codec_dai = codec_dai;
goto find_platform;
}
}
dev_dbg(card->dev, "CODEC DAI %s not registered\n",
dai_link->codec_dai_name);
goto find_platform;
}
dev_dbg(card->dev, "CODEC %s not registered\n",
dai_link->codec_name);
find_platform:
/* do we need a platform? */
if (rtd->platform)
goto out;
/* 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";
/* no, then find one from the set of registered platforms */
list_for_each_entry(platform, &platform_list, list) {
if (dai_link->platform_of_node) {
if (platform->dev->of_node !=
dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->name, platform_name))
continue;
}
rtd->platform = platform;
goto out;
}
dev_dbg(card->dev, "platform %s not registered\n",
dai_link->platform_name);
return 0;
... ...
}
SoC定义了三个全局的链表头变量: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结构体)。经过这个过程后card->rtd中保存了本Machine中使用的Codec,DAI和Platform驱动的信息。
codec_list、dai_list、platform_list链表的元素从哪里来的呢?这些元素的名字则是由设备的名字(device->name)和对应的驱动的名字(device->driver->name)组成,他们的驱动程序会调用list_add 函数将他们加入到各自的链表中。
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */
<2> snd_card_create函数如下:
int snd_card_create(int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
... ...
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
if (!card)
return -ENOMEM;
if (xid)
strlcpy(card->id, xid, sizeof(card->id));
err = 0;
... ...
snd_cards_lock |= 1 << idx; /* lock it */
if (idx >= snd_ecards_limit)
snd_ecards_limit = idx + 1; /* increase the limit */
mutex_unlock(&snd_card_mutex);
card->number = idx;
card->module = module;
INIT_LIST_HEAD(&card->devices);
init_rwsem(&card->controls_rwsem);
rwlock_init(&card->ctl_files_rwlock);
mutex_init(&card->user_ctl_lock);
INIT_LIST_HEAD(&card->controls);
INIT_LIST_HEAD(&card->ctl_files);
spin_lock_init(&card->files_lock);
INIT_LIST_HEAD(&card->files_list);
init_waitqueue_head(&card->shutdown_sleep);
atomic_set(&card->refcount, 0);
#ifdef CONFIG_PM
mutex_init(&card->power_lock);
init_waitqueue_head(&card->power_sleep);
#endif
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
err = snd_ctl_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to register control minors\n");
goto __error;
}
err = snd_info_card_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to create card info\n");
goto __error_ctl;
}
if (extra_size > 0)
card->private_data = (char *)card + sizeof(struct snd_card);
*card_ret = card;
return 0;
__error_ctl:
snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
__error:
kfree(card);
return err;
}
snd_card_create函数主要内容为申请内存,确定snd_card编号及初始化,调用snd_ctl_create建立逻辑设备,调用snd_info_card_create建立proc文件中的info节点:通常就是/proc/asound/card0
<4> soc_probe_dai_link函数如下:
static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;
int ret;
dev_dbg(card->dev, "probe %s dai link %d late %d\n",
card->name, num, order);
/* config components */
codec_dai->codec = codec;
cpu_dai->platform = platform;
codec_dai->card = card;
cpu_dai->card = card;
/* set default power off timeout */
rtd->pmdown_time = pmdown_time;
/* probe the cpu_dai */
if (!cpu_dai->probed &&
cpu_dai->driver->probe_order == order) {
if (!try_module_get(cpu_dai->dev->driver->owner))
return -ENODEV;
if (cpu_dai->driver->probe) {
ret = cpu_dai->driver->probe(cpu_dai);
if (ret < 0) {
pr_err("asoc: failed to probe CPU DAI %s: %d\n",
cpu_dai->name, ret);
module_put(cpu_dai->dev->driver->owner);
return ret;
}
}
cpu_dai->probed = 1;
/* mark cpu_dai as probed and add to card dai list */
list_add(&cpu_dai->card_list, &card->dai_dev_list);
}
/* probe the CODEC */
if (!codec->probed &&
codec->driver->probe_order == order) {
ret = soc_probe_codec(card, codec);
if (ret < 0)
return ret;
}
/* probe the platform */
if (!platform->probed &&
platform->driver->probe_order == order) {
ret = soc_probe_platform(card, platform);
if (ret < 0)
return ret;
}
/* probe the CODEC DAI */
if (!codec_dai->probed && codec_dai->driver->probe_order == order) {
if (codec_dai->driver->probe) {
ret = codec_dai->driver->probe(codec_dai);
if (ret < 0) {
pr_err("asoc: failed to probe CODEC DAI %s: %d\n",
codec_dai->name, ret);
return ret;
}
}
/* mark codec_dai as probed and add to card dai list */
codec_dai->probed = 1;
list_add(&codec_dai->card_list, &card->dai_dev_list);
}
/* complete DAI probe during last probe */
if (order != SND_SOC_COMP_ORDER_LAST)
return 0;
ret = soc_post_component_init(card, codec, num, 0);
if (ret)
return ret;
ret = device_create_file(rtd->dev, &dev_attr_pmdown_time);
if (ret < 0)
pr_warn("asoc: failed to add pmdown_time sysfs:%d\n", ret);
/* create the pcm */
ret = soc_new_pcm(rtd, num);//-------------------------<5>
if (ret < 0) {
pr_err("asoc: can't create pcm %s :%d\n",
dai_link->stream_name, ret);
return ret;
}
/* add platform data for AC97 devices */
if (rtd->codec_dai->driver->ac97_control)
snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
return 0;
}
soc_probe_dai_link函数会调用各个子结构的Probe函数,包括:
cpu_dai->driver->probe
soc_probe_codec —> driver->probe(codec)
soc_probe_platform —>driver->probe(platform)
codec_dai->driver->probe
最后调用soc_new_pcm函数
<5>snd_card_register函数如下:
int snd_card_register(struct snd_card *card)
{
int err;
if (snd_BUG_ON(!card))
return -EINVAL;
if (!card->card_dev) {
card->card_dev = device_create(sound_class, card->dev,
MKDEV(0, 0), card,
"card%i", card->number);
if (IS_ERR(card->card_dev))
card->card_dev = NULL;
}
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 0;
}
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 (card->card_dev) {
err = device_create_file(card->card_dev, &card_id_attrs);
if (err < 0)
return err;
err = device_create_file(card->card_dev, &card_number_attrs);
if (err < 0)
return err;
}
return 0;
}
/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */
注册声卡,注册完成后会产生card*,如下:
/sys/class/sound # ls
card0 pcmC0D0c pcmC0D1c pcmC0D2c pcmC0D3c timer
controlC0 pcmC0D0p pcmC0D1p pcmC0D2p pcmC0D3p
那么我们对该函数做一些总结:
1、把相应的codec,dai和platform实例赋值到card->rtd[]中,再添加到card->rtd_list链表中,rtd也就是一snd_soc_pcm_runtime结构体。
2、添加card->dai_link+i->list到card->dai_link_list
3、遍历这个全局codec_list结构体,为codec申请空间
4、标准的alsa函数创建声卡实例
5、如果有的话,调用card的probe:card->probe(card)
6、扫描card->rtd_list链表,调用cpu_dai、codec_dais、platform的component->probe。
7、扫描card->rtd_list链表 ,调用各个子结构(cpu_dai、codec_dais、platform)的probe函数,还通过soc_new_pcm函数创建了pcm逻辑设备。
8、对dapm和dai支持的格式做出一些初始化合设置工作后,调用了 card->late_probe(card)进行一些最后的初始化合设置工作
9、调用标准alsa驱动的声卡注册函数对声卡进行注册
至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platfrom驱动和Codec驱动的初始化工作。/* by面朝大海0902 https://blog.csdn.net/lihuan680680/article/details/122406208 */
asoc 框架中 codec driver分析可以参考:ALSA驱动asoc框架之Codec
asoc 框架中 platform driver分析可以参考:ALSA驱动asoc框架之Platform