mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [FYI 0/4] arm: mmci: example implementation of double buffering
@ 2011-01-12 18:19 Per Forlin
  2011-01-12 18:19 ` [FYI 1/4] mmci: fixup broken_blockend variant patch Per Forlin
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Per Forlin @ 2011-01-12 18:19 UTC (permalink / raw)
  To: linux-mmc, linux-arm-kernel, linux-kernel, linaro-dev
  Cc: Chris Ball, Per Forlin

The mmci host driver implementation for double buffering is not intended
nor ready for mainline yet. It is only an example of how to implement
pre_req() and post_req(). The reason for this is that the basic DMA
support for MMCI is not complete yet.

This patch series depends on
"[PATCH 0/5] mmc: add double buffering for mmc block requests"

Linus Walleij (1):
  mmci: add PrimeCell generic DMA to MMCI/PL180

Per Forlin (1):
  ARM: mmci: add support for double buffering

Ulf Hansson (2):
  mmci: fixup broken_blockend variant patch
  MMCI: Corrections for DMA

 drivers/mmc/host/mmci.c   |  538 ++++++++++++++++++++++++++++++++++++++-------
 drivers/mmc/host/mmci.h   |   21 ++-
 include/linux/amba/mmci.h |   16 ++
 3 files changed, 496 insertions(+), 79 deletions(-)


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [FYI 1/4] mmci: fixup broken_blockend variant patch
  2011-01-12 18:19 [FYI 0/4] arm: mmci: example implementation of double buffering Per Forlin
@ 2011-01-12 18:19 ` Per Forlin
  2011-01-12 18:19 ` [FYI 2/4] mmci: add PrimeCell generic DMA to MMCI/PL180 Per Forlin
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Per Forlin @ 2011-01-12 18:19 UTC (permalink / raw)
  To: linux-mmc, linux-arm-kernel, linux-kernel, linaro-dev
  Cc: Chris Ball, Ulf Hansson

From: Ulf Hansson <ulf.hansson@stericsson.com>

NOT to be mainlined, only sets the base for the double
buffer example implementation. The DMA implemenation for
MMCI is under development.

host->last_blockend flag is now set only for the last
MCI_DATABLOCKEND, the previous code would just react to
any blockend, which was buggy. Consolidate Ux500 and U300
bug flags to a single one and use only the MCI_DATAEND irq
on U300 as well, this is faster anyway.

Also make sure the MCI_DATABLOCKENDMASK is set only when
needed, instead of being set always and then masked off.

Tested successfully on Ux500, U300 and ARM RealView
PB1176.
---
 drivers/mmc/host/mmci.c |   45 ++++++++++++++++++++++-----------------------
 drivers/mmc/host/mmci.h |    4 ++--
 2 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 5630228..aafede4 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -48,8 +48,6 @@ static unsigned int fmax = 515633;
  *		  is asserted (likewise for RX)
  * @broken_blockend: the MCI_DATABLOCKEND is broken on the hardware
  *		and will not work at all.
- * @broken_blockend_dma: the MCI_DATABLOCKEND is broken on the hardware when
- *		using DMA.
  * @sdio: variant supports SDIO
  * @st_clkdiv: true if using a ST-specific clock divider algorithm
  */
@@ -60,7 +58,6 @@ struct variant_data {
 	unsigned int		fifosize;
 	unsigned int		fifohalfsize;
 	bool			broken_blockend;
-	bool			broken_blockend_dma;
 	bool			sdio;
 	bool			st_clkdiv;
 };
@@ -76,7 +73,7 @@ static struct variant_data variant_u300 = {
 	.fifohalfsize		= 8 * 4,
 	.clkreg_enable		= 1 << 13, /* HWFCEN */
 	.datalength_bits	= 16,
-	.broken_blockend_dma	= true,
+	.broken_blockend	= true,
 	.sdio			= true,
 };
 
@@ -199,7 +196,7 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
 static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 {
 	struct variant_data *variant = host->variant;
-	unsigned int datactrl, timeout, irqmask;
+	unsigned int datactrl, timeout, irqmask0, irqmask1;
 	unsigned long long clks;
 	void __iomem *base;
 	int blksz_bits;
@@ -210,7 +207,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 	host->data = data;
 	host->size = data->blksz * data->blocks;
 	host->data_xfered = 0;
-	host->blockend = false;
+	host->last_blockend = false;
 	host->dataend = false;
 
 	mmci_init_sg(host, data);
@@ -230,20 +227,20 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 	datactrl = MCI_DPSM_ENABLE | blksz_bits << 4;
 	if (data->flags & MMC_DATA_READ) {
 		datactrl |= MCI_DPSM_DIRECTION;
-		irqmask = MCI_RXFIFOHALFFULLMASK;
+		irqmask1 = MCI_RXFIFOHALFFULLMASK;
 
 		/*
 		 * If we have less than a FIFOSIZE of bytes to transfer,
 		 * trigger a PIO interrupt as soon as any data is available.
 		 */
 		if (host->size < variant->fifosize)
-			irqmask |= MCI_RXDATAAVLBLMASK;
+			irqmask1 |= MCI_RXDATAAVLBLMASK;
 	} else {
 		/*
 		 * We don't actually need to include "FIFO empty" here
 		 * since its implicit in "FIFO half empty".
 		 */
-		irqmask = MCI_TXFIFOHALFEMPTYMASK;
+		irqmask1 = MCI_TXFIFOHALFEMPTYMASK;
 	}
 
 	/* The ST Micro variants has a special bit to enable SDIO */
@@ -252,8 +249,14 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 			datactrl |= MCI_ST_DPSM_SDIOEN;
 
 	writel(datactrl, base + MMCIDATACTRL);
-	writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0);
-	mmci_set_mask1(host, irqmask);
+	irqmask0 = readl(base + MMCIMASK0);
+	if (variant->broken_blockend)
+		irqmask0 &= ~MCI_DATABLOCKENDMASK;
+	else
+		irqmask0 |= MCI_DATABLOCKENDMASK;
+	irqmask0 &= ~MCI_DATAENDMASK;
+	writel(irqmask0, base + MMCIMASK0);
+	mmci_set_mask1(host, irqmask1);
 }
 
 static void
@@ -301,7 +304,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 			data->error = -EIO;
 
 		/* Force-complete the transaction */
-		host->blockend = true;
+		host->last_blockend = true;
 		host->dataend = true;
 
 		/*
@@ -337,7 +340,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 	 *
 	 * In the U300, the IRQs can arrive out-of-order,
 	 * e.g. MCI_DATABLOCKEND sometimes arrives after MCI_DATAEND,
-	 * so for this case we use the flags "blockend" and
+	 * so for this case we use the flags "last_blockend" and
 	 * "dataend" to make sure both IRQs have arrived before
 	 * concluding the transaction. (This does not apply
 	 * to the Ux500 which doesn't fire MCI_DATABLOCKEND
@@ -353,7 +356,8 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 		 */
 		if (!variant->broken_blockend)
 			host->data_xfered += data->blksz;
-		host->blockend = true;
+		if (host->data_xfered == data->blksz * data->blocks)
+			host->last_blockend = true;
 	}
 
 	if (status & MCI_DATAEND)
@@ -364,11 +368,12 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 	 * on others we must sync with the blockend signal since they can
 	 * appear out-of-order.
 	 */
-	if (host->dataend && (host->blockend || variant->broken_blockend)) {
+	if (host->dataend &&
+	    (host->last_blockend || variant->broken_blockend)) {
 		mmci_stop_data(host);
 
 		/* Reset these flags */
-		host->blockend = false;
+		host->last_blockend = false;
 		host->dataend = false;
 
 		/*
@@ -770,7 +775,6 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 	struct variant_data *variant = id->data;
 	struct mmci_host *host;
 	struct mmc_host *mmc;
-	unsigned int mask;
 	int ret;
 
 	/* must have platform data */
@@ -951,12 +955,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 			goto irq0_free;
 	}
 
-	mask = MCI_IRQENABLE;
-	/* Don't use the datablockend flag if it's broken */
-	if (variant->broken_blockend)
-		mask &= ~MCI_DATABLOCKEND;
-
-	writel(mask, host->base + MMCIMASK0);
+	writel(MCI_IRQENABLE, host->base + MMCIMASK0);
 
 	amba_set_drvdata(dev, mmc);
 
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index df06f01..7ac8c4d 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -137,7 +137,7 @@
 #define MCI_IRQENABLE	\
 	(MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK|	\
 	MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK|	\
-	MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATABLOCKENDMASK)
+	MCI_CMDRESPENDMASK|MCI_CMDSENTMASK)
 
 /* These interrupts are directed to IRQ1 when two IRQ lines are available */
 #define MCI_IRQ1MASK \
@@ -177,7 +177,7 @@ struct mmci_host {
 	struct timer_list	timer;
 	unsigned int		oldstat;
 
-	bool			blockend;
+	bool			last_blockend;
 	bool			dataend;
 
 	/* pio stuff */
-- 
1.7.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [FYI 2/4] mmci: add PrimeCell generic DMA to MMCI/PL180
  2011-01-12 18:19 [FYI 0/4] arm: mmci: example implementation of double buffering Per Forlin
  2011-01-12 18:19 ` [FYI 1/4] mmci: fixup broken_blockend variant patch Per Forlin
