分类: LINUX
2012-05-02 17:25:35
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_getposition
*
* returns the current transfer points for the dma source and destination
*/
int s3c2410_dma_getposition(dmach_t channel, dma_addr_t *src, dma_addr_t *dst)
{
获取保存该channel信息的对象,初始化的时候讲过
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
if (src != NULL) //获取源地址
*src = dma_rdreg(chan, S3C2410_DMA_DCSRC);
if (dst != NULL) //获取目的地址
*dst = dma_rdreg(chan, S3C2410_DMA_DCDST);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_getposition);
这个函数获取某个channel当前正在传输的源地址和目的地址. 主要就是通过读该channel的源和目的寄存器获得的. S3C2410_DMA_DCSRC, S3C2410_DMA_DCDST就是源和目的的偏移地址.可参考2410的datasheet. dma_rdreg就是读寄存器.
Arch/arm/plat-s3c24xx/dma.c:
#define dma_rdreg(chan, reg) readl((chan)->regs + (reg))
接着看下一个export的函数
/* s3c2410_dma_devconfig
*
* configure the dma source/destination hardware type and address
*
* source: S3C2410_DMASRC_HW: source is hardware
* S3C2410_DMASRC_MEM: source is memory
*
* hwcfg: the value for xxxSTCn register,
* bit 0: 0=increment pointer, 1=leave pointer
* bit 1: 0=source is AHB, 1=source is APB
*
* devaddr: physical address of the source
*/
Arch/arm/plat-s3c24xx/dma.c:
int s3c2410_dma_devconfig(int channel,
enum s3c2410_dmasrc source,
int hwcfg,
unsigned long devaddr)
{
//获取保存该channel信息的对象,初始化的时候讲过
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: source=%d, hwcfg=%08x, devaddr=%08lx/n",
__FUNCTION__, (int)source, hwcfg, devaddr);
chan->source = source; //保存DMA源
chan->dev_addr = devaddr; //保存源地址
//根据不同的DMA源来初始化DMA channel
switch (source) {
case S3C2410_DMASRC_HW:
/* source is hardware */
pr_debug("%s: hw source, devaddr=%08lx, hwcfg=%d/n",
__FUNCTION__, devaddr, hwcfg);
dma_wrreg(chan, S3C2410_DMA_DISRCC, hwcfg & 3);
dma_wrreg(chan, S3C2410_DMA_DISRC, devaddr); //源地址
dma_wrreg(chan, S3C2410_DMA_DIDSTC, (0<<1) | (0<<0));
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;
}
EXPORT_SYMBOL(s3c2410_dma_devconfig);
这个函数用来配置某个channel的源的类型及源地址, 然后为某种源设置好地址增长方式, 具体寄存器含义参考2410 datasheet, 2410下 DMA的各种操作模式可参考我的另一篇文章.
Arch/arm/plat-s3c24xx/dma.c:
int s3c2410_dma_set_buffdone_fn(dmach_t channel, s3c2410_dma_cbfn_t rtn)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, callback rtn=%p/n", __FUNCTION__, chan, rtn);
chan->callback_fn = rtn; //设置回调函数
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_set_buffdone_fn);
该函数主要为某个channel设置一个done的回调函数. 该回调函数会在传输完成后被调用.
Arch/arm/plat-s3c24xx/dma.c:
/* do we need to protect the settings of the fields from
* irq?
*/
int s3c2410_dma_set_opfn(dmach_t channel, s3c2410_dma_opfn_t rtn)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, op rtn=%p/n", __FUNCTION__, chan, rtn);
chan->op_fn = rtn;
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_set_opfn);
该函数主要为某个channel设置一个操作的回调函数. 该回调函数会在操作该channel时被调用(有哪些操作会在s3c2410_dma_ctrl里看到)
Arch/arm/plat-s3c24xx/dma.c:
int s3c2410_dma_setflags(dmach_t channel, unsigned int flags)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: chan=%p, flags=%08x/n", __FUNCTION__, chan, flags);
chan->flags = flags; //设置标记
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_setflags);
该函数主要为某个channel设置一个标记, 标记有:
Include/asm-arm/arch-s3c2410/dma.h:
/* flags */
#define S3C2410_DMAF_SLOW (1<<0) /* slow, so don't worry about
* waiting for reloads */
#define S3C2410_DMAF_AUTOSTART (1<<1) /* auto-start if buffer queued */
我们会在后面看到flag的使用
Arch/arm/plat-s3c24xx/dma.c:
/* DMA configuration for each channel
*
* DISRCC -> source of the DMA (AHB,APB)
* DISRC -> source address of the DMA
* DIDSTC -> destination of the DMA (AHB,APD)
* DIDST -> destination address of the DMA
*/
/* s3c2410_dma_config
*
* xfersize: size of unit in bytes (1,2,4)
* dcon: base value of the DCONx register
*/
int s3c2410_dma_config(dmach_t channel,
int xferunit,
int dcon)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
pr_debug("%s: chan=%d, xfer_unit=%d, dcon=%08x/n",
__FUNCTION__, channel, xferunit, dcon);
if (chan == NULL)
return -EINVAL;
pr_debug("%s: Initial dcon is %08x/n", __FUNCTION__, dcon);
dcon |= chan->dcon & dma_sel.dcon_mask;
pr_debug("%s: New dcon is %08x/n", __FUNCTION__, dcon);
//设置每个传输单元的大小
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;
}
dcon |= S3C2410_DCON_HWTRIG; //硬件请求模式
dcon |= S3C2410_DCON_INTREQ; //打开中断
pr_debug("%s: dcon now %08x/n", __FUNCTION__, dcon);
//保存配置到全局变量中
chan->dcon = dcon;
chan->xfer_unit = xferunit;
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_config);
该函数主要用来配置某个channel的请求模式, 传输单元大小等. 从中可以看出目前只支持硬件请求模式
Arch/arm/plat-s3c24xx/dma.c:
int
s3c2410_dma_ctrl(dmach_t channel, enum s3c2410_chan_op op)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
if (chan == NULL)
return -EINVAL;
switch (op) {
case S3C2410_DMAOP_START:
return s3c2410_dma_start(chan); //开始一个DMA传输
case S3C2410_DMAOP_STOP:
return s3c2410_dma_dostop(chan); //停止一个DMA传输
case S3C2410_DMAOP_PAUSE:
case S3C2410_DMAOP_RESUME:
return -ENOENT;
case S3C2410_DMAOP_FLUSH:
return s3c2410_dma_flush(chan); //
case S3C2410_DMAOP_STARTED: //指示传输开始
return s3c2410_dma_started(chan);
case S3C2410_DMAOP_TIMEOUT:
return 0;
}
return -ENOENT; /* unknown, don't bother */
}
EXPORT_SYMBOL(s3c2410_dma_ctrl);
OK, 这个函数主要就是用来启用, 停止DMA操作了, 比较重要的一个函数. 等分析完了export的接口后, 我们在来逐个分析每个DMA操作.
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_free
*
* release the given channel back to the system, will stop and flush
* any outstanding transfers, and ensure the channel is ready for the
* next claimant.
*
* Note, although a warning is currently printed if the freeing client
* info is not the same as the registrant's client info, the free is still
* allowed to go through.
*/
int s3c2410_dma_free(dmach_t channel, struct s3c2410_dma_client *client)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
unsigned long flags;
if (chan == NULL)
return -EINVAL;
local_irq_save(flags);
if (chan->client != client) {
printk(KERN_WARNING "dma%d: possible free from different client (channel %p, passed %p)/n",
channel, chan->client, client);
}
/* sort out stopping and freeing the channel */
if (chan->state != S3C2410_DMA_IDLE) { //该channel正在使用中
pr_debug("%s: need to stop dma channel %p/n",
__FUNCTION__, chan);
/* possibly flush the channel */
s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STOP); //停止该channel
}
//reset该channel的相关信息
chan->client = NULL;
chan->in_use = 0;
if (chan->irq_claimed)
free_irq(chan->irq, (void *)chan); //释放该中断
chan->irq_claimed = 0;
if (!(channel & DMACH_LOW_LEVEL))
dma_chan_map[channel] = NULL;
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_free);
根据注释我们很清楚了, 该函数主要就是释放一个channel, 使其处于ready状态,
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_request_dma
*
* get control of an dma channel
*/
int s3c2410_dma_request(unsigned int channel,
struct s3c2410_dma_client *client,
void *dev)
{
struct s3c2410_dma_chan *chan;
unsigned long flags;
int err;
pr_debug("dma%d: s3c2410_request_dma: client=%s, dev=%p/n",
channel, client->name, dev);
local_irq_save(flags);
//获取空闲的channel
chan = s3c2410_dma_map_channel(channel);
if (chan == NULL) { //无空闲channel则返回失败
local_irq_restore(flags);
return -EBUSY;
}
dbg_showchan(chan);
//保存使用该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);
chan->irq_claimed = 1; //标记注册
local_irq_restore(flags);
err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED,
client->name, (void *)chan); //注册该中断
local_irq_save(flags);
if (err) { //失败则reset该channel
chan->in_use = 0;
chan->irq_claimed = 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_enabled = 1;
}
local_irq_restore(flags);
/* need to setup */
pr_debug("%s: channel initialised, %p/n", __FUNCTION__, chan);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_request);
该函数主要就是为请求的用户找到一个空闲的channel, 并把它分配给该用户, 同时打开中断, 保存相关信息.
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_enqueue
*
* queue an given buffer for dma transfer.
*
* id the device driver's id information for this buffer
* data the physical address of the buffer data
* size the size of the buffer in bytes
*
* If the channel is not running, then the flag S3C2410_DMAF_AUTOSTART
* is checked, and if set, the channel is started. If this flag isn't set,
* then an error will be returned.
*
* It is possible to queue more than one DMA buffer onto a channel at
* once, and the code will deal with the re-loading of the next buffer
* when necessary.
*/
int s3c2410_dma_enqueue(unsigned int channel, void *id,
dma_addr_t data, int size)
{
struct s3c2410_dma_chan *chan = lookup_dma_channel(channel);
struct s3c2410_dma_buf *buf;
unsigned long flags;
if (chan == NULL)
return -EINVAL;
pr_debug("%s: id=%p, data=%08x, size=%d/n",
__FUNCTION__, id, (unsigned int)data, size);
//从高速缓冲中分配一块buffer用于DMA传输, dma_kmem是我们在初始化的时候就创建好的
buf = kmem_cache_alloc(dma_kmem, GFP_ATOMIC);
if (buf == NULL) {
pr_debug("%s: out of memory (%ld alloc)/n",
__FUNCTION__, (long)sizeof(*buf));
return -ENOMEM;
}
//pr_debug("%s: new buffer %p/n", __FUNCTION__, buf);
//dbg_showchan(chan);
//初始化这块要被传输的buf
buf->next = NULL;
buf->data = buf->ptr = data; //指向要传输的data
buf->size = size; //传输大小
buf->id = id;
buf->magic = BUF_MAGIC;
local_irq_save(flags);
if (chan->curr == NULL) { //当前channel没有在传输
/* we've got nothing loaded... */
pr_debug("%s: buffer %p queued onto empty channel/n",
__FUNCTION__, buf);
chan->curr = buf; //直接挂在curr上
chan->end = buf;
chan->next = NULL;
} else { // 当前channel正在传输
pr_debug("dma%d: %s: buffer %p queued onto non-empty channel/n",
chan->number, __FUNCTION__, buf);
if (chan->end == NULL)
pr_debug("dma%d: %s: %p not empty, and chan->end==NULL?/n",
chan->number, __FUNCTION__, chan);
//把buffer挂到队列的最后面, 并重设end
chan->end->next = buf;
chan->end = buf;
}
/* if necessary, update the next buffer field */
if (chan->next == NULL)
chan->next = buf;
/* check to see if we can load a buffer */
if (chan->state == S3C2410_DMA_RUNNING) { //该channel正在运行
if (chan->load_state == S3C2410_DMALOAD_1LOADED && 1) { //已有buf load了
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { //等待load
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) { //检查能否load
s3c2410_dma_loadbuffer(chan, chan->next); //load buffer
}
} else if (chan->state == S3C2410_DMA_IDLE) { //该channel空闲着
if (chan->flags & S3C2410_DMAF_AUTOSTART) { //如果设了自动启动标记,则直接启动该次传输
s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_START); //启动传输
}
}
local_irq_restore(flags);
return 0;
}
EXPORT_SYMBOL(s3c2410_dma_enqueue);
该函数首先从先前创建的高速缓冲池中获取一个buf, 并把要传输的data保存在该buf中, 然后根据当前channel的运行状态来选择是load 该buf, 还是直接传输该buf.
Channel在运行过程中会有很多的状态, 所有状态如下:
Include/asm-arm/arch-s3c2410/dma.h:
/* enum s3c2410_dma_loadst
*
* This represents the state of the DMA engine, wrt to the loaded / running
* transfers. Since we don't have any way of knowing exactly the state of
* the DMA transfers, we need to know the state to make decisions on wether
* we can
*
* S3C2410_DMA_NONE
*
* There are no buffers loaded (the channel should be inactive)
*
* S3C2410_DMA_1LOADED
*
* There is one buffer loaded, however it has not been confirmed to be
* loaded by the DMA engine. This may be because the channel is not
* yet running, or the DMA driver decided that it was too costly to
* sit and wait for it to happen.
*
* S3C2410_DMA_1RUNNING
*
* The buffer has been confirmed running, and not finisged
*
* S3C2410_DMA_1LOADED_1RUNNING
*
* There is a buffer waiting to be loaded by the DMA engine, and one
* currently running.
*/
enum s3c2410_dma_loadst {
S3C2410_DMALOAD_NONE,
S3C2410_DMALOAD_1LOADED,
S3C2410_DMALOAD_1RUNNING,
S3C2410_DMALOAD_1LOADED_1RUNNING,
};
各种装态注释的很明显了, 我就不再罗索了.
Channel运行时会有一个正在传输的buf, 一个已经加载的buf, 还有很多等待加载的buf.
我们来把这个函数中调用的函数也逐个分析下:
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_waitforload
*
* wait for the DMA engine to load a buffer, and update the state accordingly
*/
static int
s3c2410_dma_waitforload(struct s3c2410_dma_chan *chan, int line)
{
int timeout = chan->load_timeout; //初始化时load_timeout 被设成了 1 << 18
int took;
//该函数只在S3C2410_DMALOAD_1LOADED状态下被调用
if (chan->load_state != S3C2410_DMALOAD_1LOADED) {
printk(KERN_ERR "dma%d: s3c2410_dma_waitforload() called in loadstate %d from line %d/n", chan->number, chan->load_state, line);
return 0;
}
if (chan->stats != NULL)
chan->stats->loads++; //更新统计信息
while (--timeout > 0) {
//获取还剩的传输量, 左移(32-20) 只是把[21:20]位移调, 因为它仅和0比较,所以无需确切的数据
if ((dma_rdreg(chan, S3C2410_DMA_DSTAT) << (32-20)) != 0) {
took = chan->load_timeout - timeout; //等待了这么长时间
//保存统计信息,该函数更新最长,最短超时时间, 并更新总超时时间
s3c2410_dma_stats_timeout(chan->stats, took);
switch (chan->load_state) {
case S3C2410_DMALOAD_1LOADED:
//因为有数据在传输了, 因此更新channel的状态, 从这我们也能看到, 一次只能有一个
//buf被load
chan->load_state = S3C2410_DMALOAD_1RUNNING;
break;
default:
printk(KERN_ERR "dma%d: unknown load_state in s3c2410_dma_waitforload() %d/n", chan->number, chan->load_state);
}
return 1;
}
}
if (chan->stats != NULL) {
chan->stats->timeout_failed++;
}
return 0;
}
该函数很简单, 它等待已经load的buf被start传输, 然后更新相关统计信息, 也正因为load的buf被开始传输了, 因此该函数完后, 应该会有一个新的buf被load. 至于原先load的buf是如何被start的,我们以后在看.
接下来我们看s3c2410_dma_canload函数:
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_canload
*
* work out if we can queue another buffer into the DMA engine
*/
static int
s3c2410_dma_canload(struct s3c2410_dma_chan *chan)
{
//在这2个状态下是可以load的
if (chan->load_state == S3C2410_DMALOAD_NONE ||
chan->load_state == S3C2410_DMALOAD_1RUNNING)
return 1;
return 0;
}
跑完s3c2410_dma_waitforload后如果正确, 则状态应该是S3C2410_DMALOAD_1RUNNING, 所以这里就是可以加载了, 那当然要看加载函数了
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_loadbuffer
*
* load a buffer, and update the channel state
*/
static inline int
s3c2410_dma_loadbuffer(struct s3c2410_dma_chan *chan,
struct s3c2410_dma_buf *buf)
{
unsigned long reload;
pr_debug("s3c2410_chan_loadbuffer: loading buff %p (0x%08lx,0x%06x)/n",
buf, (unsigned long)buf->data, buf->size);
if (buf == NULL) {
dmawarn("buffer is NULL/n");
return -EINVAL;
}
/* check the state of the channel before we do anything */
//状态错误, 只能有1个loaded的buf
if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
dmawarn("load_state is S3C2410_DMALOAD_1LOADED/n");
}
//状态错误, 只能有1个loaded的buf
if (chan->load_state == S3C2410_DMALOAD_1LOADED_1RUNNING) {
dmawarn("state is S3C2410_DMALOAD_1LOADED_1RUNNING/n");
}
/* it would seem sensible if we are the last buffer to not bother
* with the auto-reload bit, so that the DMA engine will not try
* and load another transfer after this one has finished...
*/
//判断是否要自动加载后续的buf, 如果有后续的buf则自动加载
if (chan->load_state == S3C2410_DMALOAD_NONE) {
pr_debug("load_state is none, checking for noreload (next=%p)/n",
buf->next);
reload = (buf->next == NULL) ? S3C2410_DCON_NORELOAD : 0;
} else {
//pr_debug("load_state is %d => autoreload/n", chan->load_state);
reload = S3C2410_DCON_AUTORELOAD;
}
if ((buf->data & 0xf0000000) != 0x30000000) {
dmawarn("dmaload: buffer is %p/n", (void *)buf->data);
}
writel(buf->data, chan->addr_reg); //写地址寄存器
//不解释了, 看寄存器说明吧
dma_wrreg(chan, S3C2410_DMA_DCON,
chan->dcon | reload | (buf->size/chan->xfer_unit));
chan->next = buf->next; //更新链表
/* update the state of the channel */
//更新状态
switch (chan->load_state) {
case S3C2410_DMALOAD_NONE:
chan->load_state = S3C2410_DMALOAD_1LOADED;
break;
case S3C2410_DMALOAD_1RUNNING:
chan->load_state = S3C2410_DMALOAD_1LOADED_1RUNNING;
break;
default:
dmawarn("dmaload: unknown state %d in loadbuffer/n",
chan->load_state);
break;
}
return 0;
}
该函数主要是把要传输的数据的地址先存入寄存器中, 等当前的传输完成后会根据时候auto reload的情况来确定是否开始这次的传输.
OK, 到目前为止讲完了所有的export的函数, 现在还剩下dma的操作函数和中断函数没讲了, let’s go!
我们先看中断函数, 该函数在一次传输完成后被调用
Arch/arm/plat-s3c24xx/dma.c:
static irqreturn_t
s3c2410_dma_irq(int irq, void *devpw)
{
struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw;
struct s3c2410_dma_buf *buf;
buf = chan->curr; //当前传输完毕的buf
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
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); //buf传输完成后的操作
/* free resouces */
s3c2410_dma_freebuf(buf); //释放buf, 我们看到传输前有申请buf
} else {
}
/* only reload if the channel is still running... our buffer done
* routine may have altered the state by requesting the dma channel
* to stop or shutdown... */
/* todo: check that when the channel is shut-down from inside this
* function, we cope with unsetting reload, etc */
//还有要传输的buf,则继续传输
if (chan->next != NULL && chan->state != S3C2410_DMA_IDLE) {
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 (%s)/n",
chan->number, __FUNCTION__);
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); //加载下一个buf
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 | DMACH_LOW_LEVEL,
S3C2410_DMAOP_STOP); //停止dma传输
}
}
no_load:
return IRQ_HANDLED;
}
我们看到当传输队列中还有buf要传输时, 没有看到start的操作, 这是为什么呢? 因为在load的时候我们分析过, 如果后续还有buf要传输, 则自动加载运行, 所以这里没有必要手工start.
s3c2410_dma_buffdone()函数仅仅是调用前面注册的回调函数, 这里不列出来了.
s3c2410_dma_freebuf()也很简单, 就是把buf归还到缓冲池去.
我们看下s3c2410_dma_lastxfer
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_lastxfer
*
* called when the system is out of buffers, to ensure that the channel
* is prepared for shutdown.
*/
static inline void
s3c2410_dma_lastxfer(struct s3c2410_dma_chan *chan)
{
#if 0
pr_debug("dma%d: s3c2410_dma_lastxfer: load_state %d/n",
chan->number, chan->load_state);
#endif
switch (chan->load_state) {
case S3C2410_DMALOAD_NONE:
break;
case S3C2410_DMALOAD_1LOADED:
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) { //等待加载的buf被执行
/* flag error? */
printk(KERN_ERR "dma%d: timeout waiting for load (%s)/n",
chan->number, __FUNCTION__);
return;
}
break;
case S3C2410_DMALOAD_1LOADED_1RUNNING:
/* I belive in this case we do not have anything to do
* until the next buffer comes along, and we turn off the
* reload */
return;
default:
pr_debug("dma%d: lastxfer: unhandled load_state %d with no next/n",
chan->number, chan->load_state);
return;
}
/* hopefully this'll shut the damned thing up after the transfer... */
//清楚自动加载标记, 因为无后续要传输的buf, 所以要清这个标记
dma_wrreg(chan, S3C2410_DMA_DCON, chan->dcon | S3C2410_DCON_NORELOAD);
}
很简单的一个函数, 这里不多说了.
好了, 到这个该着中分析操作函数了.
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_start
*
* start a dma channel going
*/
static int s3c2410_dma_start(struct s3c2410_dma_chan *chan)
{
unsigned long tmp;
unsigned long flags;
pr_debug("s3c2410_start_dma: channel=%d/n", chan->number);
local_irq_save(flags);
if (chan->state == S3C2410_DMA_RUNNING) { //已经有run的了
pr_debug("s3c2410_start_dma: already running (%d)/n", chan->state);
local_irq_restore(flags);
return 0;
}
chan->state = S3C2410_DMA_RUNNING; //更新状态
/* check wether there is anything to load, and if not, see
* if we can find anything to load
*/
if (chan->load_state == S3C2410_DMALOAD_NONE) { //没有加载过buf
if (chan->next == NULL) { //没有buf要传送的
printk(KERN_ERR "dma%d: channel has nothing loaded/n",
chan->number);
chan->state = S3C2410_DMA_IDLE;
local_irq_restore(flags);
return -EINVAL;
}
s3c2410_dma_loadbuffer(chan, chan->next); //加载buf, 加载状态也会相应更新
}
dbg_showchan(chan);
/* enable the channel */
if (!chan->irq_enabled) {
enable_irq(chan->irq); //使能中断
chan->irq_enabled = 1;
}
/* start the channel going */
//启动DMA传输,
tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
tmp &= ~S3C2410_DMASKTRIG_STOP;
tmp |= S3C2410_DMASKTRIG_ON;
dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);
pr_debug("dma%d: %08lx to DMASKTRIG/n", chan->number, tmp);
#if 0
/* the dma buffer loads should take care of clearing the AUTO
* reloading feature */
tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
tmp &= ~S3C2410_DCON_NORELOAD;
dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
#endif
s3c2410_dma_call_op(chan, S3C2410_DMAOP_START); //调用注册的op回调函数
dbg_showchan(chan);
/* if we've only loaded one buffer onto the channel, then chec
* to see if we have another, and if so, try and load it so when
* the first buffer is finished, the new one will be loaded onto
* the channel */
//由于当前load的已经在运行了, 因此如果还有要传输的buf则load进来
if (chan->next != NULL) {
if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
//等待该buf被运行, 别忘了我们设了自动加载运行.
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
pr_debug("%s: buff not yet loaded, no more todo/n",
__FUNCTION__);
} else {
chan->load_state = S3C2410_DMALOAD_1RUNNING;
s3c2410_dma_loadbuffer(chan, chan->next); //加载buf
}
} else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {
s3c2410_dma_loadbuffer(chan, chan->next); //加载buf
}
}
local_irq_restore(flags);
return 0;
}
整个启动流程就这样完了.
Arch/arm/plat-s3c24xx/dma.c:
static int s3c2410_dma_dostop(struct s3c2410_dma_chan *chan)
{
unsigned long flags;
unsigned long tmp;
pr_debug("%s:/n", __FUNCTION__);
dbg_showchan(chan);
local_irq_save(flags);
s3c2410_dma_call_op(chan, S3C2410_DMAOP_STOP); //通知用户该操作
//停止DMA传输
tmp = dma_rdreg(chan, S3C2410_DMA_DMASKTRIG);
tmp |= S3C2410_DMASKTRIG_STOP;
//tmp &= ~S3C2410_DMASKTRIG_ON;
dma_wrreg(chan, S3C2410_DMA_DMASKTRIG, tmp);
#if 0
/* should also clear interrupts, according to WinCE BSP */
tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
tmp |= S3C2410_DCON_NORELOAD;
dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
#endif
//更新状态
/* should stop do this, or should we wait for flush? */
chan->state = S3C2410_DMA_IDLE;
chan->load_state = S3C2410_DMALOAD_NONE;
local_irq_restore(flags);
return 0;
}
该函数比较简单, 接着看
Arch/arm/plat-s3c24xx/dma.c:
/* s3c2410_dma_flush
*
* stop the channel, and remove all current and pending transfers
*/
static int s3c2410_dma_flush(struct s3c2410_dma_chan *chan)
{
struct s3c2410_dma_buf *buf, *next;
unsigned long flags;
pr_debug("%s: chan %p (%d)/n", __FUNCTION__, chan, chan->number);
dbg_showchan(chan);
local_irq_save(flags);
if (chan->state != S3C2410_DMA_IDLE) {
pr_debug("%s: stopping channel.../n", __FUNCTION__ );
s3c2410_dma_ctrl(chan->number, S3C2410_DMAOP_STOP); //停调传输
}
buf = chan->curr;
if (buf == NULL)
buf = chan->next;
chan->curr = chan->next = chan->end = NULL;
if (buf != NULL) {
for ( ; buf != NULL; buf = next) {
next = buf->next;
pr_debug("%s: free buffer %p, next %p/n",
__FUNCTION__, buf, buf->next);
s3c2410_dma_buffdone(chan, buf, S3C2410_RES_ABORT); //通知用户中断了传输
s3c2410_dma_freebuf(buf); //释放buf
}
}
dbg_showregs(chan);
s3c2410_dma_waitforstop(chan);
#if 0
/* should also clear interrupts, according to WinCE BSP */
{
unsigned long tmp;
tmp = dma_rdreg(chan, S3C2410_DMA_DCON);
tmp |= S3C2410_DCON_NORELOAD;
dma_wrreg(chan, S3C2410_DMA_DCON, tmp);
}
#endif
dbg_showregs(chan);
local_irq_restore(flags);
return 0;
}
该函数的作用就是中断所有的传输, 并把所有队列中等待传输的buf都清掉.
Arch/arm/plat-s3c24xx/dma.c:
static int s3c2410_dma_started(struct s3c2410_dma_chan *chan)
{
unsigned long flags;
local_irq_save(flags);
dbg_showchan(chan);
/* if we've only loaded one buffer onto the channel, then chec
* to see if we have another, and if so, try and load it so when
* the first buffer is finished, the new one will be loaded onto
* the channel */
//看注释吧, 不解释了
if (chan->next != NULL) {
if (chan->load_state == S3C2410_DMALOAD_1LOADED) {
if (s3c2410_dma_waitforload(chan, __LINE__) == 0) {
pr_debug("%s: buff not yet loaded, no more todo/n",
__FUNCTION__);
} else {
chan->load_state = S3C2410_DMALOAD_1RUNNING;
s3c2410_dma_loadbuffer(chan, chan->next);
}
} else if (chan->load_state == S3C2410_DMALOAD_1RUNNING) {
s3c2410_dma_loadbuffer(chan, chan->next);
}
}
local_irq_restore(flags);
return 0;
}
最后的这个函数就由大家自己分析吧.
从s3c2410_dma_config函数可以看出, 该驱动只支持硬件请求模式, 而从s3c2410_dma_devconfig函数可以看出, 该驱动只支持设备和memory之间的DMA传输.
至于如何使用的问题, 可以去代码里搜一下哪些地方调用了export出来的函数就懂了, 2410的板子上PCM会用到DMA传输, 使用流程为:
s3c2410_dma_request ->
s3c2410_dma_devconfig ->
s3c2410_dma_config ->
s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_START);
当然一般还会注册回调函数的.
到此为止整个DMA的操作流程都分析完了, 希望对你有所帮助,
以后会写些cache, mmu, write buffer等方面的驱动分析.