天天看點

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就開始歡快的工作了。

繼續閱讀