From: Bin Shi <Bin.Shi@xxxxxxx> 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@xxxxxxx> Signed-off-by: Barry Song <Barry.Song@xxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html