新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > s3c2410上iis接口的uda341驅動的學習

        s3c2410上iis接口的uda341驅動的學習

        作者: 時間:2016-11-11 來源:網絡 收藏
        首先看uda1341的datasheet,將其中的關鍵點記錄下來:

        接口: i2s, 還有一個L3接口,應該是控制其中的dsp(可以在playback模式提供soft mute等功能)
        格式: MSB-justified and LSB-justified format compatible,Three combinational data formats with MSB data output and LSB 16, 18 or 20 bits data input. (從時序圖上看, MSB和LSB表示大小端, justified可能是指ws信號變化后的第一個位時鐘的上升沿采集第一位,原始的i2s格式是第2個位時鐘上升沿開始采樣的.
        速率: 1fs input and output format data rate
        引腳: 除去電壓,余下的引腳主要就是i2s接口和L3接口的引腳了
        uda1341還有個L3接口,由3根線構成:時鐘,數據,模式.soc可以通過它來控制uda1341的音頻處理功能,還可以獲取一些狀態信息. 從協議上看很簡單,首先mode引腳拉低,送一個8bit的地址,地址的最后兩位是一個選擇分量,然后拉高mode,發送8bit數據,根據之前的選擇分量又表示3種寄存器.這8位數據的高位本身又是個選擇分量,datasheet里面定義了各種選擇分量時對應值的表格,到時候根據它寫個狀態機就可以了.

        uda1341需要了解的東西就這么多了,接下來就可以看實際的驅動了.網上找了個lfc修改過的uda1341驅動,不過仿佛是基于OSS的,先對它進行分析,了解下驅動本身的東西,后文貼的源碼我以注釋的形式增加了自己的一些理解.

        本文引用地址:http://www.104case.com/article/201611/316666.htm

        模塊初始化:

        static int __init s3c2410_uda1341_init(void) {//初始化輸入和輸出的緩沖區(xxx_stream),這兩個struct封裝了dma會用到的一些信息.//這個結構后文用到的時候再分析memzero(&input_stream, sizeof(audio_stream_t));             memzero(&output_stream, sizeof(audio_stream_t));//內核提供的驅動注冊函數return driver_register(&s3c2410iis_driver);}static struct device_driver s3c2410iis_driver = {.name = "s3c2410-iis",.bus = &platform_bus_type,.probe = s3c2410iis_probe,.remove = s3c2410iis_remove,};

        driver_register是sysfs提供的注冊驅動的函數,這里表示該驅動是為platform總線上的設備服務的.當總線上有新設備的時候就會調用s3c2410iis_probe來判斷該設備是否存在且能使用.

        static int s3c2410iis_probe(struct device *dev) {//轉換為platform_device,因為該驅動是在platform總線上的,所以device結構肯定是嵌入在platform_device內struct platform_device *pdev = to_platform_device(dev);struct resource *res;unsigned long flags;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (res == NULL) {printk(KERN_INFO PFX "failed to get memory region resoucen");return -ENOENT;}//2410iis的虛擬地址,不知道什么時候ioremap過來的iis_base = (void *)S3C24XX_VA_IIS ;if (iis_base == 0) {printk(KERN_INFO PFX "failed to ioremap() regionn");return -EINVAL;}//clk相關的操作,見后文的分析iis_clock = clk_get(dev, "iis");if (iis_clock == NULL) {printk(KERN_INFO PFX "failed to find clock sourcen");return -ENOENT;}/**************************modify by lfc*****************************///2.6.11內核有此函數,意思是增加一個引用計數,最新的內核已經沒有了clk_use(iis_clock);//使能iis的時鐘,因為2410啟動時disable了該時鐘的//對此處修改有疑問,因為后面的init_s3c2410_iis_bus中又disable了iis的時鐘clk_enable(iis_clock);/*****************************end add********************************/local_irq_save(flags);//配置L3接口,i2s接口占用的GPIO引腳/* GPB 4: L3CLOCK, OUTPUT */s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);s3c2410_gpio_pullup(S3C2410_GPB4,1);... ...local_irq_restore(flags);//初始化2410的iis控制器init_s3c2410_iis_bus();//初始化uda1341init_uda1341();//初始化緩沖用的stream//輸出用dma的channel2output_stream.dma_ch = DMA_CH2;if (audio_init_dma(&output_stream, "UDA1341 out")) {audio_clear_dma(&output_stream,&s3c2410iis_dma_out);printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channelsn" );return -EBUSY;}//輸入用dma的channel1input_stream.dma_ch = DMA_CH1;if (audio_init_dma(&input_stream, "UDA1341 in")) {audio_clear_dma(&input_stream,&s3c2410iis_dma_in);printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channelsn" );return -EBUSY;}//audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);printk(AUDIO_NAME_VERBOSE " initializedn");return 0;}//初始化2410的iis控制器static void init_s3c2410_iis_bus(void){writel(0, iis_base + S3C2410_IISPSR);writel(0, iis_base + S3C2410_IISCON);writel(0, iis_base + S3C2410_IISMOD);writel(0, iis_base + S3C2410_IISFCON);clk_disable(iis_clock);}

        clk_get(dev, "iis")定義在plat-s3c24xx的clock.c里面,這個文件提供了所有s3c系列的cpu的時鐘方面的管理接口.

        struct clk *clk_get(struct device *dev, const char *id){struct clk *p;struct clk *clk = ERR_PTR(-ENOENT);int idno;if (dev == NULL || dev->bus != &platform_bus_type)idno = -1;elseidno = to_platform_device(dev)->id;mutex_lock(&clocks_mutex);//從維護的一個struct clk鏈表中找到名字等于輸入參數的結點,這個鏈表中的元素是具體的cpu的初始時注冊進去的,見后文描述list_for_each_entry(p, &clocks, list) {if (p->id == idno &&strcmp(id, p->name) == 0 &&try_module_get(p->owner)) {clk = p;break;}}... ...mutex_unlock(&clocks_mutex);return clk;}

        在match-s3c2410/clock.c里面,定義了啟動時要使能或不使能的時鐘源

        static struct clk init_clocks_disable[] = {{.name      = "nand",.id      = -1,.parent      = &clk_h,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_NAND,}, {.name      = "iis",.id      = -1,.parent      = &clk_p,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_IIS,},... ...};static struct clk init_clocks[] = {{.name      = "lcd",.id      = -1,.parent      = &clk_h,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_LCDC,}, {.name      = "gpio",.id      = -1,.parent      = &clk_p,.enable      = s3c2410_clkcon_enable,.ctrlbit   = S3C2410_CLKCON_GPIO,}, ... ...};

        系統初始化的時候會調用該文件的s3c2410_baseclk_add里面,調用了plat-s3c24xx提供的接口注冊這些時鐘源:

        int __init s3c2410_baseclk_add(void){... ...clkp = init_clocks;for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {/* ensure that we note the clock state */clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;ret = s3c24xx_register_clock(clkp);if (ret < 0) {printk(KERN_ERR "Failed to register clock %s (%d)n",clkp->name, ret);}}clkp = init_clocks_disable;for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {ret = s3c24xx_register_clock(clkp);if (ret < 0) {printk(KERN_ERR "Failed to register clock %s (%d)n",clkp->name, ret);}s3c2410_clkcon_enable(clkp, 0);}... ...}

        uda1341通過L3接口初始化:

        static void init_uda1341(void){/* GPB 4: L3CLOCK *//* GPB 3: L3DATA *//* GPB 2: L3MODE */unsigned long flags;uda1341_volume = 62 - ((DEF_VOLUME * 61) / 100);uda1341_boost = 0;//        uda_sampling = DATA2_DEEMP_NONE;//        uda_sampling &= ~(DATA2_MUTE);local_irq_save(flags);s3c2410_gpio_setpin(S3C2410_GPB2,1);//L3MODE=1s3c2410_gpio_setpin(S3C2410_GPB4,1);//L3CLOCK=1local_irq_restore(flags);uda1341_l3_address(UDA1341_REG_STATUS);uda1341_l3_data(0x40 | STAT0_SC_384FS | STAT0_IF_MSB|STAT0_DC_FILTER); // reset uda1341uda1341_l3_data(STAT1 | STAT1_ADC_ON | STAT1_DAC_ON);uda1341_l3_address(UDA1341_REG_DATA0);uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volumeuda1341_l3_data(DATA1 |DATA1_BASS(uda1341_boost)| DATA1_TREBLE(0));uda1341_l3_data((DATA2 |DATA2_DEEMP_NONE) &~(DATA2_MUTE));uda1341_l3_data(EXTADDR(EXT2));uda1341_l3_data(EXTDATA(EXT2_MIC_GAIN(0x6)) | EXT2_MIXMODE_CH1);//input channel 1 select(input channel 2 off)}

        其中的uda1341_l3_address(),uda1341_l3_data()都是操作的data,clk,mode gpio口的電平,配合udelay延時,來模擬的L3接口協議.傳輸時調用local_irq_save關了中斷的.

        probe函數的最后部分是對輸入和輸出流的初始化,主要就是初始化對應的dma通道.從2410的datasheet中dma部分可知,通道1支持 iis的sdi,通道2支持iis的sdi,sdo.所以這里將通道1用于輸入流,通道2用于了輸出流.下面是對dma部分的初始化:

        static int __init audio_init_dma(audio_stream_t * s, char *desc){int ret ;//2.6.10內核定義的enum,就兩個值,表示dma的源是hardware還是memorys3c2410_dmasrc_t source;int hwcfg;unsigned long devaddr;dmach_t channel;int dcon;unsigned int flags = 0;if(s->dma_ch == DMA_CH2){channel = 2;//因為是輸出,所以源是內存source = S3C2410_DMASRC_MEM;hwcfg = 3;//2410的iis接口中fifo數據寄存器的物理地址,16bit寬devaddr = 0x55000010;//DCON寄存器的初始值,這里的取值表示://handshake mode,傳輸完成產生中斷,DREQ and DACK are synchronized to PCLK (APB clock)//讀寫各一字節后釋放總線,single service mode,auto reload,dma的請求源是iis sdodcon = 0xa0800000;//?flags = S3C2410_DMAF_AUTOSTART;//配置dma傳輸中,iis控制器一端的寄存器值//hwcfg=3,表示設備是在APB總線上,且傳輸過程中地址不遞增//看2410datasheet的框圖,可以看到iis控制器是在APB總線上//讀iis數據fifo的時候是一直讀同一個地址s3c2410_dma_devconfig(channel, source, hwcfg, devaddr);//源地址位寬為2個字節,硬件源觸發,完成一次傳輸要產生中斷s3c2410_dma_config(channel, 2, dcon);//安裝一個回調函數s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback);//保存一個標志s3c2410_dma_setflags(channel, flags);//請求對應的dma通道,詳細內容見后文ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_out, NULL);s->dma_ok = 1;return ret;}else if(s->dma_ch == DMA_CH1){channel =1;source =S3C2410_DMASRC_HW;hwcfg =3;devaddr = 0x55000010;//表示dma的請求源是i2s sdi,其他類似dcon = 0xa2900000;flags = S3C2410_DMAF_AUTOSTART;s3c2410_dma_devconfig(channel, source, hwcfg, devaddr);s3c2410_dma_config(channel, 2, dcon);s3c2410_dma_set_buffdone_fn(channel, audio_dmain_done_callback);s3c2410_dma_setflags(channel, flags);ret = s3c2410_dma_request(s->dma_ch, &s3c2410iis_dma_in, NULL);s->dma_ok =1;return ret ;}elsereturn 1;}

        int s3c2410_dma_devconfig(int channel,s3c2410_dmasrc_t source,int hwcfg,unsigned long devaddr){//系統定義了4個s3c2410_dma_chan_t結構,記錄了這4個dma通道的所有信息//此處根據輸入參數選擇將要配置的通道s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];check_channel(channel);pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lxn",__FUNCTION__, (int)source, hwcfg, devaddr);chan->source = source;//保存源的目的地址,比如0x55000010,表示源就是2410的iis接口的fifo數據寄存器chan->dev_addr = devaddr;switch (source) {case S3C2410_DMASRC_HW:/* source is hardware */pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%dn",__FUNCTION__, devaddr, hwcfg);//源是iis控制器,所以地址固定,在APB總線上dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);dma_wrreg(chan, S3C2410_DMA_DISRC,  devaddr);//目的地址在AHB總線上,地址遞增,因為是內存緩沖區dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));//add_reg指向dma的目的地址寄存器DIDSTchan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DIDST);return 0;case S3C2410_DMASRC_MEM:/* source is memory */pr_debug( "%s: mem source, devaddr=%08lx, hwcfg=%dn",__FUNCTION__, devaddr, hwcfg);dma_wrreg(chan, S3C2410_DMA_DISRCC, (0<<1) | (0<<0));dma_wrreg(chan, S3C2410_DMA_DIDST,  devaddr);dma_wrreg(chan, S3C2410_DMA_DIDSTC, hwcfg & 3);chan->addr_reg = dma_regaddr(chan, S3C2410_DMA_DISRC);return 0;}printk(KERN_ERR "dma%d: invalid source type (%d)n", channel, source);return -EINVAL;}

        int s3c2410_dma_config(dmach_t channel,int xferunit,int dcon){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];check_channel(channel);switch (xferunit) {case 1://配置源地址的位寬dcon |= S3C2410_DCON_BYTE;break;case 2:dcon |= S3C2410_DCON_HALFWORD;break;case 4:dcon |= S3C2410_DCON_WORD;break;default:pr_debug("%s: bad transfer size %dn", __FUNCTION__, xferunit);return -EINVAL;}//設置dma請求源是硬件,如iis sdi等dcon |= S3C2410_DCON_HWTRIG;//設置一次dma傳輸結束后觸發中斷dcon |= S3C2410_DCON_INTREQ;chan->dcon = dcon;chan->xfer_unit = xferunit;return 0;}

        int s3c2410_dma_request(unsigned int channel, s3c2410_dma_client_t *client,void *dev){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];unsigned long flags;int err;check_channel(channel);local_irq_save(flags);dbg_showchan(chan);//是否已經有人申請過該通道if (chan->in_use) {if (client != chan->client) {printk(KERN_ERR "dma%d: already in usen", channel);local_irq_restore(flags);return -EBUSY;} else {printk(KERN_ERR "dma%d: client already has channeln", channel);}}chan->client = client;chan->in_use = 1;if (!chan->irq_claimed) {//安裝對應dma通道的中斷,可以看出每個dma通道共用了同一個中斷服務程序s3c2410_dma_irqerr = request_irq(chan->irq, s3c2410_dma_irq, SA_INTERRUPT,client->name, (void *)chan);if (err) {chan->in_use = 0;local_irq_restore(flags);printk(KERN_ERR "%s: cannot get IRQ %d for DMA %dn",client->name, chan->irq, chan->number);return err;}chan->irq_claimed = 1;chan->irq_enabled = 1;}local_irq_restore(flags);return 0;}

        至此,已經完成了dma等相關部分的初始化,最后調用oss驅動提供的接口注冊音頻相關的回調函數:

        static struct file_operations smdk2410_audio_fops = {llseek: smdk2410_audio_llseek,write: smdk2410_audio_write,read: smdk2410_audio_read,poll: smdk2410_audio_poll,ioctl: smdk2410_audio_ioctl,open: smdk2410_audio_open,release: smdk2410_audio_release};static struct file_operations smdk2410_mixer_fops = {ioctl: smdk2410_mixer_ioctl,open: smdk2410_mixer_open,release: smdk2410_mixer_release};
        接下來分析其中的open函數:

        static int smdk2410_audio_open(struct inode *inode, struct file *file){//檢查讀寫計數int cold = !audio_active;DPRINTK("audio_openn");if ((file->f_flags & O_ACCMODE) == O_RDONLY) {if (audio_rd_refcount || audio_wr_refcount)return -EBUSY;audio_rd_refcount++;} else if ((file->f_flags & O_ACCMODE) == O_WRONLY) {if (audio_wr_refcount)return -EBUSY;audio_wr_refcount++;} else if ((file->f_flags & O_ACCMODE) == O_RDWR) {if (audio_rd_refcount || audio_wr_refcount)return -EBUSY;audio_rd_refcount++;audio_wr_refcount++;} elsereturn -EINVAL;if (cold) {//第一次open,賦初值//44100audio_rate = AUDIO_RATE_DEFAULT;//2audio_channels = AUDIO_CHANNELS_DEFAULT;//8192,8kaudio_fragsize = AUDIO_FRAGSIZE_DEFAULT;//8audio_nbfrags = AUDIO_NBFRAGS_DEFAULT;if ((file->f_mode & FMODE_WRITE)){//初始化iis接口的幾個寄存器,為發送做準備init_s3c2410_iis_bus_tx();//清除輸出流,后文見實現細節audio_clear_buf(&output_stream);}if ((file->f_mode & FMODE_READ)){init_s3c2410_iis_bus_rx();audio_clear_buf(&input_stream);}}return 0;}

        clear stream的實現:

        static void audio_clear_buf(audio_stream_t * s){DPRINTK("audio_clear_bufn");//flush dma 通道if(s->dma_ok) s3c2410_dma_ctrl(s->dma_ch, S3C2410_DMAOP_FLUSH);if (s->buffers) {//如果已經分配了緩沖區,即不是第一次使用了,執行以下操作int frag;//對鏈上的每個非空的dma緩沖區進行釋放,這些值的具體含義要到后文分配dma緩沖區的時候分析//看了audio_setup_buf后再看這段代碼就明白了for (frag = 0; frag < s->nbfrags; frag++) {if (!s->buffers[frag].master)continue;dma_free_coherent(NULL,s->buffers[frag].master,s->buffers[frag].start,s->buffers[frag].dma_addr);}kfree(s->buffers);s->buffers = NULL;}s->buf_idx = 0;s->buf = NULL;}

        接下來分析write函數,這里面會涉及到dma緩沖區申請等操作:

        static ssize_t smdk2410_audio_write(struct file *file, const char *buffer,size_t count, loff_t * ppos){const char *buffer0 = buffer;audio_stream_t *s = &output_stream;int chunksize, ret = 0;switch (file->f_flags & O_ACCMODE) {case O_WRONLY:case O_RDWR:break;default:return -EPERM;}//如果還沒有分配緩沖區,則調用audio_setup_buf,后文分析if (!s->buffers && audio_setup_buf(s))return -ENOMEM;//FIXME:將count的低2位置0,不知道目的是什么?count &= ~0x03;while (count > 0) {//b指向當前的fragmentaudio_buf_t *b = s->buf;//獲取信號量,有阻塞和非阻塞兩種方式if (file->f_flags & O_NONBLOCK) {ret = -EAGAIN;if (down_trylock(&b->sem))break;} else {ret = -ERESTARTSYS;if (down_interruptible(&b->sem))break;}if (audio_channels == 2) {//chunksize等于一個fragment的size(8k)減去這個fragment已經有了的數據的size(b->size),也就是還能容納的字節數chunksize = s->fragsize - b->size;if (chunksize > count)chunksize = count;//從用戶空間的buffer拷貝chunksize個字節到當前fragment的空閑位置if (copy_from_user(b->start + b->size, buffer, chunksize)) {up(&b->sem);return -EFAULT;}//已經有了的數據size加上本次復制的數據b->size += chunksize;} else {//單聲道只寫一半的數據,但是另一半的空間的還是要空出來chunksize = (s->fragsize - b->size) >> 1;if (chunksize > count)chunksize = count;DPRINTK("write %d to %dn", chunksize*2, s->buf_idx);//單聲道數據的拷貝辦法,沒看懂,和oss應用傳遞的數據格式有關,以后再看if (copy_from_user_mono_stereo(b->start + b->size,buffer, chunksize)) {up(&b->sem);return -EFAULT;}b->size += chunksize*2;}buffer += chunksize;count -= chunksize;if (b->size < s->fragsize) {//這次寫的數據還沒有寫滿這個fragment,退出,因為要寫滿才開始dma傳輸up(&b->sem);break;}//將本次的數據加入dma模塊的待傳輸隊列中,詳見后文分析if((ret = s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size))) {printk(PFX"dma enqueue failed.n");return ret;}b->size = 0;//FIXME:感覺這里的代碼有問題,next_buf會指向一個fragment,但是沒有考慮到setup buffer的時候沒有分配到8個fragment的情況.//這個時候下一個fragment的數據是無效的NEXT_BUF(s, buf);}if ((buffer - buffer0))ret = buffer - buffer0;DPRINTK("audio_write : end count=%dnn", ret);return ret;}

        建立流的實現:

        static int audio_setup_buf(audio_stream_t * s){int frag;int dmasize = 0;char *dmabuf = 0;dma_addr_t dmaphys = 0;//緩沖區一共由8個fragment構成,每個fragment的size是8kif (s->buffers)return -EBUSY;s->nbfrags = audio_nbfrags;s->fragsize = audio_fragsize;//用kmalloc分配描述8個fragment的數據結構的空間,下面是這個數據結構的定義,它將會保存dma緩沖區的地址,大小等//typedef struct {//int size; /* buffer size *///char *start; /* point to actual buffer *///dma_addr_t dma_addr; /* physical buffer address *///struct semaphore sem; /* down before touching the buffer *///int master; /* owner for buffer allocation, contain size when true *///} audio_buf_t;s->buffers = (audio_buf_t *)kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);if (!s->buffers)goto err;memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags);for (frag = 0; frag < s->nbfrags; frag++) {//循環8次,初始化這8個fragmentaudio_buf_t *b = &s->buffers[frag];if (!dmasize) {//第一個fragment首先嘗試分配8*8k大小的dma緩沖區dmasize = (s->nbfrags - frag) * s->fragsize;do {//FIXME:采用的一致性dma映射方式,而不是流式,此處值得考究//LDD3中建議采用的流式映射,而且最后的GFP_DMA參數按理說是老式設備(不能尋址32位地址)才需要用到的//第一個struct device* 參數為null,不知道有沒有影響dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL|GFP_DMA);if (!dmabuf)//如果分配失敗,就減小8k,繼續嘗試分配,直到分配成功或連最小的8k都無法分配idmasize -= s->fragsize;} while (!dmabuf && dmasize);//這個fragment沒有分配到dma緩沖區,則報錯if (!dmabuf)goto err;//分配成功后,fragment里面的master字段保存這個dma緩沖區的大小b->master = dmasize;}//start字段保存dma緩沖區的起始內核虛擬地址b->start = dmabuf;//dma_addr字段保存dma緩沖區的總線地址b->dma_addr = dmaphys;sema_init(&b->sem, 1);DPRINTK("buf %d: start %p dma %dn", frag, b->start, b->dma_addr);//修改幾個變量,將會用于下一個fragment的賦值dmabuf += s->fragsize;dmaphys += s->fragsize;dmasize -= s->fragsize;}//更新當前buffer的index和指針s->buf_idx = 0;s->buf = &s->buffers[0];return 0;err:printk(AUDIO_NAME ": unable to allocate audio memoryn ");audio_clear_buf(s);return -ENOMEM;}

        從audio_setup_buf的實現可以看出,雖然指定了8個fragment,但是只有第一次進入循環的時候才調用 dma_alloc_coherent進行了dma緩沖區的申請操作,如果不能一次申請到8個fragment size的空間,就減1,嘗試7個,依次類推.比如最終只申請到2*8k的dma緩沖區,那么fragment0就保存它的起始虛擬地址和起始總線地址, 并且設置master字段為整個dma緩沖區的size,這也標志了fragment0是保存的整個dma緩沖區的起始地址.fragment1的起始虛擬地址就是整個dma緩沖區的起始虛擬地址加8k(fragment size),fragment2即以后的fragment就是無效的.

        2.6.10內核中s3c2410_dma_enqueue的分析,最新的內核修改了這個部分.

        int s3c2410_dma_enqueue(unsigned int channel, void *id,dma_addr_t data, int size){s3c2410_dma_chan_t *chan = &s3c2410_chans[channel];s3c2410_dma_buf_t *buf;unsigned long flags;check_channel(channel);//分配描述本次dma傳輸的數據塊信息的數據結構,每次調用都會分配一個這個結構,并且加到它們構成的鏈表的末尾//因為調用這個函數的時候之前的傳輸可能還沒有完成buf = (s3c2410_dma_buf_t *)kmalloc(sizeof(*buf), GFP_ATOMIC);if (buf == NULL) {pr_debug("%s: out of memory (%d alloc)n",__FUNCTION__, sizeof(*buf));return -ENOMEM;}pr_debug("%s: new buffer %pn", __FUNCTION__, buf);//dbg_showchan(chan);buf->next  = NULL;buf->data  = buf->ptr = data;buf->size  = size;buf->id    = id;buf->magic = BUF_MAGIC;local_irq_save(flags);if (chan->curr == NULL) {//這個dma channel上的鏈表還是空的chan->curr = buf;chan->end  = buf;chan->next = NULL;} else {//這個dma channel已經有數據在傳輸了,加到鏈表末尾chan->end->next = buf;chan->end = buf;}//下一個要load的bufif (chan->next == NULL)chan->next = buf;/* check to see if we can load a buffer */if (chan->state == S3C2410_DMA_RUNNING) {if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) {if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {printk(KERN_ERR "dma%d: loadbuffer:""timeout loading buffern",chan->number);dbg_showchan(chan);local_irq_restore(flags);return -EINVAL;}}while (s3c2410_dma_canload(chan) && chan->next != NULL) {s3c2410_dma_loadbuffer(chan, chan->next);}} else if (chan->state == S3C2410_DMA_IDLE) {if (chan->flags & S3C2410_DMAF_AUTOSTART) {//如果允許自動開始,那么加入隊列后就啟動dma傳輸s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START);}}local_irq_restore(flags);return 0;}

        dma完成一次傳輸后的回調函數:

        static void audio_dmaout_done_callback(s3c2410_dma_chan_t *ch, void *buf, int size,s3c2410_dma_buffresult_t result){audio_buf_t *b = (audio_buf_t *) buf;//釋放信號量,可以在寫函數里面處理下一個fragment了up(&b->sem);wake_up(&b->sem.wait);}

        至此就可以實現音頻的播放了.

        看懂以上代碼后再看read函數就沒什么難度了,整個read的流程如下:


        如果第一次調用,那么建立8個用于dma的fragment,并且加入到dma隊列里面開始接收數據
        讀取一個fragment開始,讀取里面的數據
        重新將該fragment提交給dma模塊用于接收數據


        評論


        技術專區

        關閉
        主站蜘蛛池模板: 阿勒泰市| 满城县| 枣强县| 河源市| 昌平区| 修武县| 庆安县| 三河市| 托里县| 庄河市| 上高县| 滨州市| 洪雅县| 宜州市| 且末县| 巧家县| 格尔木市| 中超| 元阳县| 广宁县| 江陵县| 上虞市| 邮箱| 红原县| 宝山区| 抚顺市| 中宁县| 丰城市| 建宁县| 合江县| 广昌县| 凯里市| 三河市| 南漳县| 临洮县| 西乌| 五原县| 长乐市| 社旗县| 阳山县| 钦州市|