@ 2011-01-12 18:19 ` Per Forlin
  2011-01-12 18:19 ` [FYI 3/4] MMCI: Corrections for DMA Per Forlin
  2011-01-12 18:19 ` [FYI 4/4] ARM: mmci: add support for double buffering Per Forlin
  3 siblings, 0 replies; 5+ messages in thread
From: Per Forlin @ 2011-01-12 18:19 UTC (permalink / raw)
  To: linux-mmc, linux-arm-kernel, linux-kernel, linaro-dev
  Cc: Chris Ball, Linus Walleij

From: Linus Walleij <linus.walleij@stericsson.com>

NOT to be mainlined, only sets the base for the double
buffer example implementation. The DMA implemenation for
MMCI is under development.

This extends the MMCI/PL180 driver with generic DMA engine support
using the PrimeCell DMA engine interface.
---
 drivers/mmc/host/mmci.c   |  264 +++++++++++++++++++++++++++++++++++++++++++--
 drivers/mmc/host/mmci.h   |   13 +++
 include/linux/amba/mmci.h |   16 +++
 3 files changed, 282 insertions(+), 11 deletions(-)

diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index aafede4..38fcbde 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -2,7 +2,7 @@
  *  linux/drivers/mmc/host/mmci.c - ARM PrimeCell MMCI PL180/1 driver
  *
  *  Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
- *  Copyright (C) 2010 ST-Ericsson AB.
+ *  Copyright (C) 2010 ST-Ericsson SA
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -24,8 +24,10 @@
 #include <linux/clk.h>
 #include <linux/scatterlist.h>
 #include <linux/gpio.h>
-#include <linux/amba/mmci.h>
 #include <linux/regulator/consumer.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/amba/mmci.h>
 
 #include <asm/div64.h>
 #include <asm/io.h>
@@ -41,11 +43,15 @@ static unsigned int fmax = 515633;
  * struct variant_data - MMCI variant-specific quirks
  * @clkreg: default value for MCICLOCK register
  * @clkreg_enable: enable value for MMCICLOCK register
+ * @dmareg_enable: enable value for MMCIDATACTRL register
  * @datalength_bits: number of bits in the MMCIDATALENGTH register
  * @fifosize: number of bytes that can be written when MMCI_TXFIFOEMPTY
  *	      is asserted (likewise for RX)
  * @fifohalfsize: number of bytes that can be written when MCI_TXFIFOHALFEMPTY
  *		  is asserted (likewise for RX)
+ * @txsize_threshold: Sets DMA burst size to minimal if transfer size is
+ *		less or equal to this threshold. This shall be specified in
+ *		number of bytes. Set 0 for no burst compensation
  * @broken_blockend: the MCI_DATABLOCKEND is broken on the hardware
  *		and will not work at all.
  * @sdio: variant supports SDIO
@@ -54,9 +60,11 @@ static unsigned int fmax = 515633;
 struct variant_data {
 	unsigned int		clkreg;
 	unsigned int		clkreg_enable;
+	unsigned int		dmareg_enable;
 	unsigned int		datalength_bits;
 	unsigned int		fifosize;
 	unsigned int		fifohalfsize;
+	unsigned int		txsize_threshold;
 	bool			broken_blockend;
 	bool			sdio;
 	bool			st_clkdiv;
@@ -80,8 +88,10 @@ static struct variant_data variant_u300 = {
 static struct variant_data variant_ux500 = {
 	.fifosize		= 30 * 4,
 	.fifohalfsize		= 8 * 4,
+	.txsize_threshold	= 16,
 	.clkreg			= MCI_CLK_ENABLE,
 	.clkreg_enable		= 1 << 14, /* HWFCEN */
+	.dmareg_enable		= 1 << 12, /* DMAREQCTRL */
 	.datalength_bits	= 24,
 	.broken_blockend	= true,
 	.sdio			= true,
@@ -193,6 +203,209 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
 	sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
 }
 
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+static void __devinit mmci_setup_dma(struct mmci_host *host)
+{
+	struct mmci_platform_data *plat = host->plat;
+	dma_cap_mask_t mask;
+
+	if (!plat || !plat->dma_filter) {
+		dev_err(mmc_dev(host->mmc), "no DMA platform data!\n");
+		return;
+	}
+
+	/* Try to acquire a generic DMA engine slave channel */
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+	/*
+	 * If only an RX channel is specified, the driver will
+	 * attempt to use it bidirectionally, however if it is
+	 * is specified but cannot be located, DMA will be disabled.
+	 */
+	host->dma_rx_channel = dma_request_channel(mask,
+						plat->dma_filter,
+						plat->dma_rx_param);
+	/* E.g if no DMA hardware is present */
+	if (!host->dma_rx_channel) {
+		dev_err(mmc_dev(host->mmc), "no RX DMA channel!\n");
+		return;
+	}
+	if (plat->dma_tx_param) {
+		host->dma_tx_channel = dma_request_channel(mask,
+							   plat->dma_filter,
+							   plat->dma_tx_param);
+		if (!host->dma_tx_channel) {
+			dma_release_channel(host->dma_rx_channel);
+			host->dma_rx_channel = NULL;
+			return;
+		}
+	} else {
+		host->dma_tx_channel = host->dma_rx_channel;
+	}
+	host->dma_enable = true;
+	dev_info(mmc_dev(host->mmc), "use DMA channels DMA RX %s, DMA TX %s\n",
+		 dma_chan_name(host->dma_rx_channel),
+		 dma_chan_name(host->dma_tx_channel));
+}
+
+/*
+ * This is used in __devinit or __devexit so inline it
+ * so it can be discarded.
+ */
+static inline void mmci_disable_dma(struct mmci_host *host)
+{
+	if (host->dma_rx_channel)
+		dma_release_channel(host->dma_rx_channel);
+	if (host->dma_tx_channel)
+		dma_release_channel(host->dma_tx_channel);
+	host->dma_enable = false;
+}
+
+static void mmci_dma_data_end(struct mmci_host *host)
+{
+	struct mmc_data *data = host->data;
+
+	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+		     (data->flags & MMC_DATA_WRITE)
+		     ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+	host->dma_on_current_xfer = false;
+}
+
+static void mmci_dma_terminate(struct mmci_host *host)
+{
+	struct mmc_data *data = host->data;
+	struct dma_chan *chan;
+
+	dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n");
+	if (data->flags & MMC_DATA_READ)
+		chan = host->dma_rx_channel;
+	else
+		chan = host->dma_tx_channel;
+	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+		     (data->flags & MMC_DATA_WRITE)
+		     ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+}
+
+static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+{
+	struct variant_data *variant = host->variant;
+	struct dma_slave_config rx_conf = {
+		.src_addr = host->phybase + MMCIFIFO,
+		.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+		.direction = DMA_FROM_DEVICE,
+		.src_maxburst = variant->fifohalfsize >> 2, /* # of words */
+	};
+	struct dma_slave_config tx_conf = {
+		.dst_addr = host->phybase + MMCIFIFO,
+		.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+		.direction = DMA_TO_DEVICE,
+		.dst_maxburst = variant->fifohalfsize >> 2, /* # of words */
+	};
+	struct mmc_data *data = host->data;
+	enum dma_data_direction direction;
+	struct dma_chan *chan;
+	struct dma_async_tx_descriptor *desc;
+	struct scatterlist *sg;
+	dma_cookie_t cookie;
+	int i;
+
+	datactrl |= MCI_DPSM_DMAENABLE;
+	datactrl |= variant->dmareg_enable;
+
+	if (data->flags & MMC_DATA_READ) {
+		if (host->size <= variant->txsize_threshold)
+			rx_conf.src_maxburst = 1;
+
+		direction = DMA_FROM_DEVICE;
+		chan = host->dma_rx_channel;
+		chan->device->device_control(chan, DMA_SLAVE_CONFIG,
+					     (unsigned long) &rx_conf);
+	} else {
+		if (host->size <= variant->txsize_threshold)
+			tx_conf.dst_maxburst = 1;
+
+		direction = DMA_TO_DEVICE;
+		chan = host->dma_tx_channel;
+		chan->device->device_control(chan, DMA_SLAVE_CONFIG,
+					     (unsigned long) &tx_conf);
+	}
+
+	/* Check for weird stuff in the sg list */
+	for_each_sg(data->sg, sg, data->sg_len, i) {
+		dev_vdbg(mmc_dev(host->mmc), "MMCI SGlist %d dir %d: length: %08x\n",
+			 i, direction, sg->length);
+		if (sg->offset & 3 || sg->length & 3)
+			return -EINVAL;
+	}
+
+	dma_map_sg(mmc_dev(host->mmc), data->sg,
+		   data->sg_len, direction);
+
+	desc = chan->device->device_prep_slave_sg(chan,
+					data->sg, data->sg_len, direction,
+					DMA_CTRL_ACK);
+	if (!desc)
+		goto unmap_exit;
+
+	host->dma_desc = desc;
+	dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d "
+		 "blksz %04x blks %04x flags %08x\n",
+		 data->sg_len, data->blksz, data->blocks, data->flags);
+	cookie = desc->tx_submit(desc);
+
+	/* Here overloaded DMA controllers may fail */
+	if (dma_submit_error(cookie))
+		goto unmap_exit;
+
+	host->dma_on_current_xfer = true;
+	chan->device->device_issue_pending(chan);
+
+	/* Trigger the DMA transfer */
+	writel(datactrl, host->base + MMCIDATACTRL);
+	/*
+	 * Let the MMCI say when the data is ended and it's time
+	 * to fire next DMA request. When that happens, MMCI will
+	 * call mmci_data_end()
+	 */
+	writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK,
+	       host->base + MMCIMASK0);
+	return 0;
+
+unmap_exit:
+	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
+	return -ENOMEM;
+}
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void mmci_setup_dma(struct mmci_host *host)
+{
+}
+
+static inline void mmci_disable_dma(struct mmci_host *host)
+{
+}
+
+static inline void mmci_dma_data_end(struct mmci_host *host)
+{
+}
+
+static inline void mmci_dma_terminate(struct mmci_host *host)
+{
+}
+
+static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+{
+	return -ENOSYS;
+}
+#endif
+
 static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 {
 	struct variant_data *variant = host->variant;
@@ -210,8 +423,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 	host->last_blockend = false;
 	host->dataend = false;
 
-	mmci_init_sg(host, data);
-
 	clks = (unsigned long long)data->timeout_ns * host->cclk;
 	do_div(clks, 1000000000UL);
 
@@ -225,13 +436,32 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 	BUG_ON(1 << blksz_bits != data->blksz);
 
 	datactrl = MCI_DPSM_ENABLE | blksz_bits << 4;
-	if (data->flags & MMC_DATA_READ) {
+
+	if (data->flags & MMC_DATA_READ)
 		datactrl |= MCI_DPSM_DIRECTION;
+
+	if (host->dma_enable) {
+		int ret;
+
+		/*
+		 * Attempt to use DMA operation mode, if this
+		 * should fail, fall back to PIO mode
+		 */
+		ret = mmci_dma_start_data(host, datactrl);
+		if (!ret)
+			return;
+	}
+
+	/* IRQ mode, map the SG list for CPU reading/writing */
+	mmci_init_sg(host, data);
+
+	if (data->flags & MMC_DATA_READ) {
 		irqmask1 = MCI_RXFIFOHALFFULLMASK;
 
 		/*
-		 * If we have less than a FIFOSIZE of bytes to transfer,
-		 * trigger a PIO interrupt as soon as any data is available.
+		 * If we have less than a FIFOSIZE of bytes to
+		 * transfer, trigger a PIO interrupt as soon as any
+		 * data is available.
 		 */
 		if (host->size < variant->fifosize)
 			irqmask1 |= MCI_RXDATAAVLBLMASK;
@@ -309,9 +539,12 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 
 		/*
 		 * We hit an error condition.  Ensure that any data
-		 * partially written to a page is properly coherent.
+		 * partially written to a page is properly coherent,
+		 * unless we're using DMA.
 		 */
-		if (data->flags & MMC_DATA_READ) {
+		if (host->dma_on_current_xfer)
+			mmci_dma_terminate(host);
+		else if (data->flags & MMC_DATA_READ) {
 			struct sg_mapping_iter *sg_miter = &host->sg_miter;
 			unsigned long flags;
 
@@ -354,7 +587,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 		 * flag is unreliable: since it can stay high between
 		 * IRQs it will corrupt the transfer counter.
 		 */
-		if (!variant->broken_blockend)
+		if (!variant->broken_blockend && !host->dma_on_current_xfer)
 			host->data_xfered += data->blksz;
 		if (host->data_xfered == data->blksz * data->blocks)
 			host->last_blockend = true;
@@ -370,6 +603,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 	 */
 	if (host->dataend &&
 	    (host->last_blockend || variant->broken_blockend)) {
+		mmci_dma_data_end(host);
 		mmci_stop_data(host);
 
 		/* Reset these flags */
@@ -411,8 +645,11 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
 	}
 
 	if (!cmd->data || cmd->error) {
-		if (host->data)
+		if (host->data) {
+			if (host->dma_on_current_xfer)
+				mmci_dma_terminate(host);
 			mmci_stop_data(host);
+		}
 		mmci_request_end(host, cmd->mrq);
 	} else if (!(cmd->data->flags & MMC_DATA_READ)) {
 		mmci_start_data(host, cmd->data);
@@ -832,6 +1069,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 		dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n",
 			host->mclk);
 	}
+	host->phybase = dev->res.start;
 	host->base = ioremap(dev->res.start, resource_size(&dev->res));
 	if (!host->base) {
 		ret = -ENOMEM;
@@ -942,6 +1180,8 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 	    && host->gpio_cd_irq < 0)
 		mmc->caps |= MMC_CAP_NEEDS_POLL;
 
+	mmci_setup_dma(host);
+
 	ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
 	if (ret)
 		goto unmap;
@@ -970,6 +1210,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
  irq0_free:
 	free_irq(dev->irq[0], host);
  unmap:
+	mmci_disable_dma(host);
 	if (host->gpio_wp != -ENOSYS)
 		gpio_free(host->gpio_wp);
  err_gpio_wp:
@@ -1008,6 +1249,7 @@ static int __devexit mmci_remove(struct amba_device *dev)
 		writel(0, host->base + MMCICOMMAND);
 		writel(0, host->base + MMCIDATACTRL);
 
+		mmci_disable_dma(host);
 		free_irq(dev->irq[0], host);
 		if (!host->singleirq)
 			free_irq(dev->irq[1], host);
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index 7ac8c4d..39b7ac7 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -148,8 +148,11 @@
 
 struct clk;
 struct variant_data;
+struct dma_chan;
+struct dma_async_tx_descriptor;
 
 struct mmci_host {
+	phys_addr_t		phybase;
 	void __iomem		*base;
 	struct mmc_request	*mrq;
 	struct mmc_command	*cmd;
@@ -183,6 +186,16 @@ struct mmci_host {
 	/* pio stuff */
 	struct sg_mapping_iter	sg_miter;
 	unsigned int		size;
+
 	struct regulator	*vcc;
+
+	/* DMA stuff */
+	bool			dma_enable;
+	bool			dma_on_current_xfer;
+#ifdef CONFIG_DMA_ENGINE
+	struct dma_chan		*dma_rx_channel;
+	struct dma_chan		*dma_tx_channel;
+	struct dma_async_tx_descriptor *dma_desc;
+#endif
 };
 
diff --git a/include/linux/amba/mmci.h b/include/linux/amba/mmci.h
index f4ee9ac..dbeba68 100644
--- a/include/linux/amba/mmci.h
+++ b/include/linux/amba/mmci.h
@@ -6,6 +6,8 @@
 
 #include <linux/mmc/host.h>
 
+/* Just some dummy forwarding */
+struct dma_chan;
 /**
  * struct mmci_platform_data - platform configuration for the MMCI
  * (also known as PL180) block.
@@ -27,6 +29,17 @@
  * @cd_invert: true if the gpio_cd pin value is active low
  * @capabilities: the capabilities of the block as implemented in
  * this platform, signify anything MMC_CAP_* from mmc/host.h
+ * @dma_filter: function used to select an apropriate RX and TX
+ * DMA channel to be used for DMA, if and only if you're deploying the
+ * generic DMA engine
+ * @dma_rx_param: parameter passed to the DMA allocation
+ * filter in order to select an apropriate RX channel. If
+ * there is a bidirectional RX+TX channel, then just specify
+ * this and leave dma_tx_param set to NULL
+ * @dma_tx_param: parameter passed to the DMA allocation
+ * filter in order to select an apropriate TX channel. If this
+ * is NULL the driver will attempt to use the RX channel as a
+ * bidirectional channel
  */
 struct mmci_platform_data {
 	unsigned int f_max;
@@ -38,6 +51,9 @@ struct mmci_platform_data {
 	int	gpio_cd;
 	bool	cd_invert;
 	unsigned long capabilities;
+	bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+	void *dma_rx_param;
+	void *dma_tx_param;
 };
 
 #endif
-- 
1.7.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [FYI 3/4] MMCI: Corrections for DMA
  2011-01-12 18:19 [FYI 0/4] arm: mmci: example implementation of double buffering Per Forlin
  2011-01-12 18:19 ` [FYI 1/4] mmci: fixup broken_blockend variant patch Per Forlin
  2011-01-12 18:19 ` [FYI 2/4] mmci: add PrimeCell generic DMA to MMCI/PL180 Per Forlin
@ 2011-01-12 18:19 ` Per Forlin
  2011-01-12 18:19 ` [FYI 4/4] ARM: mmci: add support for double buffering Per Forlin
  3 siblings, 0 replies; 5+ messages in thread
From: Per Forlin @ 2011-01-12 18:19 UTC (permalink / raw)
  To: linux-mmc, linux-arm-kernel, linux-kernel, linaro-dev
  Cc: Chris Ball, Ulf Hansson

From: Ulf Hansson <ulf.hansson@stericsson.com>

NOT to be mainlined, only sets the base for the double
buffer example implementation. The DMA implemenation for
MMCI is under development.

Make use of DMA_PREP_INTERRUPT to get a callback when DMA has
successfully completed the data transfer.

To entirely ending the transfer and request, both the DMA
callback and MCI_DATAEND must occur.
---
 drivers/mmc/host/mmci.c |  173 +++++++++++++++++++++++++++++++----------------
 1 files changed, 114 insertions(+), 59 deletions(-)

diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 38fcbde..ab44f5f 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -203,6 +203,34 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
 	sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
 }
 
+static void
+mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
+{
+	void __iomem *base = host->base;
+
+	dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n",
+	    cmd->opcode, cmd->arg, cmd->flags);
+
+	if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
+		writel(0, base + MMCICOMMAND);
+		udelay(1);
+	}
+
+	c |= cmd->opcode | MCI_CPSM_ENABLE;
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		if (cmd->flags & MMC_RSP_136)
+			c |= MCI_CPSM_LONGRSP;
+		c |= MCI_CPSM_RESPONSE;
+	}
+	if (/*interrupt*/0)
+		c |= MCI_CPSM_INTERRUPT;
+
+	host->cmd = cmd;
+
+	writel(cmd->arg, base + MMCIARGUMENT);
+	writel(c, base + MMCICOMMAND);
+}
+
 /*
  * All the DMA operation mode stuff goes inside this ifdef.
  * This assumes that you have a generic DMA device interface,
@@ -290,6 +318,39 @@ static void mmci_dma_terminate(struct mmci_host *host)
 		     (data->flags & MMC_DATA_WRITE)
 		     ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
 	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+	host->dma_on_current_xfer = false;
+}
+
+static void mmci_dma_callback(void *arg)
+{
+	unsigned long flags;
+	struct mmci_host *host = arg;
+	struct mmc_data *data;
+
+	dev_vdbg(mmc_dev(host->mmc), "DMA transfer done!\n");
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	mmci_dma_data_end(host);
+
+	/*
+	 * Make sure MMCI has received MCI_DATAEND before
+	 * ending the transfer and request.
+	 */
+	if (host->dataend) {
+		data = host->data;
+		mmci_stop_data(host);
+
+		host->data_xfered += data->blksz * data->blocks;
+		host->dataend = false;
+
+		if (!data->stop)
+			mmci_request_end(host, data->mrq);
+		else
+			mmci_start_command(host, data->stop, 0);
+	}
+
+	spin_unlock_irqrestore(&host->lock, flags);
 }
 
 static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
@@ -314,6 +375,8 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 	struct scatterlist *sg;
 	dma_cookie_t cookie;
 	int i;
+	unsigned int irqmask0;
+	int sg_len;
 
 	datactrl |= MCI_DPSM_DMAENABLE;
 	datactrl |= variant->dmareg_enable;
@@ -344,15 +407,19 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 			return -EINVAL;
 	}
 
-	dma_map_sg(mmc_dev(host->mmc), data->sg,
-		   data->sg_len, direction);
+	sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+				data->sg_len, direction);
+	if (!sg_len)
+		goto map_err;
 
 	desc = chan->device->device_prep_slave_sg(chan,
-					data->sg, data->sg_len, direction,
-					DMA_CTRL_ACK);
+					data->sg, sg_len, direction,
+					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
 	if (!desc)
 		goto unmap_exit;
 
+	desc->callback = mmci_dma_callback;
+	desc->callback_param = host;
 	host->dma_desc = desc;
 	dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d "
 		 "blksz %04x blks %04x flags %08x\n",
@@ -366,20 +433,25 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 	host->dma_on_current_xfer = true;
 	chan->device->device_issue_pending(chan);
 
-	/* Trigger the DMA transfer */
-	writel(datactrl, host->base + MMCIDATACTRL);
 	/*
-	 * Let the MMCI say when the data is ended and it's time
-	 * to fire next DMA request. When that happens, MMCI will
-	 * call mmci_data_end()
+	 * MMCI monitors both MCI_DATAEND and the DMA callback.
+	 * Both events must occur before the transfer is considered
+	 * to be completed. MCI_DATABLOCKEND is not used in DMA mode.
 	 */
-	writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK,
-	       host->base + MMCIMASK0);
+	host->last_blockend = true;
+	irqmask0 = readl(host->base + MMCIMASK0);
+	irqmask0 |= MCI_DATAENDMASK;
+	irqmask0 &= ~MCI_DATABLOCKENDMASK;
+	writel(irqmask0, host->base + MMCIMASK0);
+
+	/* Trigger the DMA transfer */
+	writel(datactrl, host->base + MMCIDATACTRL);
 	return 0;
 
 unmap_exit:
-	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
 	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
+map_err:
+	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
 	return -ENOMEM;
 }
 #else
@@ -478,43 +550,20 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 		if (mmc_card_sdio(host->mmc->card))
 			datactrl |= MCI_ST_DPSM_SDIOEN;
 
-	writel(datactrl, base + MMCIDATACTRL);
+	/* Setup IRQ */
 	irqmask0 = readl(base + MMCIMASK0);
-	if (variant->broken_blockend)
+	if (variant->broken_blockend) {
+		host->last_blockend = true;
 		irqmask0 &= ~MCI_DATABLOCKENDMASK;
-	else
+	} else {
 		irqmask0 |= MCI_DATABLOCKENDMASK;
+	}
 	irqmask0 &= ~MCI_DATAENDMASK;
 	writel(irqmask0, base + MMCIMASK0);
 	mmci_set_mask1(host, irqmask1);
-}
-
-static void
-mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
-{
-	void __iomem *base = host->base;
-
-	dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n",
-	    cmd->opcode, cmd->arg, cmd->flags);
-
-	if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
-		writel(0, base + MMCICOMMAND);
-		udelay(1);
-	}
 
-	c |= cmd->opcode | MCI_CPSM_ENABLE;
-	if (cmd->flags & MMC_RSP_PRESENT) {
-		if (cmd->flags & MMC_RSP_136)
-			c |= MCI_CPSM_LONGRSP;
-		c |= MCI_CPSM_RESPONSE;
-	}
-	if (/*interrupt*/0)
-		c |= MCI_CPSM_INTERRUPT;
-
-	host->cmd = cmd;
-
-	writel(cmd->arg, base + MMCIARGUMENT);
-	writel(c, base + MMCICOMMAND);
+	/* Start the data transfer */
+	writel(datactrl, base + MMCIDATACTRL);
 }
 
 static void
@@ -601,26 +650,29 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 	 * on others we must sync with the blockend signal since they can
 	 * appear out-of-order.
 	 */
-	if (host->dataend &&
-	    (host->last_blockend || variant->broken_blockend)) {
-		mmci_dma_data_end(host);
-		mmci_stop_data(host);
-
-		/* Reset these flags */
+	if (host->dataend && host->last_blockend) {
 		host->last_blockend = false;
-		host->dataend = false;
 
 		/*
-		 * Variants with broken blockend flags need to handle the
-		 * end of the entire transfer here.
+		 * Make sure there is no dma transfer running before
+		 * ending the transfer and the request.
 		 */
-		if (variant->broken_blockend && !data->error)
+		if (!host->dma_on_current_xfer) {
 			host->data_xfered += data->blksz * data->blocks;
+			mmci_stop_data(host);
+			host->dataend = false;
 
-		if (!data->stop) {
-			mmci_request_end(host, data->mrq);
-		} else {
-			mmci_start_command(host, data->stop, 0);
+			/*
+			 * Variants with broken blockend flags need to handle
+			 * the end of the entire transfer here.
+			 */
+			if (variant->broken_blockend && !data->error)
+				host->data_xfered += data->blksz * data->blocks;
+
+			if (!data->stop)
+				mmci_request_end(host, data->mrq);
+			else
+				mmci_start_command(host, data->stop, 0);
 		}
 	}
 }
@@ -1130,10 +1182,13 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 	mmc->max_req_size = (1 << variant->datalength_bits) - 1;
 
 	/*
-	 * Set the maximum segment size.  Since we aren't doing DMA
-	 * (yet) we are only limited by the data length register.
+	 * Set the maximum segment size. Right now DMA sets the
+	 * limit and not the data length register. Thus until the DMA
+	 * driver not handles this, the segment size is limited by DMA.
+	 * DMA limit: src_addr_width x (64 KB -1). src_addr_width
+	 * can be 1.
 	 */
-	mmc->max_seg_size = mmc->max_req_size;
+	mmc->max_seg_size = 65535;
 
 	/*
 	 * Block size can be up to 2048 bytes, but must be a power of two.
-- 
1.7.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [FYI 4/4] ARM: mmci: add support for double buffering
  2011-01-12 18:19 [FYI 0/4] arm: mmci: example implementation of double buffering Per Forlin
                   ` (2 preceding siblings ...)
  2011-01-12 18:19 ` [FYI 3/4] MMCI: Corrections for DMA Per Forlin
@ 2011-01-12 18:19 ` Per Forlin
  3 siblings, 0 replies; 5+ messages in thread
