AM335x(TQ335x)学习笔记——WM8960声卡驱动移植 --1

2019-07-15 15:14发布

AM335x(TQ335x)学习笔记——WM8960声卡驱动移植

经过一段时间的调试,终于调好了TQ335x的声卡驱动。TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法。Linux声卡驱动架构有OSS和ALSA两种架构,目前最常用的架构是ALSA,本文也使用ALSA架构对WM8960驱动进行移植。ASoC是对ALSA驱动架构的进一步封装。ASoC将ALSA驱动中的各模块抽象为三部分:Platform、Codec和Machine。Platform主要是平台硬件驱动,包括SoC的IIS模块、DMA等,在本文中就是指AM335x的McASP模块及AM335x用于音频读写操作的EDMA。Codec是编解码芯片驱动,在本文中就是WM8960。Machine是用来描述单板音频系统连接关系的驱动,在本文中其作用是将WM8960与McASP绑定起来,注册声卡设备节点。由于3.17版本的内核已经带有TI维护的McASP驱动和Wolf公司维护的WM8960驱动,因此,原理上讲,我们只需要编写Machine部分,建立WM8960与McASP的连接关系即可。不幸的是Wolf对WM8960的维护不是太完善,还需要我们进一步修改。下面我们来看下WM8960在TQ335x上的移植方法。1. 在DTS中添加声卡信息Step1.  完善sound信息在DTS有一个节点名为sound,该节点用来描述单板上声卡设备信息,修改后的内容如下:
  • sound {  
  •     compatible = "ti,tq-evm-audio";  
  •     ti,model = "AM335x-EVM";  
  •     ti,audio-codec = <&wm8960>;  
  •     ti,mcasp-controller = <&mcasp1>;  
  •     ti,codec-clock-rate = <24576000>;  
  •     ti,audio-routing =  
  •         "Headphone Jack",       "HP_L",  
  •         "Headphone Jack",       "HP_R",  
  •         "LINPUT1",              "Line In";  
  • };  

复制代码

含义解释:
(1) compatible = "ti,tq-evm-audio" -->  指定声卡兼容的设备,与Machine驱动中的compatible匹配。(2) ti,model = "AM335x-EVM" --> 声卡的名称,原则上讲可以随意指定,但最好具有一定的可读性,这里没有修改。(3) ti,audio-codec = <&wm8960> --> 指定单板使用的Codec,具体的Codec信息由其指向的节点wm8960描述。(4) ti,mcasp-controller = <&mcasp1> --> 指定单板使用的Codec连接到AM335x的McASP1上,McASP1的具体信息由其指向的节点mcasp1描述。(5) ti,codec-clock-rate = <24576000> --> 指定Codec的MCLK时钟频率,单位是HZ。TQ335x的Codec使用24.576MHZ的有源晶振提供MCLK,故设置为24576000。(6) ti,audio-routing  --> DAPM信息描述,用来指定Codec与McASP的连接关系。此处若不设置,则需要在Machine驱动中进行设置。本文在这里做了修改。Step2. 完善Codec信息通过阅读TQ335x的原理图可知,WM8960的控制端口连接到了AM335x的I2C0端口上,因此,可以i2c0节点内添加如下信息(类似上篇文章中触摸设备驱动节点):
  • wm8960: wm8960@1a {  
  •     compatible = "wlf,wm8960";  
  •     reg = <0x1a>;  
  • };  

复制代码

