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

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

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

/********************************************************************************************/声明:本博内容均由原创,转载请注明出处,谢谢!/********************************************************************************************/

1. 注册Platform Device

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

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

static 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,他的定义如下:

static 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,},};static 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_params函数:smdk_hw_params。

2. 注册Platform Driver

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

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

static int __init snd_soc_init(void){……return platform_driver_register(&soc_driver);}

soc_driver的定义如下:

/* ASoC platform driver */static 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的绑定工作,下只是代码的部分选节,详细的代码请直接参考完整的代码树。

/* 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函数创建声卡实例:

/* 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);以一种进取的和明智的方式同它们奋斗。

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

相关文章:

你感兴趣的文章:

标签云: