[PATCH 1/2] mmc: sdhci: combined dma buffer support for sdma

Barry Song 21cnbao at gmail.com
Mon Dec 23 08:22:03 EST 2013


From: Bin Shi <Bin.Shi at csr.com>

Since some SD host controller has just sdma scheme, no adma for
sd/mmc/sdio host, in current sdhci driver, sdma just enable max_segs
be 1, so all the sg list length will be 1 and host driver will
handler each one by one with lots of cost in request/cmd_done/
irq_handler/tasklet_handler, which make worse read/write performance.

A better solution is copy sg data to pre-defined dma coherent buffer
and write to sd, or copy data from dma buffer to sg data and let high
layer core to access. So we define quirks2
SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER to distinglish with
normal dma mapping.

Also this will involve one more memory copy, but good IO performance
is got:
On CSR SiRFprimaII, reading 8192KB will speed up from 17444KB/s to
18687KB/s, 7% lift.

Signed-off-by: Bin Shi <Bin.Shi at csr.com>
Signed-off-by: Barry Song <Barry.Song at csr.com>
---
 drivers/mmc/host/sdhci.c  |   98 ++++++++++++++++++++++++++++++++++++---------
 include/linux/mmc/sdhci.h |    5 ++
 2 files changed, 84 insertions(+), 19 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index bd8a098..aac92bd 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -726,6 +726,40 @@ static void sdhci_set_transfer_irqs(struct sdhci_host *host)
 		sdhci_clear_set_irqs(host, dma_irqs, pio_irqs);
 }
 
+static inline void sdhci_sg_to_dma(struct sdhci_host *host, struct mmc_data *data)
+{
+        unsigned int len, i;
+        struct scatterlist *sg;
+        void *dmabuf = host->combined_dma_buffer;
+        void *sgbuf;
+
+        sg = data->sg;
+        len = data->sg_len;
+
+        for (i = 0; i < len; i++) {
+                sgbuf = sg_virt(&sg[i]);
+                memcpy(dmabuf, sgbuf, sg[i].length);
+                dmabuf += sg[i].length;
+        }
+}
+
+static inline void sdhci_dma_to_sg(struct sdhci_host *host, struct mmc_data *data)
+{
+        unsigned int len, i;
+        struct scatterlist *sg;
+        void *dmabuf = host->combined_dma_buffer;
+        void *sgbuf;
+
+        sg = data->sg;
+        len = data->sg_len;
+
+        for (i = 0; i < len; i++) {
+                sgbuf = sg_virt(&sg[i]);
+                memcpy(sgbuf, dmabuf, sg[i].length);
+                dmabuf += sg[i].length;
+        }
+}
+
 static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
 {
 	u8 count;
@@ -836,22 +870,34 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
 		} else {
 			int sg_cnt;
 
-			sg_cnt = dma_map_sg(mmc_dev(host->mmc),
-					data->sg, data->sg_len,
-					(data->flags & MMC_DATA_READ) ?
-						DMA_FROM_DEVICE :
-						DMA_TO_DEVICE);
-			if (sg_cnt == 0) {
-				/*
-				 * This only happens when someone fed
-				 * us an invalid request.
-				 */
-				WARN_ON(1);
-				host->flags &= ~SDHCI_REQ_USE_DMA;
-			} else {
-				WARN_ON(sg_cnt != 1);
-				sdhci_writel(host, sg_dma_address(data->sg),
+			/*
+			 * Transfer data from the SG list to
+			 * the DMA buffer.
+			 */
+			if (host->quirks2 & SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER) {
+				if (data->flags & MMC_DATA_WRITE)
+					sdhci_sg_to_dma(host, data);
+				sdhci_writel(host, host->combined_dma_addr,
 					SDHCI_DMA_ADDRESS);
+			} else {
+
+				sg_cnt = dma_map_sg(mmc_dev(host->mmc),
+						data->sg, data->sg_len,
+						(data->flags & MMC_DATA_READ) ?
+							DMA_FROM_DEVICE :
+							DMA_TO_DEVICE);
+				if (sg_cnt == 0) {
+					/*
+					 * This only happens when someone fed
+					 * us an invalid request.
+					 */
+					WARN_ON(1);
+					host->flags &= ~SDHCI_REQ_USE_DMA;
+				} else {
+					WARN_ON(sg_cnt != 1);
+						sdhci_writel(host, sg_dma_address(data->sg),
+							SDHCI_DMA_ADDRESS);
+				}
 			}
 		}
 	}
@@ -939,9 +985,11 @@ static void sdhci_finish_data(struct sdhci_host *host)
 		if (host->flags & SDHCI_USE_ADMA)
 			sdhci_adma_table_post(host, data);
 		else {
-			dma_unmap_sg(mmc_dev(host->mmc), data->sg,
-				data->sg_len, (data->flags & MMC_DATA_READ) ?
-					DMA_FROM_DEVICE : DMA_TO_DEVICE);
+			if (!(host->quirks2 & SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER)) {
+				dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+					data->sg_len, (data->flags & MMC_DATA_READ) ?
+						DMA_FROM_DEVICE : DMA_TO_DEVICE);
+			}
 		}
 	}
 
@@ -2147,6 +2195,15 @@ static void sdhci_tasklet_finish(unsigned long param)
 	mrq = host->mrq;
 
 	/*
+	 * Transfer data from DMA buffer to
+	 * SG list.
+	 */
+	if ((host->quirks2 & SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER) &&
+		mrq->data && (mrq->data->flags & MMC_DATA_READ))
+			if (host->flags & SDHCI_REQ_USE_DMA)
+				sdhci_dma_to_sg(host, mrq->data);
+
+	/*
 	 * The controller needs a reset of internal state machines
 	 * upon error conditions.
 	 */
@@ -3152,7 +3209,10 @@ int sdhci_add_host(struct sdhci_host *host)
 	if (host->flags & SDHCI_USE_ADMA)
 		mmc->max_segs = 128;
 	else if (host->flags & SDHCI_USE_SDMA)
-		mmc->max_segs = 1;
+		if (host->quirks2 & SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER)
+			mmc->max_segs = 128;
+		else
+			mmc->max_segs = 1;
 	else /* PIO */
 		mmc->max_segs = 128;
 
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index 3e781b8..c2fc13f 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -98,6 +98,8 @@ struct sdhci_host {
 #define SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON		(1<<4)
 /* Controller has a non-standard host control register */
 #define SDHCI_QUIRK2_BROKEN_HOST_CONTROL		(1<<5)
+/* For better performance for SDMA controller, alloc a buffer to combine */
+#define SDHCI_QUIRK2_SG_LIST_COMBINED_DMA_BUFFER	(1<<6)
 
 	int irq;		/* Device IRQ */
 	void __iomem *ioaddr;	/* Mapped address */
@@ -160,6 +162,9 @@ struct sdhci_host {
 	dma_addr_t adma_addr;	/* Mapped ADMA descr. table */
 	dma_addr_t align_addr;	/* Mapped bounce buffer */
 
+	dma_addr_t combined_dma_addr;	/* combined dma buffer */
+	void *combined_dma_buffer;/* Mapped combined dma buffer */
+
 	struct tasklet_struct card_tasklet;	/* Tasklet structures */
 	struct tasklet_struct finish_tasklet;
 
-- 
1.7.5.4




More information about the linux-arm-kernel mailing list