×
单片机 > 单片机程序设计 > 详情

s3c2410上iis接口的uda341驱动的学习

发布时间:2020-05-16 发布时间:
|
首先看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的,先对它进行分析,了解下驱动本身的东西,后文贴的源码我以注释的形式增加了自己的一些理解.

模块初始化:

 

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 resouce
");
                return -ENOENT;
        }
//2410iis的虚拟地址,不知道什么时候ioremap过来的
        iis_base = (void *)S3C24XX_VA_IIS ;
        if (iis_base == 0) {
                printk(KERN_INFO PFX "failed to ioremap() region
");
                return -EINVAL;
        }
//clk相关的操作,见后文的分析
        iis_clock = clk_get(dev, "iis");
        if (iis_clock == NULL) {
                printk(KERN_INFO PFX "failed to find clock source
");
                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();
//初始化uda1341
        init_uda1341();
//初始化缓冲用的stream
//输出用dma的channel2
        output_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 channels
" );
                return -EBUSY;
        }
//输入用dma的channel1
        input_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 channels
" );
                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 " initialized
");
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;
   else
      idno = 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)
",
                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)
",
                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=1
        s3c2410_gpio_setpin(S3C2410_GPB4,1);//L3CLOCK=1
        local_irq_restore(flags);
uda1341_l3_address(UDA1341_REG_STATUS);
        uda1341_l3_data(0x40 | STAT0_SC_384FS | STAT0_IF_MSB|STAT0_DC_FILTER); // reset uda1341
        uda1341_l3_data(STAT1 | STAT1_ADC_ON | STAT1_DAC_ON);
uda1341_l3_address(UDA1341_REG_DATA0);
        uda1341_l3_data(DATA0 |DATA0_VOLUME(0x0)); // maximum volume
        uda1341_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还是memory
        s3c2410_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 sdo
               dcon = 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 ;
        }
else
return 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=%08lx
",
       __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=%d
",
          __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的目的地址寄存器DIDST
      chan->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=%d
",
           __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)
", 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 %d
", __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 use
", channel);
         local_irq_restore(flags);
         return -EBUSY;
      } else {
         printk(KERN_ERR "dma%d: client already has channel
", channel);
      }
   }
chan->client = client;
   chan->in_use = 1;
if (!chan->irq_claimed) {
//安装对应dma通道的中断,可以看出每个dma通道共用了同一个中断服务程序s3c2410_dma_irq
      err = 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 %d
",
                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_open
");
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++;
} else
return -EINVAL;
if (cold) {
//第一次open,赋初值
//44100
audio_rate = AUDIO_RATE_DEFAULT;
//2
audio_channels = AUDIO_CHANNELS_DEFAULT;
//8192,8k
audio_fragsize = AUDIO_FRAGSIZE_DEFAULT;
//8
audio_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_buf
");
//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指向当前的fragment
      audio_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 %d
", 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.
");
         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=%d

", 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是8k
   if (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个fragment
      audio_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 %d
", 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 memory
 ");
   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)
",
          __FUNCTION__, sizeof(*buf));
      return -ENOMEM;
   }
pr_debug("%s: new buffer %p
", __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的buf
   if (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 buffer
",
                   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模块用于接收数据 


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM 汇编的必知必会