之前已经写了一篇文章《3线接口与wm8976声卡驱动》,但那不是裸板驱动,只是修改了声卡uda1314的硬件部分的代码,移植过来的,需要在内核下其他模块的支持,才把wm8976用起来了。为了更深入的理解声卡的工作过程,这次不止写了wm8976的裸板驱动,还有IIS和DMA的编程,并利用它们一起来实现裸板播放音乐的功能。也是说把存储在nand flash上的一首歌(.wav格式)通过声卡wm8976播放出来,就是我这次实验的目的。
要想实现这件事,只需要把wm8976配置好,然后把声音数据通过IIS发送给wm8976即可,作为声卡编解码芯片, wm8976会自动把音频数据解码,把模拟信号作用在扬声器上,发出音乐来。
这里涉及两个问题:
1、配置wm8976就是要读写wm8976内部的寄存器,通过什么办法从wm8976的寄存器中读取数据或往其中写入数据?
2、wm8976的IIS数据线显然连接在开发板的IIS总线上,IIS控制器从哪里获得音频数据?
完全回答这两个问题,就达到了播放音乐的目的了。
实际上这里wm8976硬件驱动的内容不多,更多的内容是使用wm8976播放音乐的过程中涉及的其他问题,具体就是 IIS和DMA的使用问题。
首先,wm8976的控制数据和音频数据是分开传输的,wm8976的控制数据由3线接口来传输,而音频数据专门由IIS接口负责传输。
先看第一个问题:
JZ2440上的wm8976的3线接口是接着s3c2440的GPIO上的,用这个GPIO模拟3线接口的时序即可。
3线接口时序:
刚开始全部设为高电平:
set_csb(1);
set_dat(1);
set_clk(1);
然后第一个数据:
set_clk(0);
set_dat(1);
delay();
set_clk(1);
至于具体写入什么内容到wm8976内部寄存器来配置其工作方式,这里就不阅读其芯片手册了,直接把别人的东西拿来用:
初始化wm8976:
/* software reset */
wm8976_write_reg(0, 0);
/* OUT2的左/右声道打开
* 左/右通道输出混音打开
* 左/右DAC打开
*/
wm8976_write_reg(0x3, 0x6f);
wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b
wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable
wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK
wm8976_write_reg(0x4, 0x10);//16bit
wm8976_write_reg(0x2B,0x10);//BTL OUTPUT
wm8976_write_reg(0x9, 0x50);//Jack detect enable
wm8976_write_reg(0xD, 0x21);//Jack detect
wm8976_write_reg(0x7, 0x01);//Jack detect
这样就通过3线接口配置好wm8976了,这部分还是比较简单的。
看第二个问题:
这里的实现方法是把声音从nand拷贝到SDRAM,再通过DAM把声音从SDRAM传输到IIS的发送FIFO寄存器。文件是如何放在nand中的呢?烧写,把wav文件像烧写内核或文件系统那样下载到nand中。这里把文件烧在nand的0x60000。
代码中用到read_wav()函数:把wav文件从nand中读到SDRAM中,并获取文件头部信息。其中要用到nand_read()函数,这个就不具体说,不从零写了,nand等硬件的裸板驱动, 复制之前写BootLoader部分的代码来支持。
nand_read()的调用: nand_read(0x60000, (unsigned char *)wav_buf, 0x200000);
其中wav_buf是SDRAM的起始地址0x30000000。
现在文件就处在SDRAM 中了,下面通过DMA把它传输到IIS的FIFO寄存器。
dma初始化部分的代码:
#define DMA0_BASE_ADDR 0x4B000000
#define DMA1_BASE_ADDR 0x4B000040
#define DMA2_BASE_ADDR 0x4B000080
#define DMA3_BASE_ADDR 0x4B0000C0
struct s3c_dma_regs {
unsigned long disrc;
unsigned long disrcc;
unsigned long didst;
unsigned long didstc;
unsigned long dcon;
unsigned long dstat;
unsigned long dcsrc;
unsigned long dcdst;
unsigned long dmasktrig;
};
struct s3c_dma_regs *dma_regs;
static int dma_src_rep;
static int dma_len_rep;
/* 数据传输: 源,目的,长度 */
void dma_init(unsigned int src, unsigned int len)
{
dma_src_rep = src;//0x30000000
dma_len_rep = len;
dma_regs = (struct s3c_dma_regs *)DMA2_BASE_ADDR;
/* 把源,目的,长度告诉DMA */
dma_regs->disrc = src; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = 0x55000010; /* 目的的物理地址,IIS FIFO寄存器地址 */
dma_regs->didstc = (0<<2) | (1<<1) | (1<<0); /* 目的位于APB总线, 目的地址不变 */
dma_regs->dcon = (1<<31)|(0<<30)|(1<<29)|(0<<28)|(0<<27)|(0<<24)|(1<<23)|(1<<20)|(len/2); /* 使能中断,单个传输,硬件触发 */
}
DMA相关知识:
DMA传输:包含源,目的,传输总大小,每次传输的大小(字节、半字、字)、传输计数等,源/目的地址增、减、不变。
DMA方向:外设到内存,内存到外设。
DMA和那些片上接口(IIS,SPI等)都是片上的,通过配置寄存器操作,IIS外设,SPI外设接在这些接口上,通过这些接口控制器通信。DMA与那些接口的控制器也是有联系的,他们之间的传输也有一定的协议,把数据拷到那些控制器,再由控制器组织数据发送给外设。协议的东西,硬件上已经做好,我们要做的只是配置寄存器,让他们自动执行。
需要注意的是平时写驱动,写spi,iis,uart等驱动,是CPU与这些接口的控制器交互数据,现在是dma与那些控制器交互数据,dma 和那些控制器都是需要CPU来配置好,然后让他们自动拷贝,拷完了就告诉CPU,作相应处理。由于与控制器的交互的对象由CPU改为dma,所以对那些接口的控制器(IIS、SPI控制器等)的配置也是不一样的。具体的配置内容不做分析了,看芯片手册好了。
IIS初始化部分代码:
void iis_init(int bits_per_sample, int fs)
{
int tmp_fs;
int i;
int min = 0xffff;
int pre = 0;
/* 配置GPIO用于IIS */
GPECON &= ~((3<<0) | (3<<2) | (3<<4) | (3<<6) | (3<<8));
GPECON |= ((2<<0) | (2<<2) | (2<<4) | (2<<6) | (2<<8));
/* bit[9] : Master clock select, 0-PCLK
* bit[8] : 0 = Master mode
* bit[7:6] : 10 = Transmit mode
* bit[4] : 0-IIS compatible format
* bit[2] : 384fs, 确定了MASTER CLOCK之后, fs = MASTER CLOCK/384
* bit[1:0] : Serial bit clock frequency select, 32fs
*/
if (bits_per_sample == 16)
IISMOD = (2<<6) | (0<<4) | (1<<3) | (1<<2) | (1);
else
IISMOD = (2<<6) | (0<<4) | (0<<3) | (1<<2) | (1);
/* Master clock = PCLK/(n+1)
* fs = Master clock / 384
* fs = PCLK / (n+1) / 384
*/
for (i = 0; i <= 31; i++)
{
tmp_fs = PCLK/384/(i+1);
if (ABS(tmp_fs, fs) < min)
{
min = ABS(tmp_fs, fs);
pre = i;
}
}
IISPSR = (pre << 5) | (pre);
/*
* bit15 : Transmit FIFO access mode select, 1-DMA
* bit13 : Transmit FIFO, 1-enable
*/
IISFCON = (1<<15) | (1<<13);
/*
* bit[5] : Transmit DMA service request, 1-enable
* bit[1] : IIS prescaler, 1-enable
*/
IISCON = (1<<5) | (1<<1) ;
}
下面是s3c2440的IIS控制器的一些相关知识:
64字节 FIFO (TxFIFO 和 和 RxFIFO ):发送数据传输中,数据被写入到 TxFIFO;接收数据传输中,从 RxFIFO读取数据。
16位移位寄存器(SFTR ):发送模式中并行数据被移位到串行数据输出,并且接收模式中串行数据输入被移位到并行数据。
传输模式:
IISMOD[7:6] :
01-接收模式
10-发送模式
11-发送和接收模式,In this mode, IIS bus interface can transmit and receive data simultaneously.
发送FIFO/接收FIFO访问模式选择位:IISFCON[15]/IISFCON[14]
1、正常模式:0,IIS的FIFO与CPU通信。
IIS 控制寄存器包含 发送FIFO和接收 FIFO 的FIFO就绪标志位,当FIFO准备传输数据时,如果发射的FIFO不是空的,则FIFO就绪的标志设置为“1”。如果传送的FIFO是空的,则FIFO就绪标志设置为“0”。接收中的 FIFO 未满,接收 FIFO 的FIFO 就绪标志设置为'1';它表明 FIFO 是否准备好了接收数据。CPU可以根据这些标志来决定什么时候来读写FIFO。
2、dma模式: 1,IIS的FIFO与dma通信。
In this mode, transmit or receive FIFO is accessible by the DMA controller. DMA service request in transmit or receive mode is made by the FIFO ready flag automatically.
本例使用的是发送FIFO,DMA模式:IISFCON[15]=1。dma传输中的目的地址是0x55000010,是发送FIFO入口地址。
好,现在准备好了音乐文件,即从nand上复制到SDRAM了,又初始化好了dma和iis,我们再用3线接口读写wm8976的内部寄存器来初始化它。然后我们启动dma和iis,dma就会自动的从SDRAM中拷贝音乐到iis的发送FIFO,这些数据会被传到IIS的16位移位寄存器,iis控制器把它一位一位的出现在IISDO上,wm8976接收数据解码,转为模拟信号播放,这样就可以听到音乐了。这些都是自动执行,不需CPU参与。
裸板中的main()函数的大概流程:
read_wav(wav_buf, &fs, &channels, &bits_per_sample, &wav_size);
/* soc init
* 初始化IIS之后它才会给codec芯片发送时钟
* codec芯片才能被使用
*/
iis_init(bits_per_sample, fs);
dma_init(wav_buf, wav_size);
/* machine init */
jz2440_init(); //配置为3线
/* codec init */
wm8976_init();
set_volume(volume);
dma_start();
iis_start();
iis_stop();
dma_stop();