一、实验环境
1.1 虚拟机环境
a) Vmware版本:Vmware Workstation 12.5.7
b) Ubuntu版本:9.10
c) 内核版本:2.6.31.14
d) toolchain版本:arm-linux-gcc 4.3.2
1.2 开发板
优龙FS2410开发板,UDA1341声卡
内核版本:3.4.2
二、调试过程记录
1. 编译声卡驱动,修改语法错误
2. 配置内核去掉原来的声卡驱动
-> Device Drivers
-> Sound card support
-> Advanced Linux Sound Architecture
-> ALSA for SoC audio support
< > ASoC support for Samsung // CONFIG_SND_SOC_SAMSUNG 这项去掉勾选后,下面这项会连带自动消失
< > SoC I2S Audio support UDA134X wired to a S3C24XX // CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X // s3c24xx_uda134x.c
3. 编译内核,然后通过nfs把新的内核下载到开发板,并启动
4. 把新的声卡驱动通过nfs拷贝到根文件系统里,然后安装
insmod /alsa/driver/myalsa/platform/s3c2440_iis.ko
insmod /alsa/driver/myalsa/platform/s3c2440_dma.ko
insmod /alsa/driver/myalsa/codec/uda1341.ko
insmod /alsa/driver/myalsa/machine/s3c2440_uda1341.ko
mkdir /dev/snd
cd /dev/snd/
ln -s /dev/controlC0
ln -s /dev/pcmC0D0p
ln -s /dev/pcmC0D0c
cd /
为了方便,把以上这些语句放到prepare.sh里:
#!/bin/sh #insmod alsa/driver/myalsa/platform/s3c2440_iis.ko #insmod alsa/driver/myalsa/platform/s3c2440_dma.ko #这样写的话,执行时会报错:can’t insert s3c2440_iis.ko,invalid parameter c2440_iis.ko,原因待查 #insmod alsa/driver/myalsa/codec/uda1341.ko #同上 #insmod alsa/driver/myalsa/machine/s3c2440_uda1341.ko #同上 mkdir -p /dev/snd && cd /dev/snd cd /alsa/driver/myalsa/platform/ #得按照这种套路写才行? insmod s3c2440_iis.ko insmod s3c2440_dma.ko cd /alsa/driver/myalsa/codec/ insmod uda1341.ko #insmod wm8976.ko #在JZ2440开发板上,codec驱动用wm8976.ko cd /alsa/driver/myalsa/machine/ insmod s3c2440_uda1341.ko cd /dev/snd && ln -s /dev/controlC0 && ln -s /dev/pcmC0D0p && ln -s /dev/pcmC0D0c cd /
执行prepare.sh,报错:
soc-audio: coherent DMA mask is unset
asoc: platform pcm constructor failed
asoc: can’t create pcm 100ask_UDA1341:-12
解决办法:
参考内核自带的sound\soc\samsung\dma.c 的dma_new,在我们的s3c2440_dma.c的s3c2440_dma_new()中添加:
if (!card->dev->dma_mask)
card->dev->dma_mask = &dma_mask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
5. aplay windows.wav后kernel oops了:
LR指向snd_pcm_info,PC指向0
猜测是在snd_pcm_info里调用了某个不存在的子函数,推测是在这里出错:substream->ops->ioctl(substream, SNDRV_PCM_IOCTL1_INFO, info);
在《第2课第1.1_17节_ALSA声卡05_ASoC驱动框架》里,曾分析过这个ioctl的赋值是在soc_new_pcm里:
soc_pcm_ops->ioctl = platform->driver->ops->ioctl;
遂查看s3c2440_dma_ops,发现确实没有ioctl,遂参考内核的sound\soc\samsung\dma.c,加上 .ioctl = snd_pcm_lib_ioctl,
6. 再次aplay windows.wav,又kernel oops:
pc is at copy_from_user
backtrace:
snd_pcm_lib_write_transfer
copy_from_user
查snd_pcm_lib_write_transfer:
...
else { char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff); if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames))) return -EFAULT; }
而runtime->dma_area的赋值是在:
(sound/soc/samsung/dma.c)
dma_hw_params
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); //substream->dma_buffer是在dma_new里分配的
struct snd_pcm_runtime *runtime = substream->runtime;
runtime->dma_area = bufp->area;
查我们的s3c2440_dma_new,发现确实没有提供这个信息给内核,遂加上:
buf->area = playback_dma_info.virt_addr;
buf->bytes = playback_dma_info.buf_max_size;
7. 重新编译安装驱动,再次aplay windows.wav,这次没有oops,但报:aplay: pcm_write:1939:write error: Input/output error
aplay windows.wav & 让它后台运行
cat /proc/interrupts
发现中断一次都没有发生,难办了☹!
8. 用devmem2来查看相关寄存器有没有被正确的设置:
注:我在实验中没有用二期课程中的寄存器编辑器ker_wr和regeditor,因为在编译ker_rw的时候,报了一些错误,比如:
error: asm/arch/regs-gpio.h: No such file or directory
error: implicit declaration of function 'class_device_create'
原因应该是原代码是针对2.6的内核编译的,和3.4.2内核不兼容。修改了代码之后,虽然编译通过,而且也能运行,但读不出寄存器的值,原因待查。
/ # devmem2 0x4B000080
/dev/mem opened.
Memory mapped at address 0xb6f9a000.
Value at address 0x4B000080 (0xb6f9a080): 0x33B00000 //读DMA2_BASE_ADDR寄存器, 结果是有值的
/ # devmem2 0x55000000
/dev/mem opened.
Memory mapped at address 0xb6f50000.
Value at address 0x55000000 (0xb6f50000): 0x100 //读IISCON寄存器,结果都是0 (bit8的1是默认值,代表Left/Right channel index (Read only))
原因是:s3c2440的IIS的时钟没有使能?(排查思路是怎样的?是凭经验么?)
解决办法:在s3c2440_iis_init()里加入:
clk = clk_get(NULL, "iis");
clk_enable(clk);
clk_put(clk);
疑问:为什么裸板程序没有问题?
答案:因为裸板程序开始运行时,板子的时钟都是默认使能的,而linux启动后,为了省电,默认都是关闭的!
9. 重新编译安装驱动,再次aplay windows.wav,仍然没有声音,
cat /proc/interrupts
中断还是没有发生
/ # devmem2 0x55000000
/dev/mem opened.
Memory mapped at address 0xb6fd8000.
Value at address 0x55000000 (0xb6fd8000): 0x1A2 //读IISCON寄存器,bit0(iis_start)=0,表明iis传输没有启动
解决办法:
s3c2440_iis.c加入s3c2440_i2s_trigger、s3c2440_iis_start、s3c2440_iis_stop
10. 重新编译安装驱动,再次aplay windows.wav,这次终于有声音了!
最后一个改进:
虽然能播放声音,但会有周期性杂音。视频略去排查过程,直接上结论:load_dma_period()里,要把dma_regs->dcon的bit22设为1:noreload
不知道排查思路是怎样的?
推测是由于period_size比较小,导致音频数据被分割成了多块来传输,并且由于dcon的bit[22] =0即autoreload模式,所以每次传完一个period的数据后,dma会自动将src、dst、TC的值加载到CURR_SRC、CURR_DST、CURR_TC,并开始一次新的DMA传输(所以如果新的数据还来不及加载到CURR_SRC,则导致相当于重复播放上个period的数据)。而这之后才调用了DMA中断服务(硬件总是比软件快),进而load_dma_period()加载下一个period的数据到DMA的CURR_SRC。因此出现了播放时有周期性的杂音。
如果把s3c2440_dma_hardware修改一下(仅用于实验),使其能在一个period里播完windows.wav,则也能消除周期性的杂音:
// .buffer_bytes_max = 128*1024, // .period_bytes_min = PAGE_SIZE, // .period_bytes_max = PAGE_SIZE*2, .buffer_bytes_max = 1024*1024, .period_bytes_min = 512*1024, .period_bytes_max = 512*1024,
另外,如果用内核自带的驱动,则不会出现上述问题。经初步分析,因为它利用了dcon的autoload功能,并且设计了一个比较复杂的框架,使得当一个period正在传输时,下一个period准备就绪。这样就能平滑的播放出声音。
三、参考资料
1. 韦东山 嵌入式Linux视频教程_3期项目实战之ALSA声卡:第2课第1.1_17节_ALSA声卡11_从零编写之调试
2. 李兰溪 S3C24XX DMA框架源码分析
3. rushzengjianmei Alsa period_size/periods/buffer_size计算逻辑