From: Per Forlin @ 2011-01-12 18:19 UTC (permalink / raw)
  To: linux-mmc, linux-arm-kernel, linux-kernel, linaro-dev
  Cc: Chris Ball, Per Forlin

This is an example of how to utilize the double buffer
support in the mmc framework. This patch is not intended
nor ready for mainline.

Implement pre_req() and post_reg() hooks. pre_req()
runs dma_map_sg and prepares the dma descriptor and
post_req calls dma_unmap_sg.

Signed-off-by: Per Forlin <per.forlin@linaro.org>
---
 drivers/mmc/host/mmci.c |  164 ++++++++++++++++++++++++++++++++++++-----------
 drivers/mmc/host/mmci.h |    4 +
 2 files changed, 130 insertions(+), 38 deletions(-)

diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index ab44f5f..7f0b12a 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -276,6 +276,7 @@ static void __devinit mmci_setup_dma(struct mmci_host *host)
 		host->dma_tx_channel = host->dma_rx_channel;
 	}
 	host->dma_enable = true;
+
 	dev_info(mmc_dev(host->mmc), "use DMA channels DMA RX %s, DMA TX %s\n",
 		 dma_chan_name(host->dma_rx_channel),
 		 dma_chan_name(host->dma_tx_channel));
@@ -296,11 +297,6 @@ static inline void mmci_disable_dma(struct mmci_host *host)
 
 static void mmci_dma_data_end(struct mmci_host *host)
 {
-	struct mmc_data *data = host->data;
-
-	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
-		     (data->flags & MMC_DATA_WRITE)
-		     ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
 	host->dma_on_current_xfer = false;
 }
 
