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

 前面一节的内容我们提到,ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的     耦合      以及部分和设备或板子特定的代码,再次引用上一节的内容:Machine驱动负责处理机器特有的一些控件和音频事件(例如,当播放音频时,需要先行打开一个     放大器      );单独的Platform和Codec驱动是不能工作的,它必须由Machine驱动把它们结合在一起才能完成整个设备的音频处理工作。

 LinuxALSA声卡驱动之一:ASoC架构中的Machine_设计制作_模拟技术

ASoC的一切都从Machine驱动开始,包括声卡的注册,绑定Platform和Codec驱动等等,下面就让我们从Machine驱动开始讨论吧。

/********************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/********************************************************************************************/

1. 注册Platform Device

ASoC把声卡注册为Platform Device,我们以装配有WM8994的一款S     ams   ung的开发板SMDK为例子做说明,WM8994是一颗Wolfson生产的多功能Codec芯片。

代码的位于:/sound/soc/samsung/smdk_wm8994.c,我们关注模块的初始化函数:

[cpp] view pl     ai   n copy

sta     ti   c int __init smdk_audio_init(void)

{

int ret;

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);

return ret;

}


由此可见,模块初始化时,注册了一个名为soc-audio的Platform设备,同时把smdk设到platform_device结构的dev.drvdata字段中,这里引出了第一个数据结构snd_soc_card的实例smdk,他的定义如下:

[cpp] view plain copy

sta  TI c 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-audio",

.codec_name = "wm8994-codec",

.init = smdk_wm8994_init_paiftx,

.ops = &smdk_ops,

}, { /* Sec_Fifo Playback i/f */

.name = "Sec_FIFO TX",

.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,

},

};

sta  TI c struct snd_soc_card smdk = {

.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_pa     ram   s函数:smdk_hw_params。

2. 注册Platform Driver

按照     Linux   的设备模型,有platform_device,就一定会有platform_driver。ASoC的platform_driver在以下文件中定义:sound/soc/soc-core.c。

还是先从模块的入口看起:

[cpp] view plain copy

sta  TI c int __init snd_soc_init(void)

{

......

return platform_driver_regis     te   r(&soc_driver);

}

soc_driver的定义如下:

[cpp] view plain copy

/* ASoC platform driver */

sta  TI c struct platform_driver soc_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 plain copy

/* bind DAIs */

for (i = 0; i < card->num_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 plain copy

/* card bind complete so register a sound card */

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 plain copy

/* initialise the sound card only once */

if (card->probe) {

ret = card->probe(card);

if (ret < 0)

goto card_probe_error;

}

/* early DAI link probe */

for (order = SND_SOC_COMP_ORDER_FI     RS   T; order <= SND_SOC_COMP_ORDER_LAST;

order++) {

for (i = 0; i < card->num_links; i++) {

ret = soc_probe_dai_link(card, i, order);

if (ret < 0) {

pr_err("asoc: failed to instantiate card %s: %d\n",

card->name, ret);

goto probe_dai_err;

}

}

}

for (i = 0; i < card->num_aux_devs; i++) {

ret = soc_probe_aux_dev(card, i);

if (ret < 0) {

pr_err("asoc: failed to add auxiliary devices %s: %d\n",

card->name, ret);

goto probe_aux_dev_err;

}

}

在上面的soc_probe_dai_link()函数中做了比较多的事情,把他展开继续讨论:

[cpp] view plain copy

static int soc_probe_dai_link(struct snd_soc_card *card, int num, int order)

{

......

/* 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 (cpu_dai->driver->probe) {

ret = cpu_dai->driver->probe(cpu_dai);

}

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);

}

/* probe the platform */

if (!platform->probed &&

platform->driver->probe_order == order) {

ret = soc_probe_platform(card, platform);

}

/* 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);

}

/* 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;

......

/* create the pcm */

ret = soc_new_pcm(rtd, num);

........

return 0;

}

该函数出了挨个调用了codec,dai和platform驱动的probe函数外,在最后还调用了soc_new_pcm()函数用于创建标准alsa驱动的pcm逻辑设备。现在把该函数的部分代码也贴出来:


[cpp] view plain copy

/* create a new pcm */

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)

{

......

struct snd_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);

/* DAPM dai link stream work */

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 (ret < 0) {

pr_err("asoc: platform pcm constructor failed\n");

return ret;

}

}

pcm->private_free = platform->driver->pcm_free;

return ret;

}

该函数首先初始化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 plain copy

if (card->late_probe) {

ret = card->late_probe(card);

if (ret < 0) {

dev_err(card->dev, "%s late_probe() failed: %d\n",

card->name, ret);

goto probe_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_     pi   ns(codec);

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_aux_dev_err;

}


至此,整个Machine驱动的初始化已经完成,通过各个子结构的probe调用,实际上,也完成了部分Platf     rom   驱动和Codec驱动的初始化工作,整个过程可以用一下的序列图表示:

 LinuxALSA声卡驱动之一:ASoC架构中的Machine_设计制作_模拟技术

图3.1  基于3.0内核  soc_probe序列图

下面的序列图是本文章第一个版本,基于内核2.6.35,大家也可以参考一下两个版本的差异:

 LinuxALSA声卡驱动之一:ASoC架构中的Machine_设计制作_模拟技术

图3.2  基于2.6.35  soc_probe序列图



96
176
0
66

相关资讯

  1. 1、上映十天被迫下架,《晴雅集》使郭敬明赔了个底朝天4466
  2. 2、《金牌流浪狗》1月21日映打造宠物电影新经典3302
  3. 3、小舍得:蔡菊英的结局不好?4处剧情铺垫,透露真相1134
  4. 4、甄子丹输给《蚁人2》?别被迷惑,《大师兄》烂在了剧本上!2236
  5. 5、是经典再续还是卖情怀?《新乌龙院》演员名单引忧虑4631
  6. 6、昆汀的新片《好莱坞往事》竟然暗藏一条西部片的故事线?3364
  7. 7、上海亲验之后,你依然觉得这部是奥斯卡大热吗?4130
  8. 8、耗资165亿韩元打造的大片,上映了三天票房14亿韩元!4625
  9. 9、近期正热播的四部剧集,《有翡》在列,哪部是你正在追的?3257
  10. 10、中影派拉蒙将拍《马可波罗》《速激》导演执导2519
全部评论(0)
我也有话说
0
收藏
点赞
顶部