linux音频驱动分析
creator sz111@126.com
int __init utu2440_uda1341_init(void)
{
int ret = 0;
//printk("ghcstop.........probe\n");
//首先是对L3总线的一些控制操作。
ret = l3_attach_client(&uda1341, "l3-bit-24x0-gpio", "uda1341");
if (ret)
{
printk("l3_attach_client() failed.\n");
return ret;
}
l3_open(&uda1341);
start_uda1341();
//定义输出和输入stream和对应的DMA
output_stream.dma_ch = S3C2410_DMA_CH2;
output_stream.dmaclient.name = "audio_out";
//申请输出DMA
if (audio_init_dma(&output_stream, "UDA1341 out"))
{
audio_clear_dma(&output_stream);
printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n");
return -EBUSY;
}
input_stream.dma_ch = S3C2410_DMA_CH1;
input_stream.dmaclient.name = "audio_in";
if (audio_init_dma(&input_stream, "UDA1341 in"))
{
audio_clear_dma(&input_stream);
printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n");
return -EBUSY;
}
audio_dev_dsp = register_sound_dsp(&utu2440_audio_fops, -1);
audio_dev_mixer = register_sound_mixer(&utu2440_mixer_fops, -1);
printk(AUDIO_NAME_VERBOSE " initialized\n");
return 0;
}
上述中audio_stream的数据结构如下:
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;
typedef struct
{
audio_buf_t *buffers; /* pointer to audio buffer structures */
audio_buf_t *buf; /* current buffer used by read/write */
u_int buf_idx; /* index for the pointer above */
u_int fragsize; /* fragment i.e. buffer size */
u_int nbfrags; /* nbr of fragments */
int bytecount; /* nbr of processed bytes */
int fragcount; /* nbr of fragment transitions */
u_int channels; /* audio channels 1:mono, 2:stereo */
u_int rate; /* audio rate */
dmach_t dma_ch; /* DMA channel (channel2 for audio) */
int active:1; /* actually in progress */
int stopped:1; /* might be active but stopped */
wait_queue_head_t frag_wq; /* for poll(), etc. */
s3c2410_dma_client_t dmaclient; /* kernel 2.6 dma client */
} audio_stream_t;
void __exit utu2440_uda1341_exit(void)
{
unregister_sound_dsp(audio_dev_dsp);
unregister_sound_mixer(audio_dev_mixer);
audio_clear_dma(&output_stream);
audio_clear_dma(&input_stream);
l3_close(&uda1341);
l3_detach_client(&uda1341);
printk(AUDIO_NAME_VERBOSE " unloaded\n");
//return 0;
}
/* s3c2410_request_dma
*
* get control of an dma channel
*/
//这个函数的任务就是申请DMA通道和DMA中断。
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;
pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p\n",
channel, client->name, dev);
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) {
pr_debug("dma%d: %s : requesting irq %d\n",
channel, __FUNCTION__, chan->irq);
//申请DMA中断,每个通道有一个他对应的中断,但是所有的通道的中断
//都是采用s3c2410_dma_irq函数进行处理的,所以要在s3c2410_dma_irq
//函数中对通道进行判断,所以在request_irq中最后一个数据是channel的
//指针。虽然这个申请的中断并不是共享中断。
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);
/* need to setup */
pr_debug("%s: channel initialised, %p\n", __FUNCTION__, chan);
return 0;
}
//dma中断的处理程序。
static irqreturn_t
s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
{
s3c2410_dma_chan_t *chan = (s3c2410_dma_chan_t *)devpw;
s3c2410_dma_buf_t *buf;
buf = chan->curr;
dbg_showchan(chan);
/* modify the channel state */
switch (chan->load_state) {
case S3C2410_DMALOAD_1RUNNING:
/* TODO - if we are running only one buffer, we probably
* want to reload here, and then worry about the buffer
* callback */
chan->load_state = S3C2410_DMALOAD_NONE;
break;
case S3C2410_DMALOAD_1LOADED:
/* iirc, we should go back to NONE loaded here, we
* had a buffer, and it was never verified as being
* loaded.
*/
chan->load_state = S3C2410_DMALOAD_NONE;
break;
case S3C2410_DMALOAD_1LOADED_1RUNNING:
/* we'll worry about checking to see if another buffer is
* ready after we've called back the owner. This should
* ensure we do not wait around too long for the DMA
* engine to start the next transfer
*/
chan->load_state = S3C2410_DMALOAD_1LOADED;
break;
case S3C2410_DMALOAD_NONE:
printk(KERN_ERR "dma%d: IRQ with no loaded buffer?\n",
chan->number);
break;
default:
printk(KERN_ERR "dma%d: IRQ in invalid load_state %d\n",
chan->number, chan->load_state);
break;
}
if (buf != NULL) {
/* update the chain to make sure that if we load any more
* buffers when we call the callback function, things should
* work properly */
chan->curr = buf->next;
buf->next = NULL;
if (buf->magic != BUF_MAGIC) {
printk(KERN_ERR "dma%d: %s: buf %p incorrect magic\n",
chan->number, __FUNCTION__, buf);
return IRQ_HANDLED;
}
s3c2410_dma_buffdone(chan, buf, S3C2410_RES_OK);
/* free resouces */
s3c2410_dma_freebuf(buf);
} else {
}
if (chan->next != NULL) {
unsigned long flags;
switch (chan->load_state) {
case S3C2410_DMALOAD_1RUNNING:
/* don't need to do anything for this state */
break;
case S3C2410_DMALOAD_NONE:
/* can load buffer immediately */
break;
case S3C2410_DMALOAD_1LOADED:
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
/* flag error? */
printk(KERN_ERR "dma%d: timeout waiting for load\n",
chan->number);
return IRQ_HANDLED;
}
break;
case S3C2410_DMALOAD_1LOADED_1RUNNING:
goto no_load;
default:
printk(KERN_ERR "dma%d: unknown load_state in irq, %d\n",
chan->number, chan->load_state);
return IRQ_HANDLED;
}
local_irq_save(flags);
s3c2410_dma_loadbuffer(chan, chan->next);
local_irq_restore(flags);
} else {
s3c2410_dma_lastxfer(chan);
/* see if we can stop this channel.. */
if (chan->load_state == S3C2410_DMALOAD_NONE) {
pr_debug("dma%d: end of transfer, stopping channel (%ld)\n",
chan->number, jiffies);
s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP);
}
}
no_load:
return IRQ_HANDLED;
}
Atomic transfer:指的是DMA的单次原子操作,它可以是Unit模式(传输1个data size),也可以是burst模式(传输4个data size),具体对应DCON[28]。
Data Size:指的是单次原子操作的数据位宽,8、16、32,具体对应DCON[21:20]。
Request
Source:DMA请求的来源有两种,软件&硬件模块,由DCON[23]控制;当为前者时,由软件对DMASKTRIG寄存器的位0置位触发一次
DMA 操作。当为后者时,具体来源由DCON[26:24]控制,不同硬件模块的某时间触发一次DMA操作,具体要见不同的硬件模块。
DMA service
mode:DMA的工作模式有两种,单一服务模式&整体服务模式。前一模式下,一次DMA请求完成一项原子操作,并且transfer
count的值减1。后一模式下,一次DMA请求完成一批原子操作,直到transfer
count等于0表示完成一次整体服务。具体对应DCON[27]。
RELOAD:在reload模式下,当transfer count的值变为零时,将自动加src、dst、TC的值加载到CURR_DST、
CURR_SRC、CURR_TC,并开始一次新的DMA传输。该模式一般和整体服务模式一起使用,也就是说当一次整体服务开始后,src、dst、TC
的值都已经被加载,因此可以更改为下一次
服务的地址,2410说明文档中建议加入以下语句来判断当前的服务开始,src、dst、TC的值可以被更改了:while((rDSTATn & 0xfffff) == 0) ;
Req&Ack:DMA请求和应答的协议有两种,Demard mode 和 Handshake mode。两者对Request和Ack的时序定义有所不同:在Demard模式下,如果
DMA完成一次请求如果Request仍然有效,那么DMA就认为这是下一次DMA请求;在Handshake模式下,DMA完成一次请求后等待Request信号无效,然后把ACK也置无效,再等待下一次Request。这个设计外部DMA请求时可能要用到。
传输总长度:DMA一次整体服务传输的总长度为:
Data Size × Atomic transfer size × TC(字节)。
static int __init audio_init_dma(audio_stream_t * s, char *desc)
{
int ret;
if (s->dma_ch == S3C2410_DMA_CH2) // 输出DMA
{
//申请DMA
ret = s3c2410_dma_request(s->dma_ch, &(s->dmaclient), NULL);
if( ret )
{
dprintk("%s: dma request err\n", __FUNCTION__ );
return ret;
}
ao_dcon =
S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH2_I2SSDO|S3C2410_DCON_NORELOAD|
s3c2410_dma_config(s->dma_ch, 2, ao_dcon); // a out, halfword
/* flags */
#define S3C2410_DMAF_SLOW (1dma_ch, S3C2410_DMAF_AUTOSTART); // a out
//在这里定义buffdone callback,他会在dma中断里面调用。
s3c2410_dma_set_buffdone_fn(s->dma_ch, audio_dmaout_done_callback);
//设定sourc为mem,
#define BUF_ON_MEM (ON_AHB | ADDR_INC)
#define BUF_ON_APB (ON_APB | ADDR_FIX)
s3c2410_dma_devconfig(s->dma_ch, S3C2410_DMASRC_MEM, BUF_ON_APB, 0x55000010);
dprintk("%s: dma request done audio out channel\n", __FUNCTION__ );
return 0;
}
else if (s->dma_ch == S3C2410_DMA_CH1)
{
ret = s3c2410_dma_request(s->dma_ch, &(s->dmaclient), NULL);
if( ret )
{
dprintk("%s: dma request err\n", __FUNCTION__ );
return ret;
}
ai_dcon =
S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH1_I2SSDI|S3C2410_DCON_NORELOAD|
s3c2410_dma_config(s->dma_ch, 2, ai_dcon); // a in, halfword
s3c2410_dma_setflags(s->dma_ch, S3C2410_DMAF_AUTOSTART); // a in
s3c2410_dma_set_buffdone_fn(s->dma_ch, audio_dmain_done_callback);
s3c2410_dma_devconfig(s->dma_ch, S3C2410_DMASRC_HW, BUF_ON_APB, 0x55000010);
dprintk("%s: dma request done audio in channel\n", __FUNCTION__ );
return 0;
}
else
return 1;
}
然后就是对open的介绍了。
static int
utu2440_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)
{
audio_rate = AUDIO_RATE_DEFAULT;
audio_channels = AUDIO_CHANNELS_DEFAULT;
/*
* the UDA1341 is stereo only ==> 2 channels
*/
if ((file->f_mode & FMODE_WRITE))
{
output_stream.fragsize = AUDIO_FRAGSIZE_DEFAULT; //每个缓冲区的大小
output_stream.nbfrags = AUDIO_NBFRAGS_DEFAULT; //环形缓冲区的数量
output_stream.channels = audio_channels;
start_utu2440_iis_bus_tx();
audio_clear_buf(&output_stream);
init_waitqueue_head(&output_stream.frag_wq);
}
if ((file->f_mode & FMODE_READ))
{
input_stream.fragsize = AUDIO_FRAGSIZE_DEFAULT;
input_stream.nbfrags = AUDIO_NBFRAGS_DEFAULT;
input_stream.channels = audio_channels;
start_utu2440_iis_bus_rx();
audio_clear_buf(&input_stream);
init_waitqueue_head(&input_stream.frag_wq);
}
}
return 0;
}
关键部分的函数。
static ssize_t
utu2440_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;
dprintk("audio_write : start count=%d\n", count);
switch (file->f_flags & O_ACCMODE)
{
case O_WRONLY:
case O_RDWR:
break;
default:
return -EPERM;
}
if (!s->buffers && audio_setup_buf(s))
return -ENOMEM;
count &= ~0x03;
while (count > 0)
{
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 (s->channels == 2)
{ //每次传输的是一个缓冲区的大小,b->size代表目前传输了多少。
chunksize = s->fragsize - b->size;
if (chunksize > count)
chunksize = count;
dprintk("write %d to %d\n", chunksize, s->buf_idx);
if (copy_from_user(b->start + b->size, buffer, chunksize))
{
up(&b->sem);
return -EFAULT;
}
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);
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 fragsize)
{
up(&b->sem);
break;
}
s->active = 1; // ghcstop add
//每次从b->dma_addr开始传输 b->size个数据,一直传输到所有的缓冲区满,
//等待dma传输中断,释放sem,可以有空闲的缓冲区。
s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size);
b->size = 0;
NEXT_BUF(s, buf);
}
if ((buffer - buffer0))
ret = buffer - buffer0;
dprintk("audio_write : end count=%d\n\n", ret);
return ret;
}
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/49088/showart_499493.html