wm8960音频工作不正常的问题排查
背景:
之前写过两个文章来记录了wm8960音频播放出现砰砰声的问题的原因和其对应的解决方法。后来为了简单起见就选择了将MCLK时钟改为12MHz的方法,也解决了客户的问题。昨天生产部门又反馈新一批的板子又有大批量出现音频播放声音不正常的现象,不同于上次出现的砰砰声,这次是播放时直接出现嗞啦的声音,而且几秒钟的音频文件要播放几分钟才播放完毕,看来又出现新的问题了。
排查过程:
出现这个现象,可以很确定的判断大概率是音频芯片工作不正常的问题,为了验证想法,我们可以拿示波器量一下wm8960音频芯片的BCLK和LRC的时钟频率。原理图的引脚如下:(PS:芯片的引脚顺序是从芯片上的小圆点开始是第一脚,逆时针方向数)
测量发现BCLK有11MHz之高,LRC时钟也很不正常。结合WM8960的时钟分频图来看,这个时钟很不对。
然后我们可以继续量一下SYSCLK的时钟情况如何。
通过查看WM8960的芯片手册,我们可以发现以下信息:
在R48 (30h)Additional Control (4)寄存器中的第6:4位,可以设置WM8960芯片上的GPIO的功能,我们可以将其设置为SYSCLK output功能。
修改内核源码:sound/soc/fsl/imx-wm8960.c,在static int imx_wm8960_late_probe函数中:
// snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6);
/* GPIO1 used as reserve */
- // snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 1<<4);
+ snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 4<<4);
7<<4是设置WM8960_ADDCTL4寄存器的掩码,正好对应寄存器的6:4位,然后将这4位设置为二进制的100,即SYSCLK output功能。
同时我们可以修改内核源码sound/soc/codecs/wm8960.c,在static int pll_factors函数中:
pll_div->k = K;
- pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+ printk("WM8960 PLL: N=%x K=%x pre_div=%d\n",
pll_div->n, pll_div->k, pll_div->pre_div);
return 0;
这样可以打印出PLL锁相环的倍频的倍数,判断其倍频是否合理,具体内容可以参考WM8960芯片手册中的以下部分:
由以上可知,PLL的倍频倍数N的取值范围在5 < PLLN < 13,PLL的最优输出时钟为90MHz到100MHz之间。
我们可以根据我们板子的实际情况进行理论分析:我们设置的MCLK=12MHz,需要的SYSCLK 时钟 = 12.288MHz
f2=4(CLKSEL) x 2(SYSCLKDIV) x 11.2896(SYSCLK) = 90.3168
R = 98.304 / 12 = 7.5264
PLLN = int R = 7
k = int (2<sup>24</sup> x (7.5264 – 7)) = 8831527 = 86C227
实际中我们发现内核打印出来的信息为:
虽然N和K的值是对的,但是我们发现了一个很不正常的值,pre_div的值为1
根据WM8960芯片手册中内容
我们发现这个pre_div的值为1意味着MCLK在输入进PLL锁相环之前被进行了一次分频,变成了6MHz,这样的话,从PLL锁相环中输出的时钟也会相应的减半变成90.3168/2HMz,这样的值肯定是不在90MHz到100MHz之间的,由此也造成了芯片的工作不正常。
驱动中错误的进行的算法如下(它以SYSCLKDIV为1分频时优先计算,并对MCLK时钟进行了pre_div为2分频的处理):
f2=4(CLKSEL) x 1(SYSCLKDIV) x 11.2896(SYSCLK) = 45.1584
R = 45.1584 / 6 = 7.5264
PLLN = int R = 7
k = int (2<sup>24</sup> x (7.5264 – 7)) = 8831527 = 86C227
至此,问题被发现了,解决方法如之前解决砰砰声时一样
可以将Linux内核源码sound/soc/codecs/wm8960.c中的
/* -1 for reserved value */
static const int sysclk_divs[] = { 1, -1, 2, -1 };
改为
/* -1 for reserved value */
static const int sysclk_divs[] = { -1, -1, 2, -1 };
使驱动程序在使用sysclk_divs[0]计算的时候无法计算通过,这样它就会采用sysclk_divs[2]的二分频算法进行计算,这样pre_div就不会再进行2分频的处理了。
修改完成之后我们发现内核打印出来的信息为:
实测播放声音也正常了。