天天看点

SDIO驱动(16)使用DMA传输数据2

DMA控制器驱动框架中的第二个函数:

s3c24xx_dma_order_set(&s3c2440_dma_order);
           

参数s3c2440_dma_order是一个全局变量,抽象的是下图物理channel和逻辑channel及其互相关系:

SDIO驱动(16)使用DMA传输数据2
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就开始欢快的工作了。

继续阅读