分类: LINUX
2010-02-03 16:42:54
四,我们顺着上一篇讲到的DMA流程,一步一步分析其余代码。
4.1 mxc_dma_request函数:
int mxc_dma_request(mxc_dma_device_t channel_id, char *dev_name)
{
mxc_dma_channel_t *dma;
mx2_dma_priv_t *dma_private = NULL;
mx2_dma_info_t *dma_info = mxc_dma_get_info(channel_id);
int index;
int ret;
if (dma_info == NULL) {
return -EINVAL;
}
if ((index = get_dma_channel(dma_info->dma_chan)) < 0) {
return -ENODEV;
}
dma = g_dma_channels + index;
dma_private = (mx2_dma_priv_t *) dma->private;
if (dma_private == NULL) {
printk(KERN_ERR
"request dma channel %d which is not initialize completed.!\n",
index);
ret = -EFAULT;
goto exit;
}
dma->active = 0;
dma_private->dma_info = NULL;
dma->cb_fn = NULL;
dma->cb_args = NULL;
dma->dev_name = dev_name;
dma->mode = dma_info->mode ? MXC_DMA_MODE_WRITE : MXC_DMA_MODE_READ;
init_dma_bd(dma_private);
if (!(ret = __init_dma_channel(dma, dma_info))) {
dma_private->dma_info = dma_info;
return index;
}
exit:
put_dma_channel(index);
return ret;
}
这个函数通过预定义的ID来找到一个空闲的通道号。
在/arch/arm/mach-mx27/dma.c里预定义了这些DEVICE_ID,如下:
static dma_info_entry_t active_dma_info[] = {
{MXC_DMA_TEST_RAM2RAM, &ram2ram_dma_info},
{MXC_DMA_TEST_RAM2D2RAM2D, &ram2d2ram2d_dma_info},
{MXC_DMA_TEST_RAM2RAM2D, &ram2ram2d_dma_info},
……
};
可以发现我们这个例子时用到的MXC_DMA_TEST_RAM2RAM。
接下来根据找到的通道号把对应的全局变量g_dma_channels作些初始化(包括初始化DMA的寄存器,缓冲区,申请中断等等),这些都不难理解。
4.2.设置回调函数
int mxc_dma_callback_set(int channel_num, mxc_dma_callback_t callback,
void *arg)
{
mxc_dma_channel_t *dma;
printk("mxc_dma_callback_set: enter\n");
if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
return -EINVAL;
}
dma = g_dma_channels + channel_num;
if (!dma->lock) {
return -ENODEV;
}
if (dma->active) {
return -EBUSY;
}
dma->cb_fn = callback;
dma->cb_args = arg;
return 0;
}
直接把回调函数指针callback及其参数赋给了对应的g_dma_channels的成员。
dma->cb_fn = callback;
dma->cb_args = arg;
可想而知这个callback一定会在中断函数dma_irq_handler里调用的。
4.3. 配置DMA通道
int mxc_dma_config(int channel_num, mxc_dma_requestbuf_t * dma_buf, int num_buf,
mxc_dma_mode_t mode)
{
mxc_dma_channel_t *dma;
mx2_dma_priv_t *dma_private;
if ((dma_buf == NULL) || (num_buf < 1)) {
return -EINVAL;
}
if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
return -EINVAL;
}
dma = g_dma_channels + channel_num;
dma_private = (mx2_dma_priv_t *) dma->private;
if (dma_private == NULL) {
printk(KERN_ERR
"config dma %d which is not completed initialization \n",
channel_num);
return -EFAULT;
}
if (dma->lock == 0) {
return -ENODEV;
}
/*TODO: dma chainning can not support on bi-dir channel */
if (dma_private->dma_chaining && (dma->mode != mode)) {
return -EINVAL;
}
/*TODO: fill dma buffer into driver .
* If driver is no enought buffer to save them , it will return -EBUSY
*/
if (fill_dma_bd(dma, dma_buf, num_buf, mode)) {
return -EBUSY;
}
return 0;
}
真正做实际工作的是fill_dma_bd()函数
static inline int fill_dma_bd(mxc_dma_channel_t * dma,
mxc_dma_requestbuf_t * buf, int num,
mxc_dma_mode_t mode)
{
int i, wr;
unsigned long flags, mask;
mx2_dma_priv_t *priv = dma->private;
mx2_dma_bd_t *p, *q;
if ((atomic_read(&(priv->bd_used)) + num) > MAX_BD_SIZE) {
return -EBUSY;
}
for (i = 0; i < num; i++) {
wr = priv->bd_wr;
p = priv->bd_ring + wr;
p->mode = mode;
p->count = buf[i].num_of_bytes;
p->src_addr = buf[i].src_addr;
p->dst_addr = buf[i].dst_addr;
if (i == num - 1) {
p->state = DMA_BD_ST_LAST | DMA_BD_ST_PEND;
} else {
p->state = DMA_BD_ST_PEND;
}
priv->bd_wr = (wr + 1) % MAX_BD_SIZE;
atomic_inc(&(priv->bd_used));
if (atomic_read(&(priv->bd_used)) != 2)
continue;
/* Disable interrupt of this channel */
local_irq_save(flags);
local_irq_disable();
save_dma_interrupt(mask);
mask_dma_interrupt(dma->channel);
local_irq_restore(flags);
/*TODO ::
* If channel is transfering and supports chain_buffer,
* when the new buffer is 2st buffer , repeat must be enabled
*/
if (priv->dma_chaining && dma->active) {
q = priv->bd_ring + priv->bd_rd;
if (q && (q->state & DMA_BD_ST_BUSY)) {
if (atomic_read(&(priv->bd_used)) == 2) {
setup_dmac(dma);
}
}
}
restore_dma_interrupt(mask);
}
return 0;
}
priv->bd_ring相当于一个环形数据结构,在这个结构里有几个已使用的dma_bd用priv->bd_used表示,priv->bd_wr是一个索引,指向最新加入(或者说是使用)的dma_bd,每一个dma_bd都存放着源地址src_addr,目标地址dst_addr和存取字节数count,可以把dma_bd理解成DMA要完成的一次小任务。
4.4. 使能DMA
int mxc_dma_enable(int channel_num)
{
mxc_dma_channel_t *dma;
mx2_dma_priv_t *priv;
if ((channel_num >= MAX_DMA_CHANNELS) || (channel_num < 0)) {
return -EINVAL;
}
dma = g_dma_channels + channel_num;
if (dma->lock == 0) {
return -EINVAL;
}
priv = (mx2_dma_priv_t *) dma->private;
if (priv == NULL) {
printk(KERN_ERR "enable a uncompleted dma channel %d\n",
channel_num);
return -EFAULT;
}
if (dma->active) {
return 0;
}
dma->active = 1;
priv->trans_bytes = 0;
enable_dma_clk();
atomic_inc(&g_dma_actived);
__clear_dma_interrupt(channel_num);
setup_dmac(dma);
disable_dma_clk();
return 0;
}
它最终调用setup_dmac(dma)来开始DMA的传输。
static void setup_dmac(mxc_dma_channel_t * dma)
{
mx2_dma_priv_t *priv = (mx2_dma_priv_t *) dma->private;
dma_regs_t *dma_base = (dma_regs_t *) (priv->dma_base);
mx2_dma_bd_t *p, *q;
unsigned long ctrl_val;
printk("setup_dmac: enter\n");
if (dma->active == 0) {
printk(KERN_ERR
"dma channel %d is not enabled, when receiving this channel 's interrupt\n",
dma->channel);
return;
}
if (atomic_read(&(priv->bd_used)) <= 0) {
printk(KERN_ERR "dma channel %d is empty\n", dma->channel);
dma->active = 0;
atomic_dec(&g_dma_actived);
return;
}
p = priv->bd_ring + priv->bd_rd;
q = next_dma_bd(priv);
if (!(p->state & DMA_BD_ST_BUSY)) {
/*NOTICE:: This is first buffer or dma chain does not support chain-buffer. So CEN must clear & set again */
ctrl_val =
__raw_readl(&(dma_base->Ctl)) &
(~(DMA_CTL_ACRPT | DMA_CTL_RPT | DMA_CTL_CEN));
__raw_writel(ctrl_val, &(dma_base->Ctl));
if (p->mode != dma->mode) {
dma->mode = p->mode; /* bi-dir channel do mode change */
if (dma->mode == MXC_DMA_MODE_READ) {
DMA_CTL_SET_SMOD(ctrl_val,
priv->dma_info->sourceType);
DMA_CTL_SET_SSIZ(ctrl_val,
priv->dma_info->sourcePort);
DMA_CTL_SET_DMOD(ctrl_val,
priv->dma_info->destType);
DMA_CTL_SET_DSIZ(ctrl_val,
priv->dma_info->destPort);
} else {
DMA_CTL_SET_SMOD(ctrl_val,
priv->dma_info->destType);
DMA_CTL_SET_SSIZ(ctrl_val,
priv->dma_info->destPort);
DMA_CTL_SET_DMOD(ctrl_val,
priv->dma_info->sourceType);
DMA_CTL_SET_DSIZ(ctrl_val,
priv->dma_info->sourcePort);
}
}
__raw_writel(p->src_addr, &(dma_base->SourceAddr));
__raw_writel(p->dst_addr, &(dma_base->DestAddr));
__raw_writel(p->count, &(dma_base->Count));
p->state |= DMA_BD_ST_BUSY;
p->state &= ~(DMA_BD_ST_PEND);
ctrl_val |= DMA_CTL_CEN;
__raw_writel(ctrl_val, &(dma_base->Ctl));
if (q && priv->dma_chaining) { /*DO chain-buffer */
__raw_writel(q->src_addr, &(dma_base->SourceAddr));
__raw_writel(q->dst_addr, &(dma_base->DestAddr));
__raw_writel(q->count, &(dma_base->Count));
q->state |= DMA_BD_ST_BUSY;
q->state &= ~(DMA_BD_ST_PEND);
ctrl_val |= DMA_CTL_ACRPT | DMA_CTL_RPT | DMA_CTL_CEN;
__raw_writel(ctrl_val, &(dma_base->Ctl));
}
} else { /* Just dma channel which supports dma buffer can run to there */
BUG_ON(!priv->dma_chaining);
if (q) { /* p is tranfering, then q must be set into dma controller */
/*WARNING:: [1] dangerous area begin.
* If the p is completed during MCU run in this erea, the dma channel is crashed.
*/
__raw_writel(q->src_addr, &(dma_base->SourceAddr));
__raw_writel(q->dst_addr, &(dma_base->DestAddr));
__raw_writel(q->count, &(dma_base->Count));
/*WARNING:: [2] dangerous area end */
ctrl_val =
__raw_readl(&(dma_base->Ctl)) | (DMA_CTL_ACRPT |
DMA_CTL_RPT |
DMA_CTL_CEN);
__raw_writel(ctrl_val, &(dma_base->Ctl));
/* WARNING:: This is workaround and it is dangerous:
* the judgement is not safety.
*/
if (!__get_dma_interrupt(dma->channel)) {
q->state |= DMA_BD_ST_BUSY;
q->state &= ~(DMA_BD_ST_PEND);
} else {
/*Waiting re-enable is in ISR */
printk(KERN_ERR
"Warning:: The privous transfer is completed. Maybe the chain buffer is stopped.");
}
} else { /* Last buffer is transfering: just clear RPT bit */
ctrl_val =
__raw_readl(&(dma_base->Ctl)) &
(~(DMA_CTL_ACRPT | DMA_CTL_RPT));
__raw_writel(ctrl_val, &(dma_base->Ctl));
}
}
}
priv->bd_rd也是一个索引,指向当前要进行处理的一个dma_bd。
p = priv->bd_ring + priv->bd_rd是把当前的dma_bd取出来,接下来设置通道源地址/目标地址寄等存器,最后通过设置DMA_CTL_CEN位,使能这个通道,开始一次的DMA传输。
五,小结:这只是对i.mx27平台上的DMA工作流程作的一个大体上的浅析,很多细节都略过去了。有兴趣的朋友请自行研究。