DMA控制器驱动框架中的第二个函数:
s3c24xx_dma_order_set(&s3c2440_dma_order);
参数s3c2440_dma_order是一个全局变量,抽象的是下图物理channel和逻辑channel及其互相关系:
static struct s3c24xx_dma_order __initdata s3c2440_dma_order = {
.channels = {
[DMACH_SDI] = {
.list = {
[0] = 3 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
[2] = 1 | DMA_CH_VALID,
[3] = 0 | DMA_CH_VALID,
},
},
[DMACH_I2S_IN] = {
.list = {
[0] = 1 | DMA_CH_VALID,
[1] = 2 | DMA_CH_VALID,
},
},
......
},
};
对于SDIO/MMC/SD,其DMA源有4个(上图红框),位于物理channel的0~3通道;同样,I2SSDI的DMA源有2个,位于通道0、1等等。
再来看s3c24xx_dma_order_set()函数:
int __init s3c24xx_dma_order_set(struct s3c24xx_dma_order *ord)
{
struct s3c24xx_dma_order *nord = dma_order;
if (nord == NULL)
nord = kmalloc(sizeof(struct s3c24xx_dma_order), GFP_KERNEL);
if (nord == NULL) {
printk(KERN_ERR "no memory to store dma channel order\n");
return -ENOMEM;
}
dma_order = nord;
memcpy(nord, ord, sizeof(struct s3c24xx_dma_order));
return 0;
}
还是很简单:为全局变量dma_order分配空间并把s3c2440_dma_order的内容copy过去,何必多此一举?
最后一个函数:
s3c24xx_dma_init_map(&s3c2440_dma_sel);
int __init s3c24xx_dma_init_map(struct s3c24xx_dma_selection *sel)
{
struct s3c24xx_dma_map *nmap;
size_t map_sz = sizeof(*nmap) * sel->map_size;
int ptr;
nmap = kmalloc(map_sz, GFP_KERNEL);
if (nmap == NULL)
return -ENOMEM;
memcpy(nmap, sel->map, map_sz);
memcpy(&dma_sel, sel, sizeof(*sel));
dma_sel.map = nmap;
for (ptr = 0; ptr < sel->map_size; ptr++)
s3c24xx_dma_check_entry(nmap+ptr, ptr);
return 0;
}
和 s 3 c 2 4 xx_dma_order_set() 函数如出一辙,这里使用s3c2440_dma_sel变量初始化dma_sel。
这样,DMA Provider把基本的准备工作做好了,下面看Consumer如何使用它。
2、DMA的使用
在SDIO驱动的probe函数中:
static int __devinit s3cmci_probe(struct platform_device *pdev)
{
if (s3cmci_host_usedma(host)) {
host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
host);
if (host->dma < 0) {
dev_err(&pdev->dev, "cannot get DMA channel.\n");
if (!s3cmci_host_canpio()) {
ret = -EBUSY;
goto probe_free_gpio_wp;
} else {
dev_warn(&pdev->dev, "falling back to PIO.\n");
host->dodma = 0;
}
}
}
}
使用DMA传输数据,就从provider哪里request:
static struct s3c2410_dma_client s3cmci_dma_client = {
.name = "s3c-mci",
};
s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client, host);
/*
* 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);
chan = s3c2410_dma_map_channel(channel);
if (chan == NULL) {
local_irq_restore(flags);
return -EBUSY;
}
dbg_showchan(chan);
chan->client = client;
chan->in_use = 1;
if (!chan->irq_claimed) {
pr_debug("dma%d: %s : requesting irq %d\n",
channel, __func__, 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) {
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", __func__, chan);
return chan->number | DMACH_LOW_LEVEL;
}
DMACH_SDI是逻辑channel,在使用之前肯定需要映射到实际的物理channel上去,这个功能由 s 3 c 2 4 1 0 _dma_map_channel()函数负责完成;映射成功后设置其in_use标志,在30行。之后申请DMA对应的中断处理函数。
至此,DMA是申请下来了,下面该使用它了。用法就是围绕“二、DMA介绍”中提出的5点信息:
static struct mmc_host_ops s3cmci_ops = {
.request = s3cmci_request,
....
};
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
s3cmci_send_request(mmc);
}
static void s3cmci_send_request(struct mmc_host *mmc)
{
if (s3cmci_host_usedma(host))
res = s3cmci_prepare_dma(host, cmd->data);
else
res = s3cmci_prepare_pio(host, cmd->data);
}
以上都是准备工作,干活的是:
static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
int dma_len, i;
int rw = data->flags & MMC_DATA_WRITE;
BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
if (dma_len == 0)
return -ENOMEM;
host->dma_complete = 0;
host->dmatogo = dma_len;
for (i = 0; i < dma_len; i++) {
int res;
dbg(host, dbg_dma, "enqueue %i: %[email protected]%u\n", i,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
res = s3c2410_dma_enqueue(host->dma, host,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
if (res) {
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
return -EBUSY;
}
}
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
return 0;
}
s 3 cmci_prepare_dma( )函数 第8行,先确定数据传输方向:写数据,方向是mem->dev;读数据,反向是dev->mem,然后作为参数传进s3cmci_dma_setup()函数:
static void s3cmci_dma_setup(struct s3cmci_host *host,
enum s3c2410_dmasrc source)
{
static enum s3c2410_dmasrc last_source = -1;
static int setup_ok;
if (last_source == source)
return;
last_source = source;
s3c2410_dma_devconfig(host->dma, source,
host->mem->start + host->sdidata);
if (!setup_ok) {
s3c2410_dma_config(host->dma, 4);
s3c2410_dma_set_buffdone_fn(host->dma,
s3cmci_dma_done_callback);
s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART);
setup_ok = 1;
}
}
s3c2410_dma_devconfig()函数配置DMA的源/目的寄存器:
/*
* configure the dma source/destination hardware type and address
* source: S3C2410_DMASRC_HW: source is hardware
* S3C2410_DMASRC_MEM: source is memory
* devaddr: physical address of the source
*/
int s3c2410_dma_devconfig(unsigned int channel,
enum s3c2410_dmasrc source,
unsigned long devaddr)
{
struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
chan->source = source;
chan->dev_addr = devaddr;
switch (source) {
case S3C2410_DMASRC_HW:
/* source is hardware */
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);
break;
case S3C2410_DMASRC_MEM:
/* source is memory */
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);
break;
}
return 0;
}
s3cmci_dma_setup()函数用来配置DMA通道的属性,第15行,setup_ok是一个static变量,意味着下次进入该函数其值为上次设置的值;首次进入自然为0,s3c2410_dma_config()函数配置DMA的控制寄存器(DMA CONTROL REGISTER),如传输数据个数(transfer count)、传输完成是否产生中断等。 s3c2410_dma_set_buffdone_fn()函数设置 DMA通道对应的回调函数,这里就是s3cmci_dma_done_callback()。s3c2410_dma_setflags()函数设置通道S3C2410_DMAF_AUTOSTART标志,意味着如果缓存取队列不为空的情况下,DMA控制器将自动发起一次DMA传输。
s3cmci_prepare_dma()函数第9行,控制DMA的开启、停止、暂停等:
int s3c2410_dma_ctrl(unsigned int channel, enum s3c2410_chan_op op)
{
struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);
if (chan == NULL)
return -EINVAL;
switch (op) {
case S3C2410_DMAOP_START:
return s3c2410_dma_start(chan);
case S3C2410_DMAOP_STOP:
return s3c2410_dma_dostop(chan);
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 */
}
20~35行把数据buf入队列,然后37行发起DMA传输。如果设置了 S3C2410_DMAF_AUTOSTART标志,在DMA空闲状态下传输自动进行。
经过以上设置,数据传输方向、传输使用的channel、传输的数据大小/位宽、传输控制、传输状态、DMA传输情况的通知等都具备,DMA就开始欢快的工作了。