韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡)

一、实验环境

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,这次终于有声音了!

韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡)_第1张图片

    最后一个改进:

    虽然能播放声音,但会有周期性杂音。视频略去排查过程,直接上结论: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计算逻辑

你可能感兴趣的:(韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡))