如果有问题,请加QQ群 891339868 进行交流
TMS320VC5509A通过MCBSP接口与NUC972的IIS接口进行音频传输(三)
今天咱们继续了解DSP5509A的MCBSP接口与NUC972的IIS接口的音频传输的第三部分,就是DSP5509A在linux上的驱动部分。
首先咱们了解一下linux系统上的音频系统。目前linux上主流的音频框架是ALSA(Advanced Linux Sound Architecture的简称),具体的就不说了,框架比较复杂,网上的资料也比较多,可以去仔细了解了解,今天咱们主要说说针对这个项目,咱们具体需要做什么工作。针对嵌入式linux系统,ALSA框架拓展了一套专用的框架,ASoC(ALSA System on chip的简称)。ASoC分为三部分:一是Machine驱动,二是Codec驱动,三是Platform驱动。他们之间的关系如下图所示:
简单的说,Platform驱动是CPU端的驱动,Codec驱动codec的驱动,Machine驱动是将前两种驱动进行有机结合的驱动。咱们就根据NUC972的BSP对着三部分分别进行分析。
NUC972的BSP是基于linux3.1开发的,音频部分在/sound/soc/路径中,包含ALSA驱动的核心部分和各种平台和codec的驱动代码其中/sound/soc/codecs文件夹里面包含各种codec的驱动代码,/sound/soc/nuc970文件夹里面包含NUC970系列的machine驱动和platform驱动,而将DSP5509A模拟成codec的驱动需要咱们自己编写。首先看一下/sound/soc/nuc970文件夹里面包含的内容,主要包含三个文件,nuc970-audio.c、nuc970-i2s.c、nuc970-pcm.c。其中nuc970-audio.c对应上面描述的Machine驱动,nuc970-i2s.c和nuc970-pcm.c对应上面描述的platform驱动。Platform为什么分两部分呢?是因为ASoc在具体实现Platform时,将Platform分为两部分,一部分是snd_soc_platform_driver,负责管理音频数据,把音频数据通过DMA或其他操作传送至cpu dai(cpu传送数据的物理接口的逻辑抽象)中,第二部分是snd_soc_dai_driver,主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的DMA等参数与snd_soc_platform_driver进行交互。具体来说,nuc970-i2s.c文件就是nuc970系列的snd_soc_dai_driver,nuc970-pcm.c就是nuc970系列的snd_soc_platform_dirver。
首先看nuc970-pcm.c文件,看驱动文件咱们一般都是按从文件的最后向上看的,如下图所示,这是该驱动文件的最后几行:
可以看的出来snd_soc_platform_driver也是被抽象成一个platform格式的驱动,注意,这里的platform驱动是linux内核中驱动的一种逻辑格式,和上面所说的nuc970平台的platform驱动不是一回事儿。这里面最关键的是“nuc970-audio-pcm”这个名字,Machine驱动就是根据这个名字在内核中自动匹配这个驱动文件的,其他的不用修改,主要是NUC970系列cpu的DMA操作音频的代码,新唐的工程师已经给咱们写好了。
再看一下nuc970-i2s.c这个文件,如下图所示,这是该驱动文件的最后几行:
和snd_soc_platform_driver类似,snd_soc_dai_driver也是被抽象成了一个platform驱动,同样的,这里面最重要的是“nuc970-audio-i2s”这个名字,Machine驱动就是根据这个名字在内核中自动匹配这个驱动文件的,其他的不用修改,主要是NUC970系列cpu的dai接口的代码,包括设置频率、格式等信息,新唐的工程师已经给咱们写好了。
接着咱们再了解了解nuc970系列cpu的Machine驱动,就是nuc970-audio.c文件。先看最后几行,如下图所示:
同样的,该驱动也是被抽象成了一个platform驱动,主要包含两部分内容,一个是声卡的注册,一个是声卡的卸载,分别对应nuc970_audio_probe函数和nuc970_audio_remove函数。
再看一下声卡注册的内容,如下图所示:
从这几行代码中可以很清楚的看出来,向系统中注册声卡就是注册nuc970evb_i2s_dai和nuc_970evb_audio_machine这两个结构体里面的内容。首先看一下nuc970evb_audio_machine这个结构体,它是struct snd_soc_card格式,也就是声卡的抽象,其中里面name变量的值“nuc970_IIS”就是该声卡在系统中注册声卡的名字,dai_link变量包含的就是在声卡初始化的时候,Machine驱动需要在内核中寻找匹配的各个部分驱动的名字和对应的操作方法,具体的就是结构体struct snd_soc_dai_link nuc970evb_i2s_dai。这个结构体中关键的有以下几个名字,第一个是cpu_dai_name,对应nuc970-i2s.c文件中抽象成的平台驱动模块的名字,第二个是platform_name,对应nuc970-pcm.c文件中成的平台驱动模块的名字,第三个是codec_dai_name,第四个是codec_name,这两个是codec dai的名字和codec的名字,在讨论到codec驱动部分的时候在详细说,还有一个ops变量,这个是驱动提供的操作codec和cpu的接口,具体定义在结构体struct snd_soc_ops nuc970_audio_ops中,其中主要的变量是hw_params,这个变量在回调函数nuc970_audio_hw_params中实现,具体的实现代码如下图所示:
在这个函数中主要是根据上层程序传入的参数对codec dai和cpu dai、主时钟和采样率进行设置,由于在这个项目中是用DSP5509A模拟的codec,所以在这里不需要对codec进行设置,只需要对cpu端的参数进行设置就可以了,DSP5509A端的设置需要在DSP5509A的程序中进行设置,将两边的配置设置相同就行了。
接下来就需要编写将DSP5509A模拟成codec的驱动代码了。内核3.1版本的架构,如果需要将codec编译进内核,需要将codec驱动代码放到/sound/soc/codecs文件夹里面,所以我创建一个tms320vc5509a.c的文件,放到这个文件夹里面,首先看一下文件的最后几行,如下图所示:
根据上面的代码可以看得出来,该驱动被抽象成一个i2c类型的驱动,这是由于一般的codec都有一个i2c的配置接口,内核中一般也是将codec驱动抽象成一个i2c类型的驱动,所以这里也需要将DSP5509A的驱动代码抽象成i2c类型的驱动。驱动中包含另外的三部分分别是probe、remove、id_table,分别是驱动的注册、卸载、注册设备的id号,下面咱们详细的分析一下:
首先看这个实现注册的回调函数,如下图所示:
这个注册函数和普通的codec的注册函数基本上一样,只有一点儿不太一样,由于DSP5509A不是一个真正的codec,所以不需要(其实也没有办法对其进行操作),所以不需要在这里面对codec进行复位,就是上图中屏蔽的那一段代码。在这个函数中包含三个必须的结构体,一个是struct snd_soc_dai_driver TMS320VC5509A_dai,这个是对codec的dai的设置,具体内容如下图所示:
其中变量name的内容“tms320vc5509a-hifi”需要和Machine驱动中的struct soc_dai_link nuc970evb_i2s_dai的变量codec_dai_name相同。第二个必须的结构体是struct snd_soc_codec_driver soc_codec_dev_TMS320VC5509A,这个结构体内容是对codec的各种操作的接口,这里不需要对codec进行操作的接口,所以设置一个空结构体就行。第三个必须的结构体是struct regmap_config TMS320VC5509A_regmap_config,这个结构体内容是codec的各种设置接口,设置一个空结构体就行。
接着看实现卸载的回调函数,如下图所示:
这个和普通的codec卸载函数相同。
从代码看得出来,这一段代码是将名字为tms320vc5509a的i2c设备注册到i2c设备列表中,其中这个名字需要和platform_device中i2c设备的名字项目,在驱动初始化时进行匹配。
到现在为止,这几部分驱动的内容就大概了解清楚了,如果需要将驱动编译到内核中,启动时自动加载驱动,只需要更改相关的Kconfig文件即可。如下图所示,内核启动时,TMS320VC5509A已经成功被探测到:
nuc970_IIS就是在Machine驱动nuc970-audio.c中注册的声卡的名字,如下图所示:
到此为止,驱动层的代码已经完成。如果在板子上移植了alsa工具包,就可以使用arecord 工具进行录音测试,使用aplay工具进行放音测试。