[FYI 3/4] MMCI: Corrections for DMA
Per Forlin
per.forlin at linaro.org
Wed Jan 12 13:19:42 EST 2011
From: Ulf Hansson <ulf.hansson at 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.
--
1.7.1
More information about the linux-arm-kernel
mailing list