The mmci host driver implementation for double buffering is not intended nor ready for mainline yet. It is only an example of how to implement pre_req() and post_req(). The reason for this is that the basic DMA support for MMCI is not complete yet.
This patch series depends on "[PATCH 0/5] mmc: add double buffering for mmc block requests"
Linus Walleij (1): mmci: add PrimeCell generic DMA to MMCI/PL180
Per Forlin (1): ARM: mmci: add support for double buffering
Ulf Hansson (2): mmci: fixup broken_blockend variant patch MMCI: Corrections for DMA
drivers/mmc/host/mmci.c | 538 ++++++++++++++++++++++++++++++++++++++------- drivers/mmc/host/mmci.h | 21 ++- include/linux/amba/mmci.h | 16 ++ 3 files changed, 496 insertions(+), 79 deletions(-)
From: Ulf Hansson ulf.hansson@stericsson.com
NOT to be mainlined, only sets the base for the double buffer example implementation. The DMA implemenation for MMCI is under development.
host->last_blockend flag is now set only for the last MCI_DATABLOCKEND, the previous code would just react to any blockend, which was buggy. Consolidate Ux500 and U300 bug flags to a single one and use only the MCI_DATAEND irq on U300 as well, this is faster anyway.
Also make sure the MCI_DATABLOCKENDMASK is set only when needed, instead of being set always and then masked off.
Tested successfully on Ux500, U300 and ARM RealView PB1176. --- drivers/mmc/host/mmci.c | 45 ++++++++++++++++++++++----------------------- drivers/mmc/host/mmci.h | 4 ++-- 2 files changed, 24 insertions(+), 25 deletions(-)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 5630228..aafede4 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -48,8 +48,6 @@ static unsigned int fmax = 515633; * is asserted (likewise for RX) * @broken_blockend: the MCI_DATABLOCKEND is broken on the hardware * and will not work at all. - * @broken_blockend_dma: the MCI_DATABLOCKEND is broken on the hardware when - * using DMA. * @sdio: variant supports SDIO * @st_clkdiv: true if using a ST-specific clock divider algorithm */ @@ -60,7 +58,6 @@ struct variant_data { unsigned int fifosize; unsigned int fifohalfsize; bool broken_blockend; - bool broken_blockend_dma; bool sdio; bool st_clkdiv; }; @@ -76,7 +73,7 @@ static struct variant_data variant_u300 = { .fifohalfsize = 8 * 4, .clkreg_enable = 1 << 13, /* HWFCEN */ .datalength_bits = 16, - .broken_blockend_dma = true, + .broken_blockend = true, .sdio = true, };
@@ -199,7 +196,7 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data) static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) { struct variant_data *variant = host->variant; - unsigned int datactrl, timeout, irqmask; + unsigned int datactrl, timeout, irqmask0, irqmask1; unsigned long long clks; void __iomem *base; int blksz_bits; @@ -210,7 +207,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) host->data = data; host->size = data->blksz * data->blocks; host->data_xfered = 0; - host->blockend = false; + host->last_blockend = false; host->dataend = false;
mmci_init_sg(host, data); @@ -230,20 +227,20 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) datactrl = MCI_DPSM_ENABLE | blksz_bits << 4; if (data->flags & MMC_DATA_READ) { datactrl |= MCI_DPSM_DIRECTION; - irqmask = MCI_RXFIFOHALFFULLMASK; + irqmask1 = MCI_RXFIFOHALFFULLMASK;
/* * If we have less than a FIFOSIZE of bytes to transfer, * trigger a PIO interrupt as soon as any data is available. */ if (host->size < variant->fifosize) - irqmask |= MCI_RXDATAAVLBLMASK; + irqmask1 |= MCI_RXDATAAVLBLMASK; } else { /* * We don't actually need to include "FIFO empty" here * since its implicit in "FIFO half empty". */ - irqmask = MCI_TXFIFOHALFEMPTYMASK; + irqmask1 = MCI_TXFIFOHALFEMPTYMASK; }
/* The ST Micro variants has a special bit to enable SDIO */ @@ -252,8 +249,14 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) datactrl |= MCI_ST_DPSM_SDIOEN;
writel(datactrl, base + MMCIDATACTRL); - writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); - mmci_set_mask1(host, irqmask); + irqmask0 = readl(base + MMCIMASK0); + if (variant->broken_blockend) + irqmask0 &= ~MCI_DATABLOCKENDMASK; + else + irqmask0 |= MCI_DATABLOCKENDMASK; + irqmask0 &= ~MCI_DATAENDMASK; + writel(irqmask0, base + MMCIMASK0); + mmci_set_mask1(host, irqmask1); }
static void @@ -301,7 +304,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, data->error = -EIO;
/* Force-complete the transaction */ - host->blockend = true; + host->last_blockend = true; host->dataend = true;
/* @@ -337,7 +340,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, * * In the U300, the IRQs can arrive out-of-order, * e.g. MCI_DATABLOCKEND sometimes arrives after MCI_DATAEND, - * so for this case we use the flags "blockend" and + * so for this case we use the flags "last_blockend" and * "dataend" to make sure both IRQs have arrived before * concluding the transaction. (This does not apply * to the Ux500 which doesn't fire MCI_DATABLOCKEND @@ -353,7 +356,8 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, */ if (!variant->broken_blockend) host->data_xfered += data->blksz; - host->blockend = true; + if (host->data_xfered == data->blksz * data->blocks) + host->last_blockend = true; }
if (status & MCI_DATAEND) @@ -364,11 +368,12 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, * on others we must sync with the blockend signal since they can * appear out-of-order. */ - if (host->dataend && (host->blockend || variant->broken_blockend)) { + if (host->dataend && + (host->last_blockend || variant->broken_blockend)) { mmci_stop_data(host);
/* Reset these flags */ - host->blockend = false; + host->last_blockend = false; host->dataend = false;
/* @@ -770,7 +775,6 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) struct variant_data *variant = id->data; struct mmci_host *host; struct mmc_host *mmc; - unsigned int mask; int ret;
/* must have platform data */ @@ -951,12 +955,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) goto irq0_free; }
- mask = MCI_IRQENABLE; - /* Don't use the datablockend flag if it's broken */ - if (variant->broken_blockend) - mask &= ~MCI_DATABLOCKEND; - - writel(mask, host->base + MMCIMASK0); + writel(MCI_IRQENABLE, host->base + MMCIMASK0);
amba_set_drvdata(dev, mmc);
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index df06f01..7ac8c4d 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -137,7 +137,7 @@ #define MCI_IRQENABLE \ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \ - MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATABLOCKENDMASK) + MCI_CMDRESPENDMASK|MCI_CMDSENTMASK)
/* These interrupts are directed to IRQ1 when two IRQ lines are available */ #define MCI_IRQ1MASK \ @@ -177,7 +177,7 @@ struct mmci_host { struct timer_list timer; unsigned int oldstat;
- bool blockend; + bool last_blockend; bool dataend;
/* pio stuff */
From: Linus Walleij linus.walleij@stericsson.com
NOT to be mainlined, only sets the base for the double buffer example implementation. The DMA implemenation for MMCI is under development.
This extends the MMCI/PL180 driver with generic DMA engine support using the PrimeCell DMA engine interface. --- drivers/mmc/host/mmci.c | 264 +++++++++++++++++++++++++++++++++++++++++++-- drivers/mmc/host/mmci.h | 13 +++ include/linux/amba/mmci.h | 16 +++ 3 files changed, 282 insertions(+), 11 deletions(-)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index aafede4..38fcbde 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -2,7 +2,7 @@ * linux/drivers/mmc/host/mmci.c - ARM PrimeCell MMCI PL180/1 driver * * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. - * Copyright (C) 2010 ST-Ericsson AB. + * Copyright (C) 2010 ST-Ericsson SA * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -24,8 +24,10 @@ #include <linux/clk.h> #include <linux/scatterlist.h> #include <linux/gpio.h> -#include <linux/amba/mmci.h> #include <linux/regulator/consumer.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/amba/mmci.h>
#include <asm/div64.h> #include <asm/io.h> @@ -41,11 +43,15 @@ static unsigned int fmax = 515633; * struct variant_data - MMCI variant-specific quirks * @clkreg: default value for MCICLOCK register * @clkreg_enable: enable value for MMCICLOCK register + * @dmareg_enable: enable value for MMCIDATACTRL register * @datalength_bits: number of bits in the MMCIDATALENGTH register * @fifosize: number of bytes that can be written when MMCI_TXFIFOEMPTY * is asserted (likewise for RX) * @fifohalfsize: number of bytes that can be written when MCI_TXFIFOHALFEMPTY * is asserted (likewise for RX) + * @txsize_threshold: Sets DMA burst size to minimal if transfer size is + * less or equal to this threshold. This shall be specified in + * number of bytes. Set 0 for no burst compensation * @broken_blockend: the MCI_DATABLOCKEND is broken on the hardware * and will not work at all. * @sdio: variant supports SDIO @@ -54,9 +60,11 @@ static unsigned int fmax = 515633; struct variant_data { unsigned int clkreg; unsigned int clkreg_enable; + unsigned int dmareg_enable; unsigned int datalength_bits; unsigned int fifosize; unsigned int fifohalfsize; + unsigned int txsize_threshold; bool broken_blockend; bool sdio; bool st_clkdiv; @@ -80,8 +88,10 @@ static struct variant_data variant_u300 = { static struct variant_data variant_ux500 = { .fifosize = 30 * 4, .fifohalfsize = 8 * 4, + .txsize_threshold = 16, .clkreg = MCI_CLK_ENABLE, .clkreg_enable = 1 << 14, /* HWFCEN */ + .dmareg_enable = 1 << 12, /* DMAREQCTRL */ .datalength_bits = 24, .broken_blockend = true, .sdio = true, @@ -193,6 +203,209 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data) sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); }
+/* + * All the DMA operation mode stuff goes inside this ifdef. + * This assumes that you have a generic DMA device interface, + * no custom DMA interfaces are supported. + */ +#ifdef CONFIG_DMA_ENGINE +static void __devinit mmci_setup_dma(struct mmci_host *host) +{ + struct mmci_platform_data *plat = host->plat; + dma_cap_mask_t mask; + + if (!plat || !plat->dma_filter) { + dev_err(mmc_dev(host->mmc), "no DMA platform data!\n"); + return; + } + + /* Try to acquire a generic DMA engine slave channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + /* + * If only an RX channel is specified, the driver will + * attempt to use it bidirectionally, however if it is + * is specified but cannot be located, DMA will be disabled. + */ + host->dma_rx_channel = dma_request_channel(mask, + plat->dma_filter, + plat->dma_rx_param); + /* E.g if no DMA hardware is present */ + if (!host->dma_rx_channel) { + dev_err(mmc_dev(host->mmc), "no RX DMA channel!\n"); + return; + } + if (plat->dma_tx_param) { + host->dma_tx_channel = dma_request_channel(mask, + plat->dma_filter, + plat->dma_tx_param); + if (!host->dma_tx_channel) { + dma_release_channel(host->dma_rx_channel); + host->dma_rx_channel = NULL; + return; + } + } else { + host->dma_tx_channel = host->dma_rx_channel; + } + host->dma_enable = true; + dev_info(mmc_dev(host->mmc), "use DMA channels DMA RX %s, DMA TX %s\n", + dma_chan_name(host->dma_rx_channel), + dma_chan_name(host->dma_tx_channel)); +} + +/* + * This is used in __devinit or __devexit so inline it + * so it can be discarded. + */ +static inline void mmci_disable_dma(struct mmci_host *host) +{ + if (host->dma_rx_channel) + dma_release_channel(host->dma_rx_channel); + if (host->dma_tx_channel) + dma_release_channel(host->dma_tx_channel); + host->dma_enable = false; +} + +static void mmci_dma_data_end(struct mmci_host *host) +{ + struct mmc_data *data = host->data; + + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + (data->flags & MMC_DATA_WRITE) + ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + host->dma_on_current_xfer = false; +} + +static void mmci_dma_terminate(struct mmci_host *host) +{ + struct mmc_data *data = host->data; + struct dma_chan *chan; + + dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n"); + if (data->flags & MMC_DATA_READ) + chan = host->dma_rx_channel; + else + chan = host->dma_tx_channel; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + (data->flags & MMC_DATA_WRITE) + ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); +} + +static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +{ + struct variant_data *variant = host->variant; + struct dma_slave_config rx_conf = { + .src_addr = host->phybase + MMCIFIFO, + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .direction = DMA_FROM_DEVICE, + .src_maxburst = variant->fifohalfsize >> 2, /* # of words */ + }; + struct dma_slave_config tx_conf = { + .dst_addr = host->phybase + MMCIFIFO, + .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .direction = DMA_TO_DEVICE, + .dst_maxburst = variant->fifohalfsize >> 2, /* # of words */ + }; + struct mmc_data *data = host->data; + enum dma_data_direction direction; + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist *sg; + dma_cookie_t cookie; + int i; + + datactrl |= MCI_DPSM_DMAENABLE; + datactrl |= variant->dmareg_enable; + + if (data->flags & MMC_DATA_READ) { + if (host->size <= variant->txsize_threshold) + rx_conf.src_maxburst = 1; + + direction = DMA_FROM_DEVICE; + chan = host->dma_rx_channel; + chan->device->device_control(chan, DMA_SLAVE_CONFIG, + (unsigned long) &rx_conf); + } else { + if (host->size <= variant->txsize_threshold) + tx_conf.dst_maxburst = 1; + + direction = DMA_TO_DEVICE; + chan = host->dma_tx_channel; + chan->device->device_control(chan, DMA_SLAVE_CONFIG, + (unsigned long) &tx_conf); + } + + /* Check for weird stuff in the sg list */ + for_each_sg(data->sg, sg, data->sg_len, i) { + dev_vdbg(mmc_dev(host->mmc), "MMCI SGlist %d dir %d: length: %08x\n", + i, direction, sg->length); + if (sg->offset & 3 || sg->length & 3) + return -EINVAL; + } + + dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, direction); + + desc = chan->device->device_prep_slave_sg(chan, + data->sg, data->sg_len, direction, + DMA_CTRL_ACK); + if (!desc) + goto unmap_exit; + + host->dma_desc = desc; + dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d " + "blksz %04x blks %04x flags %08x\n", + data->sg_len, data->blksz, data->blocks, data->flags); + cookie = desc->tx_submit(desc); + + /* Here overloaded DMA controllers may fail */ + if (dma_submit_error(cookie)) + goto unmap_exit; + + host->dma_on_current_xfer = true; + chan->device->device_issue_pending(chan); + + /* Trigger the DMA transfer */ + writel(datactrl, host->base + MMCIDATACTRL); + /* + * Let the MMCI say when the data is ended and it's time + * to fire next DMA request. When that happens, MMCI will + * call mmci_data_end() + */ + writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK, + host->base + MMCIMASK0); + return 0; + +unmap_exit: + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); + return -ENOMEM; +} +#else +/* Blank functions if the DMA engine is not available */ +static inline void mmci_setup_dma(struct mmci_host *host) +{ +} + +static inline void mmci_disable_dma(struct mmci_host *host) +{ +} + +static inline void mmci_dma_data_end(struct mmci_host *host) +{ +} + +static inline void mmci_dma_terminate(struct mmci_host *host) +{ +} + +static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +{ + return -ENOSYS; +} +#endif + static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) { struct variant_data *variant = host->variant; @@ -210,8 +423,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) host->last_blockend = false; host->dataend = false;
- mmci_init_sg(host, data); - clks = (unsigned long long)data->timeout_ns * host->cclk; do_div(clks, 1000000000UL);
@@ -225,13 +436,32 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) BUG_ON(1 << blksz_bits != data->blksz);
datactrl = MCI_DPSM_ENABLE | blksz_bits << 4; - if (data->flags & MMC_DATA_READ) { + + if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + + if (host->dma_enable) { + int ret; + + /* + * Attempt to use DMA operation mode, if this + * should fail, fall back to PIO mode + */ + ret = mmci_dma_start_data(host, datactrl); + if (!ret) + return; + } + + /* IRQ mode, map the SG list for CPU reading/writing */ + mmci_init_sg(host, data); + + if (data->flags & MMC_DATA_READ) { irqmask1 = MCI_RXFIFOHALFFULLMASK;
/* - * If we have less than a FIFOSIZE of bytes to transfer, - * trigger a PIO interrupt as soon as any data is available. + * If we have less than a FIFOSIZE of bytes to + * transfer, trigger a PIO interrupt as soon as any + * data is available. */ if (host->size < variant->fifosize) irqmask1 |= MCI_RXDATAAVLBLMASK; @@ -309,9 +539,12 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
/* * We hit an error condition. Ensure that any data - * partially written to a page is properly coherent. + * partially written to a page is properly coherent, + * unless we're using DMA. */ - if (data->flags & MMC_DATA_READ) { + if (host->dma_on_current_xfer) + mmci_dma_terminate(host); + else if (data->flags & MMC_DATA_READ) { struct sg_mapping_iter *sg_miter = &host->sg_miter; unsigned long flags;
@@ -354,7 +587,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, * flag is unreliable: since it can stay high between * IRQs it will corrupt the transfer counter. */ - if (!variant->broken_blockend) + if (!variant->broken_blockend && !host->dma_on_current_xfer) host->data_xfered += data->blksz; if (host->data_xfered == data->blksz * data->blocks) host->last_blockend = true; @@ -370,6 +603,7 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, */ if (host->dataend && (host->last_blockend || variant->broken_blockend)) { + mmci_dma_data_end(host); mmci_stop_data(host);
/* Reset these flags */ @@ -411,8 +645,11 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, }
if (!cmd->data || cmd->error) { - if (host->data) + if (host->data) { + if (host->dma_on_current_xfer) + mmci_dma_terminate(host); mmci_stop_data(host); + } mmci_request_end(host, cmd->mrq); } else if (!(cmd->data->flags & MMC_DATA_READ)) { mmci_start_data(host, cmd->data); @@ -832,6 +1069,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n", host->mclk); } + host->phybase = dev->res.start; host->base = ioremap(dev->res.start, resource_size(&dev->res)); if (!host->base) { ret = -ENOMEM; @@ -942,6 +1180,8 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) && host->gpio_cd_irq < 0) mmc->caps |= MMC_CAP_NEEDS_POLL;
+ mmci_setup_dma(host); + ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host); if (ret) goto unmap; @@ -970,6 +1210,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) irq0_free: free_irq(dev->irq[0], host); unmap: + mmci_disable_dma(host); if (host->gpio_wp != -ENOSYS) gpio_free(host->gpio_wp); err_gpio_wp: @@ -1008,6 +1249,7 @@ static int __devexit mmci_remove(struct amba_device *dev) writel(0, host->base + MMCICOMMAND); writel(0, host->base + MMCIDATACTRL);
+ mmci_disable_dma(host); free_irq(dev->irq[0], host); if (!host->singleirq) free_irq(dev->irq[1], host); diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 7ac8c4d..39b7ac7 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -148,8 +148,11 @@
struct clk; struct variant_data; +struct dma_chan; +struct dma_async_tx_descriptor;
struct mmci_host { + phys_addr_t phybase; void __iomem *base; struct mmc_request *mrq; struct mmc_command *cmd; @@ -183,6 +186,16 @@ struct mmci_host { /* pio stuff */ struct sg_mapping_iter sg_miter; unsigned int size; + struct regulator *vcc; + + /* DMA stuff */ + bool dma_enable; + bool dma_on_current_xfer; +#ifdef CONFIG_DMA_ENGINE + struct dma_chan *dma_rx_channel; + struct dma_chan *dma_tx_channel; + struct dma_async_tx_descriptor *dma_desc; +#endif };
diff --git a/include/linux/amba/mmci.h b/include/linux/amba/mmci.h index f4ee9ac..dbeba68 100644 --- a/include/linux/amba/mmci.h +++ b/include/linux/amba/mmci.h @@ -6,6 +6,8 @@
#include <linux/mmc/host.h>
+/* Just some dummy forwarding */ +struct dma_chan; /** * struct mmci_platform_data - platform configuration for the MMCI * (also known as PL180) block. @@ -27,6 +29,17 @@ * @cd_invert: true if the gpio_cd pin value is active low * @capabilities: the capabilities of the block as implemented in * this platform, signify anything MMC_CAP_* from mmc/host.h + * @dma_filter: function used to select an apropriate RX and TX + * DMA channel to be used for DMA, if and only if you're deploying the + * generic DMA engine + * @dma_rx_param: parameter passed to the DMA allocation + * filter in order to select an apropriate RX channel. If + * there is a bidirectional RX+TX channel, then just specify + * this and leave dma_tx_param set to NULL + * @dma_tx_param: parameter passed to the DMA allocation + * filter in order to select an apropriate TX channel. If this + * is NULL the driver will attempt to use the RX channel as a + * bidirectional channel */ struct mmci_platform_data { unsigned int f_max; @@ -38,6 +51,9 @@ struct mmci_platform_data { int gpio_cd; bool cd_invert; unsigned long capabilities; + bool (*dma_filter)(struct dma_chan *chan, void *filter_param); + void *dma_rx_param; + void *dma_tx_param; };
#endif
From: Ulf Hansson ulf.hansson@stericsson.com
NOT to be mainlined, only sets the base for the double buffer example implementation. The DMA implemenation for MMCI is under development.
Make use of DMA_PREP_INTERRUPT to get a callback when DMA has successfully completed the data transfer.
To entirely ending the transfer and request, both the DMA callback and MCI_DATAEND must occur. --- drivers/mmc/host/mmci.c | 173 +++++++++++++++++++++++++++++++---------------- 1 files changed, 114 insertions(+), 59 deletions(-)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 38fcbde..ab44f5f 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -203,6 +203,34 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data) sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); }
+static void +mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c) +{ + void __iomem *base = host->base; + + dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n", + cmd->opcode, cmd->arg, cmd->flags); + + if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) { + writel(0, base + MMCICOMMAND); + udelay(1); + } + + c |= cmd->opcode | MCI_CPSM_ENABLE; + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + c |= MCI_CPSM_LONGRSP; + c |= MCI_CPSM_RESPONSE; + } + if (/*interrupt*/0) + c |= MCI_CPSM_INTERRUPT; + + host->cmd = cmd; + + writel(cmd->arg, base + MMCIARGUMENT); + writel(c, base + MMCICOMMAND); +} + /* * All the DMA operation mode stuff goes inside this ifdef. * This assumes that you have a generic DMA device interface, @@ -290,6 +318,39 @@ static void mmci_dma_terminate(struct mmci_host *host) (data->flags & MMC_DATA_WRITE) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + host->dma_on_current_xfer = false; +} + +static void mmci_dma_callback(void *arg) +{ + unsigned long flags; + struct mmci_host *host = arg; + struct mmc_data *data; + + dev_vdbg(mmc_dev(host->mmc), "DMA transfer done!\n"); + + spin_lock_irqsave(&host->lock, flags); + + mmci_dma_data_end(host); + + /* + * Make sure MMCI has received MCI_DATAEND before + * ending the transfer and request. + */ + if (host->dataend) { + data = host->data; + mmci_stop_data(host); + + host->data_xfered += data->blksz * data->blocks; + host->dataend = false; + + if (!data->stop) + mmci_request_end(host, data->mrq); + else + mmci_start_command(host, data->stop, 0); + } + + spin_unlock_irqrestore(&host->lock, flags); }
static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) @@ -314,6 +375,8 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) struct scatterlist *sg; dma_cookie_t cookie; int i; + unsigned int irqmask0; + int sg_len;
datactrl |= MCI_DPSM_DMAENABLE; datactrl |= variant->dmareg_enable; @@ -344,15 +407,19 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) return -EINVAL; }
- dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, direction); + sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, + data->sg_len, direction); + if (!sg_len) + goto map_err;
desc = chan->device->device_prep_slave_sg(chan, - data->sg, data->sg_len, direction, - DMA_CTRL_ACK); + data->sg, sg_len, direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) goto unmap_exit;
+ desc->callback = mmci_dma_callback; + desc->callback_param = host; host->dma_desc = desc; dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d " "blksz %04x blks %04x flags %08x\n", @@ -366,20 +433,25 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) host->dma_on_current_xfer = true; chan->device->device_issue_pending(chan);
- /* Trigger the DMA transfer */ - writel(datactrl, host->base + MMCIDATACTRL); /* - * Let the MMCI say when the data is ended and it's time - * to fire next DMA request. When that happens, MMCI will - * call mmci_data_end() + * MMCI monitors both MCI_DATAEND and the DMA callback. + * Both events must occur before the transfer is considered + * to be completed. MCI_DATABLOCKEND is not used in DMA mode. */ - writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK, - host->base + MMCIMASK0); + host->last_blockend = true; + irqmask0 = readl(host->base + MMCIMASK0); + irqmask0 |= MCI_DATAENDMASK; + irqmask0 &= ~MCI_DATABLOCKENDMASK; + writel(irqmask0, host->base + MMCIMASK0); + + /* Trigger the DMA transfer */ + writel(datactrl, host->base + MMCIDATACTRL); return 0;
unmap_exit: - chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); +map_err: + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); return -ENOMEM; } #else @@ -478,43 +550,20 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (mmc_card_sdio(host->mmc->card)) datactrl |= MCI_ST_DPSM_SDIOEN;
- writel(datactrl, base + MMCIDATACTRL); + /* Setup IRQ */ irqmask0 = readl(base + MMCIMASK0); - if (variant->broken_blockend) + if (variant->broken_blockend) { + host->last_blockend = true; irqmask0 &= ~MCI_DATABLOCKENDMASK; - else + } else { irqmask0 |= MCI_DATABLOCKENDMASK; + } irqmask0 &= ~MCI_DATAENDMASK; writel(irqmask0, base + MMCIMASK0); mmci_set_mask1(host, irqmask1); -} - -static void -mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c) -{ - void __iomem *base = host->base; - - dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n", - cmd->opcode, cmd->arg, cmd->flags); - - if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) { - writel(0, base + MMCICOMMAND); - udelay(1); - }
- c |= cmd->opcode | MCI_CPSM_ENABLE; - if (cmd->flags & MMC_RSP_PRESENT) { - if (cmd->flags & MMC_RSP_136) - c |= MCI_CPSM_LONGRSP; - c |= MCI_CPSM_RESPONSE; - } - if (/*interrupt*/0) - c |= MCI_CPSM_INTERRUPT; - - host->cmd = cmd; - - writel(cmd->arg, base + MMCIARGUMENT); - writel(c, base + MMCICOMMAND); + /* Start the data transfer */ + writel(datactrl, base + MMCIDATACTRL); }
static void @@ -601,26 +650,29 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, * on others we must sync with the blockend signal since they can * appear out-of-order. */ - if (host->dataend && - (host->last_blockend || variant->broken_blockend)) { - mmci_dma_data_end(host); - mmci_stop_data(host); - - /* Reset these flags */ + if (host->dataend && host->last_blockend) { host->last_blockend = false; - host->dataend = false;
/* - * Variants with broken blockend flags need to handle the - * end of the entire transfer here. + * Make sure there is no dma transfer running before + * ending the transfer and the request. */ - if (variant->broken_blockend && !data->error) + if (!host->dma_on_current_xfer) { host->data_xfered += data->blksz * data->blocks; + mmci_stop_data(host); + host->dataend = false;
- if (!data->stop) { - mmci_request_end(host, data->mrq); - } else { - mmci_start_command(host, data->stop, 0); + /* + * Variants with broken blockend flags need to handle + * the end of the entire transfer here. + */ + if (variant->broken_blockend && !data->error) + host->data_xfered += data->blksz * data->blocks; + + if (!data->stop) + mmci_request_end(host, data->mrq); + else + mmci_start_command(host, data->stop, 0); } } } @@ -1130,10 +1182,13 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) mmc->max_req_size = (1 << variant->datalength_bits) - 1;
/* - * Set the maximum segment size. Since we aren't doing DMA - * (yet) we are only limited by the data length register. + * Set the maximum segment size. Right now DMA sets the + * limit and not the data length register. Thus until the DMA + * driver not handles this, the segment size is limited by DMA. + * DMA limit: src_addr_width x (64 KB -1). src_addr_width + * can be 1. */ - mmc->max_seg_size = mmc->max_req_size; + mmc->max_seg_size = 65535;
/* * Block size can be up to 2048 bytes, but must be a power of two.
This is an example of how to utilize the double buffer support in the mmc framework. This patch is not intended nor ready for mainline.
Implement pre_req() and post_reg() hooks. pre_req() runs dma_map_sg and prepares the dma descriptor and post_req calls dma_unmap_sg.
Signed-off-by: Per Forlin per.forlin@linaro.org --- drivers/mmc/host/mmci.c | 164 ++++++++++++++++++++++++++++++++++++----------- drivers/mmc/host/mmci.h | 4 + 2 files changed, 130 insertions(+), 38 deletions(-)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index ab44f5f..7f0b12a 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -276,6 +276,7 @@ static void __devinit mmci_setup_dma(struct mmci_host *host) host->dma_tx_channel = host->dma_rx_channel; } host->dma_enable = true; + dev_info(mmc_dev(host->mmc), "use DMA channels DMA RX %s, DMA TX %s\n", dma_chan_name(host->dma_rx_channel), dma_chan_name(host->dma_tx_channel)); @@ -296,11 +297,6 @@ static inline void mmci_disable_dma(struct mmci_host *host)
static void mmci_dma_data_end(struct mmci_host *host) { - struct mmc_data *data = host->data; - - dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, - (data->flags & MMC_DATA_WRITE) - ? DMA_TO_DEVICE : DMA_FROM_DEVICE); host->dma_on_current_xfer = false; }
@@ -353,7 +349,9 @@ static void mmci_dma_callback(void *arg) spin_unlock_irqrestore(&host->lock, flags); }
-static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +static struct dma_async_tx_descriptor *mmci_dma_cfg(struct mmc_data *data, + struct mmci_host *host, + struct dma_chan **chan_dma) { struct variant_data *variant = host->variant; struct dma_slave_config rx_conf = { @@ -368,19 +366,13 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) .direction = DMA_TO_DEVICE, .dst_maxburst = variant->fifohalfsize >> 2, /* # of words */ }; - struct mmc_data *data = host->data; enum dma_data_direction direction; struct dma_chan *chan; struct dma_async_tx_descriptor *desc; struct scatterlist *sg; - dma_cookie_t cookie; int i; - unsigned int irqmask0; int sg_len;
- datactrl |= MCI_DPSM_DMAENABLE; - datactrl |= variant->dmareg_enable; - if (data->flags & MMC_DATA_READ) { if (host->size <= variant->txsize_threshold) rx_conf.src_maxburst = 1; @@ -404,11 +396,12 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) dev_vdbg(mmc_dev(host->mmc), "MMCI SGlist %d dir %d: length: %08x\n", i, direction, sg->length); if (sg->offset & 3 || sg->length & 3) - return -EINVAL; + return NULL; }
sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, - data->sg_len, direction); + data->sg_len, direction); + if (!sg_len) goto map_err;
@@ -420,7 +413,42 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
desc->callback = mmci_dma_callback; desc->callback_param = host; - host->dma_desc = desc; + + *chan_dma = chan; + return desc; +unmap_exit: + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); +map_err: + *chan_dma = NULL; + return NULL; +} + +static void mmci_dma_prepare(struct mmc_data *data, struct mmci_host *host) +{ + + if (data != host->data_next) + host->dma_desc = mmci_dma_cfg(data, host, &host->cur_chan); + else { + host->dma_desc = host->dma_desc_next; + host->cur_chan = host->next_chan; + + host->dma_desc_next = NULL; + host->data_next = NULL; + host->next_chan = NULL; + } + + BUG_ON(!host->dma_desc); + BUG_ON(!host->cur_chan); +} + +static int mmci_dma_start_data(struct mmci_host *host) +{ + struct mmc_data *data = host->data; + struct dma_async_tx_descriptor *desc = host->dma_desc; + struct dma_chan *chan = host->cur_chan; + dma_cookie_t cookie; + enum dma_data_direction direction; + dev_vdbg(mmc_dev(host->mmc), "Submit MMCI DMA job, sglen %d " "blksz %04x blks %04x flags %08x\n", data->sg_len, data->blksz, data->blocks, data->flags); @@ -433,6 +461,24 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) host->dma_on_current_xfer = true; chan->device->device_issue_pending(chan);
+ return 0; +unmap_exit: + if (data->flags & MMC_DATA_READ) + direction = DMA_FROM_DEVICE; + + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + + return -ENOMEM; +} + +static int mmci_dma_start_fifo(struct mmci_host *host, unsigned int datactrl) +{ + unsigned int irqmask0; + + datactrl |= MCI_DPSM_DMAENABLE; + datactrl |= host->variant->dmareg_enable; + /* * MMCI monitors both MCI_DATAEND and the DMA callback. * Both events must occur before the transfer is considered @@ -447,12 +493,45 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) /* Trigger the DMA transfer */ writel(datactrl, host->base + MMCIDATACTRL); return 0; +} + +static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmci_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (host->dma_enable) + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + (data->flags & MMC_DATA_WRITE) + ? DMA_TO_DEVICE : DMA_FROM_DEVICE); +} + +static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq, + bool host_is_idle) +{ + struct mmci_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (host->dma_enable && !host_is_idle) { + struct dma_async_tx_descriptor *desc; + struct dma_chan *chan; + + desc = mmci_dma_cfg(data, host, &chan); + if (desc == NULL) + goto no_next; + + host->dma_desc_next = desc; + host->data_next = data; + host->next_chan = chan; + } + + return; + + no_next: + host->dma_desc_next = NULL; + host->data_next = NULL; + host->next_chan = NULL;
-unmap_exit: - dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, direction); -map_err: - chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); - return -ENOMEM; } #else /* Blank functions if the DMA engine is not available */ @@ -472,10 +551,23 @@ static inline void mmci_dma_terminate(struct mmci_host *host) { }
-static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +static inline int mmci_dma_start_data(struct mmci_host *host) { return -ENOSYS; } + +static inline int mmci_dma_start_fifo(struct mmci_host *host, + unsigned int datactrl) +{ + return -ENOSYS; +} + +static void mmci_dma_prepare(struct mmc_data *data, struct mmci_host *host) +{ +} + +#define mmci_post_request NULL +#define mmci_pre_request NULL #endif
static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) @@ -519,7 +611,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode */ - ret = mmci_dma_start_data(host, datactrl); + ret = mmci_dma_start_fifo(host, datactrl); if (!ret) return; } @@ -662,13 +754,6 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, mmci_stop_data(host); host->dataend = false;
- /* - * Variants with broken blockend flags need to handle - * the end of the entire transfer here. - */ - if (variant->broken_blockend && !data->error) - host->data_xfered += data->blksz * data->blocks; - if (!data->stop) mmci_request_end(host, data->mrq); else @@ -705,6 +790,8 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, mmci_request_end(host, cmd->mrq); } else if (!(cmd->data->flags & MMC_DATA_READ)) { mmci_start_data(host, cmd->data); + if (host->dma_enable) + mmci_dma_start_data(host); } }
@@ -944,6 +1031,13 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
mmci_start_command(host, mrq->cmd, 0);
+ if (host->dma_enable && mrq->data) { + mmci_dma_prepare(mrq->data, host); + + if (mrq->data->flags & MMC_DATA_READ) + mmci_dma_start_data(host); + } + spin_unlock_irqrestore(&host->lock, flags); }
@@ -1053,6 +1147,8 @@ static irqreturn_t mmci_cd_irq(int irq, void *dev_id)
static const struct mmc_host_ops mmci_ops = { .request = mmci_request, + .pre_req = mmci_pre_request, + .post_req = mmci_post_request, .set_ios = mmci_set_ios, .get_ro = mmci_get_ro, .get_cd = mmci_get_cd, @@ -1180,15 +1276,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) * single request. */ mmc->max_req_size = (1 << variant->datalength_bits) - 1; - - /* - * Set the maximum segment size. Right now DMA sets the - * limit and not the data length register. Thus until the DMA - * driver not handles this, the segment size is limited by DMA. - * DMA limit: src_addr_width x (64 KB -1). src_addr_width - * can be 1. - */ - mmc->max_seg_size = 65535; + mmc->max_seg_size = mmc->max_req_size;
/* * Block size can be up to 2048 bytes, but must be a power of two. diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 39b7ac7..828ab5a 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -196,6 +196,10 @@ struct mmci_host { struct dma_chan *dma_rx_channel; struct dma_chan *dma_tx_channel; struct dma_async_tx_descriptor *dma_desc; + struct dma_async_tx_descriptor *dma_desc_next; + struct mmc_data *data_next; + struct dma_chan *cur_chan; + struct dma_chan *next_chan; #endif };