花了大概40天时间从零开始调音频驱动到目前的基本成功,中间也走了不少弯路,今天抽点时间把整个流程走下来,希望以后能作为参考。
1.dm6467平台sdk_3_10版本中使用到的audio模块框架采用asoc模式,asoc框架主要包含4部分内容:
(1) codec模块驱动:如,visiondigi板子上采用tlv320aic23芯片,代码中文件tlv320aic23.c提供了该部分驱动。主要实现了codec部分的digital audio interface部分代码。
(2)平台驱动:如,dm6467平台中采用mcasp模块进行音频功能实现,代码中文件davinci_mcasp.c、 davinci_pcm.c以及soc-dapm.c文件提供了该部分代码,其中文件davinci_mcasp.c部分实现了dm6467平台中digital audio interface(DAI)相关代码,与codec中digital audio interface部分匹配使用。文件davinci_pcm.c部分主要通过substream建立DMA通道。文件soc-dapm.c主要是动态音频电源管理,用于切换各种需要的音频路径。
2.各部分驱动代码功能:
(1)板驱动: davinci_evm.c文件中,系统启动时,首先执行evm_init函数,该函数中注册了platform类型设备"soc-audio",并指定了snd_soc_device设备结构体dm6467_evm_snd_devdata,该结构体中制定了codec设备为soc_codec_dev_tlv320aic23,包括(widget、clock、route的设置)。
(2)平台驱动:
-------a.davinci_mcasp.c文件中,注册了platform类型的driver 和device,名字为均为"davinci-mcasp"。该driver中注册了probe函数davinci_mcasp_probe,该函数中注册了snd_soc_dai类型的结构davinci_mcasp_dai,描述了dm6467平台的dai操作接口(如I2S、DIT以及davinci_mcasp_dai_ops的操作)。
-------b.davinci_pcm.c文件中,注册了snd_soc_platform类型的结构davinci_soc_platform。
(3)codec驱动:注册了snd_soc_dai类型的结构tlv320aic23_dai,该dai仅与codec本身相关,其中codec名字"tlv320aic23"。
(4)板驱动:主要实现将上述2部分的驱动连接起来,代码中文件davinci_evm.c提供了该部分驱动。
(5)soc核心协议:文件soc-core.c,该文件注册了platform类型的driver结构soc_driver,driver名字为"soc-audio",并注册了相应的probe函数soc_probe.
3.各模块启动流程:
kernel启动时,
(1) 执行了文件 board-dm646x-evm.c 。
-------a.进入evm_init()函数进行各模块硬件初始化。其中音频相关的函数dm646x_init_mcasp0(&dm646x_evm_snd_data[0])主要完成名字为“davinci-mcasp”device的注册,以及dm646x_evm_snd_data参数的初始化。
-------b.进入evm_init_i2c()函数,注册device为"i2c_davinci"的I2C总线,并把名字“tlv320aic23”挂在总线上,为后续配置codec芯片的寄存器做准备。
(2) 执行了文件soc-core.c中snd_soc_init函数。
-----a.在platform平台上注册了名字为"soc-audio"的driver,并把类型为platform_driver的参数soc-driver填充进此driver。
-----b.在变量soc-driver中的成员函数指针probe和remove是分别将soc-audio的声卡注册和注销。
-----c.在变量soc-driver中有一个重要的成员为函数指针pm指向soc-pm-ops。而soc-pm-ops同时又指向三个函数。
I 函数 soc_suspend:执行了snd_power_wait来对声卡电源进行管理,同时执行digital_mute对DAC声音进行关闭,并挂起所有PCM流,关闭所有等待的流并保存其当前状态。
II 函数 soc_resume:判断CPU dai是否是ac97接口,如果是ac97接口就会对其处理执行soc_resume_deferred函数。(目前我做的是I2S接口,所以ac97没做详细研究)
III 函数 soc_poweroff:关闭声卡电源,关闭之前会取消所有等待队列的任务,如果有任务在执行则等待其执行完毕再关闭电源。
(3) 执行了文件tlv320aic23.c中tlv320aic23_modinit函数。
-----a.该函数注册了名字为“tlv320aic23”的driver在card平台上,并把结构体变量tlv320aic23_dai加入在dai->list的链表上。
-----b. 接着执行结构体变量tlv320aic23_dai的填充,其中一个比较重要的成员是tlv320aic23_dai_ops,它是由一系列的函数指针组成,这些函数指针指向的函数即是配置tlv320aic23工作的寄存器,这里就不一一具体列出来了,不过要注意这里的配置要跟CPU dm6467要一致才能正常工作。
(4) 执行了文件davinci_pcm.c中davinci_soc_platform_init函数。
-----a. 该函数注册了名字为“davinci-audio”的driver在platform平台上,并把结构体变量davinci_soc_platform加入在platform->list的链表上。
-----b. 接着也是执行结构体变量davinci_soc_platform的填充,其中有3个函数指针。函数指针davinci_pcm_ops指向若干个davinci PCM的操作(包括open、ioctl、perpare等等,不一一列举了)。函数指针davinci_pcm_new所指向的函数的功能为音频的playback或caputre分配dma buffer。函数指针davinci_pcm_free即为释放被打开的pcm buffer。
(5) 执行了文件davinci_mcasp.c中davinci_mcasp_init函数。
-----a.该函数是模块在初始化的时候,将注册一个名字为“davinci-mcasp”的driver在platform总线上。
-----b.注册名字为“davinci-mcasp”的driver会同时填充一个类型为platform_driver的实体变量davinci_mcasp_driver。其中此变量最重要的是填充其成员函数probe。
-----c.probe函数指针指向davinci_mcasp_probe函数,其作用填充了类型为davinci_audio_dev的变量dev 和 类型为davinci_pcm_dma_params的变量dma_data。以及注册了snd_soc_dai类型的结构davinci_mcasp_dai,描述了dm6467平台的dai操作接口(如I2S、DIT以及davinci_mcasp_dai_ops的操作)。
-----d. 结构体davinci_mcasp_dai_ops有三个函数指针,这三个函数指针都非常重要。工作特点分别如下:
I 函数davinci_mcasp_trigger:通过传进来的参数cmd来选择是进入davinci_mcasp_start 函数还是davinci_mcasp_stop。这两个函数是对dm6467负责数据FIFO的寄存器的打开或关闭。
II 函数 davinci_mcasp_hw_params:主要设置并且判断dm6467工作在PLAYBACK or CAPTURE、IIS or DIT、WORD_8 or WORD_16 or WORD_32。
III 函数 davinci_mcasp_set_dai_fmt: 主要是针对dm6467与外围codec之间的接口dai的配置。注意:这里的配置要跟上述第三点中的codec芯片tlv320aic23要一致才能正常工作。
(6) 执行了文件davinci_evm.c中evm_init函数。
-----a.该函数注册了platform类型设备"soc-audio",由于该设备名字与第二步中soc-core.c中注册的driver(name: soc-audio),名字一致,匹配成功,因此将开始执行该driver中注册的probe函数soc_probe,查找到设备"soc-audio"的私有数据信息,即snd_soc_device设备实体dm6467_evm_snd_devdata,从而查找到其成员card信息dm6467_snd_soc_card_evm,然后执行snd_soc_register_card动作,该动作中进行card的构造过程,并加入到card_list链表中。
-----b.在进行card的构造过程中,先执行cpu_dai中的probe函数,然后再执行codec_dev即dm6467_evm_snd_devdata结构成员codec_dev数据soc_codec_dev_tlv320aic23中的probe函数tlv320aic23_probe。
-----c.在函数tlv320aic23_probe函数中通过i2c模式注册i2c设备驱动,驱动名字"tlv320aic23",与文件board_dm646x_evm.c中i2c_board_info类型结构数据i2c_info中定义的i2c从设备"tlv320aic23"匹配一致,然后执行tlv320aic23的i2c_driver中的probe函数tlv320aic23_codec_probe。
-----d.在函数tlv320aic23_codec_probe中执行tlv320aic23_init函数,对tlv320aic23芯片进行初始化。该函数中同时注册了pcm设备,并且添加了该pcm设备的control操作和widgets。然后执行snd_soc_init_card操作。
-----e.在函数snd_soc_init_card中,首先执行card结构中的dai_link下的init动作,以进行codec与平台之间的link操作。然后进行card_register动作。