ALSA System on Chip(ASOC)
2015-07-26 10:27
639 查看
此文档仅作为开发随笔记录文档,可作为正式文档做材料参考,但不做正式文档。
Written bywolfgang huang(stillinux@gmail.com)
此类文档仅记录Android4.1.2+Kernel2.6.37+OMAP3730平台ALSA开发及内核要点,备注好资料应用,以供后续开发人员快速入手,也可作为科普类资料,供其他相关人员学习。
ALSA System on Chip(ASOC)
本系列文档的目标是使读者从安卓HAL一直走到硬件层的处理流程,使Kernel的ALSA构架内部细节透露出来。但是由于鄙人水平有限,难免有错误理解,如有错误,请联系,立即改正。
下面我们细述linux音频子系统中的ASOC,对于整体的框架作图如下。
我们接下来分块的对ASOC的子模块在内核中如何启动,注册等运作流程。
在linux设备模型下,device与相应的driver相匹配,从而支持linux透过drvier对于其绑定的device做到控制管理。故使对应的设备要在linux下进行良好的工作,则必须正确加载device和driver。
Platform模块:
对于platform的设备启动流程如下:
omap_init_audio->platform_device_register(&omap_pcm);
将omap_pcm的设备注册到platform总线的设备链表上。
对于platform的驱动,则有:
snd_omap_pcm_init->platform_driver_register(&omap_pcm_driver);
将omap_pcm_driver注册到platform总线的驱动链表上。
由linux驱动模型,当注册driver或device时,都会到其隶属总线的对应链表上去查找匹配的驱动或设备(linux更相信设备)。这样匹配后调用对应的driver->probe,则调用到我们的omap_pcm_probe,其调用snd_soc_register_platform,注册omap_soc_platform到ASOC的platform_list中,且由于device中对于其id设置为-1,则platform的名称设置为dev->driver->name。
Note:linux驱动模型下,platform总线绑定的依据有三种,of设备树的dtb方式,还有id_table的方式,最后就是默认的name比对方式。对于其他总线,诸如SPI用modalias比对等方式,此处不做过多细述。
Cpu_dai模块:
对于cpu_dai启动流程如下:
omap_init_audio->platform_device_register(&omap_mcbsp1);
将omap_mcbsp的设备注册到platform总线的设备链表上。
对于cpu_dai的驱动,则有:
snd_omap_mcbsp_init->platform_driver_register(&asoc_mcbsp_driver);
将asoc_mcbsp_driver注册到platform总线的驱动链表上。
由上面分析,我们知道驱动模型绑定后,根据名称都为omap-mcbsp-dai匹配后,调用到其驱动的probe接口,即asoc_mcbsp_probe。其调用snd_soc_register_dai,将omap_mcbsp_dai注册到ASOC的dai_list链表上。
Codec模块和Codec_dai模块:
在我们使用的OMAP平台上,codec使用的集成的TPS65951,其设备加载过程较为复杂,下面进行细述。
其设备的加载流程:
devkit8000_init->devkit8000_i2c_init->omap_register_i2c_bus(..devkit8000_i2c1_boardinfo)。
tps65951,使用的是twl4030兼容的驱动。
twl_init->i2c_add_driver(&twl_driver)<throughid_table(与上面的boardinfo匹配)>
->twl_probe->add_children->add_child<has_codec成立的条件下>
->platform_device_add()<name=”twl4030-audio”>。
将tps65951中的音频声卡codec设备加入到platform总线的设备链表。
但是此处并没有完成codec资源的解析,其又调用一次匹配后,进行资源的分块解析。
该过程为:
twl4030_codec_init->platform_driver_register(&twl4030_codec_driver);
与上面注册的codec设备匹配后,运行其驱动的probe,即twl4030_codec_probe,
其完成对于codec时钟的设置后,调用mfd_add_devices将获得资源重组到新的platform_device中,并将其加入到platform总线上,其名称为twl4030_codec。此时才是最终的tps65951设备声卡加入的真正设备资源。
对于其真正的设备资源,对应的驱动有:
twl4030_modinit-> platform_driver_register(&twl4030_codec_driver);<name=”twl4030_codec”>。
则匹配后调用其驱动的probe,即twl4030_codec_probe(与上面的有区别)。其调用snd_soc_register_codec,注册了对应的
则根据驱动模型绑定后,我们知道运行其驱动的probe,即twl4030_codec_probe,其调用snd_soc_register_codec,注册soc_codec_dev_twl4030到ASOC的codec_list,并将对应的twl4030_dai注册到ASOC的dai_list中,twl4030_dai有hifi和voice两种模式,即codec支持这两种音频数据流。其最终根据ASOC的配置环境snd_soc_dai_link,选用对应的配置模式。
ASOC(Machine):
最后对应ASOC整体的框架注册过程:
对应其设备注册流程如下:
omap3beagle_soc_init-> platform_device_add(omap3beagle_snd_device);<name=”soc-audio”>
对应的驱动注册则有:
snd_soc_init-> platform_driver_register(&soc_driver);根据驱动模型绑定后,则会调用到soc_probe函数。其获取ASOC定义的snd_soc_card配置数据snd_soc_omap3beagle。
snd_soc_card中的成员num_link定义有几个snd_soc_pcm_runtime,对于该处为1,成员dai_link,则定义了该snd_card对应的配置环境数据。
其结构定义如下:
根据注释,在观察我们的具体配置:
soc_probe获取对应的snd_soc_card配置数据后,调用snd_soc_register_card。
根据配置数据的num_links,分配对应的snd_soc_pcm_runtime,并绑定各自snd_soc_pcm_runtime与dai_link,接着将加入的snd_soc_card加入到card_list,ASOC最多支持八个snd_soc_card。最后调用snd_soc_instantiate_cards。该函数在其他的注册接口也有调用,但是其只有在dai_link中的组件全部都组建完成才能继续往下走。故在此处综述。
snd_soc_instantiate_cards:遍历card_list,依次调用snd_soc_instantiate_card。
我们把该函数拆分为四段函数进行分析,分别为soc_bind_dai_link,snd_card_create,soc_probe_dai_link及snd_card_register。AC97我们在此略过,一般嵌入式设备都使用I2S。
其完成的工作单调而统一,就是依次比对dai_link指定的四大模块,缺一不可,且对应于多个num_links的还需要一一绑定。找到对应的dai_link指定的模块后,就将runtime与其对应模块进行绑定。
其分配对应的snd_card,并分配对应的id,且ASOC最大支持八个snd_card。之后调用snd_ctl_create->snd_device_new(card, SNDRV_DEV_CONTROL,…);注册该snd_card绑定的mixer控制设备节点,controlCx。且其目前只是创建,在后面snd_card_register的时候注册到用户空间设备节点。该函数调用snd_info_card_create,创建/proc/asound/cardX,向用户透露信息。
该函数对四个模块依次调用probe接口,这些probe函数不进行细述,主要是对于声卡的初始化,并把声卡内部的控件加入到系统snd_kcontrol中,其他模块未做主要事宜或者没有probe接口。接着初始化close_delayed_work工作队列,其在关闭接口时,用于延时调用,防止pop刺耳噪音。接下来对于DAPM的初始化和同步。开始DAPM将加入的codec->dapm_widget进行分类型初始化,并绑定相关的电源操作接口,最后以snd_kcontrol提供用户控制。调用snd_soc_dapm_sync,对加入的控件分类加到上电和下电的链表中,并根据情况对不同的链表上下电处理。之后就是相关sysfs属性文件的注册,注册到sysfs文件系统中。最后调用soc_new_pcm,根据对应配置的codec_dai的packback和capture创建对应的pcm设备空间,并完成其初始化,绑定对应的操作接口,但是其也要在最后snd_card_register的时候才能注册到sysfs和devtmpfs中。
snd_card_register->snd_device_register_all:完成controlCx,pcmCxDx(p/c)的到sysfs和devtmpfs文件系统的注册。<timer在alsa_timer_init -> snd_register_device 完成,不过我们不关注>
通过上述分析,我们知道了音频子系统,用户操作的接口在内核中是经过了怎样的流程才注册到用户空间。对于具体操作这些设备节点,又会是怎样的流程我们在后续的文档进行分析。对于访问具体的控件,调整路径,电源控制,都留在后续的文档一一分析。
下面我们以图表的形式分析这些设备节点的注册过程。
Written bywolfgang huang(stillinux@gmail.com)
此类文档仅记录Android4.1.2+Kernel2.6.37+OMAP3730平台ALSA开发及内核要点,备注好资料应用,以供后续开发人员快速入手,也可作为科普类资料,供其他相关人员学习。
ALSA System on Chip(ASOC)
本系列文档的目标是使读者从安卓HAL一直走到硬件层的处理流程,使Kernel的ALSA构架内部细节透露出来。但是由于鄙人水平有限,难免有错误理解,如有错误,请联系,立即改正。
下面我们细述linux音频子系统中的ASOC,对于整体的框架作图如下。
我们接下来分块的对ASOC的子模块在内核中如何启动,注册等运作流程。
在linux设备模型下,device与相应的driver相匹配,从而支持linux透过drvier对于其绑定的device做到控制管理。故使对应的设备要在linux下进行良好的工作,则必须正确加载device和driver。
Platform模块:
对于platform的设备启动流程如下:
omap_init_audio->platform_device_register(&omap_pcm);
将omap_pcm的设备注册到platform总线的设备链表上。
对于platform的驱动,则有:
snd_omap_pcm_init->platform_driver_register(&omap_pcm_driver);
将omap_pcm_driver注册到platform总线的驱动链表上。
由linux驱动模型,当注册driver或device时,都会到其隶属总线的对应链表上去查找匹配的驱动或设备(linux更相信设备)。这样匹配后调用对应的driver->probe,则调用到我们的omap_pcm_probe,其调用snd_soc_register_platform,注册omap_soc_platform到ASOC的platform_list中,且由于device中对于其id设置为-1,则platform的名称设置为dev->driver->name。
Note:linux驱动模型下,platform总线绑定的依据有三种,of设备树的dtb方式,还有id_table的方式,最后就是默认的name比对方式。对于其他总线,诸如SPI用modalias比对等方式,此处不做过多细述。
Cpu_dai模块:
对于cpu_dai启动流程如下:
omap_init_audio->platform_device_register(&omap_mcbsp1);
将omap_mcbsp的设备注册到platform总线的设备链表上。
对于cpu_dai的驱动,则有:
snd_omap_mcbsp_init->platform_driver_register(&asoc_mcbsp_driver);
将asoc_mcbsp_driver注册到platform总线的驱动链表上。
由上面分析,我们知道驱动模型绑定后,根据名称都为omap-mcbsp-dai匹配后,调用到其驱动的probe接口,即asoc_mcbsp_probe。其调用snd_soc_register_dai,将omap_mcbsp_dai注册到ASOC的dai_list链表上。
Codec模块和Codec_dai模块:
在我们使用的OMAP平台上,codec使用的集成的TPS65951,其设备加载过程较为复杂,下面进行细述。
其设备的加载流程:
devkit8000_init->devkit8000_i2c_init->omap_register_i2c_bus(..devkit8000_i2c1_boardinfo)。
tps65951,使用的是twl4030兼容的驱动。
twl_init->i2c_add_driver(&twl_driver)<throughid_table(与上面的boardinfo匹配)>
->twl_probe->add_children->add_child<has_codec成立的条件下>
->platform_device_add()<name=”twl4030-audio”>。
将tps65951中的音频声卡codec设备加入到platform总线的设备链表。
但是此处并没有完成codec资源的解析,其又调用一次匹配后,进行资源的分块解析。
该过程为:
twl4030_codec_init->platform_driver_register(&twl4030_codec_driver);
与上面注册的codec设备匹配后,运行其驱动的probe,即twl4030_codec_probe,
其完成对于codec时钟的设置后,调用mfd_add_devices将获得资源重组到新的platform_device中,并将其加入到platform总线上,其名称为twl4030_codec。此时才是最终的tps65951设备声卡加入的真正设备资源。
对于其真正的设备资源,对应的驱动有:
twl4030_modinit-> platform_driver_register(&twl4030_codec_driver);<name=”twl4030_codec”>。
则匹配后调用其驱动的probe,即twl4030_codec_probe(与上面的有区别)。其调用snd_soc_register_codec,注册了对应的
则根据驱动模型绑定后,我们知道运行其驱动的probe,即twl4030_codec_probe,其调用snd_soc_register_codec,注册soc_codec_dev_twl4030到ASOC的codec_list,并将对应的twl4030_dai注册到ASOC的dai_list中,twl4030_dai有hifi和voice两种模式,即codec支持这两种音频数据流。其最终根据ASOC的配置环境snd_soc_dai_link,选用对应的配置模式。
ASOC(Machine):
最后对应ASOC整体的框架注册过程:
对应其设备注册流程如下:
omap3beagle_soc_init-> platform_device_add(omap3beagle_snd_device);<name=”soc-audio”>
对应的驱动注册则有:
snd_soc_init-> platform_driver_register(&soc_driver);根据驱动模型绑定后,则会调用到soc_probe函数。其获取ASOC定义的snd_soc_card配置数据snd_soc_omap3beagle。
snd_soc_card中的成员num_link定义有几个snd_soc_pcm_runtime,对于该处为1,成员dai_link,则定义了该snd_card对应的配置环境数据。
其结构定义如下:
struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ const char *stream_name; /* Stream name */ const char *codec_name; /* for multi-codec */ const char *platform_name; /* for multi-platform */ const char *cpu_dai_name; const char *codec_dai_name; /* Keep DAI active over suspend */ unsigned int ignore_suspend:1; /* Symmetry requirements */ unsigned int symmetric_rates:1; /* codec/machine specific init - e.g. add machine controls */ int (*init)(struct snd_soc_pcm_runtime *rtd); /* machine stream operations */ struct snd_soc_ops *ops; };
根据注释,在观察我们的具体配置:
static struct snd_soc_dai_link omap3beagle_dai = { .name = "TWL4030", .stream_name = "TWL4030", //runtime名称 .cpu_dai_name = "omap-mcbsp-dai.1", //cpu_dai<I2S> .platform_name = "omap-pcm-audio", //platform .codec_dai_name = "twl4030-hifi", //codec_dai,选择高保真音频数据流格式 .codec_name = "twl4030-codec", //tps65951 .ops = &omap3beagle_ops, };
soc_probe获取对应的snd_soc_card配置数据后,调用snd_soc_register_card。
static int snd_soc_register_card(struct snd_soc_card *card) { int i; if (!card->name || !card->dev) return -EINVAL; card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links, GFP_KERNEL); if (card->rtd == NULL) return -ENOMEM; for (i = 0; i < card->num_links; i++) card->rtd[i].dai_link = &card->dai_link[i]; INIT_LIST_HEAD(&card->list); card->instantiated = 0; mutex_init(&card->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; }
根据配置数据的num_links,分配对应的snd_soc_pcm_runtime,并绑定各自snd_soc_pcm_runtime与dai_link,接着将加入的snd_soc_card加入到card_list,ASOC最多支持八个snd_soc_card。最后调用snd_soc_instantiate_cards。该函数在其他的注册接口也有调用,但是其只有在dai_link中的组件全部都组建完成才能继续往下走。故在此处综述。
snd_soc_instantiate_cards:遍历card_list,依次调用snd_soc_instantiate_card。
static void snd_soc_instantiate_card(struct snd_soc_card *card) { struct platform_device *pdev = to_platform_device(card->dev); int ret, i; mutex_lock(&card->mutex); if (card->instantiated) { mutex_unlock(&card->mutex); return; } /* bind DAIs */ for (i = 0; i < card->num_links; i++) soc_bind_dai_link(card, i); /* bind completed ? */ if (card->num_rtd != card->num_links) { mutex_unlock(&card->mutex); return; } /* * #define SNDRV_DEFAULT_IDX1 (-1) * #define SNDRV_DEFAULT_STR1 NULL */ /* card bind complete so register a sound card */ ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, card->owner, 0, &card->snd_card); if (ret < 0) { printk(KERN_ERR "asoc: can't create sound card for card %s\n", card->name); mutex_unlock(&card->mutex); return; } card->snd_card->dev = card->dev; #ifdef CONFIG_PM /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); #endif /* initialise the sound card only once */ if (card->probe) { ret = card->probe(pdev); if (ret < 0) goto card_probe_error; } for (i = 0; c887 i < card->num_links; i++) { ret = soc_probe_dai_link(card, i); if (ret < 0) { pr_err("asoc: failed to instantiate card %s: %d\n", card->name, ret); goto probe_dai_err; } } 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->name); ret = snd_card_register(card->snd_card); if (ret < 0) { printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name); goto probe_dai_err; } #ifdef CONFIG_SND_SOC_AC97_BUS /* register any AC97 codecs */ for (i = 0; i < card->num_rtd; i++) { ret = soc_register_ac97_dai_link(&card->rtd[i]); if (ret < 0) { printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name); while (--i >= 0) soc_unregister_ac97_dai_link(&card->rtd[i]); goto probe_dai_err; } } #endif card->instantiated = 1; mutex_unlock(&card->mutex); return; probe_dai_err: for (i = 0; i < card->num_links; i++) soc_remove_dai_link(card, i); card_probe_error: if (card->remove) card->remove(pdev); snd_card_free(card->snd_card); mutex_unlock(&card->mutex); }
我们把该函数拆分为四段函数进行分析,分别为soc_bind_dai_link,snd_card_create,soc_probe_dai_link及snd_card_register。AC97我们在此略过,一般嵌入式设备都使用I2S。
static int soc_bind_dai_link(struct snd_soc_card *card, int num) { 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; struct snd_soc_platform *platform; struct snd_soc_dai *codec_dai, *cpu_dai; if (rtd->complete) return 1; dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num); /* do we already have the CPU DAI for this link ? */ if (rtd->cpu_dai) { goto find_codec; } /* no, then find CPU DAI from registered DAIs*/ list_for_each_entry(cpu_dai, &dai_list, list) { //依次遍历,查找是否有dai_link按名称指定的cpu_dai if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) { if (!try_module_get(cpu_dai->dev->driver->owner)) return -ENODEV; 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) { //依次遍历,查找是否有dai_link按名称指定的codec if (!strcmp(codec->name, dai_link->codec_name)) { rtd->codec = codec; if (!try_module_get(codec->dev->driver->owner)) return -ENODEV; /* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/ list_for_each_entry(codec_dai, &dai_list, list) { //依次遍历,查找是否有dai_link按名称指定的codec_dai 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 already have the CODEC DAI for this link ? */ if (rtd->platform) { goto out; } /* no, then find CPU DAI from registered DAIs*/ list_for_each_entry(platform, &platform_list, list) { //依次遍历,查找是否有dai_link按名称指定的platform if (!strcmp(platform->name, dai_link->platform_name)) { if (!try_module_get(platform->dev->driver->owner)) return -ENODEV; rtd->platform = platform; goto out; } } dev_dbg(card->dev, "platform %s not registered\n", dai_link->platform_name); return 0; out: /* mark rtd as complete if we found all 4 of our client devices */ if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) { rtd->complete = 1; card->num_rtd++; } return 1; }
其完成的工作单调而统一,就是依次比对dai_link指定的四大模块,缺一不可,且对应于多个num_links的还需要一一绑定。找到对应的dai_link指定的模块后,就将runtime与其对应模块进行绑定。
int snd_card_create(int idx, const char *xid, struct module *module, int extra_size, struct snd_card **card_ret) { struct snd_card *card; int err, idx2; 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); …… /* 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; } …… return err; }
其分配对应的snd_card,并分配对应的id,且ASOC最大支持八个snd_card。之后调用snd_ctl_create->snd_device_new(card, SNDRV_DEV_CONTROL,…);注册该snd_card绑定的mixer控制设备节点,controlCx。且其目前只是创建,在后面snd_card_register的时候注册到用户空间设备节点。该函数调用snd_info_card_create,创建/proc/asound/cardX,向用户透露信息。
static int soc_probe_dai_link(struct snd_soc_card *card, int num) { 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\n", card->name, num); /* config components */ codec_dai->codec = codec; codec->card = card; cpu_dai->platform = platform; rtd->card = card; rtd->dev.parent = card->dev; 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) { if (cpu_dai->driver->probe) { ret = cpu_dai->driver->probe(cpu_dai); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n", cpu_dai->name); return ret; } } cpu_dai->probed = 1; /* mark cpu_dai as probed and add to card cpu_dai list */ list_add(&cpu_dai->card_list, &card->dai_dev_list); } /* probe the CODEC */ if (!codec->probed) { if (codec->driver->probe) { ret = codec->driver->probe(codec); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe CODEC %s\n", codec->name); return ret; } } soc_init_codec_debugfs(codec); /* mark codec as probed and add to card codec list */ codec->probed = 1; list_add(&codec->card_list, &card->codec_dev_list); } /* probe the platform */ if (!platform->probed) { if (platform->driver->probe) { ret = platform->driver->probe(platform); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe platform %s\n", platform->name); return ret; } } /* mark platform as probed and add to card platform list */ platform->probed = 1; list_add(&platform->card_list, &card->platform_dev_list); } /* probe the CODEC DAI */ if (!codec_dai->probed) { if (codec_dai->driver->probe) { ret = codec_dai->driver->probe(codec_dai); if (ret < 0) { printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n", codec_dai->name); return ret; } } /* mark cpu_dai as probed and add to card cpu_dai list */ codec_dai->probed = 1; list_add(&codec_dai->card_list, &card->dai_dev_list); } /* DAPM dai link stream work */ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); /* now that all clients have probed, initialise the DAI link */ if (dai_link->init) { ret = dai_link->init(rtd); if (ret < 0) { printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name); return ret; } } /* Make sure all DAPM widgets are instantiated */ snd_soc_dapm_new_widgets(codec); snd_soc_dapm_sync(codec); /* register the rtd device */ rtd->dev.release = rtd_release; rtd->dev.init_name = dai_link->name; ret = device_register(&rtd->dev); /* TWL4030 in the /sys/devices/platform/soc-audio */ if (ret < 0) { printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret); return ret; } rtd->dev_registered = 1; ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time); if (ret < 0) printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n"); /* add DAPM sysfs entries for this codec */ ret = snd_soc_dapm_sys_add(&rtd->dev); if (ret < 0) printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n"); /* add codec sysfs entries */ ret = device_create_file(&rtd->dev, &dev_attr_codec_reg); if (ret < 0) printk(KERN_WARNING "asoc: failed to add codec sysfs files\n"); /* create the pcm */ ret = soc_new_pcm(rtd, num); if (ret < 0) { printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name); 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; }
该函数对四个模块依次调用probe接口,这些probe函数不进行细述,主要是对于声卡的初始化,并把声卡内部的控件加入到系统snd_kcontrol中,其他模块未做主要事宜或者没有probe接口。接着初始化close_delayed_work工作队列,其在关闭接口时,用于延时调用,防止pop刺耳噪音。接下来对于DAPM的初始化和同步。开始DAPM将加入的codec->dapm_widget进行分类型初始化,并绑定相关的电源操作接口,最后以snd_kcontrol提供用户控制。调用snd_soc_dapm_sync,对加入的控件分类加到上电和下电的链表中,并根据情况对不同的链表上下电处理。之后就是相关sysfs属性文件的注册,注册到sysfs文件系统中。最后调用soc_new_pcm,根据对应配置的codec_dai的packback和capture创建对应的pcm设备空间,并完成其初始化,绑定对应的操作接口,但是其也要在最后snd_card_register的时候才能注册到sysfs和devtmpfs中。
snd_card_register->snd_device_register_all:完成controlCx,pcmCxDx(p/c)的到sysfs和devtmpfs文件系统的注册。<timer在alsa_timer_init -> snd_register_device 完成,不过我们不关注>
通过上述分析,我们知道了音频子系统,用户操作的接口在内核中是经过了怎样的流程才注册到用户空间。对于具体操作这些设备节点,又会是怎样的流程我们在后续的文档进行分析。对于访问具体的控件,调整路径,电源控制,都留在后续的文档一一分析。
下面我们以图表的形式分析这些设备节点的注册过程。
相关文章推荐
- UVA - 129 Krypton Factor
- 让老版的浏览器支持html5
- CodeForces-218C Hamburgers
- 【持久层】数据库事务基础——事务的隔离级别
- java的jdbc简单封装
- HTML<a>标签的target属性
- Functional MRI (second edition) -- 7. BOLD fMRI: Origins and Properties
- BZOJ 2190: [SDOI2008]仪仗队( 欧拉函数 )
- 主程序与子程序参数传递
- 香川中文离线地图App上线
- Qt学习心得之网络编程简单的局域网聊天服务端建立
- mybatis实战教程(mybatis in action),mybatis入门到精通
- HDU4283 You Are the One 动态规划
- 虚拟机上的Ubuntu开机显示“无法应用原保存的显示器配置”
- J2EE基础EJB
- 二叉树左右旋性质不变
- Scala详解---------类
- Object-c 设置器与访问器
- cast from 'void *' to 'int' loses precision
- HDU 5312(数学推导+技巧)