@@ -353,7 +349,9 @@ static void mmci_dma_callback(void *arg)
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
-static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+static struct dma_async_tx_descriptor *mmci_dma_cfg(struct mmc_data *data,
+						    struct mmci_host *host,
+						    struct dma_chan **chan_dma)
 {
 	struct variant_data *variant = host->variant;
 	struct dma_slave_config rx_conf = {
@@ -368,19 +366,13 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 		.direction = DMA_TO_DEVICE,
 		.dst_maxburst = variant->fifohalfsize >> 2, /* # of words */
 	};
-	struct mmc_data *data = host->data;
 	enum dma_data_direction direction;
 	struct dma_chan *chan;
 	struct dma_async_tx_descriptor *desc;
 	struct scatterlist *sg;
-	dma_cookie_t cookie;
 	int i;
-	unsigned int irqmask0;
 	int sg_len;
 
-	datactrl |= MCI_DPSM_DMAENABLE;
-	datactrl |= variant->dmareg_enable;
-
 	if (data->flags & MMC_DATA_READ) {
 		if (host->size <= variant->txsize_threshold)
 			rx_conf.src_maxburst = 1;
@@ -404,11 +396,12 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 		dev_vdbg(mmc_dev(host->mmc), "MMCI SGlist %d dir %d: length: %08x\n",
 			 i, direction, sg->length);
 		if (sg->offset & 3 || sg->length & 3)
-			return -EINVAL;
+			return NULL;
 	}
 
 	sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
-				data->sg_len, direction);
+			    data->sg_len, direction);
+
 	if (!sg_len)
 		goto map_err;
 
