On Thu, 29 Nov 2018 at 18:41, Adrian Hunter <adrian.hunter@xxxxxxxxx> wrote: > > On 29/11/18 11:44 AM, Chunyan Zhang wrote: > > On Thu, 29 Nov 2018 at 17:25, Adrian Hunter <adrian.hunter@xxxxxxxxx> wrote: > >> > >> On 29/11/18 8:07 AM, Chunyan Zhang wrote: > >>> Some standard SD host controllers can support both external dma > >>> controllers as well as ADMA/SDMA in which the SD host controller > >>> acts as DMA master. TI's omap controller is the case as an example. > >>> > >>> Currently the generic SDHCI code supports ADMA/SDMA integrated in > >>> the host controller but does not have any support for external DMA > >>> controllers implemented using dmaengine, meaning that custom code is > >>> needed for any systems that use an external DMA controller with SDHCI. > >>> > >>> Signed-off-by: Chunyan Zhang <zhang.chunyan@xxxxxxxxxx> > >>> --- > >>> drivers/mmc/host/Kconfig | 14 ++++ > >>> drivers/mmc/host/sdhci.c | 185 ++++++++++++++++++++++++++++++++++++++++++++++- > >>> drivers/mmc/host/sdhci.h | 8 ++ > >>> 3 files changed, 206 insertions(+), 1 deletion(-) > >>> > >>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > >>> index 1b58739..4183f43 100644 > >>> --- a/drivers/mmc/host/Kconfig > >>> +++ b/drivers/mmc/host/Kconfig > >>> @@ -969,6 +969,7 @@ config MMC_SDHCI_XENON > >>> config MMC_SDHCI_OMAP > >>> tristate "TI SDHCI Controller Support" > >>> depends on MMC_SDHCI_PLTFM && OF > >>> + select MMC_SDHCI_EXTERNAL_DMA if DMA_ENGINE > >>> help > >>> This selects the Secure Digital Host Controller Interface (SDHCI) > >>> support present in TI's DRA7 SOCs. The controller supports > >>> @@ -977,3 +978,16 @@ config MMC_SDHCI_OMAP > >>> If you have a controller with this interface, say Y or M here. > >>> > >>> If unsure, say N. > >>> + > >>> +config MMC_SDHCI_EXTERNAL_DMA > >>> + bool "Support external DMA in standard SD host controller" > >>> + depends on MMC_SDHCI > >>> + depends on DMA_ENGINE > >>> + help > >>> + This is an option for using external DMA device via dmaengine > >>> + framework. > >>> + > >>> + If you have a controller which support using external DMA device > >>> + for data transfer, can say Y. > >>> + > >>> + If unsure, say N. > >> > >> So if you are going to select this, then you don't need the prompt or help > >> anymore i.e. > >> > >> config MMC_SDHCI_EXTERNAL_DMA > >> bool > >> > >> > >>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > >>> index 99bdae5..ad7cc80 100644 > >>> --- a/drivers/mmc/host/sdhci.c > >>> +++ b/drivers/mmc/host/sdhci.c > >>> @@ -14,6 +14,7 @@ > >>> */ > >>> > >>> #include <linux/delay.h> > >>> +#include <linux/dmaengine.h> > >>> #include <linux/ktime.h> > >>> #include <linux/highmem.h> > >>> #include <linux/io.h> > >>> @@ -1309,6 +1310,162 @@ static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq) > >>> del_timer(&host->timer); > >>> } > >>> > >>> +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > >>> +static int sdhci_external_dma_init(struct sdhci_host *host) > >>> +{ > >>> + int ret = 0; > >>> + struct mmc_host *mmc = host->mmc; > >>> + > >>> + host->tx_chan = dma_request_chan(mmc->parent, "tx"); > >>> + if (IS_ERR(host->tx_chan)) { > >>> + ret = PTR_ERR(host->tx_chan); > >>> + if (ret != -EPROBE_DEFER) > >>> + pr_warn("Failed to request TX DMA channel.\n"); > >>> + host->tx_chan = NULL; > >>> + return ret; > >>> + } > >>> + > >>> + host->rx_chan = dma_request_chan(mmc->parent, "rx"); > >>> + if (IS_ERR(host->rx_chan)) { > >>> + if (host->tx_chan) { > >>> + dma_release_channel(host->tx_chan); > >>> + host->tx_chan = NULL; > >>> + } > >>> + > >>> + ret = PTR_ERR(host->rx_chan); > >>> + if (ret != -EPROBE_DEFER) > >>> + pr_warn("Failed to request RX DMA channel.\n"); > >>> + host->rx_chan = NULL; > >>> + } > >>> + > >>> + return ret; > >>> +} > >>> + > >>> +static inline struct dma_chan * > >>> +sdhci_external_dma_channel(struct sdhci_host *host, struct mmc_data *data) > >>> +{ > >>> + return data->flags & MMC_DATA_WRITE ? host->tx_chan : host->rx_chan; > >>> +} > >>> + > >>> +static int sdhci_external_dma_setup(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + int ret, i; > >>> + struct dma_async_tx_descriptor *desc; > >>> + struct mmc_data *data = cmd->data; > >>> + struct dma_chan *chan; > >>> + struct dma_slave_config cfg; > >>> + dma_cookie_t cookie; > >>> + > >>> + if (!host->mapbase) > >>> + return -EINVAL; > >>> + > >>> + if (!data) > >>> + return 0; > >> > >> It would read better if the above 2 if-statements were the other way around i.e. > >> > >> if (!data) > >> return 0; > >> > >> if (!host->mapbase) > >> return -EINVAL; > >> > > > > Ok. > > > >>> + > >>> + cfg.src_addr = host->mapbase + SDHCI_BUFFER; > >>> + cfg.dst_addr = host->mapbase + SDHCI_BUFFER; > >>> + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; > >>> + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; > >>> + cfg.src_maxburst = data->blksz / 4; > >>> + cfg.dst_maxburst = data->blksz / 4; > >>> + > >>> + /* Sanity check: all the SG entries must be aligned by block size. */ > >>> + for (i = 0; i < data->sg_len; i++) { > >>> + if ((data->sg + i)->length % data->blksz) > >>> + return -EINVAL; > >>> + } > >>> + > >>> + chan = sdhci_external_dma_channel(host, data); > >>> + > >>> + ret = dmaengine_slave_config(chan, &cfg); > >>> + if (ret) > >>> + return ret; > >>> + > >>> + desc = dmaengine_prep_slave_sg(chan, data->sg, data->sg_len, > >>> + mmc_get_dma_dir(data), > >>> + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); > >>> + if (!desc) > >>> + return -EINVAL; > >>> + > >>> + desc->callback = NULL; > >>> + desc->callback_param = NULL; > >>> + > >>> + cookie = dmaengine_submit(desc); > >>> + if (cookie < 0) > >>> + ret = cookie; > >>> + > >>> + return ret; > >>> +} > >>> + > >>> +static void sdhci_external_dma_release(struct sdhci_host *host) > >>> +{ > >>> + if (host->tx_chan) { > >>> + dma_release_channel(host->tx_chan); > >>> + host->tx_chan = NULL; > >>> + } > >>> + > >>> + if (host->rx_chan) { > >>> + dma_release_channel(host->rx_chan); > >>> + host->rx_chan = NULL; > >>> + } > >>> + > >>> + sdhci_switch_external_dma(host, false); > >>> +} > >>> + > >>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + if (sdhci_external_dma_setup(host, cmd)) { > >>> + sdhci_external_dma_release(host); > >>> + pr_err("%s: Failed to setup external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > >>> + mmc_hostname(host->mmc)); > >>> + } else { > >>> + /* Prepare for using external dma */ > >>> + host->flags |= SDHCI_REQ_USE_DMA; > >>> + } > >>> + > >>> + sdhci_prepare_data(host, cmd); > >>> +} > >>> + > >>> +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + struct dma_chan *chan; > >>> + > >>> + if (cmd->opcode != MMC_SET_BLOCK_COUNT && cmd->data) { > >> > >> MMC_SET_BLOCK_COUNT doesn't have data so that part is not needed > > > > Didn't get you here. > > This is for other packets except MMC_SET_BLOCK_COUNT. > > > > MMC_SET_BLOCK_COUNT is not a data transfer command, so cmd->data == NULL anyway. Oh, got you. "if (cmd->data)" is enough here :) Thank you, Chunyan