linux音頻驅動分析

linux音頻驅動分析
creator   [email protected]
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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章