@@ -420,7 +413,42 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 
 	desc->callback = mmci_dma_callback;
 	desc->callback_param = host;
-	host->dma_desc = desc;
+
+	*chan_dma = chan;
+	return desc;
+unmap_exit:
+	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
+map_err:
+	*chan_dma = NULL;
+	return NULL;
+}
+
+static void mmci_dma_prepare(struct mmc_data *data, struct mmci_host *host)
+{
+
+	if (data != host->data_next)
+		host->dma_desc = mmci_dma_cfg(data, host, &host->cur_chan);
+	else {
+		host->dma_desc = host->dma_desc_next;
+		host->cur_chan = host->next_chan;
+
+		host->dma_desc_next = NULL;
+		host->data_next = NULL;
+		host->next_chan = NULL;
+	}
+
+	BUG_ON(!host->dma_desc);
+	BUG_ON(!host->cur_chan);
+}
+
+static int mmci_dma_start_data(struct mmci_host *host)
+{
+	struct mmc_data *data = host->data;
+	struct dma_async_tx_descriptor *desc = host->dma_desc;
+	struct dma_chan *chan = host->cur_chan;
+	dma_cookie_t cookie;
+	enum dma_data_direction direction;
+
 	dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d "
 		 "blksz %04x blks %04x flags %08x\n",
 		 data->sg_len, data->blksz, data->blocks, data->flags);
