Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16811
  • 博文数量: 5
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 60
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-14 16:47
文章分类
文章存档

2008年(5)

我的朋友

分类: LINUX

2008-10-15 16:36: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\n");
return -ENOENT;
}
//2410iis的虚拟地址,不知道什么时候ioremap过来的
iis_base = (void *)S3C24XX_VA_IIS ;
if (iis_base == 0) {
printk(KERN_INFO PFX "failed to ioremap() region\n");
return -EINVAL;
}
//clk相关的操作,见后文的分析
iis_clock = clk_get(dev, "iis");
if (iis_clock == NULL) {
printk(KERN_INFO PFX "failed to find clock source\n");
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\n" );
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\n" );
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\n");

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)\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=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\n",
__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\n",
__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\n",
__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 %d\n", __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\n", channel);
local_irq_restore(flags);
return -EBUSY;
} else {
printk(KERN_ERR "dma%d: client already has channel\n", 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\n",
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\n");
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\n");
//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\n", 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=%d\n\n", 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\n", 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\n ");
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 %p\n", __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\n",
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的流程如下:

  1. 如果第一次调用,那么建立8个用于dma的fragment,并且加入到dma队列里面开始接收数据
  2. 读取一个fragment开始,读取里面的数据
  3. 重新将该fragment提交给dma模块用于接收数据

阅读(2177) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~