一、实验环境
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
二、修改代码
2.1 修改s3c2440_dma.c
1) static volatile struct s3c_dma_regs *dma_regs;
改为:
static volatile struct s3c_dma_regs *dma_regs_ch1; //for playback
static volatile struct s3c_dma_regs *dma_regs_ch2; //for capture
相应的,修改所有涉及到DMA寄存器操作的函数,根据substream->stream的类型,来选择对应的dma_regs_chx,
2) 添加preallocate_dma_buffer(),修改s3c2440_dma_new(),为playback substream和capture substream分别分配DMA缓冲区
3) 把原先定义的全局变量struct s3c2440_dma_info playback_dma_info改为在s3c2440_dma_open()里
static int s3c2440_dma_open(struct snd_pcm_substream *substream)
{
struct s3c2440_dma_info *prtd;
...
prtd = kzalloc(sizeof(struct s3c2440_dma_info), GFP_KERNEL);
runtime->private_data = prtd;
...
}
相应的,修改所有访问playback_dma_info的函数,改为访问substream->runtime->private_data
2.2(这是后来调试时才发现要加的)
参考内核的s3c24xx-i2s.c,修改s3c2440-i2s.c:
添加s3c2440_snd_txctrl()、s3c2440_snd_rxctrl(),然后修改s3c2440_i2s_trigger()
static int s3c2440_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
//s3c2440_iis_start();
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c2440_snd_rxctrl(1);
else
s3c2440_snd_txctrl(1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
default:
//s3c2440_iis_stop();
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c2440_snd_rxctrl(0);
else
s3c2440_snd_txctrl(0);
ret = -EINVAL;
break;
}
exit_err:
return ret;
}
三、调试
1. 编译、安装驱动,过程略,详见:韦东山嵌入式Linux视频教程_3期项目实战之ALSA声卡_从零编写之调试(基于优龙FS2410开发板,UDA1341声卡)
2. 执行aplay windows.wav 后,耳机里是高频杂音!
/ # cat /proc/interrupts 可见确实发生了DMA中断
CPU0
25: 0 s3c s3c2410-wdt
30: 26510 s3c S3C2410 Timer Tick
35: 2 s3c myalsa for playback
…
经查,需要在s3c2440_dma_hw_params()中加上:
prtd->phy_addr = substream->dma_buffer.addr;
原先是 playback_dma_info.phy_addr,在s3c2440_dma_new()中直接作为dma_alloc_writecombine的输出参数被初始化的:
playback_dma_info.virt_addr = (unsigned int)dma_alloc_writecombine(pcm->card->dev, s3c2440_dma_hardware.buffer_bytes_max,
&playback_dma_info.phy_addr, GFP_KERNEL);
重新编译加载驱动,然后再次执行aplay windows.wav 后,耳机里终于能听到声音了!
3. 执行arecord -d 5 my1.wav,但过了5秒钟没有任何反应!强制ctrl-c后,报:
Recording WAVE 'stdin' : Unsigned 8 bit, Rate 8000 Hz, Mono
^CAborted by signal Interrupt...
arecord: pcm_read:2031: read error: Interrupted system call
/ # cat /proc/interrupts 没有发生任何DMA中断
4. 用devmem2 查看寄存器DISRC1(0x4B000040), DIDST1(0x4B000048), IISCON(0x55000000)的值:
(注:双箭头的左边是执行arecord之前的值,右边是执行arecord后的值)
/ # devmem2 0x4B000040
Value at address 0x4B000040 (0xb6fba040): 0x0 <==> 0x55000010
/ # devmem2 0x4B000048
Value at address 0x4B000048 (0xb6f46048): 0x0 <==> 0x33B80000
/ # devmem2 0x55000000
Value at address 0x55000000 (0xb6fc6000): 0x100<==> 0x122 Bit[0]=0 即IIS interface disable,说明这位的1没写进去!
查内核自带的soundsocsamsung s3c24xx-i2s.c,发现除了要操作IISCON之外,还要操作IISMOD、IISFCO。
遂参考内核的s3c24xx-i2s.c,修改s3c2440-i2s.c:
添加s3c2440_snd_txctrl()、s3c2440_snd_rxctrl(),然后修改s3c2440_i2s_trigger()。
5. 重新编译加载驱动,然后再次执行arecord -d 5 my1.wav,这次5秒后正常退出,没有报错。
查看IISCON的值:
/ # devmem2 0x55000000
Value at address 0x55000000 (0xb6fbd000): 0x10B ,说明bit[0]的1写进去了!
/ # cat /proc/interrupts
CPU0
25: 0 s3c s3c2410-wdt
30: 139493 s3c S3C2410 Timer Tick
34: 1 s3c my alsa for capture 说明DMA中断发生了
执行aplay my1.wav,能够播放声音!说明录音成功!
6. 在JZ2440,wm8976声卡上测试,也是成功的。而程序的修改,只需要把s3c2440_dma.c和s3c2440_iis.c拷贝覆盖过去即可。
附:源代码
myalsa_FS2410_can_record
myalsa_JZ2440_can_record
四、参考资料
1. 韦东山 嵌入式Linux视频教程_3期项目实战之ALSA声卡
2. 李兰溪 S3C24XX DMA框架源码分析
3. linux 3.4.2 内核源代码
4. s3c2440数据手册