之前已經寫了一篇文章《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線接口時序:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX61keOpXWE9UeJpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN3kTNzYzM1EDMzcDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
剛開始全部設為高電平:
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();