天天看點

s3c2440裸闆實作播放音樂

        之前已經寫了一篇文章《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線接口時序:

s3c2440裸闆實作播放音樂

剛開始全部設為高電平:

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();
           

繼續閱讀