Linux ALSA声卡驱动之一:ASoC架构中的Machine

前面一节的内容我们提到,asoc被分为machine、platform和codec三大部分,其中的machine驱动负责platform和codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个放大器);单独的platform和codec驱动是不能工作的,它必须由machine驱动把它们结合在一起才能完成整个设备的音频处理工作。
asoc的一切都从machine驱动开始,包括声卡的注册,绑定platform和codec驱动等等,下面就让我们从machine驱动开始讨论吧。
/********************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/********************************************************************************************/
1. 注册platform device
asoc把声卡注册为platform device,我们以装配有wm8994的一款samsung的开发板smdk为例子做说明,wm8994是一颗wolfson生产的多功能codec芯片。
代码的位于:/sound/soc/samsung/smdk_wm8994.c,我们关注模块的初始化函数:
[cpp]view plaincopy
staticint__initsmdk_audio_init(void)
{
intret;
smdk_snd_device=platform_device_alloc(soc-audio,-1);
if(!smdk_snd_device)
return-enomem;
platform_set_drvdata(smdk_snd_device,&smdk);
ret=platform_device_add(smdk_snd_device);
if(ret)
platform_device_put(smdk_snd_device);
returnret;
}
由此可见,模块初始化时,注册了一个名为soc-audio的platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:
[cpp]view plaincopy
staticstructsnd_soc_dai_linksmdk_dai[]={
{/*primarydaii/f*/
.name=wm8994aif1,
.stream_name=pri_dai,
.cpu_dai_name=samsung-i2s.0,
.codec_dai_name=wm8994-aif1,
.platform_name=samsung-audio,
.codec_name=wm8994-codec,
.init=smdk_wm8994_init_paiftx,
.ops=&smdk_ops,
},{/*sec_fifoplaybacki/f*/
.name=sec_fifotx,
.stream_name=sec_dai,
.cpu_dai_name=samsung-i2s.4,
.codec_dai_name=wm8994-aif1,
.platform_name=samsung-audio,
.codec_name=wm8994-codec,
.ops=&smdk_ops,
},
};
staticstructsnd_soc_cardsmdk={
.name=smdk-i2s,
.owner=this_module,
.dai_link=smdk_dai,
.num_links=array_size(smdk_dai),
};
通过snd_soc_card结构,又引出了machine驱动的另外两个个数据结构:
snd_soc_dai_link(实例:smdk_dai[] )
snd_soc_ops(实例:smdk_ops )
其中,snd_soc_dai_link中,指定了platform、codec、codec_dai、cpu_dai的名字,稍后machine驱动将会利用这些名字去匹配已经在系统中注册的platform,codec,dai,这些注册的部件都是在另外相应的platform驱动和codec驱动的代码文件中定义的,这样看来,machine驱动的设备初始化代码无非就是选择合适platform和codec以及dai,用他们填充以上几个数据结构,然后注册platform设备即可。当然还要实现连接platform和codec的dai_link对应的ops实现,本例就是smdk_ops,它只实现了hw_params函数:smdk_hw_params。
2. 注册platform driver
按照linux的设备模型,有platform_device,就一定会有platform_driver。asoc的platform_driver在以下文件中定义:sound/soc/soc-core.c。
还是先从模块的入口看起:
[cpp]view plaincopy
staticint__initsnd_soc_init(void)
{
......
returnplatform_driver_register(&soc_driver);
}
soc_driver的定义如下:
[cpp]view plaincopy
/*asocplatformdriver*/
staticstructplatform_driversoc_driver={
.driver={
.name=soc-audio,
.owner=this_module,
.pm=&soc_pm_ops,
},
.probe=soc_probe,
.remove=soc_remove,
};
我们看到platform_driver的name字段为soc-audio,正好与platform_device中的名字相同,按照linux的设备模型,platform总线会匹配这两个名字相同的device和driver,同时会触发soc_probe的调用,它正是整个asoc驱动初始化的入口。
3. 初始化入口soc_probe()
soc_probe函数本身很简单,它先从platform_device参数中取出snd_soc_card,然后调用snd_soc_register_card,通过snd_soc_register_card,为snd_soc_pcm_runtime数组申请内存,每一个dai_link对应snd_soc_pcm_runtime数组的一个单元,然后把snd_soc_card中的dai_link配置复制到相应的snd_soc_pcm_runtime中,最后,大部分的工作都在snd_soc_instantiate_card中实现,下面就看看snd_soc_instantiate_card做了些什么:
该函数首先利用card->instantiated来判断该卡是否已经实例化,如果已经实例化则直接返回,否则遍历每一对dai_link,进行codec、platform、dai的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。
[cpp]view plaincopy
/*binddais*/
for(i=0;inum_links;i++)
soc_bind_dai_link(card,i);
asoc定义了三个全局的链表头变量: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)。经过这个过程后,snd_soc_pcm_runtime:(card->rtd)中保存了本machine中使用的codec,dai和platform驱动的信息。
snd_soc_instantiate_card接着初始化codec的寄存器缓存,然后调用标准的alsa函数创建声卡实例:
[cpp]view plaincopy
/*cardbindcompletesoregisterasoundcard*/
ret=snd_card_create(sndrv_default_idx1,sndrv_default_str1,
card->owner,0,&card->snd_card);
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);
然后,依次调用各个子结构的probe函数:
[cpp]view plaincopy
/*initialisethesoundcardonlyonce*/
if(card->probe){
ret=card->probe(card);
if(ret<0)
gotocard_probe_error;
}
/*earlydailinkprobe*/
for(order=snd_soc_comp_order_first;order<=snd_soc_comp_order_last;
order++){
for(i=0;inum_links;i++){
ret=soc_probe_dai_link(card,i,order);
if(retname,ret);
gotoprobe_dai_err;
}
}
}
for(i=0;inum_aux_devs;i++){
ret=soc_probe_aux_dev(card,i);
if(retname,ret);
gotoprobe_aux_dev_err;
}
}
在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:
[cpp]view plaincopy
staticintsoc_probe_dai_link(structsnd_soc_card*card,intnum,intorder)
{
......
/*setdefaultpowerofftimeout*/
rtd->pmdown_time=pmdown_time;
/*probethecpu_dai*/
if(!cpu_dai->probed&&
cpu_dai->driver->probe_order==order){
if(cpu_dai->driver->probe){
ret=cpu_dai->driver->probe(cpu_dai);
}
cpu_dai->probed=1;
/*markcpu_daiasprobedandaddtocarddailist*/
list_add(&cpu_dai->card_list,&card->dai_dev_list);
}
/*probethecodec*/
if(!codec->probed&&
codec->driver->probe_order==order){
ret=soc_probe_codec(card,codec);
}
/*probetheplatform*/
if(!platform->probed&&
platform->driver->probe_order==order){
ret=soc_probe_platform(card,platform);
}
/*probethecodecdai*/
if(!codec_dai->probed&&codec_dai->driver->probe_order==order){
if(codec_dai->driver->probe){
ret=codec_dai->driver->probe(codec_dai);
}
/*markcodec_daiasprobedandaddtocarddailist*/
codec_dai->probed=1;
list_add(&codec_dai->card_list,&card->dai_dev_list);
}
/*completedaiprobeduringlastprobe*/
if(order!=snd_soc_comp_order_last)
return0;
ret=soc_post_component_init(card,codec,num,0);
if(ret)
returnret;
......
/*createthepcm*/
ret=soc_new_pcm(rtd,num);
........
return0;
}
该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:
[cpp]view plaincopy
/*createanewpcm*/
intsoc_new_pcm(structsnd_soc_pcm_runtime*rtd,intnum)
{
......
structsnd_pcm_ops*soc_pcm_ops=&rtd->ops;
soc_pcm_ops->open=soc_pcm_open;
soc_pcm_ops->close=soc_pcm_close;
soc_pcm_ops->hw_params=soc_pcm_hw_params;
soc_pcm_ops->hw_free=soc_pcm_hw_free;
soc_pcm_ops->prepare=soc_pcm_prepare;
soc_pcm_ops->trigger=soc_pcm_trigger;
soc_pcm_ops->pointer=soc_pcm_pointer;
ret=snd_pcm_new(rtd->card->snd_card,new_name,
num,playback,capture,&pcm);
/*dapmdailinkstreamwork*/
init_delayed_work(&rtd->delayed_work,close_delayed_work);
rtd->pcm=pcm;
pcm->private_data=rtd;
if(platform->driver->ops){
soc_pcm_ops->mmap=platform->driver->ops->mmap;
soc_pcm_ops->pointer=platform->driver->ops->pointer;
soc_pcm_ops->ioctl=platform->driver->ops->ioctl;
soc_pcm_ops->copy=platform->driver->ops->copy;
soc_pcm_ops->silence=platform->driver->ops->silence;
soc_pcm_ops->ack=platform->driver->ops->ack;
soc_pcm_ops->page=platform->driver->ops->page;
}
if(playback)
snd_pcm_set_ops(pcm,sndrv_pcm_stream_playback,soc_pcm_ops);
if(capture)
snd_pcm_set_ops(pcm,sndrv_pcm_stream_capture,soc_pcm_ops);
if(platform->driver->pcm_new){
ret=platform->driver->pcm_new(rtd);
if(retprivate_free=platform->driver->pcm_free;
returnret;
}
该函数首先初始化snd_soc_runtime中的snd_pcm_ops字段,也就是rtd->ops中的部分成员,例如open,close,hw_params等,紧接着调用标准alsa驱动中的创建pcm的函数snd_pcm_new()创建声卡的pcm实例,pcm的private_data字段设置为该runtime变量rtd,然后用platform驱动中的snd_pcm_ops替换部分pcm中的snd_pcm_ops字段,最后,调用platform驱动的pcm_new回调,该回调实现该platform下的dma内存申请和dma初始化等相关工作。到这里,声卡和他的pcm实例创建完成。
回到snd_soc_instantiate_card函数,完成snd_card和snd_pcm的创建后,接着对dapm和dai支持的格式做出一些初始化合设置工作后,调用了card->late_probe(card)进行一些最后的初始化合设置工作,最后则是调用标准alsa驱动的声卡注册函数对声卡进行注册:
[cpp]view plaincopy
if(card->late_probe){
ret=card->late_probe(card);
if(retdev,%slate_probe()failed:%d,
card->name,ret);
gotoprobe_aux_dev_err;
}
}
snd_soc_dapm_new_widgets(&card->dapm);
if(card->fully_routed)
list_for_each_entry(codec,&card->codec_dev_list,card_list)
snd_soc_dapm_auto_nc_codec_pins(codec);
ret=snd_card_register(card->snd_card);
if(retname);
gotoprobe_aux_dev_err;
}
至此,整个machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分platfrom驱动和codec驱动的初始化工作,整个过程可以用一下的序列图表示:
图3.1 基于3.0内核 soc_probe序列图
下面的序列图是本文章第一个版本,基于内核2.6.35,大家也可以参考一下两个版本的差异:
图3.2 基于2.6.35 soc_probe序列图

将直接转换推向奈奎斯特带宽的设计以及和中频采样进行比较
索尼自爆PSVR暂目前只有VR短游戏的原因
海外10国线上销量荣耀强势崛起,力压苹果席卷多国线上
Nvidia与ARM共同合作打造AI芯片专用IP
Acrel-6000电气火灾监控系统在丹东振安区医院项目中的应用
Linux ALSA声卡驱动之一:ASoC架构中的Machine
谷歌:Pixel 5的屏幕缝隙对防水性没有影响
移动通信网和蜂窝到底有什么关系
5G与SD-WAN未来将会是相互融合相辅相成的关系
镜头自动光圈马达驱动MS41908参数与替代
ADS环境下基于S3C2410串口应用程序的开发
PLC常用开关量输入输出元器件的符号
常用的传感器安装螺纹有哪些
快讯:欧普照明与世茂集团达成战略合作
可怕的类人机器人,机器人对于人类是灾难还是革命?
真“芯”有实意,采购黑话破译手册速看~
抗强震、更稳定 | 海盛翔和激光屏以设计塑造品质、以品质见证实
高压气密性检测仪的检测步骤
箱式变电站安装和运行要求
5G竞技赛美国目前正处于领先位置