含义解释:
(1) compatible = "wlf,wm8960" --> 指定Codec兼容设备,与Codec驱动中的compatible匹配。(2) reg = <0x1a> --> WM8960的I2C地址是1A,故设置为0x1a。Step3. 完善Platform信息AM335x的Platform信息主要指McASP和EMDA设置信息。由于默认的DTS已经配置好了McASP及EDMA的大部分信息,需要我们配置的是McASP的pinmux和i2s信息。(1) 修改pinmux信息需要具体参考TQ335x的原理图,下面是根据原理图中的引脚连接方式修改的pinmux信息,如果有啥不懂的可以留言讨论:
  • am335x_evm_audio_pins: am335x_evm_audio_pins {  
  •     pinctrl-single,pins = <  
  •         0x1A0 (PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_aclkr.mcasp1_aclkx */  
  •         0x1A4 (PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_fsr.mcasp1_fsx */  
  •         0x1A8 (PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* mcasp0_axr1.mcasp1_axr0 */  
  •         0x1AC (PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcasp0_ahclkx.mcasp1_axr1 */  
  •     >;  
  • };  

复制代码

(2) i2s的配置信息需要在mcasp1节点中修改,具体的修改如下:
  • &mcasp1 {  
  •         pinctrl-names = "default";  
  •         pinctrl-0 = <&am335x_evm_audio_pins>;  
  •   
  •         status = "okay";  
  •   
  •         op-mode = <0>;          /* MCASP_IIS_MODE */  
  •         tdm-slots = <2>;  
  •         /* 4 serializers */  
  •         serial-dir = <  /* 0: INACTIVE, 1: TX, 2: RX */  
  •             1 2 0 0  
  •         >;  
  •         tx-num-evt = <1>;  
  •         rx-num-evt = <1>;  
  • };  

复制代码

含义:
(1) pinctrl-0 = <&am335x_evm_audio_pins> --> 指定mcasp1的pinmux信息。(2) op-mode = <0> --> 指定McASP为I2S工作模式。(3) tdm-slots = <2> --> 指定通道数。AM335x的手册以更广泛意义的单词slot命名,具体到I2S接口,其含义就是Channel。(4) serial-dir --> 指定serializer的方向。AM335x的手册中提到每个McASP有16个serializer,但AM335x这款芯片的McAPS只有4个serializer,分别用于AXR0、AXR1、AXR2和ARX3。由于TQ335x中将AXR0作为发送(输出)、ARX1作为接收(输入)且没有ARX2和ARX3,故设置4个serial-dir为1、2、0、0(0表示没有使用,1表示发送,2表示接收)。
(5) tx-num-evt = <1> --> 指定发送FIFO大小,本文设置为1。
(6) rx-num-evt = <1> --> 指定接收FIFO大小,本文设置为1。至此,就完成了DTS的全部配置,后面我会将完整的DTS文件上传到我的资源。2. Codec驱动完善Step1. 修改Codec驱动,使其支持DTS由于我们在DTS中指定了Codec的compatible为"wlf,wm8960",而Linux内核自带的WM8960驱动并没有支持新式的DTS模式关联。修改方法很简单,添加i2c_driver的.driver中指定of_match_table即可,修改后的代码片段如下:
  • static const struct of_device_id wm8960_of_match[] = {  
  •     { .compatible = "wlf,wm8960", },  
  •     { }  
  • };  
  • MODULE_DEVICE_TABLE(of, wm8960_of_match);  
  •   
  • static struct i2c_driver wm8960_i2c_driver = {  
  •     .driver = {  
  •         .name = "wm8960",  
  •         .owner = THIS_MODULE,  
  •         .of_match_table = wm8960_of_match,  
  •     },  
  •     .probe =    wm8960_i2c_probe,  
  •     .remove =   wm8960_i2c_remove,  
  •     .id_table = wm8960_i2c_id,  
  • };  

复制代码

Step2. 完善WM8960的初始化信息
默认的WM8960驱动初始化信息不够完整,还需要对WM8960进行额外的初始化,修改后的代码片段如下:
  • static int wm8960_probe(struct snd_soc_codec *codec)  
  • {  
  •     struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);  
  •     struct wm8960_data *pdata = dev_get_platdata(codec->dev);  
  •     int ret;  
  •   
  •     wm8960->set_bias_level = wm8960_set_bias_level_out3;  
  •   
  •     if (!pdata) {  
  •         dev_warn(codec->dev, "No platform data supplied ");  
  •     } else {  
  •         if (pdata->capless)  
  •             wm8960->set_bias_level = wm8960_set_bias_level_capless;  
  •     }  
  •   
  •     ret = wm8960_reset(codec);  
  •     if (ret < 0) {  
  •         dev_err(codec->dev, "Failed to issue reset ");  
  •         return ret;  
  •     }  
  •   
  •     wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY);  
  •   
  •     /* Latch the update bits */  
  •     snd_soc_update_bits(codec, WM8960_LINVOL, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_RINVOL, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_LADC, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_RADC, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_LDAC, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_RDAC, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_LOUT1, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_ROUT1, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100);  
  •   
  •     /* other configuration */  
  •     snd_soc_update_bits(codec, WM8960_POWER1, 0x1ea, 0x1ea);  
  •     snd_soc_update_bits(codec, WM8960_POWER2, 0x1f8, 0x1f8);  
  •     snd_soc_update_bits(codec, WM8960_POWER3, 0xcc, 0xcc);  
  •     snd_soc_update_bits(codec, WM8960_LOUTMIX, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_ROUTMIX, 0x100, 0x100);  
  •     snd_soc_update_bits(codec, WM8960_POWER3, 0xc, 0xc);  
  •     snd_soc_update_bits(codec, WM8960_LOUT1, 0x7f, 0x7f);  
  •     snd_soc_update_bits(codec, WM8960_ROUT1, 0x7f, 0x7f);  
  •     snd_soc_update_bits(codec, WM8960_IFACE2, 0x40, 0x40);  
  •     snd_soc_update_bits(codec, WM8960_MONOMIX2, 0x120, 0x120);  
  •     snd_soc_update_bits(codec, WM8960_LINPATH, 0x1f8, 0x138);  
  •     snd_soc_update_bits(codec, WM8960_LINVOL, 0x19f, 0x11f);  
  •     snd_soc_update_bits(codec, WM8960_RINVOL, 0x19f, 0x11f);  
  •     snd_soc_update_bits(codec, WM8960_LOUT2, 0x1ff, 0x1ff);  
  •     snd_soc_update_bits(codec, WM8960_ROUT2, 0x1ff, 0x1ff);  
  •     snd_soc_update_bits(codec, WM8960_CLASSD3, 0x1a, 0x12);  
  •     snd_soc_update_bits(codec, WM8960_CLASSD1, 0xc0, 0xc0);  
  •   
  •     snd_soc_add_codec_controls(codec, wm8960_snd_controls,  
  •                      ARRAY_SIZE(wm8960_snd_controls));  
  •     wm8960_add_widgets(codec);  
  •   
  •     return 0;  
  • }  

复制代码

具体的含义可以参考WM8960的芯片手册,这里我就不一一介绍了。
Step3. 调整WM8960驱动结构内核中自带的WM8960驱动结构很旧,编写Machine是需要过多的了解Codec芯片内部细节,本文对WM8960的驱动结构进行了调整,可以使Machine忽略Codec的内部细节。修改的大体内容如下:(1) 添加set_sysclk函数,接收Machine设置的sysclk时钟频率。具体本文就是DTS中设置的24576000。(2) 在hw_params中添加BCLK、DACCLK、ADCCLK的配置操作。hw_params可以根据参数和sysclk对以上参数进行设置,放在这里很合适。(3) 去除函数wm8960_set_dai_clkdiv,并将wm8960_set_dai_pll设置为驱动内部函数,不作为set_pll接口提供给内核驱动(实际上内核驱动也不调用这个函数)。Step4. 修改WM8960的route信息根据TQ335x的原理图可知,使用WM8960进行录音或放音时使用的LRCLK是同一个,都是DACCLK,故在snd_soc_dapm_route添加如下两行信息:
  • { "Left DAC", NULL, "Left Input Mixer" },  
  • { "Right DAC", NULL, "Right Input Mixer" },  

复制代码

这样在录音时也会使能DAC产生LRCLK。
由于调试时间比较长,可能有些修改我没有描述到,完整的wm8960.c文件我会一并上传到我的资源,可以下载参考。

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。