@@ -433,6 +461,24 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 	host->dma_on_current_xfer = true;
 	chan->device->device_issue_pending(chan);
 
+	return 0;
+unmap_exit:
+	if (data->flags & MMC_DATA_READ)
+		direction = DMA_FROM_DEVICE;
+
+	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
+	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
+
+	return -ENOMEM;
+}
+
+static int mmci_dma_start_fifo(struct mmci_host *host, unsigned int datactrl)
+{
+	unsigned int irqmask0;
+
+	datactrl |= MCI_DPSM_DMAENABLE;
+	datactrl |= host->variant->dmareg_enable;
+
 	/*
 	 * MMCI monitors both MCI_DATAEND and the DMA callback.
 	 * Both events must occur before the transfer is considered
@@ -447,12 +493,45 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
 	/* Trigger the DMA transfer */
 	writel(datactrl, host->base + MMCIDATACTRL);
 	return 0;
+}
+
+static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct mmci_host *host = mmc_priv(mmc);
+	struct mmc_data *data = mrq->data;
+
+	if (host->dma_enable)
+		dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+			     (data->flags & MMC_DATA_WRITE)
+			     ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+}
+
+static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq,
+			     bool host_is_idle)
+{
+	struct mmci_host *host = mmc_priv(mmc);
+	struct mmc_data *data = mrq->data;
+
+	if (host->dma_enable && !host_is_idle) {
+		struct dma_async_tx_descriptor *desc;
+		struct dma_chan *chan;
+
+		desc = mmci_dma_cfg(data, host, &chan);
+		if (desc == NULL)
+			goto no_next;
+
+		host->dma_desc_next = desc;
+		host->data_next = data;
+		host->next_chan = chan;
+	}
+
+	return;
+
+ no_next:
+	host->dma_desc_next = NULL;
+	host->data_next = NULL;
+	host->next_chan = NULL;
 
-unmap_exit:
-	dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction);
-map_err:
-	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);
-	return -ENOMEM;
 }
 #else
 /* Blank functions if the DMA engine is not available */
