Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1057563
  • 博文数量: 166
  • 博客积分: 10217
  • 博客等级: 上将
  • 技术积分: 2133
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-09 19:45
文章分类

全部博文(166)

文章存档

2012年(3)

2011年(7)

2010年(18)

2009年(59)

2008年(79)

我的朋友

分类: LINUX

2008-08-14 11:20:51

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
阅读(1614) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~