s3c2410上iis接口的uda341驅(qū)動的學(xué)習(xí)
接口: i2s, 還有一個(gè)L3接口,應(yīng)該是控制其中的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. (從時(shí)序圖上看, MSB和LSB表示大小端, justified可能是指ws信號變化后的第一個(gè)位時(shí)鐘的上升沿采集第一位,原始的i2s格式是第2個(gè)位時(shí)鐘上升沿開始采樣的.
速率: 1fs input and output format data rate
引腳: 除去電壓,余下的引腳主要就是i2s接口和L3接口的引腳了
uda1341還有個(gè)L3接口,由3根線構(gòu)成:時(shí)鐘,數(shù)據(jù),模式.soc可以通過它來控制uda1341的音頻處理功能,還可以獲取一些狀態(tài)信息. 從協(xié)議上看很簡單,首先mode引腳拉低,送一個(gè)8bit的地址,地址的最后兩位是一個(gè)選擇分量,然后拉高mode,發(fā)送8bit數(shù)據(jù),根據(jù)之前的選擇分量又表示3種寄存器.這8位數(shù)據(jù)的高位本身又是個(gè)選擇分量,datasheet里面定義了各種選擇分量時(shí)對應(yīng)值的表格,到時(shí)候根據(jù)它寫個(gè)狀態(tài)機(jī)就可以了.
uda1341需要了解的東西就這么多了,接下來就可以看實(shí)際的驅(qū)動了.網(wǎng)上找了個(gè)lfc修改過的uda1341驅(qū)動,不過仿佛是基于OSS的,先對它進(jìn)行分析,了解下驅(qū)動本身的東西,后文貼的源碼我以注釋的形式增加了自己的一些理解.
本文引用地址:http://butianyuan.cn/article/201611/316666.htm模塊初始化:
static int __init s3c2410_uda1341_init(void) {//初始化輸入和輸出的緩沖區(qū)(xxx_stream),這兩個(gè)struct封裝了dma會用到的一些信息.//這個(gè)結(jié)構(gòu)后文用到的時(shí)候再分析memzero(&input_stream, sizeof(audio_stream_t)); memzero(&output_stream, sizeof(audio_stream_t));//內(nèi)核提供的驅(qū)動注冊函數(shù)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提供的注冊驅(qū)動的函數(shù),這里表示該驅(qū)動是為platform總線上的設(shè)備服務(wù)的.當(dāng)總線上有新設(shè)備的時(shí)候就會調(diào)用s3c2410iis_probe來判斷該設(shè)備是否存在且能使用.
static int s3c2410iis_probe(struct device *dev) {//轉(zhuǎn)換為platform_device,因?yàn)樵擈?qū)動是在platform總線上的,所以device結(jié)構(gòu)肯定是嵌入在platform_device內(nèi)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的虛擬地址,不知道什么時(shí)候ioremap過來的iis_base = (void *)S3C24XX_VA_IIS ;if (iis_base == 0) {printk(KERN_INFO PFX "failed to ioremap() regionn");return -EINVAL;}//clk相關(guān)的操作,見后文的分析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內(nèi)核有此函數(shù),意思是增加一個(gè)引用計(jì)數(shù),最新的內(nèi)核已經(jīng)沒有了clk_use(iis_clock);//使能iis的時(shí)鐘,因?yàn)?410啟動時(shí)disable了該時(shí)鐘的//對此處修改有疑問,因?yàn)楹竺娴膇nit_s3c2410_iis_bus中又disable了iis的時(shí)鐘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里面,這個(gè)文件提供了所有s3c系列的cpu的時(shí)鐘方面的管理接口.
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);//從維護(hù)的一個(gè)struct clk鏈表中找到名字等于輸入?yún)?shù)的結(jié)點(diǎn),這個(gè)鏈表中的元素是具體的cpu的初始時(shí)注冊進(jìn)去的,見后文描述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里面,定義了啟動時(shí)要使能或不使能的時(shí)鐘源
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,}, ... ...};
系統(tǒng)初始化的時(shí)候會調(diào)用該文件的s3c2410_baseclk_add里面,調(diào)用了plat-s3c24xx提供的接口注冊這些時(shí)鐘源:
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延時(shí),來模擬的L3接口協(xié)議.傳輸時(shí)調(diào)用local_irq_save關(guān)了中斷的.
probe函數(shù)的最后部分是對輸入和輸出流的初始化,主要就是初始化對應(yīng)的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內(nèi)核定義的enum,就兩個(gè)值,表示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;//因?yàn)槭禽敵?所以源是內(nèi)存source = S3C2410_DMASRC_MEM;hwcfg = 3;//2410的iis接口中fifo數(shù)據(jù)寄存器的物理地址,16bit寬devaddr = 0x55000010;//DCON寄存器的初始值,這里的取值表示://handshake mode,傳輸完成產(chǎn)生中斷,DREQ and DACK are synchronized to PCLK (APB clock)//讀寫各一字節(jié)后釋放總線,single service mode,auto reload,dma的請求源是iis sdodcon = 0xa0800000;//?flags = S3C2410_DMAF_AUTOSTART;//配置dma傳輸中,iis控制器一端的寄存器值//hwcfg=3,表示設(shè)備是在APB總線上,且傳輸過程中地址不遞增//看2410datasheet的框圖,可以看到iis控制器是在APB總線上//讀iis數(shù)據(jù)fifo的時(shí)候是一直讀同一個(gè)地址s3c2410_dma_devconfig(channel, source, hwcfg, devaddr);//源地址位寬為2個(gè)字節(jié),硬件源觸發(fā),完成一次傳輸要產(chǎn)生中斷s3c2410_dma_config(channel, 2, dcon);//安裝一個(gè)回調(diào)函數(shù)s3c2410_dma_set_buffdone_fn(channel, audio_dmaout_done_callback);//保存一個(gè)標(biāo)志s3c2410_dma_setflags(channel, flags);//請求對應(yīng)的dma通道,詳細(xì)內(nèi)容見后文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){//系統(tǒng)定義了4個(gè)s3c2410_dma_chan_t結(jié)構(gòu),記錄了這4個(gè)dma通道的所有信息//此處根據(jù)輸入?yún)?shù)選擇將要配置的通道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數(shù)據(jù)寄存器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總線上,地址遞增,因?yàn)槭莾?nèi)存緩沖區(qū)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;}//設(shè)置dma請求源是硬件,如iis sdi等dcon |= S3C2410_DCON_HWTRIG;//設(shè)置一次dma傳輸結(jié)束后觸發(fā)中斷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);//是否已經(jīng)有人申請過該通道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) {//安裝對應(yīng)dma通道的中斷,可以看出每個(gè)dma通道共用了同一個(gè)中斷服務(wù)程序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;}
至此,已經(jīng)完成了dma等相關(guān)部分的初始化,最后調(diào)用oss驅(qū)動提供的接口注冊音頻相關(guān)的回調(diào)函數(shù):
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函數(shù):
static int smdk2410_audio_open(struct inode *inode, struct file *file){//檢查讀寫計(jì)數(shù)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接口的幾個(gè)寄存器,為發(fā)送做準(zhǔn)備init_s3c2410_iis_bus_tx();//清除輸出流,后文見實(shí)現(xiàn)細(xì)節(jié)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的實(shí)現(xiàn):
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) {//如果已經(jīng)分配了緩沖區(qū),即不是第一次使用了,執(zhí)行以下操作int frag;//對鏈上的每個(gè)非空的dma緩沖區(qū)進(jìn)行釋放,這些值的具體含義要到后文分配dma緩沖區(qū)的時(shí)候分析//看了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函數(shù),這里面會涉及到dma緩沖區(qū)申請等操作:
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;}//如果還沒有分配緩沖區(qū),則調(diào)用audio_setup_buf,后文分析if (!s->buffers && audio_setup_buf(s))return -ENOMEM;//FIXME:將count的低2位置0,不知道目的是什么?count &= ~0x03;while (count > 0) {//b指向當(dāng)前的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等于一個(gè)fragment的size(8k)減去這個(gè)fragment已經(jīng)有了的數(shù)據(jù)的size(b->size),也就是還能容納的字節(jié)數(shù)chunksize = s->fragsize - b->size;if (chunksize > count)chunksize = count;//從用戶空間的buffer拷貝chunksize個(gè)字節(jié)到當(dāng)前fragment的空閑位置if (copy_from_user(b->start + b->size, buffer, chunksize)) {up(&b->sem);return -EFAULT;}//已經(jīng)有了的數(shù)據(jù)size加上本次復(fù)制的數(shù)據(jù)b->size += chunksize;} else {//單聲道只寫一半的數(shù)據(jù),但是另一半的空間的還是要空出來chunksize = (s->fragsize - b->size) >> 1;if (chunksize > count)chunksize = count;DPRINTK("write %d to %dn", chunksize*2, s->buf_idx);//單聲道數(shù)據(jù)的拷貝辦法,沒看懂,和oss應(yīng)用傳遞的數(shù)據(jù)格式有關(guān),以后再看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) {//這次寫的數(shù)據(jù)還沒有寫滿這個(gè)fragment,退出,因?yàn)橐獙憹M才開始dma傳輸up(&b->sem);break;}//將本次的數(shù)據(jù)加入dma模塊的待傳輸隊(duì)列中,詳見后文分析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會指向一個(gè)fragment,但是沒有考慮到setup buffer的時(shí)候沒有分配到8個(gè)fragment的情況.//這個(gè)時(shí)候下一個(gè)fragment的數(shù)據(jù)是無效的NEXT_BUF(s, buf);}if ((buffer - buffer0))ret = buffer - buffer0;DPRINTK("audio_write : end count=%dnn", ret);return ret;}
建立流的實(shí)現(xiàn):
static int audio_setup_buf(audio_stream_t * s){int frag;int dmasize = 0;char *dmabuf = 0;dma_addr_t dmaphys = 0;//緩沖區(qū)一共由8個(gè)fragment構(gòu)成,每個(gè)fragment的size是8kif (s->buffers)return -EBUSY;s->nbfrags = audio_nbfrags;s->fragsize = audio_fragsize;//用kmalloc分配描述8個(gè)fragment的數(shù)據(jù)結(jié)構(gòu)的空間,下面是這個(gè)數(shù)據(jù)結(jié)構(gòu)的定義,它將會保存dma緩沖區(qū)的地址,大小等//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++) {//循環(huán)8次,初始化這8個(gè)fragmentaudio_buf_t *b = &s->buffers[frag];if (!dmasize) {//第一個(gè)fragment首先嘗試分配8*8k大小的dma緩沖區(qū)dmasize = (s->nbfrags - frag) * s->fragsize;do {//FIXME:采用的一致性dma映射方式,而不是流式,此處值得考究//LDD3中建議采用的流式映射,而且最后的GFP_DMA參數(shù)按理說是老式設(shè)備(不能尋址32位地址)才需要用到的//第一個(gè)struct device* 參數(shù)為null,不知道有沒有影響dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL|GFP_DMA);if (!dmabuf)//如果分配失敗,就減小8k,繼續(xù)嘗試分配,直到分配成功或連最小的8k都無法分配idmasize -= s->fragsize;} while (!dmabuf && dmasize);//這個(gè)fragment沒有分配到dma緩沖區(qū),則報(bào)錯if (!dmabuf)goto err;//分配成功后,fragment里面的master字段保存這個(gè)dma緩沖區(qū)的大小b->master = dmasize;}//start字段保存dma緩沖區(qū)的起始內(nèi)核虛擬地址b->start = dmabuf;//dma_addr字段保存dma緩沖區(qū)的總線地址b->dma_addr = dmaphys;sema_init(&b->sem, 1);DPRINTK("buf %d: start %p dma %dn", frag, b->start, b->dma_addr);//修改幾個(gè)變量,將會用于下一個(gè)fragment的賦值dmabuf += s->fragsize;dmaphys += s->fragsize;dmasize -= s->fragsize;}//更新當(dāng)前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的實(shí)現(xiàn)可以看出,雖然指定了8個(gè)fragment,但是只有第一次進(jìn)入循環(huán)的時(shí)候才調(diào)用 dma_alloc_coherent進(jìn)行了dma緩沖區(qū)的申請操作,如果不能一次申請到8個(gè)fragment size的空間,就減1,嘗試7個(gè),依次類推.比如最終只申請到2*8k的dma緩沖區(qū),那么fragment0就保存它的起始虛擬地址和起始總線地址, 并且設(shè)置master字段為整個(gè)dma緩沖區(qū)的size,這也標(biāo)志了fragment0是保存的整個(gè)dma緩沖區(qū)的起始地址.fragment1的起始虛擬地址就是整個(gè)dma緩沖區(qū)的起始虛擬地址加8k(fragment size),fragment2即以后的fragment就是無效的.
2.6.10內(nèi)核中s3c2410_dma_enqueue的分析,最新的內(nèi)核修改了這個(gè)部分.
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傳輸?shù)臄?shù)據(jù)塊信息的數(shù)據(jù)結(jié)構(gòu),每次調(diào)用都會分配一個(gè)這個(gè)結(jié)構(gòu),并且加到它們構(gòu)成的鏈表的末尾//因?yàn)檎{(diào)用這個(gè)函數(shù)的時(shí)候之前的傳輸可能還沒有完成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) {//這個(gè)dma channel上的鏈表還是空的chan->curr = buf;chan->end = buf;chan->next = NULL;} else {//這個(gè)dma channel已經(jīng)有數(shù)據(jù)在傳輸了,加到鏈表末尾chan->end->next = buf;chan->end = buf;}//下一個(gè)要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) {//如果允許自動開始,那么加入隊(duì)列后就啟動dma傳輸s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START);}}local_irq_restore(flags);return 0;}
dma完成一次傳輸后的回調(diào)函數(shù):
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;//釋放信號量,可以在寫函數(shù)里面處理下一個(gè)fragment了up(&b->sem);wake_up(&b->sem.wait);}
至此就可以實(shí)現(xiàn)音頻的播放了.
看懂以上代碼后再看read函數(shù)就沒什么難度了,整個(gè)read的流程如下:
如果第一次調(diào)用,那么建立8個(gè)用于dma的fragment,并且加入到dma隊(duì)列里面開始接收數(shù)據(jù)
讀取一個(gè)fragment開始,讀取里面的數(shù)據(jù)
重新將該fragment提交給dma模塊用于接收數(shù)據(jù)
評論