@@ -472,10 +551,23 @@ static inline void mmci_dma_terminate(struct mmci_host *host)
 {
 }
 
-static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+static inline int mmci_dma_start_data(struct mmci_host *host)
 {
 	return -ENOSYS;
 }
+
+static inline int mmci_dma_start_fifo(struct mmci_host *host,
+				      unsigned int datactrl)
+{
+	return -ENOSYS;
+}
+
+static void mmci_dma_prepare(struct mmc_data *data, struct mmci_host *host)
+{
+}
+
+#define mmci_post_request NULL
+#define mmci_pre_request NULL
 #endif
 
 static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
@@ -519,7 +611,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
 		 * Attempt to use DMA operation mode, if this
 		 * should fail, fall back to PIO mode
 		 */
-		ret = mmci_dma_start_data(host, datactrl);
+		ret = mmci_dma_start_fifo(host, datactrl);
 		if (!ret)
 			return;
 	}
@@ -662,13 +754,6 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
 			mmci_stop_data(host);
 			host->dataend = false;
 
-			/*
-			 * Variants with broken blockend flags need to handle
-			 * the end of the entire transfer here.
-			 */
-			if (variant->broken_blockend && !data->error)
-				host->data_xfered += data->blksz * data->blocks;
-
 			if (!data->stop)
 				mmci_request_end(host, data->mrq);
 			else
