* [PATCH 0/7] Port am335 and am437 devices to sdhci-omap @ 2019-01-11 11:08 Faiz Abbas 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas ` (7 more replies) 0 siblings, 8 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas The following add driver patches for porting TI's am335x and am437x devices to the sdhci-omap driver. This involves adding external DMA support to sdhci (first 3 patches from Chunyan) plus some miscellaneous patches to take care of deviations of the controllers from the sdhci model. DT changes will be posted in a separate series. Untested versions of Chunyan's patches were posted before[1]. Tested on: am335x-evm, am335x-boneblack, am335x-sk, am437x-gpevm, am43xx-gpevm, am437x-idk, dra7xx-evm, dra72x-evm [1] https://patchwork.kernel.org/project/linux-mmc/list/?series=54897 Chunyan Zhang (3): mmc: sdhci: add support for using external DMA devices mmc: sdhci-omap: Add using external dma dt-bindings: sdhci-omap: Add example for using external dma Faiz Abbas (4): mmc: sdhci: Add quirk for disabling DTO during erase command mmc: sdhci_omap: Add DISABLE_DTO_FOR_ERASE Quirk dt-bindings: sdhci-omap: Add am335x and am437x specific bindings mmc: sdhci-omap: Add am335x and am437x specific compatibles .../devicetree/bindings/mmc/sdhci-omap.txt | 9 + drivers/mmc/host/Kconfig | 4 + drivers/mmc/host/sdhci-omap.c | 27 +- drivers/mmc/host/sdhci.c | 274 +++++++++++++++++- drivers/mmc/host/sdhci.h | 10 + 5 files changed, 319 insertions(+), 5 deletions(-) -- 2.19.2 ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-14 8:45 ` Adrian Hunter 2019-01-24 11:40 ` Adrian Hunter 2019-01-11 11:08 ` [PATCH 2/7] mmc: sdhci-omap: Add using external dma Faiz Abbas ` (6 subsequent siblings) 7 siblings, 2 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas From: Chunyan Zhang <zhang.chunyan@linaro.org> 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. Fixes by Faiz Abbas <faiz_abbas@ti.com>: 1. Map scatterlists before dmaengine_prep_slave_sg() 2. Use dma_async() functions inside of the send_command() path and synchronize once at the start of each request. Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- drivers/mmc/host/Kconfig | 3 + drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 8 ++ 3 files changed, 273 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index e26b8145efb3..333292e8ecdd 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 If you have a controller with this interface, say Y or M here. If unsure, say N. + +config MMC_SDHCI_EXTERNAL_DMA + bool diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a22e11a65658..4a9044c06e21 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> @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) } } +#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; + int sg_cnt; + + if (!host->mapbase) + return -EINVAL; + + 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; + + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); + if (sg_cnt <= 0) + return -EINVAL; + + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, + struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + + host->data_timeout = 0; + + if (sdhci_data_line_cmd(cmd)) + sdhci_set_timeout(host, cmd); + + WARN_ON(host->data); + + /* Sanity checks */ + WARN_ON(data->blksz * data->blocks > 524288); + WARN_ON(data->blksz > host->mmc->max_blk_size); + WARN_ON(data->blocks > 65535); + + host->flags |= SDHCI_REQ_USE_DMA; + host->data = data; + host->data_early = 0; + host->data->bytes_xfered = 0; + + sdhci_set_transfer_irqs(host); + + /* + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count + * can be supported, in that case 16-bit block count register must be 0. + */ + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); + } else { + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + } + + return 0; +} + +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, + struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + + if (!data) + return; + + if (sdhci_external_dma_setup(host, cmd) || + __sdhci_external_dma_prepare_data(host, cmd)) { + sdhci_external_dma_release(host); + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", + mmc_hostname(host->mmc)); + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) + return; + + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); + chan = sdhci_external_dma_channel(host, cmd->data); + if (chan) + dma_async_issue_pending(chan); +} + +static int sdhci_external_dma_cleanup(struct sdhci_host *host, + struct mmc_data *data) +{ + struct dma_chan *chan = sdhci_external_dma_channel(host, data); + int ret = 0; + + if (chan) + ret = dmaengine_terminate_async(chan); + + return ret; +} +#else +static int sdhci_external_dma_init(struct sdhci_host *host) +{ + return -EOPNOTSUPP; +} + +static void sdhci_external_dma_release(struct sdhci_host *host) +{} + +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, + struct mmc_command *cmd) +{ + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ + sdhci_prepare_data(host, cmd); +} + +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, + struct mmc_command *cmd) +{} + +static int sdhci_external_dma_cleanup(struct sdhci_host *host, + struct mmc_data *data) +{ + return 0; +} +#endif + +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) +{ + host->use_external_dma = en; +} +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); + static inline bool sdhci_auto_cmd12(struct sdhci_host *host, struct mmc_request *mrq) { @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) host->data_cmd = cmd; } - sdhci_prepare_data(host, cmd); + if (host->use_external_dma) + sdhci_external_dma_prepare_data(host, cmd); + else + sdhci_prepare_data(host, cmd); sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) timeout += 10 * HZ; sdhci_mod_timer(host, cmd->mrq, timeout); + if (host->use_external_dma) + sdhci_external_dma_pre_transfer(host, cmd); + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } EXPORT_SYMBOL_GPL(sdhci_send_command); @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) sdhci_led_activate(host); + if (host->use_external_dma && mrq->data) { + struct dma_chan *chan = sdhci_external_dma_channel(host, + mrq->data); + dmaengine_synchronize(chan); + } /* * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED * requests if Auto-CMD12 is enabled. @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, mmc_get_dma_dir(data)); + if (host->use_external_dma) + sdhci_external_dma_cleanup(host, data); } data->host_cookie = COOKIE_UNMAPPED; } @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) mmc_hostname(mmc), host->version); } - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { host->flags |= SDHCI_USE_SDMA; - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { DBG("Controller doesn't have SDMA capability\n"); - else + } else if (host->use_external_dma) { + /* Using dma-names to detect external dma capability */ + } else { host->flags |= SDHCI_USE_SDMA; + } if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && (host->flags & SDHCI_USE_SDMA)) { @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) } } + if (host->use_external_dma) { + ret = sdhci_external_dma_init(host); + if (ret == -EPROBE_DEFER) + goto unreg; + + /* + * Fall back to use the DMA/PIO integrated in standard SDHCI + * instead of external DMA devices. + */ + if (ret) + sdhci_switch_external_dma(host, false); + } + /* * If we use DMA, then it's up to the caller to set the DMA * mask, but PIO does not need the hw shim so we set a new @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + host->adma_table_sz, host->align_buffer, host->align_addr); + + if (host->use_external_dma) + sdhci_external_dma_release(host); + host->adma_table = NULL; host->align_buffer = NULL; } @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) pr_info("%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), + host->use_external_dma ? "External DMA" : (host->flags & SDHCI_USE_ADMA) ? (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) host->adma_table_sz, host->align_buffer, host->align_addr); + if (host->use_external_dma) + sdhci_external_dma_release(host); + host->adma_table = NULL; host->align_buffer = NULL; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 6cc9a3c2ac66..7a52823ebef4 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -482,6 +482,7 @@ struct sdhci_host { int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ + phys_addr_t mapbase; /* physical address base */ char *bounce_buffer; /* For packing SDMA reads/writes */ dma_addr_t bounce_addr; unsigned int bounce_buffer_size; @@ -531,6 +532,7 @@ struct sdhci_host { bool pending_reset; /* Cmd/data reset is pending */ bool irq_wake_enabled; /* IRQ wakeup is enabled */ bool v4_mode; /* Host Version 4 Enable */ + bool use_external_dma; /* Host selects to use external DMA */ struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */ struct mmc_command *cmd; /* Current command */ @@ -559,6 +561,11 @@ struct sdhci_host { struct timer_list timer; /* Timer for timeouts */ struct timer_list data_timer; /* Timer for data timeouts */ +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) + struct dma_chan *rx_chan; + struct dma_chan *tx_chan; +#endif + u32 caps; /* CAPABILITY_0 */ u32 caps1; /* CAPABILITY_1 */ bool read_caps; /* Capability flags have been read */ @@ -792,5 +799,6 @@ void sdhci_start_tuning(struct sdhci_host *host); void sdhci_end_tuning(struct sdhci_host *host); void sdhci_reset_tuning(struct sdhci_host *host); void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); +void sdhci_switch_external_dma(struct sdhci_host *host, bool en); #endif /* __SDHCI_HW_H */ -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas @ 2019-01-14 8:45 ` Adrian Hunter 2019-01-24 11:40 ` Adrian Hunter 1 sibling, 0 replies; 26+ messages in thread From: Adrian Hunter @ 2019-01-14 8:45 UTC (permalink / raw) To: Faiz Abbas, linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, mark.rutland, robh+dt, ulf.hansson On 11/01/19 1:08 PM, Faiz Abbas wrote: > From: Chunyan Zhang <zhang.chunyan@linaro.org> > > 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. > > Fixes by Faiz Abbas <faiz_abbas@ti.com>: > 1. Map scatterlists before dmaengine_prep_slave_sg() > 2. Use dma_async() functions inside of the send_command() path and > synchronize once at the start of each request. > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> Acked-by: Adrian Hunter <adrian.hunter@intel.com> > --- > drivers/mmc/host/Kconfig | 3 + > drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- > drivers/mmc/host/sdhci.h | 8 ++ > 3 files changed, 273 insertions(+), 4 deletions(-) > > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index e26b8145efb3..333292e8ecdd 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 > If you have a controller with this interface, say Y or M here. > > If unsure, say N. > + > +config MMC_SDHCI_EXTERNAL_DMA > + bool > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > index a22e11a65658..4a9044c06e21 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> > @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) > } > } > > +#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; > + int sg_cnt; > + > + if (!host->mapbase) > + return -EINVAL; > + > + 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; > + > + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); > + if (sg_cnt <= 0) > + return -EINVAL; > + > + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct mmc_data *data = cmd->data; > + > + host->data_timeout = 0; > + > + if (sdhci_data_line_cmd(cmd)) > + sdhci_set_timeout(host, cmd); > + > + WARN_ON(host->data); > + > + /* Sanity checks */ > + WARN_ON(data->blksz * data->blocks > 524288); > + WARN_ON(data->blksz > host->mmc->max_blk_size); > + WARN_ON(data->blocks > 65535); > + > + host->flags |= SDHCI_REQ_USE_DMA; > + host->data = data; > + host->data_early = 0; > + host->data->bytes_xfered = 0; > + > + sdhci_set_transfer_irqs(host); > + > + /* > + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count > + * can be supported, in that case 16-bit block count register must be 0. > + */ > + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && > + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { > + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) > + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); > + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); > + } else { > + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > + } > + > + return 0; > +} > + > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct mmc_data *data = cmd->data; > + > + if (!data) > + return; > + > + if (sdhci_external_dma_setup(host, cmd) || > + __sdhci_external_dma_prepare_data(host, cmd)) { > + sdhci_external_dma_release(host); > + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > + mmc_hostname(host->mmc)); > + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) > + return; > + > + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); > + chan = sdhci_external_dma_channel(host, cmd->data); > + if (chan) > + dma_async_issue_pending(chan); > +} > + > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > + struct mmc_data *data) > +{ > + struct dma_chan *chan = sdhci_external_dma_channel(host, data); > + int ret = 0; > + > + if (chan) > + ret = dmaengine_terminate_async(chan); > + > + return ret; > +} > +#else > +static int sdhci_external_dma_init(struct sdhci_host *host) > +{ > + return -EOPNOTSUPP; > +} > + > +static void sdhci_external_dma_release(struct sdhci_host *host) > +{} > + > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ > + sdhci_prepare_data(host, cmd); > +} > + > +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > + struct mmc_command *cmd) > +{} > + > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > + struct mmc_data *data) > +{ > + return 0; > +} > +#endif > + > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) > +{ > + host->use_external_dma = en; > +} > +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); > + > static inline bool sdhci_auto_cmd12(struct sdhci_host *host, > struct mmc_request *mrq) > { > @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > host->data_cmd = cmd; > } > > - sdhci_prepare_data(host, cmd); > + if (host->use_external_dma) > + sdhci_external_dma_prepare_data(host, cmd); > + else > + sdhci_prepare_data(host, cmd); > > sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); > > @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > timeout += 10 * HZ; > sdhci_mod_timer(host, cmd->mrq, timeout); > > + if (host->use_external_dma) > + sdhci_external_dma_pre_transfer(host, cmd); > + > sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); > } > EXPORT_SYMBOL_GPL(sdhci_send_command); > @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) > > sdhci_led_activate(host); > > + if (host->use_external_dma && mrq->data) { > + struct dma_chan *chan = sdhci_external_dma_channel(host, > + mrq->data); > + dmaengine_synchronize(chan); > + } > /* > * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED > * requests if Auto-CMD12 is enabled. > @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) > dma_unmap_sg(mmc_dev(host->mmc), data->sg, > data->sg_len, > mmc_get_dma_dir(data)); > + if (host->use_external_dma) > + sdhci_external_dma_cleanup(host, data); > } > data->host_cookie = COOKIE_UNMAPPED; > } > @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) > mmc_hostname(mmc), host->version); > } > > - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) > + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { > host->flags |= SDHCI_USE_SDMA; > - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) > + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { > DBG("Controller doesn't have SDMA capability\n"); > - else > + } else if (host->use_external_dma) { > + /* Using dma-names to detect external dma capability */ > + } else { > host->flags |= SDHCI_USE_SDMA; > + } > > if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && > (host->flags & SDHCI_USE_SDMA)) { > @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) > } > } > > + if (host->use_external_dma) { > + ret = sdhci_external_dma_init(host); > + if (ret == -EPROBE_DEFER) > + goto unreg; > + > + /* > + * Fall back to use the DMA/PIO integrated in standard SDHCI > + * instead of external DMA devices. > + */ > + if (ret) > + sdhci_switch_external_dma(host, false); > + } > + > /* > * If we use DMA, then it's up to the caller to set the DMA > * mask, but PIO does not need the hw shim so we set a new > @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) > dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + > host->adma_table_sz, host->align_buffer, > host->align_addr); > + > + if (host->use_external_dma) > + sdhci_external_dma_release(host); > + > host->adma_table = NULL; > host->align_buffer = NULL; > } > @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) > > pr_info("%s: SDHCI controller on %s [%s] using %s\n", > mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), > + host->use_external_dma ? "External DMA" : > (host->flags & SDHCI_USE_ADMA) ? > (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : > (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); > @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) > host->adma_table_sz, host->align_buffer, > host->align_addr); > > + if (host->use_external_dma) > + sdhci_external_dma_release(host); > + > host->adma_table = NULL; > host->align_buffer = NULL; > } > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > index 6cc9a3c2ac66..7a52823ebef4 100644 > --- a/drivers/mmc/host/sdhci.h > +++ b/drivers/mmc/host/sdhci.h > @@ -482,6 +482,7 @@ struct sdhci_host { > > int irq; /* Device IRQ */ > void __iomem *ioaddr; /* Mapped address */ > + phys_addr_t mapbase; /* physical address base */ > char *bounce_buffer; /* For packing SDMA reads/writes */ > dma_addr_t bounce_addr; > unsigned int bounce_buffer_size; > @@ -531,6 +532,7 @@ struct sdhci_host { > bool pending_reset; /* Cmd/data reset is pending */ > bool irq_wake_enabled; /* IRQ wakeup is enabled */ > bool v4_mode; /* Host Version 4 Enable */ > + bool use_external_dma; /* Host selects to use external DMA */ > > struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */ > struct mmc_command *cmd; /* Current command */ > @@ -559,6 +561,11 @@ struct sdhci_host { > struct timer_list timer; /* Timer for timeouts */ > struct timer_list data_timer; /* Timer for data timeouts */ > > +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > + struct dma_chan *rx_chan; > + struct dma_chan *tx_chan; > +#endif > + > u32 caps; /* CAPABILITY_0 */ > u32 caps1; /* CAPABILITY_1 */ > bool read_caps; /* Capability flags have been read */ > @@ -792,5 +799,6 @@ void sdhci_start_tuning(struct sdhci_host *host); > void sdhci_end_tuning(struct sdhci_host *host); > void sdhci_reset_tuning(struct sdhci_host *host); > void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en); > > #endif /* __SDHCI_HW_H */ > ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas 2019-01-14 8:45 ` Adrian Hunter @ 2019-01-24 11:40 ` Adrian Hunter 2019-01-28 10:20 ` Chunyan Zhang 2019-02-11 12:23 ` Faiz Abbas 1 sibling, 2 replies; 26+ messages in thread From: Adrian Hunter @ 2019-01-24 11:40 UTC (permalink / raw) To: Faiz Abbas, linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, mark.rutland, robh+dt, ulf.hansson On 11/01/19 1:08 PM, Faiz Abbas wrote: > From: Chunyan Zhang <zhang.chunyan@linaro.org> > > 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. > > Fixes by Faiz Abbas <faiz_abbas@ti.com>: > 1. Map scatterlists before dmaengine_prep_slave_sg() > 2. Use dma_async() functions inside of the send_command() path and > synchronize once at the start of each request. Sorry for the slow reply, but I do have some concerns. Please see the comments. > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- > drivers/mmc/host/Kconfig | 3 + > drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- > drivers/mmc/host/sdhci.h | 8 ++ > 3 files changed, 273 insertions(+), 4 deletions(-) > > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index e26b8145efb3..333292e8ecdd 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 > If you have a controller with this interface, say Y or M here. > > If unsure, say N. > + > +config MMC_SDHCI_EXTERNAL_DMA > + bool > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > index a22e11a65658..4a9044c06e21 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> > @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) > } > } > > +#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; > + int sg_cnt; > + > + if (!host->mapbase) > + return -EINVAL; > + > + 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; > + > + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); > + if (sg_cnt <= 0) > + return -EINVAL; > + > + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct mmc_data *data = cmd->data; > + > + host->data_timeout = 0; > + > + if (sdhci_data_line_cmd(cmd)) > + sdhci_set_timeout(host, cmd); > + > + WARN_ON(host->data); > + > + /* Sanity checks */ > + WARN_ON(data->blksz * data->blocks > 524288); > + WARN_ON(data->blksz > host->mmc->max_blk_size); > + WARN_ON(data->blocks > 65535); > + > + host->flags |= SDHCI_REQ_USE_DMA; > + host->data = data; > + host->data_early = 0; > + host->data->bytes_xfered = 0; > + > + sdhci_set_transfer_irqs(host); > + > + /* > + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count > + * can be supported, in that case 16-bit block count register must be 0. > + */ > + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && > + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { > + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) > + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); > + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); > + } else { > + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > + } It is probably worth factoring out the code that is shared with sdhci_prepare_data() where possible. > + > + return 0; > +} > + > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct mmc_data *data = cmd->data; > + > + if (!data) > + return; Even in the !data case, we still need to set up a timeout for commands with busy waiting. I suggest checking the !data case before calling sdhci_external_dma_prepare_data() > + > + if (sdhci_external_dma_setup(host, cmd) || > + __sdhci_external_dma_prepare_data(host, cmd)) { > + sdhci_external_dma_release(host); > + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > + mmc_hostname(host->mmc)); > + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. > + return; > + > + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); Block size is set in __sdhci_external_dma_prepare_data() so does it need to be set here also. > + chan = sdhci_external_dma_channel(host, cmd->data); > + if (chan) > + dma_async_issue_pending(chan); > +} > + > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > + struct mmc_data *data) Please align parameters with open parenthesis > +{ > + struct dma_chan *chan = sdhci_external_dma_channel(host, data); > + int ret = 0; > + > + if (chan) > + ret = dmaengine_terminate_async(chan); > + > + return ret; > +} > +#else > +static int sdhci_external_dma_init(struct sdhci_host *host) > +{ > + return -EOPNOTSUPP; > +} > + > +static void sdhci_external_dma_release(struct sdhci_host *host) > +{} > + > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ > + sdhci_prepare_data(host, cmd); > +} > + > +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > + struct mmc_command *cmd) > +{} > + > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > + struct mmc_data *data) Please align parameters with open parenthesis > +{ > + return 0; > +} > +#endif > + > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) > +{ > + host->use_external_dma = en; > +} > +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); > + > static inline bool sdhci_auto_cmd12(struct sdhci_host *host, > struct mmc_request *mrq) > { > @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > host->data_cmd = cmd; > } > > - sdhci_prepare_data(host, cmd); > + if (host->use_external_dma) As mentioned above wrt sdhci_external_dma_prepare_data(): if (host->use_external_dma && cmd->data) > + sdhci_external_dma_prepare_data(host, cmd); > + else > + sdhci_prepare_data(host, cmd); > > sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); > > @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > timeout += 10 * HZ; > sdhci_mod_timer(host, cmd->mrq, timeout); > > + if (host->use_external_dma) > + sdhci_external_dma_pre_transfer(host, cmd); Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done in sdhci_external_dma_prepare_data()? > + > sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); > } > EXPORT_SYMBOL_GPL(sdhci_send_command); > @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) > > sdhci_led_activate(host); > > + if (host->use_external_dma && mrq->data) { > + struct dma_chan *chan = sdhci_external_dma_channel(host, > + mrq->data); sdhci_external_dma_channel is not declared if !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > + dmaengine_synchronize(chan); So this is to cover for using dmaengine_terminate_async()? > + } > /* > * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED > * requests if Auto-CMD12 is enabled. > @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) > dma_unmap_sg(mmc_dev(host->mmc), data->sg, > data->sg_len, > mmc_get_dma_dir(data)); > + if (host->use_external_dma) > + sdhci_external_dma_cleanup(host, data); Is sdhci_external_dma_cleanup() only needed in the error case? The DMA must be stopped before the memory is unmapped and potentially freed. Isn't the DMA cleanup also needed in the bounce buffer case? Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? dmaengine_terminate_async() doesn't stop the DMA but dmaengine_terminate_sync() is not atomic, which looks like a problem. Perhaps you look at scheduling some work for the external dma error case instead of calling __sdhci_finish_mrq()? Then the work can do the dmaengine_terminate_sync() and call __sdhci_finish_mrq(). > } > data->host_cookie = COOKIE_UNMAPPED; > } > @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) > mmc_hostname(mmc), host->version); > } > > - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) > + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { > host->flags |= SDHCI_USE_SDMA; > - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) > + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { > DBG("Controller doesn't have SDMA capability\n"); > - else > + } else if (host->use_external_dma) { > + /* Using dma-names to detect external dma capability */ > + } else { > host->flags |= SDHCI_USE_SDMA; > + } These if-statements are about setting SDHCI_USE_SDMA but why is a change needed for the host->use_external_dma case? > > if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && > (host->flags & SDHCI_USE_SDMA)) { > @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) > } > } > > + if (host->use_external_dma) { > + ret = sdhci_external_dma_init(host); > + if (ret == -EPROBE_DEFER) > + goto unreg; > + > + /* > + * Fall back to use the DMA/PIO integrated in standard SDHCI > + * instead of external DMA devices. > + */ > + if (ret) > + sdhci_switch_external_dma(host, false); > + } > + > /* > * If we use DMA, then it's up to the caller to set the DMA > * mask, but PIO does not need the hw shim so we set a new > @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) > dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + > host->adma_table_sz, host->align_buffer, > host->align_addr); > + > + if (host->use_external_dma) > + sdhci_external_dma_release(host); > + > host->adma_table = NULL; > host->align_buffer = NULL; > } > @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) > > pr_info("%s: SDHCI controller on %s [%s] using %s\n", > mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), > + host->use_external_dma ? "External DMA" : > (host->flags & SDHCI_USE_ADMA) ? > (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : > (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); > @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) > host->adma_table_sz, host->align_buffer, > host->align_addr); > > + if (host->use_external_dma) > + sdhci_external_dma_release(host); > + > host->adma_table = NULL; > host->align_buffer = NULL; > } > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > index 6cc9a3c2ac66..7a52823ebef4 100644 > --- a/drivers/mmc/host/sdhci.h > +++ b/drivers/mmc/host/sdhci.h > @@ -482,6 +482,7 @@ struct sdhci_host { > > int irq; /* Device IRQ */ > void __iomem *ioaddr; /* Mapped address */ > + phys_addr_t mapbase; /* physical address base */ > char *bounce_buffer; /* For packing SDMA reads/writes */ > dma_addr_t bounce_addr; > unsigned int bounce_buffer_size; > @@ -531,6 +532,7 @@ struct sdhci_host { > bool pending_reset; /* Cmd/data reset is pending */ > bool irq_wake_enabled; /* IRQ wakeup is enabled */ > bool v4_mode; /* Host Version 4 Enable */ > + bool use_external_dma; /* Host selects to use external DMA */ Please align /**/ with above i.e. use tab > > struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */ > struct mmc_command *cmd; /* Current command */ > @@ -559,6 +561,11 @@ struct sdhci_host { > struct timer_list timer; /* Timer for timeouts */ > struct timer_list data_timer; /* Timer for data timeouts */ > > +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > + struct dma_chan *rx_chan; > + struct dma_chan *tx_chan; > +#endif > + > u32 caps; /* CAPABILITY_0 */ > u32 caps1; /* CAPABILITY_1 */ > bool read_caps; /* Capability flags have been read */ > @@ -792,5 +799,6 @@ void sdhci_start_tuning(struct sdhci_host *host); > void sdhci_end_tuning(struct sdhci_host *host); > void sdhci_reset_tuning(struct sdhci_host *host); > void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en); > > #endif /* __SDHCI_HW_H */ > ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-24 11:40 ` Adrian Hunter @ 2019-01-28 10:20 ` Chunyan Zhang 2019-01-28 10:43 ` Faiz Abbas 2019-02-11 12:23 ` Faiz Abbas 1 sibling, 1 reply; 26+ messages in thread From: Chunyan Zhang @ 2019-01-28 10:20 UTC (permalink / raw) To: Adrian Hunter Cc: Faiz Abbas, Linux Kernel Mailing List, devicetree, linux-mmc, Kishon Vijay Abraham I, Mark Rutland, Rob Herring, Ulf Hansson On Thu, 24 Jan 2019 at 19:41, Adrian Hunter <adrian.hunter@intel.com> wrote: > > On 11/01/19 1:08 PM, Faiz Abbas wrote: > > From: Chunyan Zhang <zhang.chunyan@linaro.org> > > > > 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. > > > > Fixes by Faiz Abbas <faiz_abbas@ti.com>: > > 1. Map scatterlists before dmaengine_prep_slave_sg() > > 2. Use dma_async() functions inside of the send_command() path and > > synchronize once at the start of each request. > > Sorry for the slow reply, but I do have some concerns. Please see the comments. > > > > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > > --- > > drivers/mmc/host/Kconfig | 3 + > > drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- > > drivers/mmc/host/sdhci.h | 8 ++ > > 3 files changed, 273 insertions(+), 4 deletions(-) > > > > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > > index e26b8145efb3..333292e8ecdd 100644 > > --- a/drivers/mmc/host/Kconfig > > +++ b/drivers/mmc/host/Kconfig > > @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 > > If you have a controller with this interface, say Y or M here. > > > > If unsure, say N. > > + > > +config MMC_SDHCI_EXTERNAL_DMA > > + bool > > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > > index a22e11a65658..4a9044c06e21 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> > > @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) > > } > > } > > > > +#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; > > + int sg_cnt; > > + > > + if (!host->mapbase) > > + return -EINVAL; > > + > > + 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; > > + > > + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); > > + if (sg_cnt <= 0) > > + return -EINVAL; > > + > > + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, > > + struct mmc_command *cmd) > > +{ > > + struct mmc_data *data = cmd->data; > > + > > + host->data_timeout = 0; > > + > > + if (sdhci_data_line_cmd(cmd)) > > + sdhci_set_timeout(host, cmd); > > + > > + WARN_ON(host->data); > > + > > + /* Sanity checks */ > > + WARN_ON(data->blksz * data->blocks > 524288); > > + WARN_ON(data->blksz > host->mmc->max_blk_size); > > + WARN_ON(data->blocks > 65535); > > + > > + host->flags |= SDHCI_REQ_USE_DMA; > > + host->data = data; > > + host->data_early = 0; > > + host->data->bytes_xfered = 0; > > + > > + sdhci_set_transfer_irqs(host); > > + > > + /* > > + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count > > + * can be supported, in that case 16-bit block count register must be 0. > > + */ > > + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && > > + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { > > + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) > > + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); > > + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); > > + } else { > > + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > > + } > > It is probably worth factoring out the code that is shared with > sdhci_prepare_data() where possible. > > > + > > + return 0; > > +} > > + > > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > > + struct mmc_command *cmd) > > +{ > > + struct mmc_data *data = cmd->data; > > + > > + if (!data) > > + return; > > Even in the !data case, we still need to set up a timeout for commands with > busy waiting. I suggest checking the !data case before calling > sdhci_external_dma_prepare_data() Ok. > > > + > > + if (sdhci_external_dma_setup(host, cmd) || > > + __sdhci_external_dma_prepare_data(host, cmd)) { > > + sdhci_external_dma_release(host); > > + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > > + mmc_hostname(host->mmc)); > > + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) > > MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. Ok. > > > + return; > > + > > + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); > > Block size is set in __sdhci_external_dma_prepare_data() so does it need to > be set here also. Ok. > > > + chan = sdhci_external_dma_channel(host, cmd->data); > > + if (chan) > > + dma_async_issue_pending(chan); > > +} > > + > > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > > + struct mmc_data *data) > > Please align parameters with open parenthesis > > > +{ > > + struct dma_chan *chan = sdhci_external_dma_channel(host, data); > > + int ret = 0; > > + > > + if (chan) > > + ret = dmaengine_terminate_async(chan); > > + > > + return ret; > > +} > > +#else > > +static int sdhci_external_dma_init(struct sdhci_host *host) > > +{ > > + return -EOPNOTSUPP; > > +} > > + > > +static void sdhci_external_dma_release(struct sdhci_host *host) > > +{} > > + > > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > > + struct mmc_command *cmd) > > +{ > > + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ > > + sdhci_prepare_data(host, cmd); > > +} > > + > > +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > > + struct mmc_command *cmd) > > +{} > > + > > +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > > + struct mmc_data *data) > > Please align parameters with open parenthesis > > > +{ > > + return 0; > > +} > > +#endif > > + > > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) > > +{ > > + host->use_external_dma = en; > > +} > > +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); > > + > > static inline bool sdhci_auto_cmd12(struct sdhci_host *host, > > struct mmc_request *mrq) > > { > > @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > > host->data_cmd = cmd; > > } > > > > - sdhci_prepare_data(host, cmd); > > + if (host->use_external_dma) > > As mentioned above wrt sdhci_external_dma_prepare_data(): > > if (host->use_external_dma && cmd->data) > Maybe we should move checking the !data case out of both _prepare_data, and add it to sdhci_send_command() before calling _prepare_data(), that's saying we can do set up a timeout in sdhci_send_command(). > > + sdhci_external_dma_prepare_data(host, cmd); > > + else > > + sdhci_prepare_data(host, cmd); > > > > sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); > > > > @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > > timeout += 10 * HZ; > > sdhci_mod_timer(host, cmd->mrq, timeout); > > > > + if (host->use_external_dma) > > + sdhci_external_dma_pre_transfer(host, cmd); > > Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done > in sdhci_external_dma_prepare_data()? > I'm not sure dma_async_issue_pending() can be done so early in sdhci_external_dma_prepare_data(). > > + > > sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); > > } > > EXPORT_SYMBOL_GPL(sdhci_send_command); > > @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) > > > > sdhci_led_activate(host); > > > > + if (host->use_external_dma && mrq->data) { > > + struct dma_chan *chan = sdhci_external_dma_channel(host, > > + mrq->data); > > sdhci_external_dma_channel is not declared if > !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) Ok, I guess this can be moved to sdhci_external_dma_prepare_data(). > > > + dmaengine_synchronize(chan); > > So this is to cover for using dmaengine_terminate_async()? Ok. > > > + } > > /* > > * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED > > * requests if Auto-CMD12 is enabled. > > @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) > > dma_unmap_sg(mmc_dev(host->mmc), data->sg, > > data->sg_len, > > mmc_get_dma_dir(data)); > > + if (host->use_external_dma) > > + sdhci_external_dma_cleanup(host, data); > > Is sdhci_external_dma_cleanup() only needed in the error case? > > The DMA must be stopped before the memory is unmapped and potentially freed. > > Isn't the DMA cleanup also needed in the bounce buffer case? > > Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? > > dmaengine_terminate_async() doesn't stop the DMA but > dmaengine_terminate_sync() is not atomic, which looks like a problem. > > Perhaps you look at scheduling some work for the external dma error case > instead of calling __sdhci_finish_mrq()? Then the work can do the > dmaengine_terminate_sync() and call __sdhci_finish_mrq(). Ok, I will look at these issues. > > > } > > data->host_cookie = COOKIE_UNMAPPED; > > } > > @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) > > mmc_hostname(mmc), host->version); > > } > > > > - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) > > + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { > > host->flags |= SDHCI_USE_SDMA; > > - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) > > + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { > > DBG("Controller doesn't have SDMA capability\n"); > > - else > > + } else if (host->use_external_dma) { > > + /* Using dma-names to detect external dma capability */ > > + } else { > > host->flags |= SDHCI_USE_SDMA; > > + } > > These if-statements are about setting SDHCI_USE_SDMA but why is a change > needed for the host->use_external_dma case? Yes, this is not needed, otherwise the controller cannot switch back to SDMA if it is supported in the controller. > > > > > if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && > > (host->flags & SDHCI_USE_SDMA)) { > > @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) > > } > > } > > > > + if (host->use_external_dma) { > > + ret = sdhci_external_dma_init(host); > > + if (ret == -EPROBE_DEFER) > > + goto unreg; > > + > > + /* > > + * Fall back to use the DMA/PIO integrated in standard SDHCI > > + * instead of external DMA devices. > > + */ > > + if (ret) > > + sdhci_switch_external_dma(host, false); > > + } > > + > > /* > > * If we use DMA, then it's up to the caller to set the DMA > > * mask, but PIO does not need the hw shim so we set a new > > @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) > > dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + > > host->adma_table_sz, host->align_buffer, > > host->align_addr); > > + > > + if (host->use_external_dma) > > + sdhci_external_dma_release(host); > > + > > host->adma_table = NULL; > > host->align_buffer = NULL; > > } > > @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) > > > > pr_info("%s: SDHCI controller on %s [%s] using %s\n", > > mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), > > + host->use_external_dma ? "External DMA" : > > (host->flags & SDHCI_USE_ADMA) ? > > (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : > > (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); > > @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) > > host->adma_table_sz, host->align_buffer, > > host->align_addr); > > > > + if (host->use_external_dma) > > + sdhci_external_dma_release(host); > > + > > host->adma_table = NULL; > > host->align_buffer = NULL; > > } > > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > > index 6cc9a3c2ac66..7a52823ebef4 100644 > > --- a/drivers/mmc/host/sdhci.h > > +++ b/drivers/mmc/host/sdhci.h > > @@ -482,6 +482,7 @@ struct sdhci_host { > > > > int irq; /* Device IRQ */ > > void __iomem *ioaddr; /* Mapped address */ > > + phys_addr_t mapbase; /* physical address base */ > > char *bounce_buffer; /* For packing SDMA reads/writes */ > > dma_addr_t bounce_addr; > > unsigned int bounce_buffer_size; > > @@ -531,6 +532,7 @@ struct sdhci_host { > > bool pending_reset; /* Cmd/data reset is pending */ > > bool irq_wake_enabled; /* IRQ wakeup is enabled */ > > bool v4_mode; /* Host Version 4 Enable */ > > + bool use_external_dma; /* Host selects to use external DMA */ > > Please align /**/ with above i.e. use tab > > > > > struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */ > > struct mmc_command *cmd; /* Current command */ > > @@ -559,6 +561,11 @@ struct sdhci_host { > > struct timer_list timer; /* Timer for timeouts */ > > struct timer_list data_timer; /* Timer for data timeouts */ > > > > +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > > + struct dma_chan *rx_chan; > > + struct dma_chan *tx_chan; > > +#endif > > + > > u32 caps; /* CAPABILITY_0 */ > > u32 caps1; /* CAPABILITY_1 */ > > bool read_caps; /* Capability flags have been read */ > > @@ -792,5 +799,6 @@ void sdhci_start_tuning(struct sdhci_host *host); > > void sdhci_end_tuning(struct sdhci_host *host); > > void sdhci_reset_tuning(struct sdhci_host *host); > > void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); > > +void sdhci_switch_external_dma(struct sdhci_host *host, bool en); > > > > #endif /* __SDHCI_HW_H */ > > > ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-28 10:20 ` Chunyan Zhang @ 2019-01-28 10:43 ` Faiz Abbas 2019-01-28 11:46 ` Chunyan Zhang 0 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-28 10:43 UTC (permalink / raw) To: Chunyan Zhang, Adrian Hunter Cc: Linux Kernel Mailing List, devicetree, linux-mmc, Kishon Vijay Abraham I, Mark Rutland, Rob Herring, Ulf Hansson Hi, On 28/01/19 3:50 PM, Chunyan Zhang wrote: > On Thu, 24 Jan 2019 at 19:41, Adrian Hunter <adrian.hunter@intel.com> wrote: >> >> On 11/01/19 1:08 PM, Faiz Abbas wrote: >>> From: Chunyan Zhang <zhang.chunyan@linaro.org> >>> >>> 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. >>> >>> Fixes by Faiz Abbas <faiz_abbas@ti.com>: >>> 1. Map scatterlists before dmaengine_prep_slave_sg() >>> 2. Use dma_async() functions inside of the send_command() path and >>> synchronize once at the start of each request. >> >> Sorry for the slow reply, but I do have some concerns. Please see the comments. >> >>> >>> Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> >>> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> >>> --- >>> drivers/mmc/host/Kconfig | 3 + >>> drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- >>> drivers/mmc/host/sdhci.h | 8 ++ >>> 3 files changed, 273 insertions(+), 4 deletions(-) >>> >>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig >>> index e26b8145efb3..333292e8ecdd 100644 >>> --- a/drivers/mmc/host/Kconfig >>> +++ b/drivers/mmc/host/Kconfig >>> @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 >>> If you have a controller with this interface, say Y or M here. >>> >>> If unsure, say N. >>> + >>> +config MMC_SDHCI_EXTERNAL_DMA >>> + bool >>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c >>> index a22e11a65658..4a9044c06e21 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> >>> @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) >>> } >>> } >>> >>> +#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; >>> + int sg_cnt; >>> + >>> + if (!host->mapbase) >>> + return -EINVAL; >>> + >>> + 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; >>> + >>> + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); >>> + if (sg_cnt <= 0) >>> + return -EINVAL; >>> + >>> + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, >>> + struct mmc_command *cmd) >>> +{ >>> + struct mmc_data *data = cmd->data; >>> + >>> + host->data_timeout = 0; >>> + >>> + if (sdhci_data_line_cmd(cmd)) >>> + sdhci_set_timeout(host, cmd); >>> + >>> + WARN_ON(host->data); >>> + >>> + /* Sanity checks */ >>> + WARN_ON(data->blksz * data->blocks > 524288); >>> + WARN_ON(data->blksz > host->mmc->max_blk_size); >>> + WARN_ON(data->blocks > 65535); >>> + >>> + host->flags |= SDHCI_REQ_USE_DMA; >>> + host->data = data; >>> + host->data_early = 0; >>> + host->data->bytes_xfered = 0; >>> + >>> + sdhci_set_transfer_irqs(host); >>> + >>> + /* >>> + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count >>> + * can be supported, in that case 16-bit block count register must be 0. >>> + */ >>> + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && >>> + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { >>> + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) >>> + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); >>> + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); >>> + } else { >>> + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); >>> + } >> >> It is probably worth factoring out the code that is shared with >> sdhci_prepare_data() where possible. >> >>> + >>> + return 0; >>> +} >>> + >>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, >>> + struct mmc_command *cmd) >>> +{ >>> + struct mmc_data *data = cmd->data; >>> + >>> + if (!data) >>> + return; >> >> Even in the !data case, we still need to set up a timeout for commands with >> busy waiting. I suggest checking the !data case before calling >> sdhci_external_dma_prepare_data() > > Ok. > >> >>> + >>> + if (sdhci_external_dma_setup(host, cmd) || >>> + __sdhci_external_dma_prepare_data(host, cmd)) { >>> + sdhci_external_dma_release(host); >>> + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", >>> + mmc_hostname(host->mmc)); >>> + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) >> >> MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. > > Ok. > >> >>> + return; >>> + >>> + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); >> >> Block size is set in __sdhci_external_dma_prepare_data() so does it need to >> be set here also. > > Ok. That is BLOCK_COUNT. This is BLOCK_SIZE. > >> >>> + chan = sdhci_external_dma_channel(host, cmd->data); >>> + if (chan) >>> + dma_async_issue_pending(chan); >>> +} >>> + >>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, >>> + struct mmc_data *data) >> >> Please align parameters with open parenthesis >> >>> +{ >>> + struct dma_chan *chan = sdhci_external_dma_channel(host, data); >>> + int ret = 0; >>> + >>> + if (chan) >>> + ret = dmaengine_terminate_async(chan); >>> + >>> + return ret; >>> +} >>> +#else >>> +static int sdhci_external_dma_init(struct sdhci_host *host) >>> +{ >>> + return -EOPNOTSUPP; >>> +} >>> + >>> +static void sdhci_external_dma_release(struct sdhci_host *host) >>> +{} >>> + >>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, >>> + struct mmc_command *cmd) >>> +{ >>> + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ >>> + sdhci_prepare_data(host, cmd); >>> +} >>> + >>> +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, >>> + struct mmc_command *cmd) >>> +{} >>> + >>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, >>> + struct mmc_data *data) >> >> Please align parameters with open parenthesis >> >>> +{ >>> + return 0; >>> +} >>> +#endif >>> + >>> +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) >>> +{ >>> + host->use_external_dma = en; >>> +} >>> +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); >>> + >>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host, >>> struct mmc_request *mrq) >>> { >>> @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) >>> host->data_cmd = cmd; >>> } >>> >>> - sdhci_prepare_data(host, cmd); >>> + if (host->use_external_dma) >> >> As mentioned above wrt sdhci_external_dma_prepare_data(): >> >> if (host->use_external_dma && cmd->data) >> > > Maybe we should move checking the !data case out of both > _prepare_data, and add it to sdhci_send_command() before calling > _prepare_data(), that's saying we can do set up a timeout in > sdhci_send_command(). Lets just factor out everything in common as Adrian said. The !data, BLOCK_COUNT writes and busy waiting. > >>> + sdhci_external_dma_prepare_data(host, cmd); >>> + else >>> + sdhci_prepare_data(host, cmd); >>> >>> sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); >>> >>> @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) >>> timeout += 10 * HZ; >>> sdhci_mod_timer(host, cmd->mrq, timeout); >>> >>> + if (host->use_external_dma) >>> + sdhci_external_dma_pre_transfer(host, cmd); >> >> Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done >> in sdhci_external_dma_prepare_data()? >> > > I'm not sure dma_async_issue_pending() can be done so early in > sdhci_external_dma_prepare_data(). > >>> + >>> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); >>> } >>> EXPORT_SYMBOL_GPL(sdhci_send_command); >>> @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) >>> >>> sdhci_led_activate(host); >>> >>> + if (host->use_external_dma && mrq->data) { >>> + struct dma_chan *chan = sdhci_external_dma_channel(host, >>> + mrq->data); >> >> sdhci_external_dma_channel is not declared if >> !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > > Ok, I guess this can be moved to sdhci_external_dma_prepare_data(). No. We have to synchronize from a guaranteed non-atomic context. Maybe add another empty sdhci_external_dma_channel definition in the #else part? > >> >>> + dmaengine_synchronize(chan); >> >> So this is to cover for using dmaengine_terminate_async()? Yes. > > Ok. > >> >>> + } >>> /* >>> * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED >>> * requests if Auto-CMD12 is enabled. >>> @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) >>> dma_unmap_sg(mmc_dev(host->mmc), data->sg, >>> data->sg_len, >>> mmc_get_dma_dir(data)); >>> + if (host->use_external_dma) >>> + sdhci_external_dma_cleanup(host, data); >> >> Is sdhci_external_dma_cleanup() only needed in the error case? >> >> The DMA must be stopped before the memory is unmapped and potentially freed. >> >> Isn't the DMA cleanup also needed in the bounce buffer case? >> >> Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? >> >> dmaengine_terminate_async() doesn't stop the DMA but >> dmaengine_terminate_sync() is not atomic, which looks like a problem. >> >> Perhaps you look at scheduling some work for the external dma error case >> instead of calling __sdhci_finish_mrq()? Then the work can do the >> dmaengine_terminate_sync() and call __sdhci_finish_mrq(). > > Ok, I will look at these issues. > >> >>> } >>> data->host_cookie = COOKIE_UNMAPPED; >>> } >>> @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) >>> mmc_hostname(mmc), host->version); >>> } >>> >>> - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) >>> + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { >>> host->flags |= SDHCI_USE_SDMA; >>> - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) >>> + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { >>> DBG("Controller doesn't have SDMA capability\n"); >>> - else >>> + } else if (host->use_external_dma) { >>> + /* Using dma-names to detect external dma capability */ >>> + } else { >>> host->flags |= SDHCI_USE_SDMA; >>> + } >> >> These if-statements are about setting SDHCI_USE_SDMA but why is a change >> needed for the host->use_external_dma case? > > Yes, this is not needed, otherwise the controller cannot switch back > to SDMA if it is supported in the controller. The else case means that the host always uses SDMA. Needed to make sure this doesn't happen in the external_dma case. Its not SDHCI_CAN_DO_SDMA, its SDHCI_USE_SDMA. > >> >>> >>> if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && >>> (host->flags & SDHCI_USE_SDMA)) { >>> @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) >>> } >>> } >>> >>> + if (host->use_external_dma) { >>> + ret = sdhci_external_dma_init(host); >>> + if (ret == -EPROBE_DEFER) >>> + goto unreg; >>> + >>> + /* >>> + * Fall back to use the DMA/PIO integrated in standard SDHCI >>> + * instead of external DMA devices. >>> + */ >>> + if (ret) >>> + sdhci_switch_external_dma(host, false); >>> + } >>> + >>> /* >>> * If we use DMA, then it's up to the caller to set the DMA >>> * mask, but PIO does not need the hw shim so we set a new >>> @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) >>> dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + >>> host->adma_table_sz, host->align_buffer, >>> host->align_addr); >>> + >>> + if (host->use_external_dma) >>> + sdhci_external_dma_release(host); >>> + >>> host->adma_table = NULL; >>> host->align_buffer = NULL; >>> } >>> @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) >>> >>> pr_info("%s: SDHCI controller on %s [%s] using %s\n", >>> mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), >>> + host->use_external_dma ? "External DMA" : >>> (host->flags & SDHCI_USE_ADMA) ? >>> (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : >>> (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); >>> @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) >>> host->adma_table_sz, host->align_buffer, >>> host->align_addr); >>> >>> + if (host->use_external_dma) >>> + sdhci_external_dma_release(host); >>> + >>> host->adma_table = NULL; >>> host->align_buffer = NULL; >>> } >>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h >>> index 6cc9a3c2ac66..7a52823ebef4 100644 >>> --- a/drivers/mmc/host/sdhci.h >>> +++ b/drivers/mmc/host/sdhci.h >>> @@ -482,6 +482,7 @@ struct sdhci_host { >>> >>> int irq; /* Device IRQ */ >>> void __iomem *ioaddr; /* Mapped address */ >>> + phys_addr_t mapbase; /* physical address base */ >>> char *bounce_buffer; /* For packing SDMA reads/writes */ >>> dma_addr_t bounce_addr; >>> unsigned int bounce_buffer_size; >>> @@ -531,6 +532,7 @@ struct sdhci_host { >>> bool pending_reset; /* Cmd/data reset is pending */ >>> bool irq_wake_enabled; /* IRQ wakeup is enabled */ >>> bool v4_mode; /* Host Version 4 Enable */ >>> + bool use_external_dma; /* Host selects to use external DMA */ >> >> Please align /**/ with above i.e. use tab Will fix. Thanks, Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-28 10:43 ` Faiz Abbas @ 2019-01-28 11:46 ` Chunyan Zhang 2019-01-29 11:53 ` Faiz Abbas 0 siblings, 1 reply; 26+ messages in thread From: Chunyan Zhang @ 2019-01-28 11:46 UTC (permalink / raw) To: Faiz Abbas Cc: Adrian Hunter, Linux Kernel Mailing List, devicetree, linux-mmc, Kishon Vijay Abraham I, Mark Rutland, Rob Herring, Ulf Hansson On Mon, 28 Jan 2019 at 18:40, Faiz Abbas <faiz_abbas@ti.com> wrote: > > Hi, > > On 28/01/19 3:50 PM, Chunyan Zhang wrote: > > On Thu, 24 Jan 2019 at 19:41, Adrian Hunter <adrian.hunter@intel.com> wrote: > >> > >> On 11/01/19 1:08 PM, Faiz Abbas wrote: > >>> From: Chunyan Zhang <zhang.chunyan@linaro.org> > >>> > >>> 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. > >>> > >>> Fixes by Faiz Abbas <faiz_abbas@ti.com>: > >>> 1. Map scatterlists before dmaengine_prep_slave_sg() > >>> 2. Use dma_async() functions inside of the send_command() path and > >>> synchronize once at the start of each request. > >> > >> Sorry for the slow reply, but I do have some concerns. Please see the comments. > >> > >>> > >>> Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > >>> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > >>> --- > >>> drivers/mmc/host/Kconfig | 3 + > >>> drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- > >>> drivers/mmc/host/sdhci.h | 8 ++ > >>> 3 files changed, 273 insertions(+), 4 deletions(-) > >>> > >>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > >>> index e26b8145efb3..333292e8ecdd 100644 > >>> --- a/drivers/mmc/host/Kconfig > >>> +++ b/drivers/mmc/host/Kconfig > >>> @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 > >>> If you have a controller with this interface, say Y or M here. > >>> > >>> If unsure, say N. > >>> + > >>> +config MMC_SDHCI_EXTERNAL_DMA > >>> + bool > >>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > >>> index a22e11a65658..4a9044c06e21 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> > >>> @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) > >>> } > >>> } > >>> > >>> +#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; > >>> + int sg_cnt; > >>> + > >>> + if (!host->mapbase) > >>> + return -EINVAL; > >>> + > >>> + 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; > >>> + > >>> + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); > >>> + if (sg_cnt <= 0) > >>> + return -EINVAL; > >>> + > >>> + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + struct mmc_data *data = cmd->data; > >>> + > >>> + host->data_timeout = 0; > >>> + > >>> + if (sdhci_data_line_cmd(cmd)) > >>> + sdhci_set_timeout(host, cmd); > >>> + > >>> + WARN_ON(host->data); > >>> + > >>> + /* Sanity checks */ > >>> + WARN_ON(data->blksz * data->blocks > 524288); > >>> + WARN_ON(data->blksz > host->mmc->max_blk_size); > >>> + WARN_ON(data->blocks > 65535); > >>> + > >>> + host->flags |= SDHCI_REQ_USE_DMA; > >>> + host->data = data; > >>> + host->data_early = 0; > >>> + host->data->bytes_xfered = 0; > >>> + > >>> + sdhci_set_transfer_irqs(host); > >>> + > >>> + /* > >>> + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count > >>> + * can be supported, in that case 16-bit block count register must be 0. > >>> + */ > >>> + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && > >>> + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { > >>> + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) > >>> + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); > >>> + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); > >>> + } else { > >>> + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > >>> + } > >> > >> It is probably worth factoring out the code that is shared with > >> sdhci_prepare_data() where possible. > >> > >>> + > >>> + return 0; > >>> +} > >>> + > >>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + struct mmc_data *data = cmd->data; > >>> + > >>> + if (!data) > >>> + return; > >> > >> Even in the !data case, we still need to set up a timeout for commands with > >> busy waiting. I suggest checking the !data case before calling > >> sdhci_external_dma_prepare_data() > > > > Ok. > > > >> > >>> + > >>> + if (sdhci_external_dma_setup(host, cmd) || > >>> + __sdhci_external_dma_prepare_data(host, cmd)) { > >>> + sdhci_external_dma_release(host); > >>> + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > >>> + mmc_hostname(host->mmc)); > >>> + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) > >> > >> MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. > > > > Ok. > > > >> > >>> + return; > >>> + > >>> + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); > >> > >> Block size is set in __sdhci_external_dma_prepare_data() so does it need to > >> be set here also. > > > > Ok. > > That is BLOCK_COUNT. This is BLOCK_SIZE. > > > > >> > >>> + chan = sdhci_external_dma_channel(host, cmd->data); > >>> + if (chan) > >>> + dma_async_issue_pending(chan); > >>> +} > >>> + > >>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > >>> + struct mmc_data *data) > >> > >> Please align parameters with open parenthesis > >> > >>> +{ > >>> + struct dma_chan *chan = sdhci_external_dma_channel(host, data); > >>> + int ret = 0; > >>> + > >>> + if (chan) > >>> + ret = dmaengine_terminate_async(chan); > >>> + > >>> + return ret; > >>> +} > >>> +#else > >>> +static int sdhci_external_dma_init(struct sdhci_host *host) > >>> +{ > >>> + return -EOPNOTSUPP; > >>> +} > >>> + > >>> +static void sdhci_external_dma_release(struct sdhci_host *host) > >>> +{} > >>> + > >>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{ > >>> + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ > >>> + sdhci_prepare_data(host, cmd); > >>> +} > >>> + > >>> +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > >>> + struct mmc_command *cmd) > >>> +{} > >>> + > >>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > >>> + struct mmc_data *data) > >> > >> Please align parameters with open parenthesis > >> > >>> +{ > >>> + return 0; > >>> +} > >>> +#endif > >>> + > >>> +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) > >>> +{ > >>> + host->use_external_dma = en; > >>> +} > >>> +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); > >>> + > >>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host, > >>> struct mmc_request *mrq) > >>> { > >>> @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > >>> host->data_cmd = cmd; > >>> } > >>> > >>> - sdhci_prepare_data(host, cmd); > >>> + if (host->use_external_dma) > >> > >> As mentioned above wrt sdhci_external_dma_prepare_data(): > >> > >> if (host->use_external_dma && cmd->data) > >> > > > > Maybe we should move checking the !data case out of both > > _prepare_data, and add it to sdhci_send_command() before calling > > _prepare_data(), that's saying we can do set up a timeout in > > sdhci_send_command(). > > Lets just factor out everything in common as Adrian said. The !data, > BLOCK_COUNT writes and busy waiting. Ok, and also BLOCK_SIZE which can be set in _prepare_data(). > > > > >>> + sdhci_external_dma_prepare_data(host, cmd); > >>> + else > >>> + sdhci_prepare_data(host, cmd); > >>> > >>> sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); > >>> > >>> @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > >>> timeout += 10 * HZ; > >>> sdhci_mod_timer(host, cmd->mrq, timeout); > >>> > >>> + if (host->use_external_dma) > >>> + sdhci_external_dma_pre_transfer(host, cmd); > >> > >> Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done > >> in sdhci_external_dma_prepare_data()? > >> > > > > I'm not sure dma_async_issue_pending() can be done so early in > > sdhci_external_dma_prepare_data(). > > > >>> + > >>> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); > >>> } > >>> EXPORT_SYMBOL_GPL(sdhci_send_command); > >>> @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) > >>> > >>> sdhci_led_activate(host); > >>> > >>> + if (host->use_external_dma && mrq->data) { > >>> + struct dma_chan *chan = sdhci_external_dma_channel(host, > >>> + mrq->data); > >> > >> sdhci_external_dma_channel is not declared if > >> !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > > > > Ok, I guess this can be moved to sdhci_external_dma_prepare_data(). > > No. We have to synchronize from a guaranteed non-atomic context. Maybe > add another empty sdhci_external_dma_channel definition in the #else part? > > > > >> > >>> + dmaengine_synchronize(chan); > >> > >> So this is to cover for using dmaengine_terminate_async()? > > Yes. > > > > > Ok. > > > >> > >>> + } > >>> /* > >>> * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED > >>> * requests if Auto-CMD12 is enabled. > >>> @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) > >>> dma_unmap_sg(mmc_dev(host->mmc), data->sg, > >>> data->sg_len, > >>> mmc_get_dma_dir(data)); > >>> + if (host->use_external_dma) > >>> + sdhci_external_dma_cleanup(host, data); > >> > >> Is sdhci_external_dma_cleanup() only needed in the error case? > >> > >> The DMA must be stopped before the memory is unmapped and potentially freed. > >> > >> Isn't the DMA cleanup also needed in the bounce buffer case? > >> > >> Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? > >> > >> dmaengine_terminate_async() doesn't stop the DMA but > >> dmaengine_terminate_sync() is not atomic, which looks like a problem. > >> > >> Perhaps you look at scheduling some work for the external dma error case > >> instead of calling __sdhci_finish_mrq()? Then the work can do the > >> dmaengine_terminate_sync() and call __sdhci_finish_mrq(). > > > > Ok, I will look at these issues. > > > >> > >>> } > >>> data->host_cookie = COOKIE_UNMAPPED; > >>> } > >>> @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) > >>> mmc_hostname(mmc), host->version); > >>> } > >>> > >>> - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) > >>> + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { > >>> host->flags |= SDHCI_USE_SDMA; > >>> - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) > >>> + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { > >>> DBG("Controller doesn't have SDMA capability\n"); > >>> - else > >>> + } else if (host->use_external_dma) { > >>> + /* Using dma-names to detect external dma capability */ > >>> + } else { > >>> host->flags |= SDHCI_USE_SDMA; > >>> + } > >> > >> These if-statements are about setting SDHCI_USE_SDMA but why is a change > >> needed for the host->use_external_dma case? > > > > Yes, this is not needed, otherwise the controller cannot switch back > > to SDMA if it is supported in the controller. > > The else case means that the host always uses SDMA. Needed to make sure > this doesn't happen in the external_dma case. Its not SDHCI_CAN_DO_SDMA, > its SDHCI_USE_SDMA. > > > > >> > >>> > >>> if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && > >>> (host->flags & SDHCI_USE_SDMA)) { > >>> @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) > >>> } > >>> } > >>> > >>> + if (host->use_external_dma) { > >>> + ret = sdhci_external_dma_init(host); > >>> + if (ret == -EPROBE_DEFER) > >>> + goto unreg; > >>> + > >>> + /* > >>> + * Fall back to use the DMA/PIO integrated in standard SDHCI > >>> + * instead of external DMA devices. > >>> + */ > >>> + if (ret) > >>> + sdhci_switch_external_dma(host, false); > >>> + } > >>> + > >>> /* > >>> * If we use DMA, then it's up to the caller to set the DMA > >>> * mask, but PIO does not need the hw shim so we set a new > >>> @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) > >>> dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + > >>> host->adma_table_sz, host->align_buffer, > >>> host->align_addr); > >>> + > >>> + if (host->use_external_dma) > >>> + sdhci_external_dma_release(host); > >>> + > >>> host->adma_table = NULL; > >>> host->align_buffer = NULL; > >>> } > >>> @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) > >>> > >>> pr_info("%s: SDHCI controller on %s [%s] using %s\n", > >>> mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), > >>> + host->use_external_dma ? "External DMA" : > >>> (host->flags & SDHCI_USE_ADMA) ? > >>> (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : > >>> (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); > >>> @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) > >>> host->adma_table_sz, host->align_buffer, > >>> host->align_addr); > >>> > >>> + if (host->use_external_dma) > >>> + sdhci_external_dma_release(host); > >>> + > >>> host->adma_table = NULL; > >>> host->align_buffer = NULL; > >>> } > >>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > >>> index 6cc9a3c2ac66..7a52823ebef4 100644 > >>> --- a/drivers/mmc/host/sdhci.h > >>> +++ b/drivers/mmc/host/sdhci.h > >>> @@ -482,6 +482,7 @@ struct sdhci_host { > >>> > >>> int irq; /* Device IRQ */ > >>> void __iomem *ioaddr; /* Mapped address */ > >>> + phys_addr_t mapbase; /* physical address base */ > >>> char *bounce_buffer; /* For packing SDMA reads/writes */ > >>> dma_addr_t bounce_addr; > >>> unsigned int bounce_buffer_size; > >>> @@ -531,6 +532,7 @@ struct sdhci_host { > >>> bool pending_reset; /* Cmd/data reset is pending */ > >>> bool irq_wake_enabled; /* IRQ wakeup is enabled */ > >>> bool v4_mode; /* Host Version 4 Enable */ > >>> + bool use_external_dma; /* Host selects to use external DMA */ > >> > >> Please align /**/ with above i.e. use tab > > Will fix. Do you need me to address these comments and then send you a patch or you'd like to do instead? Thanks, Chunyan > > Thanks, > Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-28 11:46 ` Chunyan Zhang @ 2019-01-29 11:53 ` Faiz Abbas 2019-01-30 5:01 ` Chunyan Zhang 0 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-29 11:53 UTC (permalink / raw) To: Chunyan Zhang Cc: Adrian Hunter, Linux Kernel Mailing List, devicetree, linux-mmc, Kishon Vijay Abraham I, Mark Rutland, Rob Herring, Ulf Hansson Chunyan, On 28/01/19 5:16 PM, Chunyan Zhang wrote: > On Mon, 28 Jan 2019 at 18:40, Faiz Abbas <faiz_abbas@ti.com> wrote: >> >> Hi, >> >> On 28/01/19 3:50 PM, Chunyan Zhang wrote: >>> On Thu, 24 Jan 2019 at 19:41, Adrian Hunter <adrian.hunter@intel.com> wrote: >>>> >>>> On 11/01/19 1:08 PM, Faiz Abbas wrote: >>>>> From: Chunyan Zhang <zhang.chunyan@linaro.org> >>>>> >>>>> 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. >>>>> >>>>> Fixes by Faiz Abbas <faiz_abbas@ti.com>: >>>>> 1. Map scatterlists before dmaengine_prep_slave_sg() >>>>> 2. Use dma_async() functions inside of the send_command() path and >>>>> synchronize once at the start of each request. >>>> >>>> Sorry for the slow reply, but I do have some concerns. Please see the comments. >>>> >>>>> >>>>> Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> >>>>> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> >>>>> --- >>>>> drivers/mmc/host/Kconfig | 3 + >>>>> drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- >>>>> drivers/mmc/host/sdhci.h | 8 ++ >>>>> 3 files changed, 273 insertions(+), 4 deletions(-) >>>>> >>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig >>>>> index e26b8145efb3..333292e8ecdd 100644 >>>>> --- a/drivers/mmc/host/Kconfig >>>>> +++ b/drivers/mmc/host/Kconfig >>>>> @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 >>>>> If you have a controller with this interface, say Y or M here. >>>>> >>>>> If unsure, say N. >>>>> + >>>>> +config MMC_SDHCI_EXTERNAL_DMA >>>>> + bool >>>>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c >>>>> index a22e11a65658..4a9044c06e21 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> >>>>> @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) >>>>> } >>>>> } >>>>> >>>>> +#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; >>>>> + int sg_cnt; >>>>> + >>>>> + if (!host->mapbase) >>>>> + return -EINVAL; >>>>> + >>>>> + 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; >>>>> + >>>>> + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); >>>>> + if (sg_cnt <= 0) >>>>> + return -EINVAL; >>>>> + >>>>> + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, >>>>> + struct mmc_command *cmd) >>>>> +{ >>>>> + struct mmc_data *data = cmd->data; >>>>> + >>>>> + host->data_timeout = 0; >>>>> + >>>>> + if (sdhci_data_line_cmd(cmd)) >>>>> + sdhci_set_timeout(host, cmd); >>>>> + >>>>> + WARN_ON(host->data); >>>>> + >>>>> + /* Sanity checks */ >>>>> + WARN_ON(data->blksz * data->blocks > 524288); >>>>> + WARN_ON(data->blksz > host->mmc->max_blk_size); >>>>> + WARN_ON(data->blocks > 65535); >>>>> + >>>>> + host->flags |= SDHCI_REQ_USE_DMA; >>>>> + host->data = data; >>>>> + host->data_early = 0; >>>>> + host->data->bytes_xfered = 0; >>>>> + >>>>> + sdhci_set_transfer_irqs(host); >>>>> + >>>>> + /* >>>>> + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count >>>>> + * can be supported, in that case 16-bit block count register must be 0. >>>>> + */ >>>>> + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && >>>>> + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { >>>>> + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) >>>>> + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); >>>>> + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); >>>>> + } else { >>>>> + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); >>>>> + } >>>> >>>> It is probably worth factoring out the code that is shared with >>>> sdhci_prepare_data() where possible. >>>> >>>>> + >>>>> + return 0; >>>>> +} >>>>> + >>>>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, >>>>> + struct mmc_command *cmd) >>>>> +{ >>>>> + struct mmc_data *data = cmd->data; >>>>> + >>>>> + if (!data) >>>>> + return; >>>> >>>> Even in the !data case, we still need to set up a timeout for commands with >>>> busy waiting. I suggest checking the !data case before calling >>>> sdhci_external_dma_prepare_data() >>> >>> Ok. >>> >>>> >>>>> + >>>>> + if (sdhci_external_dma_setup(host, cmd) || >>>>> + __sdhci_external_dma_prepare_data(host, cmd)) { >>>>> + sdhci_external_dma_release(host); >>>>> + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", >>>>> + mmc_hostname(host->mmc)); >>>>> + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) >>>> >>>> MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. >>> >>> Ok. >>> >>>> >>>>> + return; >>>>> + >>>>> + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); >>>> >>>> Block size is set in __sdhci_external_dma_prepare_data() so does it need to >>>> be set here also. >>> >>> Ok. >> >> That is BLOCK_COUNT. This is BLOCK_SIZE. >> >>> >>>> >>>>> + chan = sdhci_external_dma_channel(host, cmd->data); >>>>> + if (chan) >>>>> + dma_async_issue_pending(chan); >>>>> +} >>>>> + >>>>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, >>>>> + struct mmc_data *data) >>>> >>>> Please align parameters with open parenthesis >>>> >>>>> +{ >>>>> + struct dma_chan *chan = sdhci_external_dma_channel(host, data); >>>>> + int ret = 0; >>>>> + >>>>> + if (chan) >>>>> + ret = dmaengine_terminate_async(chan); >>>>> + >>>>> + return ret; >>>>> +} >>>>> +#else >>>>> +static int sdhci_external_dma_init(struct sdhci_host *host) >>>>> +{ >>>>> + return -EOPNOTSUPP; >>>>> +} >>>>> + >>>>> +static void sdhci_external_dma_release(struct sdhci_host *host) >>>>> +{} >>>>> + >>>>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, >>>>> + struct mmc_command *cmd) >>>>> +{ >>>>> + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ >>>>> + sdhci_prepare_data(host, cmd); >>>>> +} >>>>> + >>>>> +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, >>>>> + struct mmc_command *cmd) >>>>> +{} >>>>> + >>>>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, >>>>> + struct mmc_data *data) >>>> >>>> Please align parameters with open parenthesis >>>> >>>>> +{ >>>>> + return 0; >>>>> +} >>>>> +#endif >>>>> + >>>>> +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) >>>>> +{ >>>>> + host->use_external_dma = en; >>>>> +} >>>>> +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); >>>>> + >>>>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host, >>>>> struct mmc_request *mrq) >>>>> { >>>>> @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) >>>>> host->data_cmd = cmd; >>>>> } >>>>> >>>>> - sdhci_prepare_data(host, cmd); >>>>> + if (host->use_external_dma) >>>> >>>> As mentioned above wrt sdhci_external_dma_prepare_data(): >>>> >>>> if (host->use_external_dma && cmd->data) >>>> >>> >>> Maybe we should move checking the !data case out of both >>> _prepare_data, and add it to sdhci_send_command() before calling >>> _prepare_data(), that's saying we can do set up a timeout in >>> sdhci_send_command(). >> >> Lets just factor out everything in common as Adrian said. The !data, >> BLOCK_COUNT writes and busy waiting. > > Ok, and also BLOCK_SIZE which can be set in _prepare_data(). > >> >>> >>>>> + sdhci_external_dma_prepare_data(host, cmd); >>>>> + else >>>>> + sdhci_prepare_data(host, cmd); >>>>> >>>>> sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); >>>>> >>>>> @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) >>>>> timeout += 10 * HZ; >>>>> sdhci_mod_timer(host, cmd->mrq, timeout); >>>>> >>>>> + if (host->use_external_dma) >>>>> + sdhci_external_dma_pre_transfer(host, cmd); >>>> >>>> Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done >>>> in sdhci_external_dma_prepare_data()? >>>> >>> >>> I'm not sure dma_async_issue_pending() can be done so early in >>> sdhci_external_dma_prepare_data(). >>> >>>>> + >>>>> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); >>>>> } >>>>> EXPORT_SYMBOL_GPL(sdhci_send_command); >>>>> @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) >>>>> >>>>> sdhci_led_activate(host); >>>>> >>>>> + if (host->use_external_dma && mrq->data) { >>>>> + struct dma_chan *chan = sdhci_external_dma_channel(host, >>>>> + mrq->data); >>>> >>>> sdhci_external_dma_channel is not declared if >>>> !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) >>> >>> Ok, I guess this can be moved to sdhci_external_dma_prepare_data(). >> >> No. We have to synchronize from a guaranteed non-atomic context. Maybe >> add another empty sdhci_external_dma_channel definition in the #else part? >> >>> >>>> >>>>> + dmaengine_synchronize(chan); >>>> >>>> So this is to cover for using dmaengine_terminate_async()? >> >> Yes. >> >>> >>> Ok. >>> >>>> >>>>> + } >>>>> /* >>>>> * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED >>>>> * requests if Auto-CMD12 is enabled. >>>>> @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) >>>>> dma_unmap_sg(mmc_dev(host->mmc), data->sg, >>>>> data->sg_len, >>>>> mmc_get_dma_dir(data)); >>>>> + if (host->use_external_dma) >>>>> + sdhci_external_dma_cleanup(host, data); >>>> >>>> Is sdhci_external_dma_cleanup() only needed in the error case? >>>> >>>> The DMA must be stopped before the memory is unmapped and potentially freed. >>>> >>>> Isn't the DMA cleanup also needed in the bounce buffer case? >>>> >>>> Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? >>>> >>>> dmaengine_terminate_async() doesn't stop the DMA but >>>> dmaengine_terminate_sync() is not atomic, which looks like a problem. >>>> >>>> Perhaps you look at scheduling some work for the external dma error case >>>> instead of calling __sdhci_finish_mrq()? Then the work can do the >>>> dmaengine_terminate_sync() and call __sdhci_finish_mrq(). >>> >>> Ok, I will look at these issues. >>> >>>> >>>>> } >>>>> data->host_cookie = COOKIE_UNMAPPED; >>>>> } >>>>> @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) >>>>> mmc_hostname(mmc), host->version); >>>>> } >>>>> >>>>> - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) >>>>> + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { >>>>> host->flags |= SDHCI_USE_SDMA; >>>>> - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) >>>>> + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { >>>>> DBG("Controller doesn't have SDMA capability\n"); >>>>> - else >>>>> + } else if (host->use_external_dma) { >>>>> + /* Using dma-names to detect external dma capability */ >>>>> + } else { >>>>> host->flags |= SDHCI_USE_SDMA; >>>>> + } >>>> >>>> These if-statements are about setting SDHCI_USE_SDMA but why is a change >>>> needed for the host->use_external_dma case? >>> >>> Yes, this is not needed, otherwise the controller cannot switch back >>> to SDMA if it is supported in the controller. >> >> The else case means that the host always uses SDMA. Needed to make sure >> this doesn't happen in the external_dma case. Its not SDHCI_CAN_DO_SDMA, >> its SDHCI_USE_SDMA. >> >>> >>>> >>>>> >>>>> if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && >>>>> (host->flags & SDHCI_USE_SDMA)) { >>>>> @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) >>>>> } >>>>> } >>>>> >>>>> + if (host->use_external_dma) { >>>>> + ret = sdhci_external_dma_init(host); >>>>> + if (ret == -EPROBE_DEFER) >>>>> + goto unreg; >>>>> + >>>>> + /* >>>>> + * Fall back to use the DMA/PIO integrated in standard SDHCI >>>>> + * instead of external DMA devices. >>>>> + */ >>>>> + if (ret) >>>>> + sdhci_switch_external_dma(host, false); >>>>> + } >>>>> + >>>>> /* >>>>> * If we use DMA, then it's up to the caller to set the DMA >>>>> * mask, but PIO does not need the hw shim so we set a new >>>>> @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) >>>>> dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + >>>>> host->adma_table_sz, host->align_buffer, >>>>> host->align_addr); >>>>> + >>>>> + if (host->use_external_dma) >>>>> + sdhci_external_dma_release(host); >>>>> + >>>>> host->adma_table = NULL; >>>>> host->align_buffer = NULL; >>>>> } >>>>> @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) >>>>> >>>>> pr_info("%s: SDHCI controller on %s [%s] using %s\n", >>>>> mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), >>>>> + host->use_external_dma ? "External DMA" : >>>>> (host->flags & SDHCI_USE_ADMA) ? >>>>> (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : >>>>> (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); >>>>> @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) >>>>> host->adma_table_sz, host->align_buffer, >>>>> host->align_addr); >>>>> >>>>> + if (host->use_external_dma) >>>>> + sdhci_external_dma_release(host); >>>>> + >>>>> host->adma_table = NULL; >>>>> host->align_buffer = NULL; >>>>> } >>>>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h >>>>> index 6cc9a3c2ac66..7a52823ebef4 100644 >>>>> --- a/drivers/mmc/host/sdhci.h >>>>> +++ b/drivers/mmc/host/sdhci.h >>>>> @@ -482,6 +482,7 @@ struct sdhci_host { >>>>> >>>>> int irq; /* Device IRQ */ >>>>> void __iomem *ioaddr; /* Mapped address */ >>>>> + phys_addr_t mapbase; /* physical address base */ >>>>> char *bounce_buffer; /* For packing SDMA reads/writes */ >>>>> dma_addr_t bounce_addr; >>>>> unsigned int bounce_buffer_size; >>>>> @@ -531,6 +532,7 @@ struct sdhci_host { >>>>> bool pending_reset; /* Cmd/data reset is pending */ >>>>> bool irq_wake_enabled; /* IRQ wakeup is enabled */ >>>>> bool v4_mode; /* Host Version 4 Enable */ >>>>> + bool use_external_dma; /* Host selects to use external DMA */ >>>> >>>> Please align /**/ with above i.e. use tab >> >> Will fix. > > Do you need me to address these comments and then send you a patch or > you'd like to do instead? > This would be much appreciated. Would you be able to work on it this week? I would like to send a v2 by Friday. Thanks, Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-29 11:53 ` Faiz Abbas @ 2019-01-30 5:01 ` Chunyan Zhang 2019-01-30 11:05 ` Chunyan Zhang 0 siblings, 1 reply; 26+ messages in thread From: Chunyan Zhang @ 2019-01-30 5:01 UTC (permalink / raw) To: Faiz Abbas Cc: Adrian Hunter, Linux Kernel Mailing List, devicetree, linux-mmc, Kishon Vijay Abraham I, Mark Rutland, Rob Herring, Ulf Hansson, Mark Brown Hi Faiz, On Tue, 29 Jan 2019 at 19:50, Faiz Abbas <faiz_abbas@ti.com> wrote: > > Chunyan, > > On 28/01/19 5:16 PM, Chunyan Zhang wrote: > > On Mon, 28 Jan 2019 at 18:40, Faiz Abbas <faiz_abbas@ti.com> wrote: > >> > >> Hi, > >> > >> On 28/01/19 3:50 PM, Chunyan Zhang wrote: > >>> On Thu, 24 Jan 2019 at 19:41, Adrian Hunter <adrian.hunter@intel.com> wrote: > >>>> > >>>> On 11/01/19 1:08 PM, Faiz Abbas wrote: > >>>>> From: Chunyan Zhang <zhang.chunyan@linaro.org> > >>>>> > >>>>> 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. > >>>>> > >>>>> Fixes by Faiz Abbas <faiz_abbas@ti.com>: > >>>>> 1. Map scatterlists before dmaengine_prep_slave_sg() > >>>>> 2. Use dma_async() functions inside of the send_command() path and > >>>>> synchronize once at the start of each request. > >>>> > >>>> Sorry for the slow reply, but I do have some concerns. Please see the comments. > >>>> > >>>>> > >>>>> Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > >>>>> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > >>>>> --- > >>>>> drivers/mmc/host/Kconfig | 3 + > >>>>> drivers/mmc/host/sdhci.c | 266 ++++++++++++++++++++++++++++++++++++++- > >>>>> drivers/mmc/host/sdhci.h | 8 ++ > >>>>> 3 files changed, 273 insertions(+), 4 deletions(-) > >>>>> > >>>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > >>>>> index e26b8145efb3..333292e8ecdd 100644 > >>>>> --- a/drivers/mmc/host/Kconfig > >>>>> +++ b/drivers/mmc/host/Kconfig > >>>>> @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 > >>>>> If you have a controller with this interface, say Y or M here. > >>>>> > >>>>> If unsure, say N. > >>>>> + > >>>>> +config MMC_SDHCI_EXTERNAL_DMA > >>>>> + bool > >>>>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > >>>>> index a22e11a65658..4a9044c06e21 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> > >>>>> @@ -1118,6 +1119,226 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) > >>>>> } > >>>>> } > >>>>> > >>>>> +#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; > >>>>> + int sg_cnt; > >>>>> + > >>>>> + if (!host->mapbase) > >>>>> + return -EINVAL; > >>>>> + > >>>>> + 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; > >>>>> + > >>>>> + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); > >>>>> + if (sg_cnt <= 0) > >>>>> + return -EINVAL; > >>>>> + > >>>>> + 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 int __sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>>>> + struct mmc_command *cmd) > >>>>> +{ > >>>>> + struct mmc_data *data = cmd->data; > >>>>> + > >>>>> + host->data_timeout = 0; > >>>>> + > >>>>> + if (sdhci_data_line_cmd(cmd)) > >>>>> + sdhci_set_timeout(host, cmd); > >>>>> + > >>>>> + WARN_ON(host->data); > >>>>> + > >>>>> + /* Sanity checks */ > >>>>> + WARN_ON(data->blksz * data->blocks > 524288); > >>>>> + WARN_ON(data->blksz > host->mmc->max_blk_size); > >>>>> + WARN_ON(data->blocks > 65535); > >>>>> + > >>>>> + host->flags |= SDHCI_REQ_USE_DMA; > >>>>> + host->data = data; > >>>>> + host->data_early = 0; > >>>>> + host->data->bytes_xfered = 0; > >>>>> + > >>>>> + sdhci_set_transfer_irqs(host); > >>>>> + > >>>>> + /* > >>>>> + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count > >>>>> + * can be supported, in that case 16-bit block count register must be 0. > >>>>> + */ > >>>>> + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && > >>>>> + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { > >>>>> + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) > >>>>> + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); > >>>>> + sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); > >>>>> + } else { > >>>>> + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > >>>>> + } > >>>> > >>>> It is probably worth factoring out the code that is shared with > >>>> sdhci_prepare_data() where possible. > >>>> > >>>>> + > >>>>> + return 0; > >>>>> +} > >>>>> + > >>>>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>>>> + struct mmc_command *cmd) > >>>>> +{ > >>>>> + struct mmc_data *data = cmd->data; > >>>>> + > >>>>> + if (!data) > >>>>> + return; > >>>> > >>>> Even in the !data case, we still need to set up a timeout for commands with > >>>> busy waiting. I suggest checking the !data case before calling > >>>> sdhci_external_dma_prepare_data() > >>> > >>> Ok. > >>> > >>>> > >>>>> + > >>>>> + if (sdhci_external_dma_setup(host, cmd) || > >>>>> + __sdhci_external_dma_prepare_data(host, cmd)) { > >>>>> + sdhci_external_dma_release(host); > >>>>> + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > >>>>> + mmc_hostname(host->mmc)); > >>>>> + 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->data || cmd->opcode == MMC_SET_BLOCK_COUNT) > >>>> > >>>> MMC_SET_BLOCK_COUNT never has cmd->data and so does not need to be checked. > >>> > >>> Ok. > >>> > >>>> > >>>>> + return; > >>>>> + > >>>>> + sdhci_writew(host, cmd->data->blksz, SDHCI_BLOCK_SIZE); > >>>> > >>>> Block size is set in __sdhci_external_dma_prepare_data() so does it need to > >>>> be set here also. > >>> > >>> Ok. > >> > >> That is BLOCK_COUNT. This is BLOCK_SIZE. > >> > >>> > >>>> > >>>>> + chan = sdhci_external_dma_channel(host, cmd->data); > >>>>> + if (chan) > >>>>> + dma_async_issue_pending(chan); > >>>>> +} > >>>>> + > >>>>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > >>>>> + struct mmc_data *data) > >>>> > >>>> Please align parameters with open parenthesis > >>>> > >>>>> +{ > >>>>> + struct dma_chan *chan = sdhci_external_dma_channel(host, data); > >>>>> + int ret = 0; > >>>>> + > >>>>> + if (chan) > >>>>> + ret = dmaengine_terminate_async(chan); > >>>>> + > >>>>> + return ret; > >>>>> +} > >>>>> +#else > >>>>> +static int sdhci_external_dma_init(struct sdhci_host *host) > >>>>> +{ > >>>>> + return -EOPNOTSUPP; > >>>>> +} > >>>>> + > >>>>> +static void sdhci_external_dma_release(struct sdhci_host *host) > >>>>> +{} > >>>>> + > >>>>> +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > >>>>> + struct mmc_command *cmd) > >>>>> +{ > >>>>> + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ > >>>>> + sdhci_prepare_data(host, cmd); > >>>>> +} > >>>>> + > >>>>> +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > >>>>> + struct mmc_command *cmd) > >>>>> +{} > >>>>> + > >>>>> +static int sdhci_external_dma_cleanup(struct sdhci_host *host, > >>>>> + struct mmc_data *data) > >>>> > >>>> Please align parameters with open parenthesis > >>>> > >>>>> +{ > >>>>> + return 0; > >>>>> +} > >>>>> +#endif > >>>>> + > >>>>> +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) > >>>>> +{ > >>>>> + host->use_external_dma = en; > >>>>> +} > >>>>> +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); > >>>>> + > >>>>> static inline bool sdhci_auto_cmd12(struct sdhci_host *host, > >>>>> struct mmc_request *mrq) > >>>>> { > >>>>> @@ -1374,7 +1595,10 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > >>>>> host->data_cmd = cmd; > >>>>> } > >>>>> > >>>>> - sdhci_prepare_data(host, cmd); > >>>>> + if (host->use_external_dma) > >>>> > >>>> As mentioned above wrt sdhci_external_dma_prepare_data(): > >>>> > >>>> if (host->use_external_dma && cmd->data) > >>>> > >>> > >>> Maybe we should move checking the !data case out of both > >>> _prepare_data, and add it to sdhci_send_command() before calling > >>> _prepare_data(), that's saying we can do set up a timeout in > >>> sdhci_send_command(). > >> > >> Lets just factor out everything in common as Adrian said. The !data, > >> BLOCK_COUNT writes and busy waiting. > > > > Ok, and also BLOCK_SIZE which can be set in _prepare_data(). > > > >> > >>> > >>>>> + sdhci_external_dma_prepare_data(host, cmd); > >>>>> + else > >>>>> + sdhci_prepare_data(host, cmd); > >>>>> > >>>>> sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); > >>>>> > >>>>> @@ -1416,6 +1640,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > >>>>> timeout += 10 * HZ; > >>>>> sdhci_mod_timer(host, cmd->mrq, timeout); > >>>>> > >>>>> + if (host->use_external_dma) > >>>>> + sdhci_external_dma_pre_transfer(host, cmd); > >>>> > >>>> Why is sdhci_external_dma_pre_transfer() needed here - couldn't it be done > >>>> in sdhci_external_dma_prepare_data()? > >>>> > >>> > >>> I'm not sure dma_async_issue_pending() can be done so early in > >>> sdhci_external_dma_prepare_data(). > >>> > >>>>> + > >>>>> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); > >>>>> } > >>>>> EXPORT_SYMBOL_GPL(sdhci_send_command); > >>>>> @@ -1781,6 +2008,11 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) > >>>>> > >>>>> sdhci_led_activate(host); > >>>>> > >>>>> + if (host->use_external_dma && mrq->data) { > >>>>> + struct dma_chan *chan = sdhci_external_dma_channel(host, > >>>>> + mrq->data); > >>>> > >>>> sdhci_external_dma_channel is not declared if > >>>> !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) > >>> > >>> Ok, I guess this can be moved to sdhci_external_dma_prepare_data(). > >> > >> No. We have to synchronize from a guaranteed non-atomic context. Maybe > >> add another empty sdhci_external_dma_channel definition in the #else part? > >> > >>> > >>>> > >>>>> + dmaengine_synchronize(chan); > >>>> > >>>> So this is to cover for using dmaengine_terminate_async()? > >> > >> Yes. > >> > >>> > >>> Ok. > >>> > >>>> > >>>>> + } > >>>>> /* > >>>>> * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED > >>>>> * requests if Auto-CMD12 is enabled. > >>>>> @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) > >>>>> dma_unmap_sg(mmc_dev(host->mmc), data->sg, > >>>>> data->sg_len, > >>>>> mmc_get_dma_dir(data)); > >>>>> + if (host->use_external_dma) > >>>>> + sdhci_external_dma_cleanup(host, data); > >>>> > >>>> Is sdhci_external_dma_cleanup() only needed in the error case? > >>>> > >>>> The DMA must be stopped before the memory is unmapped and potentially freed. > >>>> > >>>> Isn't the DMA cleanup also needed in the bounce buffer case? > >>>> > >>>> Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? > >>>> > >>>> dmaengine_terminate_async() doesn't stop the DMA but > >>>> dmaengine_terminate_sync() is not atomic, which looks like a problem. > >>>> > >>>> Perhaps you look at scheduling some work for the external dma error case > >>>> instead of calling __sdhci_finish_mrq()? Then the work can do the > >>>> dmaengine_terminate_sync() and call __sdhci_finish_mrq(). > >>> > >>> Ok, I will look at these issues. > >>> > >>>> > >>>>> } > >>>>> data->host_cookie = COOKIE_UNMAPPED; > >>>>> } > >>>>> @@ -3692,12 +3926,15 @@ int sdhci_setup_host(struct sdhci_host *host) > >>>>> mmc_hostname(mmc), host->version); > >>>>> } > >>>>> > >>>>> - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) > >>>>> + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { > >>>>> host->flags |= SDHCI_USE_SDMA; > >>>>> - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) > >>>>> + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { > >>>>> DBG("Controller doesn't have SDMA capability\n"); > >>>>> - else > >>>>> + } else if (host->use_external_dma) { > >>>>> + /* Using dma-names to detect external dma capability */ > >>>>> + } else { > >>>>> host->flags |= SDHCI_USE_SDMA; > >>>>> + } > >>>> > >>>> These if-statements are about setting SDHCI_USE_SDMA but why is a change > >>>> needed for the host->use_external_dma case? > >>> > >>> Yes, this is not needed, otherwise the controller cannot switch back > >>> to SDMA if it is supported in the controller. > >> > >> The else case means that the host always uses SDMA. Needed to make sure > >> this doesn't happen in the external_dma case. Its not SDHCI_CAN_DO_SDMA, > >> its SDHCI_USE_SDMA. > >> > >>> > >>>> > >>>>> > >>>>> if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && > >>>>> (host->flags & SDHCI_USE_SDMA)) { > >>>>> @@ -3785,6 +4022,19 @@ int sdhci_setup_host(struct sdhci_host *host) > >>>>> } > >>>>> } > >>>>> > >>>>> + if (host->use_external_dma) { > >>>>> + ret = sdhci_external_dma_init(host); > >>>>> + if (ret == -EPROBE_DEFER) > >>>>> + goto unreg; > >>>>> + > >>>>> + /* > >>>>> + * Fall back to use the DMA/PIO integrated in standard SDHCI > >>>>> + * instead of external DMA devices. > >>>>> + */ > >>>>> + if (ret) > >>>>> + sdhci_switch_external_dma(host, false); > >>>>> + } > >>>>> + > >>>>> /* > >>>>> * If we use DMA, then it's up to the caller to set the DMA > >>>>> * mask, but PIO does not need the hw shim so we set a new > >>>>> @@ -4201,6 +4451,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) > >>>>> dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + > >>>>> host->adma_table_sz, host->align_buffer, > >>>>> host->align_addr); > >>>>> + > >>>>> + if (host->use_external_dma) > >>>>> + sdhci_external_dma_release(host); > >>>>> + > >>>>> host->adma_table = NULL; > >>>>> host->align_buffer = NULL; > >>>>> } > >>>>> @@ -4247,6 +4501,7 @@ int __sdhci_add_host(struct sdhci_host *host) > >>>>> > >>>>> pr_info("%s: SDHCI controller on %s [%s] using %s\n", > >>>>> mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), > >>>>> + host->use_external_dma ? "External DMA" : > >>>>> (host->flags & SDHCI_USE_ADMA) ? > >>>>> (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : > >>>>> (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); > >>>>> @@ -4335,6 +4590,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) > >>>>> host->adma_table_sz, host->align_buffer, > >>>>> host->align_addr); > >>>>> > >>>>> + if (host->use_external_dma) > >>>>> + sdhci_external_dma_release(host); > >>>>> + > >>>>> host->adma_table = NULL; > >>>>> host->align_buffer = NULL; > >>>>> } > >>>>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > >>>>> index 6cc9a3c2ac66..7a52823ebef4 100644 > >>>>> --- a/drivers/mmc/host/sdhci.h > >>>>> +++ b/drivers/mmc/host/sdhci.h > >>>>> @@ -482,6 +482,7 @@ struct sdhci_host { > >>>>> > >>>>> int irq; /* Device IRQ */ > >>>>> void __iomem *ioaddr; /* Mapped address */ > >>>>> + phys_addr_t mapbase; /* physical address base */ > >>>>> char *bounce_buffer; /* For packing SDMA reads/writes */ > >>>>> dma_addr_t bounce_addr; > >>>>> unsigned int bounce_buffer_size; > >>>>> @@ -531,6 +532,7 @@ struct sdhci_host { > >>>>> bool pending_reset; /* Cmd/data reset is pending */ > >>>>> bool irq_wake_enabled; /* IRQ wakeup is enabled */ > >>>>> bool v4_mode; /* Host Version 4 Enable */ > >>>>> + bool use_external_dma; /* Host selects to use external DMA */ > >>>> > >>>> Please align /**/ with above i.e. use tab > >> > >> Will fix. > > > > Do you need me to address these comments and then send you a patch or > > you'd like to do instead? > > > > This would be much appreciated. Would you be able to work on it this > week? I would like to send a v2 by Friday. I will try. I just have worries about handling the external dma error case. Anyway I'll send you a patch by tomorrow, since I will leave 10 days from this Friday. Thanks, Chunyan ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-30 5:01 ` Chunyan Zhang @ 2019-01-30 11:05 ` Chunyan Zhang 2019-01-31 12:32 ` Faiz Abbas 0 siblings, 1 reply; 26+ messages in thread From: Chunyan Zhang @ 2019-01-30 11:05 UTC (permalink / raw) To: Ulf Hansson, Adrian Hunter, Faiz Abbas Cc: linux-mmc, linux-kernel, Arnd Bergmann, Mark Brown, Kishon Vijay Abraham I, Chunyan Zhang 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. Fixes by Faiz Abbas <faiz_abbas@ti.com>: 1. Map scatterlists before dmaengine_prep_slave_sg() 2. Use dma_async() functions inside of the send_command() path and synchronize once at the start of each request. Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- Changes from the last version: * Moved sdhci_set_timeout() from _prepare_data() to its caller - sdhci_send_command(); * Factor out a new function sdhci_reset_data() which deal with sanity checks for mmc_data and reset for host->data, these processes were in _prepare_data(); * Factor out a new function sdhci_set_block_info() for configuring data blocks which were processing at bottom of _prepare_data(); * Added a new tasklet and functions for handling external dma error case; * Removed sdhci_external_dma_cleanup() which is not used; * Added an empty sdhci_external_dma_channel definition for !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA); * Addressed some other comments from Adrian: - Removed check for MMC_SET_BLOCK_COUNT, left checking !cmd->data only which is enough. --- drivers/mmc/host/Kconfig | 3 + drivers/mmc/host/sdhci.c | 333 +++++++++++++++++++++++++++++++++++---- drivers/mmc/host/sdhci.h | 10 ++ 3 files changed, 317 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index e26b8145efb3..333292e8ecdd 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -999,3 +999,6 @@ config MMC_SDHCI_AM654 If you have a controller with this interface, say Y or M here. If unsure, say N. + +config MMC_SDHCI_EXTERNAL_DMA + bool diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a22e11a65658..8b056bb9422d 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> @@ -987,18 +988,9 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd) } } -static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) +static inline void sdhci_reset_data(struct sdhci_host *host, + struct mmc_data *data) { - struct mmc_data *data = cmd->data; - - host->data_timeout = 0; - - if (sdhci_data_line_cmd(cmd)) - sdhci_set_timeout(host, cmd); - - if (!data) - return; - WARN_ON(host->data); /* Sanity checks */ @@ -1009,6 +1001,34 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) host->data = data; host->data_early = 0; host->data->bytes_xfered = 0; +} + +static inline void sdhci_set_block_info(struct sdhci_host *host) +{ + + /* Set the DMA boundary value and block size */ + sdhci_writew(host, + SDHCI_MAKE_BLKSZ(host->sdma_boundary, host->data->blksz), + SDHCI_BLOCK_SIZE); + /* + * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count + * can be supported, in that case 16-bit block count register must be 0. + */ + if (host->version >= SDHCI_SPEC_410 && host->v4_mode && + (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { + if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) + sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); + sdhci_writew(host, host->data->blocks, SDHCI_32BIT_BLK_CNT); + } else { + sdhci_writew(host, host->data->blocks, SDHCI_BLOCK_COUNT); + } +} + +static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + + sdhci_reset_data(host, data); if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { struct scatterlist *sg; @@ -1100,24 +1120,227 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) sdhci_set_transfer_irqs(host); - /* Set the DMA boundary value and block size */ - sdhci_writew(host, SDHCI_MAKE_BLKSZ(host->sdma_boundary, data->blksz), - SDHCI_BLOCK_SIZE); + sdhci_set_block_info(host); +} - /* - * For Version 4.10 onwards, if v4 mode is enabled, 32-bit Block Count - * can be supported, in that case 16-bit block count register must be 0. - */ - if (host->version >= SDHCI_SPEC_410 && host->v4_mode && - (host->quirks2 & SDHCI_QUIRK2_USE_32BIT_BLK_CNT)) { - if (sdhci_readw(host, SDHCI_BLOCK_COUNT)) - sdhci_writew(host, 0, SDHCI_BLOCK_COUNT); - sdhci_writew(host, data->blocks, SDHCI_32BIT_BLK_CNT); +#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; + int sg_cnt; + + if (!host->mapbase) + return -EINVAL; + + 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; + + sg_cnt = sdhci_pre_dma_transfer(host, data, COOKIE_MAPPED); + if (sg_cnt <= 0) + return -EINVAL; + + 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) +{ + struct mmc_data *data = cmd->data; + + sdhci_reset_data(host, data); + + host->flags |= SDHCI_REQ_USE_DMA; + sdhci_set_transfer_irqs(host); + + sdhci_set_block_info(host); +} + +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, + struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + struct dma_chan *chan = sdhci_external_dma_channel(host, data); + + if (!sdhci_external_dma_setup(host, cmd)) { + __sdhci_external_dma_prepare_data(host, cmd); } else { - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + sdhci_external_dma_release(host); + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", + mmc_hostname(host->mmc)); + 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->data) + return; + + chan = sdhci_external_dma_channel(host, cmd->data); + if (chan) + dma_async_issue_pending(chan); +} + +static bool sdhci_external_dma_request_done(struct sdhci_host *host) +{ + struct mmc_request *mrq; + struct dma_chan *chan; + int i; + + spin_lock_irqsave(&host->lock, flags); + + for (i = 0; i < SDHCI_MAX_MRQS; i++) { + mrq = host->mrqs_done[i]; + if (mrq) + break; } + + spin_unlock_irqrestore(&host->lock, flags); + + if (!mrq) + return true; + + sdhci_tasklet_finish(host); + + chan = sdhci_external_dma_channel(host, mrq->data); + dmaengine_synchronize(chan); + + return false; } + +static void sdhci_external_dma_tasklet_finish(unsigned long param) +{ + struct sdhci_host *host = (struct sdhci_host *)param; + + while (!sdhci_external_dma_request_done(host)) + ; +} +#else +static int sdhci_external_dma_init(struct sdhci_host *host) +{ + return -EOPNOTSUPP; +} + +static inline struct dma_chan * +sdhci_external_dma_channel(struct sdhci_host *host, struct mmc_data *data) +{ + return NULL; +} + +static void sdhci_external_dma_release(struct sdhci_host *host) +{} + +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, + struct mmc_command *cmd) +{ + /* If MMC_SDHCI_EXTERNAL_DMA not supported, PIO will be used */ + sdhci_prepare_data(host, cmd); +} + +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, + struct mmc_command *cmd) +{} + +static void sdhci_external_dma_tasklet_finish(unsigned long param) +{} +#endif + +void sdhci_switch_external_dma(struct sdhci_host *host, bool en) +{ + host->use_external_dma = en; +} +EXPORT_SYMBOL_GPL(sdhci_switch_external_dma); + static inline bool sdhci_auto_cmd12(struct sdhci_host *host, struct mmc_request *mrq) { @@ -1215,6 +1438,7 @@ static bool sdhci_needs_reset(struct sdhci_host *host, struct mmc_request *mrq) static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) { int i; + struct dma_chan *chan; for (i = 0; i < SDHCI_MAX_MRQS; i++) { if (host->mrqs_done[i] == mrq) { @@ -1232,7 +1456,13 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) WARN_ON(i >= SDHCI_MAX_MRQS); - tasklet_schedule(&host->finish_tasklet); + if (host->use_external_dma) { + chan = sdhci_external_dma_channel(host, mrq->data); + dmaengine_terminate_async(chan); + tasklet_schedule(&host->external_dma_finish_tasklet); + } else { + tasklet_schedule(&host->finish_tasklet); + } } static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) @@ -1369,12 +1599,19 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) } host->cmd = cmd; + host->data_timeout = 0; if (sdhci_data_line_cmd(cmd)) { WARN_ON(host->data_cmd); host->data_cmd = cmd; + sdhci_set_timeout(host, cmd); } - sdhci_prepare_data(host, cmd); + if (cmd->data) { + if (host->use_external_dma) + sdhci_external_dma_prepare_data(host, cmd); + else + sdhci_prepare_data(host, cmd); + } sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT); @@ -1416,6 +1653,9 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) timeout += 10 * HZ; sdhci_mod_timer(host, cmd->mrq, timeout); + if (host->use_external_dma) + sdhci_external_dma_pre_transfer(host, cmd); + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } EXPORT_SYMBOL_GPL(sdhci_send_command); @@ -1771,6 +2011,7 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) struct sdhci_host *host; int present; unsigned long flags; + struct dma_chan *chan; host = mmc_priv(mmc); @@ -1781,6 +2022,12 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) sdhci_led_activate(host); + if (host->use_external_dma && mrq->data) { + chan = sdhci_external_dma_channel(host, mrq->data); + if (chan) + dmaengine_terminate_async(chan); + } + /* * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED * requests if Auto-CMD12 is enabled. @@ -3692,12 +3939,28 @@ int sdhci_setup_host(struct sdhci_host *host) mmc_hostname(mmc), host->version); } - if (host->quirks & SDHCI_QUIRK_FORCE_DMA) + if (host->use_external_dma) { + ret = sdhci_external_dma_init(host); + if (ret == -EPROBE_DEFER) + goto unreg; + + /* + * Fall back to use the DMA/PIO integrated in standard SDHCI + * instead of external DMA devices. + */ + if (ret) + sdhci_switch_external_dma(host, false); + } + + if (host->quirks & SDHCI_QUIRK_FORCE_DMA) { host->flags |= SDHCI_USE_SDMA; - else if (!(host->caps & SDHCI_CAN_DO_SDMA)) + } else if (!(host->caps & SDHCI_CAN_DO_SDMA)) { DBG("Controller doesn't have SDMA capability\n"); - else + } else if (host->use_external_dma) { + /* Using dma-names to detect external dma capability */ + } else { host->flags |= SDHCI_USE_SDMA; + } if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && (host->flags & SDHCI_USE_SDMA)) { @@ -4201,6 +4464,10 @@ void sdhci_cleanup_host(struct sdhci_host *host) dma_free_coherent(mmc_dev(mmc), host->align_buffer_sz + host->adma_table_sz, host->align_buffer, host->align_addr); + + if (host->use_external_dma) + sdhci_external_dma_release(host); + host->adma_table = NULL; host->align_buffer = NULL; } @@ -4216,6 +4483,8 @@ int __sdhci_add_host(struct sdhci_host *host) */ tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host); + tasklet_init(&host->external_dma_finish_tasklet, + sdhci_external_dma_tasklet_finish, (unsigned long)host); timer_setup(&host->timer, sdhci_timeout_timer, 0); timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0); @@ -4247,6 +4516,7 @@ int __sdhci_add_host(struct sdhci_host *host) pr_info("%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), + host->use_external_dma ? "External DMA" : (host->flags & SDHCI_USE_ADMA) ? (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); @@ -4264,6 +4534,7 @@ int __sdhci_add_host(struct sdhci_host *host) free_irq(host->irq, host); untasklet: tasklet_kill(&host->finish_tasklet); + tasklet_kill(&host->external_dma_finish_tasklet); return ret; } @@ -4326,6 +4597,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) del_timer_sync(&host->data_timer); tasklet_kill(&host->finish_tasklet); + tasklet_kill(&host->external_dma_finish_tasklet); if (!IS_ERR(mmc->supply.vqmmc)) regulator_disable(mmc->supply.vqmmc); @@ -4335,6 +4607,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) host->adma_table_sz, host->align_buffer, host->align_addr); + if (host->use_external_dma) + sdhci_external_dma_release(host); + host->adma_table = NULL; host->align_buffer = NULL; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 6cc9a3c2ac66..43f71b479e0b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -482,6 +482,7 @@ struct sdhci_host { int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ + phys_addr_t mapbase; /* physical address base */ char *bounce_buffer; /* For packing SDMA reads/writes */ dma_addr_t bounce_addr; unsigned int bounce_buffer_size; @@ -531,6 +532,7 @@ struct sdhci_host { bool pending_reset; /* Cmd/data reset is pending */ bool irq_wake_enabled; /* IRQ wakeup is enabled */ bool v4_mode; /* Host Version 4 Enable */ + bool use_external_dma; /* Host selects to use external DMA */ struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */ struct mmc_command *cmd; /* Current command */ @@ -555,10 +557,17 @@ struct sdhci_host { unsigned int desc_sz; /* ADMA descriptor size */ struct tasklet_struct finish_tasklet; /* Tasklet structures */ + /* Tasklet struction for using external dma */ + struct tasklet_struct external_dma_finish_tasklet; struct timer_list timer; /* Timer for timeouts */ struct timer_list data_timer; /* Timer for data timeouts */ +#if IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA) + struct dma_chan *rx_chan; + struct dma_chan *tx_chan; +#endif + u32 caps; /* CAPABILITY_0 */ u32 caps1; /* CAPABILITY_1 */ bool read_caps; /* Capability flags have been read */ @@ -792,5 +801,6 @@ void sdhci_start_tuning(struct sdhci_host *host); void sdhci_end_tuning(struct sdhci_host *host); void sdhci_reset_tuning(struct sdhci_host *host); void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); +void sdhci_switch_external_dma(struct sdhci_host *host, bool en); #endif /* __SDHCI_HW_H */ -- 2.17.1 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-30 11:05 ` Chunyan Zhang @ 2019-01-31 12:32 ` Faiz Abbas 0 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-31 12:32 UTC (permalink / raw) To: Chunyan Zhang, Ulf Hansson, Adrian Hunter Cc: linux-mmc, linux-kernel, Arnd Bergmann, Mark Brown, Kishon Vijay Abraham I, Chunyan Zhang Chunyan, On 30/01/19 4:35 PM, 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. > > Fixes by Faiz Abbas <faiz_abbas@ti.com>: > 1. Map scatterlists before dmaengine_prep_slave_sg() > 2. Use dma_async() functions inside of the send_command() path and > synchronize once at the start of each request. > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- > Changes from the last version: > * Moved sdhci_set_timeout() from _prepare_data() to its caller - > sdhci_send_command(); > * Factor out a new function sdhci_reset_data() which deal with sanity > checks for mmc_data and reset for host->data, these processes were > in _prepare_data(); > * Factor out a new function sdhci_set_block_info() for configuring data > blocks which were processing at bottom of _prepare_data(); > * Added a new tasklet and functions for handling external dma error case; > * Removed sdhci_external_dma_cleanup() which is not used; > * Added an empty sdhci_external_dma_channel definition for > !IS_ENABLED(CONFIG_MMC_SDHCI_EXTERNAL_DMA); > * Addressed some other comments from Adrian: > - Removed check for MMC_SET_BLOCK_COUNT, left checking !cmd->data only > which is enough. There should have been a v2 in the patch description. It lets reviewers know that they have seen this patch before and its not the same thing. I would have preferred you had me test this before posting it to the mailing list. Otherwise there should be a "not tested" in the description. > --- > drivers/mmc/host/Kconfig | 3 + > drivers/mmc/host/sdhci.c | 333 +++++++++++++++++++++++++++++++++++---- > drivers/mmc/host/sdhci.h | 10 ++ > 3 files changed, 317 insertions(+), 29 deletions [snip] > +static void sdhci_external_dma_prepare_data(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct mmc_data *data = cmd->data; > + struct dma_chan *chan = sdhci_external_dma_channel(host, data); chan is not being used in this function. > + > + if (!sdhci_external_dma_setup(host, cmd)) { > + __sdhci_external_dma_prepare_data(host, cmd); > } else { > - sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); > + sdhci_external_dma_release(host); > + pr_err("%s: Cannot use external DMA, switch to the DMA/PIO which standard SDHCI provides.\n", > + mmc_hostname(host->mmc)); > + sdhci_prepare_data(host, cmd); > + } > +} > + > +static void sdhci_external_dma_pre_transfer(struct sdhci_host *host, > + struct mmc_command *cmd) > +{ > + struct dma_chan *chan; I think you wanted to initialize this one. > + > + if (!cmd->data) > + return; > + > + chan = sdhci_external_dma_channel(host, cmd->data); > + if (chan) > + dma_async_issue_pending(chan); > +} > + > +static bool sdhci_external_dma_request_done(struct sdhci_host *host) > +{ > + struct mmc_request *mrq; > + struct dma_chan *chan; > + int i; > + > + spin_lock_irqsave(&host->lock, flags); flags is not declared. > + > + for (i = 0; i < SDHCI_MAX_MRQS; i++) { > + mrq = host->mrqs_done[i]; > + if (mrq) > + break; > } > + > + spin_unlock_irqrestore(&host->lock, flags); > + > + if (!mrq) > + return true; > + > + sdhci_tasklet_finish(host); This doesn't even build. sdhci_tasklet_finish is declared below this. In the future, if you don't have a platform to test things on, at least build and use checkpatch before posting it to the mailing list. Should we even be calling a tasklet callback function directly? Shouldn't we do a tasklet_schedule()? Thanks, Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 1/7] mmc: sdhci: add support for using external DMA devices 2019-01-24 11:40 ` Adrian Hunter 2019-01-28 10:20 ` Chunyan Zhang @ 2019-02-11 12:23 ` Faiz Abbas 1 sibling, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-02-11 12:23 UTC (permalink / raw) To: Adrian Hunter, linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, mark.rutland, robh+dt, ulf.hansson Hi Adrian, On 24/01/19 5:10 PM, Adrian Hunter wrote: > On 11/01/19 1:08 PM, Faiz Abbas wrote: >> From: Chunyan Zhang <zhang.chunyan@linaro.org> >> >> 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. >> >> Fixes by Faiz Abbas <faiz_abbas@ti.com>: >> 1. Map scatterlists before dmaengine_prep_slave_sg() >> 2. Use dma_async() functions inside of the send_command() path and >> synchronize once at the start of each request. > > Sorry for the slow reply, but I do have some concerns. Please see the comments. >[snip]>> /* >> * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED >> * requests if Auto-CMD12 is enabled. >> @@ -2658,6 +2890,8 @@ static bool sdhci_request_done(struct sdhci_host *host) >> dma_unmap_sg(mmc_dev(host->mmc), data->sg, >> data->sg_len, >> mmc_get_dma_dir(data)); >> + if (host->use_external_dma) >> + sdhci_external_dma_cleanup(host, data); > > Is sdhci_external_dma_cleanup() only needed in the error case? > > The DMA must be stopped before the memory is unmapped and potentially freed. > > Isn't the DMA cleanup also needed in the bounce buffer case? > > Isn't the DMA cleanup also needed in the COOKIE_PRE_MAPPED case? > > dmaengine_terminate_async() doesn't stop the DMA but > dmaengine_terminate_sync() is not atomic, which looks like a problem. > > Perhaps you look at scheduling some work for the external dma error case > instead of calling __sdhci_finish_mrq()? Then the work can do the > dmaengine_terminate_sync() and call __sdhci_finish_mrq(). > Why don't we get rid of the finish_tasklet and use the already existing threaded_irq for everything? I tested the following patch out and it seems to work well. This enables us to just call dmaengine_terminate_sync() in sdhci_request_done(). diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a22e11a65658..beff2ac2dee5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1232,7 +1232,7 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) WARN_ON(i >= SDHCI_MAX_MRQS); - tasklet_schedule(&host->finish_tasklet); + host->threaded_irq_needed = true; } static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) @@ -2705,14 +2705,6 @@ static bool sdhci_request_done(struct sdhci_host *host) return false; } -static void sdhci_tasklet_finish(unsigned long param) -{ - struct sdhci_host *host = (struct sdhci_host *)param; - - while (!sdhci_request_done(host)) - ; -} - static void sdhci_timeout_timer(struct timer_list *t) { struct sdhci_host *host; @@ -3000,6 +2992,8 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) u32 intmask, mask, unexpected = 0; int max_loops = 16; + host->threaded_irq_needed = false; + spin_lock(&host->lock); if (host->runtime_suspended && !sdhci_sdio_irq_enabled(host)) { @@ -3077,6 +3071,9 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) result = IRQ_WAKE_THREAD; } + if (host->threaded_irq_needed) + result = IRQ_WAKE_THREAD; + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE | SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | SDHCI_INT_ERROR | SDHCI_INT_BUS_POWER | @@ -3131,6 +3128,10 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id) spin_unlock_irqrestore(&host->lock, flags); } + do { + isr = sdhci_request_done(host); + } while(isr); + return isr ? IRQ_HANDLED : IRQ_NONE; } @@ -4211,12 +4212,6 @@ int __sdhci_add_host(struct sdhci_host *host) struct mmc_host *mmc = host->mmc; int ret; - /* - * Init tasklets. - */ - tasklet_init(&host->finish_tasklet, - sdhci_tasklet_finish, (unsigned long)host); - timer_setup(&host->timer, sdhci_timeout_timer, 0); timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0); @@ -4229,7 +4224,7 @@ int __sdhci_add_host(struct sdhci_host *host) if (ret) { pr_err("%s: Failed to request IRQ %d: %d\n", mmc_hostname(mmc), host->irq, ret); - goto untasklet; + return ret; } ret = sdhci_led_register(host); @@ -4262,8 +4257,6 @@ int __sdhci_add_host(struct sdhci_host *host) sdhci_writel(host, 0, SDHCI_INT_ENABLE); sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); free_irq(host->irq, host); -untasklet: - tasklet_kill(&host->finish_tasklet); return ret; } @@ -4325,8 +4318,6 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) del_timer_sync(&host->timer); del_timer_sync(&host->data_timer); - tasklet_kill(&host->finish_tasklet); - if (!IS_ERR(mmc->supply.vqmmc)) regulator_disable(mmc->supply.vqmmc); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 6cc9a3c2ac66..abf4f77650b5 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -554,7 +554,7 @@ struct sdhci_host { unsigned int desc_sz; /* ADMA descriptor size */ - struct tasklet_struct finish_tasklet; /* Tasklet structures */ + bool threaded_irq_needed; struct timer_list timer; /* Timer for timeouts */ struct timer_list data_timer; /* Timer for data timeouts */ Thanks, Faiz ^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 2/7] mmc: sdhci-omap: Add using external dma 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-11 11:08 ` [PATCH 3/7] dt-bindings: sdhci-omap: Add example for " Faiz Abbas ` (5 subsequent siblings) 7 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas From: Chunyan Zhang <zhang.chunyan@linaro.org> sdhci-omap can support both external dma controller via dmaengine framework as well as ADMA which standard SD host controller provides. Fixes by Faiz Abbas <fazi_abbas@ti.com>: 1. Switch to DMA slave mode when using external DMA 2. Add offset to mapbase Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- drivers/mmc/host/Kconfig | 1 + drivers/mmc/host/sdhci-omap.c | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 333292e8ecdd..70e90b00cb37 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -979,6 +979,7 @@ config MMC_SDHCI_OMAP depends on MMC_SDHCI_PLTFM && OF select THERMAL select TI_SOC_THERMAL + 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 diff --git a/drivers/mmc/host/sdhci-omap.c b/drivers/mmc/host/sdhci-omap.c index c11c18a9aacb..8a05e94fe612 100644 --- a/drivers/mmc/host/sdhci-omap.c +++ b/drivers/mmc/host/sdhci-omap.c @@ -696,7 +696,11 @@ static int sdhci_omap_enable_dma(struct sdhci_host *host) struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host); reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_CON); - reg |= CON_DMA_MASTER; + reg &= ~CON_DMA_MASTER; + /* Switch to DMA slave mode when using external DMA */ + if (!host->use_external_dma) + reg |= CON_DMA_MASTER; + sdhci_omap_writel(omap_host, SDHCI_OMAP_CON, reg); return 0; @@ -1010,6 +1014,7 @@ static int sdhci_omap_probe(struct platform_device *pdev) const struct of_device_id *match; struct sdhci_omap_data *data; const struct soc_device_attribute *soc; + struct resource *regs; match = of_match_device(omap_sdhci_match, dev); if (!match) @@ -1022,6 +1027,10 @@ static int sdhci_omap_probe(struct platform_device *pdev) } offset = data->offset; + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + host = sdhci_pltfm_init(pdev, &sdhci_omap_pdata, sizeof(*omap_host)); if (IS_ERR(host)) { @@ -1038,6 +1047,7 @@ static int sdhci_omap_probe(struct platform_device *pdev) omap_host->timing = MMC_TIMING_LEGACY; omap_host->flags = data->flags; host->ioaddr += offset; + host->mapbase = regs->start + offset; mmc = host->mmc; sdhci_get_of_property(pdev); @@ -1105,6 +1115,10 @@ static int sdhci_omap_probe(struct platform_device *pdev) host->mmc_host_ops.execute_tuning = sdhci_omap_execute_tuning; host->mmc_host_ops.enable_sdio_irq = sdhci_omap_enable_sdio_irq; + /* Switch to external DMA only if there is the "dmas" property */ + if (of_find_property(dev->of_node, "dmas", NULL)) + sdhci_switch_external_dma(host, true); + ret = sdhci_setup_host(host); if (ret) goto err_put_sync; -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 3/7] dt-bindings: sdhci-omap: Add example for using external dma 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas 2019-01-11 11:08 ` [PATCH 2/7] mmc: sdhci-omap: Add using external dma Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-21 23:20 ` Rob Herring 2019-01-11 11:08 ` [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command Faiz Abbas ` (4 subsequent siblings) 7 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas From: Chunyan Zhang <zhang.chunyan@linaro.org> sdhci-omap can support both external dma controller via dmaengine framework as well as ADMA which standard SD host controller provides. Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt index 72c4dec7e1db..f8914a2a2aec 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt @@ -14,6 +14,11 @@ Required properties: "ddr_1_8v-rev11", "ddr_1_8v" or "ddr_3_3v", "hs200_1_8v-rev11", "hs200_1_8v", - pinctrl-<n> : Pinctrl states as described in bindings/pinctrl/pinctrl-bindings.txt +- dmas: List of DMA specifiers with the controller specific format as described + in the generic DMA client binding. A tx and rx specifier is required. +- dma-names: List of DMA request names. These strings correspond 1:1 with the + DMA specifiers listed in dmas. The string naming is to be "rx" + and "tx" for RX and TX DMA requests, respectively. Example: mmc1: mmc@4809c000 { @@ -22,4 +27,6 @@ Example: ti,hwmods = "mmc1"; bus-width = <4>; vmmc-supply = <&vmmc>; /* phandle to regulator node */ + dmas = <&sdma 61 &sdma 62>; + dma-names = "tx", "rx"; }; -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 3/7] dt-bindings: sdhci-omap: Add example for using external dma 2019-01-11 11:08 ` [PATCH 3/7] dt-bindings: sdhci-omap: Add example for " Faiz Abbas @ 2019-01-21 23:20 ` Rob Herring 2019-01-22 8:47 ` [PATCH] dt-bindings: sdhci-omap: Add properties " Chunyan Zhang 0 siblings, 1 reply; 26+ messages in thread From: Rob Herring @ 2019-01-21 23:20 UTC (permalink / raw) To: Faiz Abbas Cc: linux-kernel, devicetree, linux-mmc, zhang.chunyan, kishon, adrian.hunter, mark.rutland, ulf.hansson On Fri, Jan 11, 2019 at 04:38:47PM +0530, Faiz Abbas wrote: > From: Chunyan Zhang <zhang.chunyan@linaro.org> The subject is misleading. You are adding new properties, not just changing an example. > > sdhci-omap can support both external dma controller via dmaengine > framework as well as ADMA which standard SD host controller > provides. > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- > Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 7 +++++++ > 1 file changed, 7 insertions(+) > > diff --git a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt > index 72c4dec7e1db..f8914a2a2aec 100644 > --- a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt > +++ b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt > @@ -14,6 +14,11 @@ Required properties: > "ddr_1_8v-rev11", "ddr_1_8v" or "ddr_3_3v", "hs200_1_8v-rev11", > "hs200_1_8v", > - pinctrl-<n> : Pinctrl states as described in bindings/pinctrl/pinctrl-bindings.txt > +- dmas: List of DMA specifiers with the controller specific format as described > + in the generic DMA client binding. A tx and rx specifier is required. > +- dma-names: List of DMA request names. These strings correspond 1:1 with the > + DMA specifiers listed in dmas. The string naming is to be "rx" > + and "tx" for RX and TX DMA requests, respectively. The ordering of rx and tx should be defined and you have a mixture. > > Example: > mmc1: mmc@4809c000 { > @@ -22,4 +27,6 @@ Example: > ti,hwmods = "mmc1"; > bus-width = <4>; > vmmc-supply = <&vmmc>; /* phandle to regulator node */ > + dmas = <&sdma 61 &sdma 62>; > + dma-names = "tx", "rx"; > }; > -- > 2.19.2 > ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH] dt-bindings: sdhci-omap: Add properties for using external dma 2019-01-21 23:20 ` Rob Herring @ 2019-01-22 8:47 ` Chunyan Zhang 2019-01-22 10:20 ` Faiz Abbas 0 siblings, 1 reply; 26+ messages in thread From: Chunyan Zhang @ 2019-01-22 8:47 UTC (permalink / raw) To: Ulf Hansson, Adrian Hunter, Faiz Abbas Cc: linux-mmc, linux-kernel, Arnd Bergmann, Mark Brown, Kishon Vijay Abraham I, Sekhar Nori, Chunyan Zhang sdhci-omap can support both external dma controller via dmaengine framework as well as ADMA which standard SD host controller provides. Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt index 72c4dec7e1db..4485dbceb373 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt @@ -14,6 +14,11 @@ Required properties: "ddr_1_8v-rev11", "ddr_1_8v" or "ddr_3_3v", "hs200_1_8v-rev11", "hs200_1_8v", - pinctrl-<n> : Pinctrl states as described in bindings/pinctrl/pinctrl-bindings.txt +- dmas: List of DMA specifiers with the controller specific format as described + in the generic DMA client binding. A tx and rx specifier is required. +- dma-names: List of DMA request names. These strings correspond 1:1 with the + DMA specifiers listed in dmas. The string naming is to be "tx" + and "rx" for TX and RX DMA requests, respectively. Example: mmc1: mmc@4809c000 { @@ -22,4 +27,6 @@ Example: ti,hwmods = "mmc1"; bus-width = <4>; vmmc-supply = <&vmmc>; /* phandle to regulator node */ + dmas = <&sdma 61 &sdma 62>; + dma-names = "tx", "rx"; }; -- 2.17.1 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] dt-bindings: sdhci-omap: Add properties for using external dma 2019-01-22 8:47 ` [PATCH] dt-bindings: sdhci-omap: Add properties " Chunyan Zhang @ 2019-01-22 10:20 ` Faiz Abbas 2019-01-23 2:44 ` Chunyan Zhang 0 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-22 10:20 UTC (permalink / raw) To: Chunyan Zhang, Ulf Hansson, Adrian Hunter Cc: linux-mmc, linux-kernel, Arnd Bergmann, Mark Brown, Kishon Vijay Abraham I, Sekhar Nori, Chunyan Zhang, Rob Herring Hi Chunyan, +Rob Herring On 22/01/19 2:17 PM, Chunyan Zhang wrote: > sdhci-omap can support both external dma controller via dmaengine > framework as well as ADMA which standard SD host controller > provides. > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- Thanks for fixing this. However, this change should be part of the series (with a change log and a version number). I will send this as a part of my series after I get some Acks on the driver patches. Thanks, Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] dt-bindings: sdhci-omap: Add properties for using external dma 2019-01-22 10:20 ` Faiz Abbas @ 2019-01-23 2:44 ` Chunyan Zhang 0 siblings, 0 replies; 26+ messages in thread From: Chunyan Zhang @ 2019-01-23 2:44 UTC (permalink / raw) To: Faiz Abbas Cc: Ulf Hansson, Adrian Hunter, linux-mmc, Linux Kernel Mailing List, Arnd Bergmann, Mark Brown, Kishon Vijay Abraham I, Sekhar Nori, Chunyan Zhang, Rob Herring On Tue, 22 Jan 2019 at 18:17, Faiz Abbas <faiz_abbas@ti.com> wrote: > > Hi Chunyan, > > +Rob Herring > > On 22/01/19 2:17 PM, Chunyan Zhang wrote: > > sdhci-omap can support both external dma controller via dmaengine > > framework as well as ADMA which standard SD host controller > > provides. > > > > Signed-off-by: Chunyan Zhang <zhang.chunyan@linaro.org> > > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > > --- > > Thanks for fixing this. However, this change should be part of the Right, I actually used "--in-reply-to" with the parameter of Message-ID of Rob's mail, but it seems not work as I expected that it was expected a reply to Rob's mail for review. > series (with a change log and a version number). I will send this as a > part of my series after I get some Acks on the driver patches. Ok, thanks. Chunyan > > Thanks, > Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas ` (2 preceding siblings ...) 2019-01-11 11:08 ` [PATCH 3/7] dt-bindings: sdhci-omap: Add example for " Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-24 12:08 ` Adrian Hunter 2019-01-11 11:08 ` [PATCH 5/7] mmc: sdhci-omap: Add DISABLE_DTO_FOR_ERASE Quirk Faiz Abbas ` (3 subsequent siblings) 7 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas Some controllers might prematurely issue a data timeout during an erase command. Add a quirk to disable the interrupt when an erase command is issued. Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- drivers/mmc/host/sdhci.c | 8 ++++++++ drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4a9044c06e21..cfd716aee552 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1560,6 +1560,14 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) /* Initially, a command has no error */ cmd->error = 0; + if (cmd->opcode == MMC_ERASE && + (host->quirks2 & SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE)) { + mask = sdhci_readl(host, SDHCI_INT_ENABLE); + mask &= ~SDHCI_INT_DATA_TIMEOUT; + sdhci_writel(host, mask, SDHCI_INT_ENABLE); + sdhci_writel(host, mask, SDHCI_SIGNAL_ENABLE); + } + if ((host->quirks2 & SDHCI_QUIRK2_STOP_WITH_TC) && cmd->opcode == MMC_STOP_TRANSMISSION) cmd->flags |= MMC_RSP_BUSY; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 7a52823ebef4..d0c6d4fe5371 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -479,6 +479,8 @@ struct sdhci_host { * block count. */ #define SDHCI_QUIRK2_USE_32BIT_BLK_CNT (1<<18) +/* Controller needs to disable DTO for erase command */ +#define SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE (1<<19) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command 2019-01-11 11:08 ` [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command Faiz Abbas @ 2019-01-24 12:08 ` Adrian Hunter 2019-01-28 11:19 ` Faiz Abbas 0 siblings, 1 reply; 26+ messages in thread From: Adrian Hunter @ 2019-01-24 12:08 UTC (permalink / raw) To: Faiz Abbas, linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, mark.rutland, robh+dt, ulf.hansson On 11/01/19 1:08 PM, Faiz Abbas wrote: > Some controllers might prematurely issue a data timeout during an erase > command. Add a quirk to disable the interrupt when an erase command is > issued. I might have already asked this, but would it be possible to use the existing SDHCI_QUIRK2_DISABLE_HW_TIMEOUT quirk? > > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- > drivers/mmc/host/sdhci.c | 8 ++++++++ > drivers/mmc/host/sdhci.h | 2 ++ > 2 files changed, 10 insertions(+) > > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > index 4a9044c06e21..cfd716aee552 100644 > --- a/drivers/mmc/host/sdhci.c > +++ b/drivers/mmc/host/sdhci.c > @@ -1560,6 +1560,14 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) > /* Initially, a command has no error */ > cmd->error = 0; > > + if (cmd->opcode == MMC_ERASE && > + (host->quirks2 & SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE)) { > + mask = sdhci_readl(host, SDHCI_INT_ENABLE); > + mask &= ~SDHCI_INT_DATA_TIMEOUT; > + sdhci_writel(host, mask, SDHCI_INT_ENABLE); > + sdhci_writel(host, mask, SDHCI_SIGNAL_ENABLE); > + } > + > if ((host->quirks2 & SDHCI_QUIRK2_STOP_WITH_TC) && > cmd->opcode == MMC_STOP_TRANSMISSION) > cmd->flags |= MMC_RSP_BUSY; > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > index 7a52823ebef4..d0c6d4fe5371 100644 > --- a/drivers/mmc/host/sdhci.h > +++ b/drivers/mmc/host/sdhci.h > @@ -479,6 +479,8 @@ struct sdhci_host { > * block count. > */ > #define SDHCI_QUIRK2_USE_32BIT_BLK_CNT (1<<18) > +/* Controller needs to disable DTO for erase command */ > +#define SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE (1<<19) > > int irq; /* Device IRQ */ > void __iomem *ioaddr; /* Mapped address */ > ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command 2019-01-24 12:08 ` Adrian Hunter @ 2019-01-28 11:19 ` Faiz Abbas 0 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-28 11:19 UTC (permalink / raw) To: Adrian Hunter, linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, mark.rutland, robh+dt, ulf.hansson Hi Adrian, On 24/01/19 5:38 PM, Adrian Hunter wrote: > On 11/01/19 1:08 PM, Faiz Abbas wrote: >> Some controllers might prematurely issue a data timeout during an erase >> command. Add a quirk to disable the interrupt when an erase command is >> issued. > > I might have already asked this, but would it be possible to use the > existing SDHCI_QUIRK2_DISABLE_HW_TIMEOUT quirk? This looks promising. Let me look into this. In any case, I should probably move this to something like sdhci_set_timeout and use sdhci_set_data_timeout_irq(). I was following 93caf8e69eac ("omap_hsmmc: add erase capability") for adding this. Thanks, Faiz > >> >> Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> >> --- >> drivers/mmc/host/sdhci.c | 8 ++++++++ >> drivers/mmc/host/sdhci.h | 2 ++ >> 2 files changed, 10 insertions(+) >> >> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c >> index 4a9044c06e21..cfd716aee552 100644 >> --- a/drivers/mmc/host/sdhci.c >> +++ b/drivers/mmc/host/sdhci.c >> @@ -1560,6 +1560,14 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) >> /* Initially, a command has no error */ >> cmd->error = 0; >> >> + if (cmd->opcode == MMC_ERASE && >> + (host->quirks2 & SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE)) { >> + mask = sdhci_readl(host, SDHCI_INT_ENABLE); >> + mask &= ~SDHCI_INT_DATA_TIMEOUT; >> + sdhci_writel(host, mask, SDHCI_INT_ENABLE); >> + sdhci_writel(host, mask, SDHCI_SIGNAL_ENABLE); >> + } >> + >> if ((host->quirks2 & SDHCI_QUIRK2_STOP_WITH_TC) && >> cmd->opcode == MMC_STOP_TRANSMISSION) >> cmd->flags |= MMC_RSP_BUSY; >> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h >> index 7a52823ebef4..d0c6d4fe5371 100644 >> --- a/drivers/mmc/host/sdhci.h >> +++ b/drivers/mmc/host/sdhci.h >> @@ -479,6 +479,8 @@ struct sdhci_host { >> * block count. >> */ >> #define SDHCI_QUIRK2_USE_32BIT_BLK_CNT (1<<18) >> +/* Controller needs to disable DTO for erase command */ >> +#define SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE (1<<19) >> >> int irq; /* Device IRQ */ >> void __iomem *ioaddr; /* Mapped address */ >> > ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 5/7] mmc: sdhci-omap: Add DISABLE_DTO_FOR_ERASE Quirk 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas ` (3 preceding siblings ...) 2019-01-11 11:08 ` [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-11 11:08 ` [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings Faiz Abbas ` (2 subsequent siblings) 7 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas Add a Quirk to disable DTO during an erase command. Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- drivers/mmc/host/sdhci-omap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-omap.c b/drivers/mmc/host/sdhci-omap.c index 8a05e94fe612..ae9701d198d5 100644 --- a/drivers/mmc/host/sdhci-omap.c +++ b/drivers/mmc/host/sdhci-omap.c @@ -852,6 +852,7 @@ static const struct sdhci_pltfm_data sdhci_omap_pdata = { .quirks2 = SDHCI_QUIRK2_ACMD23_BROKEN | SDHCI_QUIRK2_PRESET_VALUE_BROKEN | SDHCI_QUIRK2_RSP_136_HAS_CRC | + SDHCI_QUIRK2_DISABLE_DTO_FOR_ERASE | SDHCI_QUIRK2_DISABLE_HW_TIMEOUT, .ops = &sdhci_omap_ops, }; -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas ` (4 preceding siblings ...) 2019-01-11 11:08 ` [PATCH 5/7] mmc: sdhci-omap: Add DISABLE_DTO_FOR_ERASE Quirk Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-21 23:21 ` Rob Herring 2019-01-11 11:08 ` [PATCH 7/7] mmc: sdhci-omap: Add am335x and am437x specific compatibles Faiz Abbas 2019-01-24 9:44 ` [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas 7 siblings, 1 reply; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas Add binding for the TI's sdhci-omap controller present in am335x and am437x devices. Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt index f8914a2a2aec..dd4dbda32ca0 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-omap.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-omap.txt @@ -7,6 +7,8 @@ For UHS devices which require tuning, the device tree should have a "cpu_thermal Required properties: - compatible: Should be "ti,dra7-sdhci" for DRA7 and DRA72 controllers Should be "ti,k2g-sdhci" for K2G + Should be "ti,am335-sdhci" for am335x controllers + Should be "ti,am437-sdhci" for am437x controllers - ti,hwmods: Must be "mmc<n>", <n> is controller instance starting 1 (Not required for K2G). - pinctrl-names: Should be subset of "default", "hs", "sdr12", "sdr25", "sdr50", -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings 2019-01-11 11:08 ` [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings Faiz Abbas @ 2019-01-21 23:21 ` Rob Herring 0 siblings, 0 replies; 26+ messages in thread From: Rob Herring @ 2019-01-21 23:21 UTC (permalink / raw) To: Faiz Abbas Cc: linux-kernel, devicetree, linux-mmc, zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas On Fri, 11 Jan 2019 16:38:50 +0530, Faiz Abbas wrote: > Add binding for the TI's sdhci-omap controller present in am335x and > am437x devices. > > Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> > --- > Documentation/devicetree/bindings/mmc/sdhci-omap.txt | 2 ++ > 1 file changed, 2 insertions(+) > Reviewed-by: Rob Herring <robh@kernel.org> ^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH 7/7] mmc: sdhci-omap: Add am335x and am437x specific compatibles 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas ` (5 preceding siblings ...) 2019-01-11 11:08 ` [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings Faiz Abbas @ 2019-01-11 11:08 ` Faiz Abbas 2019-01-24 9:44 ` [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas 7 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-11 11:08 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson, faiz_abbas Add support for new compatible for TI's am335x and am437x devices. Signed-off-by: Faiz Abbas <faiz_abbas@ti.com> --- drivers/mmc/host/sdhci-omap.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mmc/host/sdhci-omap.c b/drivers/mmc/host/sdhci-omap.c index ae9701d198d5..2e246b5b744c 100644 --- a/drivers/mmc/host/sdhci-omap.c +++ b/drivers/mmc/host/sdhci-omap.c @@ -861,6 +861,14 @@ static const struct sdhci_omap_data k2g_data = { .offset = 0x200, }; +static const struct sdhci_omap_data am335_data = { + .offset = 0x200, +}; + +static const struct sdhci_omap_data am437_data = { + .offset = 0x200, +}; + static const struct sdhci_omap_data dra7_data = { .offset = 0x200, .flags = SDHCI_OMAP_REQUIRE_IODELAY, @@ -869,6 +877,8 @@ static const struct sdhci_omap_data dra7_data = { static const struct of_device_id omap_sdhci_match[] = { { .compatible = "ti,dra7-sdhci", .data = &dra7_data }, { .compatible = "ti,k2g-sdhci", .data = &k2g_data }, + { .compatible = "ti,am335-sdhci", .data = &am335_data }, + { .compatible = "ti,am437-sdhci", .data = &am437_data }, {}, }; MODULE_DEVICE_TABLE(of, omap_sdhci_match); -- 2.19.2 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH 0/7] Port am335 and am437 devices to sdhci-omap 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas ` (6 preceding siblings ...) 2019-01-11 11:08 ` [PATCH 7/7] mmc: sdhci-omap: Add am335x and am437x specific compatibles Faiz Abbas @ 2019-01-24 9:44 ` Faiz Abbas 7 siblings, 0 replies; 26+ messages in thread From: Faiz Abbas @ 2019-01-24 9:44 UTC (permalink / raw) To: linux-kernel, devicetree, linux-mmc Cc: zhang.chunyan, kishon, adrian.hunter, mark.rutland, robh+dt, ulf.hansson Hi, On 11/01/19 4:38 PM, Faiz Abbas wrote: > The following add driver patches for porting TI's am335x and am437x > devices to the sdhci-omap driver. > > This involves adding external DMA support to sdhci (first 3 patches from > Chunyan) plus some miscellaneous patches to take care of deviations of > the controllers from the sdhci model. > > DT changes will be posted in a separate series. > > Untested versions of Chunyan's patches were posted before[1]. > > Tested on: am335x-evm, am335x-boneblack, am335x-sk, am437x-gpevm, > am43xx-gpevm, am437x-idk, dra7xx-evm, dra72x-evm > > [1] https://patchwork.kernel.org/project/linux-mmc/list/?series=54897 > > Chunyan Zhang (3): > mmc: sdhci: add support for using external DMA devices > mmc: sdhci-omap: Add using external dma > dt-bindings: sdhci-omap: Add example for using external dma > > Faiz Abbas (4): > mmc: sdhci: Add quirk for disabling DTO during erase command > mmc: sdhci_omap: Add DISABLE_DTO_FOR_ERASE Quirk > dt-bindings: sdhci-omap: Add am335x and am437x specific bindings > mmc: sdhci-omap: Add am335x and am437x specific compatibles > > .../devicetree/bindings/mmc/sdhci-omap.txt | 9 + > drivers/mmc/host/Kconfig | 4 + > drivers/mmc/host/sdhci-omap.c | 27 +- > drivers/mmc/host/sdhci.c | 274 +++++++++++++++++- > drivers/mmc/host/sdhci.h | 10 + > 5 files changed, 319 insertions(+), 5 deletions(-) > Gentle ping. Thanks, Faiz ^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2019-02-11 12:21 UTC | newest] Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-01-11 11:08 [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas 2019-01-11 11:08 ` [PATCH 1/7] mmc: sdhci: add support for using external DMA devices Faiz Abbas 2019-01-14 8:45 ` Adrian Hunter 2019-01-24 11:40 ` Adrian Hunter 2019-01-28 10:20 ` Chunyan Zhang 2019-01-28 10:43 ` Faiz Abbas 2019-01-28 11:46 ` Chunyan Zhang 2019-01-29 11:53 ` Faiz Abbas 2019-01-30 5:01 ` Chunyan Zhang 2019-01-30 11:05 ` Chunyan Zhang 2019-01-31 12:32 ` Faiz Abbas 2019-02-11 12:23 ` Faiz Abbas 2019-01-11 11:08 ` [PATCH 2/7] mmc: sdhci-omap: Add using external dma Faiz Abbas 2019-01-11 11:08 ` [PATCH 3/7] dt-bindings: sdhci-omap: Add example for " Faiz Abbas 2019-01-21 23:20 ` Rob Herring 2019-01-22 8:47 ` [PATCH] dt-bindings: sdhci-omap: Add properties " Chunyan Zhang 2019-01-22 10:20 ` Faiz Abbas 2019-01-23 2:44 ` Chunyan Zhang 2019-01-11 11:08 ` [PATCH 4/7] mmc: sdhci: Add quirk for disabling DTO during erase command Faiz Abbas 2019-01-24 12:08 ` Adrian Hunter 2019-01-28 11:19 ` Faiz Abbas 2019-01-11 11:08 ` [PATCH 5/7] mmc: sdhci-omap: Add DISABLE_DTO_FOR_ERASE Quirk Faiz Abbas 2019-01-11 11:08 ` [PATCH 6/7] dt-bindings: sdhci-omap: Add am335x and am437x specific bindings Faiz Abbas 2019-01-21 23:21 ` Rob Herring 2019-01-11 11:08 ` [PATCH 7/7] mmc: sdhci-omap: Add am335x and am437x specific compatibles Faiz Abbas 2019-01-24 9:44 ` [PATCH 0/7] Port am335 and am437 devices to sdhci-omap Faiz Abbas
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).