@@ -705,6 +790,8 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
 		mmci_request_end(host, cmd->mrq);
 	} else if (!(cmd->data->flags & MMC_DATA_READ)) {
 		mmci_start_data(host, cmd->data);
+		if (host->dma_enable)
+			mmci_dma_start_data(host);
 	}
 }
 
@@ -944,6 +1031,13 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 
 	mmci_start_command(host, mrq->cmd, 0);
 
+	if (host->dma_enable && mrq->data) {
+		mmci_dma_prepare(mrq->data, host);
+
+		if (mrq->data->flags & MMC_DATA_READ)
+			mmci_dma_start_data(host);
+	}
+
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
@@ -1053,6 +1147,8 @@ static irqreturn_t mmci_cd_irq(int irq, void *dev_id)
 
 static const struct mmc_host_ops mmci_ops = {
 	.request	= mmci_request,
+	.pre_req	= mmci_pre_request,
+	.post_req	= mmci_post_request,
 	.set_ios	= mmci_set_ios,
 	.get_ro		= mmci_get_ro,
 	.get_cd		= mmci_get_cd,
@@ -1180,15 +1276,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id)
 	 * single request.
 	 */
 	mmc->max_req_size = (1 << variant->datalength_bits) - 1;
-
-	/*
-	 * Set the maximum segment size. Right now DMA sets the
-	 * limit and not the data length register. Thus until the DMA
-	 * driver not handles this, the segment size is limited by DMA.
-	 * DMA limit: src_addr_width x (64 KB -1). src_addr_width
-	 * can be 1.
-	 */
-	mmc->max_seg_size = 65535;
+	mmc->max_seg_size = mmc->max_req_size;
 
 	/*
 	 * Block size can be up to 2048 bytes, but must be a power of two.
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
index 39b7ac7..828ab5a 100644
--- a/drivers/mmc/host/mmci.h
+++ b/drivers/mmc/host/mmci.h
@@ -196,6 +196,10 @@ struct mmci_host {
 	struct dma_chan		*dma_rx_channel;
 	struct dma_chan		*dma_tx_channel;
 	struct dma_async_tx_descriptor *dma_desc;
+	struct dma_async_tx_descriptor *dma_desc_next;
+	struct mmc_data		*data_next;
+	struct dma_chan		*cur_chan;
+	struct dma_chan		*next_chan;
 #endif
 };
 
-- 
1.7.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2011-01-12 18:21 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-01-12 18:19 [FYI 0/4] arm: mmci: example implementation of double buffering Per Forlin
2011-01-12 18:19 ` [FYI 1/4] mmci: fixup broken_blockend variant patch Per Forlin
2011-01-12 18:19 ` [FYI 2/4] mmci: add PrimeCell generic DMA to MMCI/PL180 Per Forlin
2011-01-12 18:19 ` [FYI 3/4] MMCI: Corrections for DMA Per Forlin
2011-01-12 18:19 ` [FYI 4/4] ARM: mmci: add support for double buffering Per Forlin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®