From patchwork Tue Jun 28 07:57:40 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Walleij X-Patchwork-Id: 923442 Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by demeter1.kernel.org (8.14.4/8.14.4) with ESMTP id p5S7x0pY012871 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Tue, 28 Jun 2011 07:59:21 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QbTC0-0003JT-8q; Tue, 28 Jun 2011 07:58:45 +0000 Received: from localhost ([127.0.0.1] helo=canuck.infradead.org) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1QbTBz-0002fq-79; Tue, 28 Jun 2011 07:58:43 +0000 Received: from eu1sys200aog104.obsmtp.com ([207.126.144.117]) by canuck.infradead.org with smtps (Exim 4.76 #1 (Red Hat Linux)) id 1QbTB9-0002VK-EH for linux-arm-kernel@lists.infradead.org; Tue, 28 Jun 2011 07:57:55 +0000 Received: from beta.dmz-eu.st.com ([164.129.1.35]) (using TLSv1) by eu1sys200aob104.postini.com ([207.126.147.11]) with SMTP ID DSNKTgmJeTr/ExhvcPMjADuLghqf1OjzPYez@postini.com; Tue, 28 Jun 2011 07:57:51 UTC Received: from zeta.dmz-eu.st.com (ns2.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 9822922F; Tue, 28 Jun 2011 07:57:44 +0000 (GMT) Received: from relay2.stm.gmessaging.net (unknown [10.230.100.18]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 36160194C; Tue, 28 Jun 2011 07:57:44 +0000 (GMT) Received: from exdcvycastm003.EQ1STM.local (alteon-source-exch [10.230.100.61]) (using TLSv1 with cipher RC4-MD5 (128/128 bits)) (Client CN "exdcvycastm003", Issuer "exdcvycastm003" (not verified)) by relay2.stm.gmessaging.net (Postfix) with ESMTPS id 6C596A8081; Tue, 28 Jun 2011 09:57:40 +0200 (CEST) Received: from localhost.localdomain (10.230.100.153) by smtp.stericsson.com (10.230.100.1) with Microsoft SMTP Server (TLS) id 8.3.83.0; Tue, 28 Jun 2011 09:57:43 +0200 From: Linus Walleij To: , Subject: [PATCH 3/4] mmci: sync DATAEND irq with dma|pio transfer done Date: Tue, 28 Jun 2011 09:57:40 +0200 Message-ID: <1309247860-17181-1-git-send-email-linus.walleij@stericsson.com> X-Mailer: git-send-email 1.7.3.2 MIME-Version: 1.0 X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.7.6 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110628_035752_063609_4D542865 X-CRM114-Status: GOOD ( 30.09 ) X-Spam-Score: -2.3 (--) X-Spam-Report: SpamAssassin version 3.3.1 on canuck.infradead.org summary: Content analysis details: (-2.3 points) pts rule name description ---- ---------------------- -------------------------------------------------- -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at http://www.dnswl.org/, medium trust [207.126.144.117 listed in list.dnswl.org] Cc: Linus Walleij , Lee Jones , Ulf Hansson X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Tue, 28 Jun 2011 07:59:21 +0000 (UTC) From: Ulf Hansson The end of a dma|pio data transfer is synced with the DATAEND irq. This will prevent the mmci driver from ending the request before the dma|pio job is completely done. For dma we use DMA_PREP_INTERRUPT to register a callback function which is called when dma driver is done with job. To also make sure we prevent hanging forever, waiting for DATAEND irq or a dma|pio transfer to be done, we setup a timer when either a DATAEND or dma|pio job is done. Once both conditions have occured, the timer is cancelled and the data transfer is completed. If a timeout occurs, the data transfer is terminated in a controlled manner and EAGAIN is returned to the framework. A timeout value of 50 ms has been found to work well for our usecases. Signed-off-by: Ulf Hansson Signed-off-by: Linus Walleij --- drivers/mmc/host/mmci.c | 211 +++++++++++++++++++++++++++-------------------- drivers/mmc/host/mmci.h | 7 ++- 2 files changed, 126 insertions(+), 92 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index c73d054..93dcd2a 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -37,6 +37,7 @@ #include "mmci.h" #define DRIVER_NAME "mmci-pl18x" +#define DATAEND_TIMEOUT_MS 50 static unsigned int fmax = 515633; @@ -209,6 +210,67 @@ 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); +} + +static void mmci_complete_data_xfer(struct mmci_host *host) +{ + struct mmc_data *data = host->data; + + if (((host->size == 0) && host->dataend) || data->error) { + + /* + * The data transfer is done and then there is no need for the + * a delayed work any more, thus remove it. + */ + host->dataend_timeout_active = false; + __cancel_delayed_work(&host->dataend_timeout); + + mmci_stop_data(host); + + if (!data->error) + data->bytes_xfered = data->blksz * data->blocks; + + if (!data->stop) + mmci_request_end(host, data->mrq); + else + mmci_start_command(host, data->stop, 0); + } else { + /* + * Schedule a delayed work to make sure we do not end up + * forever waiting for a data transfer to be finished. + */ + host->dataend_timeout_active = true; + schedule_delayed_work(&host->dataend_timeout, + msecs_to_jiffies(DATAEND_TIMEOUT_MS)); + } +} + /* * All the DMA operation mode stuff goes inside this ifdef. * This assumes that you have a generic DMA device interface, @@ -306,28 +368,6 @@ static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) { struct dma_chan *chan = host->dma_current; enum dma_data_direction dir; - u32 status; - int i; - - /* Wait up to 1ms for the DMA to complete */ - for (i = 0; ; i++) { - status = readl(host->base + MMCISTATUS); - if (!(status & MCI_RXDATAAVLBLMASK) || i >= 100) - break; - udelay(10); - } - - /* - * Check to see whether we still have some data left in the FIFO - - * this catches DMA controllers which are unable to monitor the - * DMALBREQ and DMALSREQ signals while allowing us to DMA to non- - * contiguous buffers. On TX, we'll get a FIFO underrun error. - */ - if (status & MCI_RXDATAAVLBLMASK) { - dmaengine_terminate_all(chan); - if (!data->error) - data->error = -EIO; - } if (data->flags & MMC_DATA_WRITE) { dir = DMA_TO_DEVICE; @@ -336,21 +376,31 @@ static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) } dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); + host->dma_current = NULL; +} - /* - * Use of DMA with scatter-gather is impossible. - * Give up with DMA and switch back to PIO mode. - */ - if (status & MCI_RXDATAAVLBLMASK) { - dev_err(mmc_dev(host->mmc), "buggy DMA detected. Taking evasive action.\n"); - mmci_dma_release(host); - } +static void mmci_dma_callback(void *arg) +{ + unsigned long flags; + struct mmci_host *host = arg; + + spin_lock_irqsave(&host->lock, flags); + + mmci_dma_unmap(host, host->data); + + /* Mark that the entire data is transferred. */ + host->size = 0; + + mmci_complete_data_xfer(host); + + spin_unlock_irqrestore(&host->lock, flags); } static void mmci_dma_data_error(struct mmci_host *host) { dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n"); dmaengine_terminate_all(host->dma_current); + host->dma_current = NULL; } static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) @@ -370,8 +420,6 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) struct dma_async_tx_descriptor *desc; int nr_sg; - host->dma_current = NULL; - if (data->flags & MMC_DATA_READ) { conf.direction = DMA_FROM_DEVICE; chan = host->dma_rx_channel; @@ -395,10 +443,15 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) dmaengine_slave_config(chan, &conf); desc = device->device_prep_slave_sg(chan, data->sg, nr_sg, - conf.direction, DMA_CTRL_ACK); + conf.direction, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); if (!desc) goto unmap_exit; + /* Setup dma callback function. */ + desc->callback = mmci_dma_callback; + desc->callback_param = host; + /* Okay, go for it. */ host->dma_current = chan; @@ -413,13 +466,6 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) /* 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: @@ -451,6 +497,27 @@ static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datac } #endif +static void mmci_dataend_timeout(struct work_struct *work) +{ + unsigned long flags; + struct mmci_host *host = + container_of(work, struct mmci_host, dataend_timeout.work); + + spin_lock_irqsave(&host->lock, flags); + + if (host->dataend_timeout_active) { + dev_err(mmc_dev(host->mmc), "datatransfer timeout!\n"); + + if (dma_inprogress(host)) + mmci_dma_data_error(host); + + host->data->error = -EAGAIN; + mmci_complete_data_xfer(host); + } + + spin_unlock_irqrestore(&host->lock, flags); +} + static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) { struct variant_data *variant = host->variant; @@ -464,6 +531,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) host->data = data; host->size = data->blksz * data->blocks; + host->dataend = false; data->bytes_xfered = 0; clks = (unsigned long long)data->timeout_ns * host->cclk; @@ -520,39 +588,10 @@ 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); } 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); -} - -static void mmci_data_irq(struct mmci_host *host, struct mmc_data *data, unsigned int status) { @@ -599,21 +638,11 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, if (status & MCI_DATABLOCKEND) dev_err(mmc_dev(host->mmc), "stray MCI_DATABLOCKEND interrupt\n"); - if (status & MCI_DATAEND || data->error) { - if (dma_inprogress(host)) - mmci_dma_unmap(host, data); - mmci_stop_data(host); - - if (!data->error) - /* The error clause is handled above, success! */ - data->bytes_xfered = data->blksz * data->blocks; + if (status & MCI_DATAEND) + host->dataend = true; - if (!data->stop) { - mmci_request_end(host, data->mrq); - } else { - mmci_start_command(host, data->stop, 0); - } - } + if (host->dataend || data->error) + mmci_complete_data_xfer(host); } static void @@ -636,8 +665,11 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, } if (!cmd->data || cmd->error) { - if (host->data) + if (host->data) { + if (dma_inprogress(host)) + mmci_dma_data_error(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); @@ -793,15 +825,10 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id) if (status & MCI_RXACTIVE && host->size < variant->fifohalfsize) mmci_set_mask1(host, MCI_RXDATAAVLBLMASK); - /* - * If we run out of data, disable the data IRQs; this - * prevents a race where the FIFO becomes empty before - * the chip itself has disabled the data path, and - * stops us racing with our data end IRQ. - */ + /* If we run out of data, disable the data IRQs. */ if (host->size == 0) { mmci_set_mask1(host, 0); - writel(readl(base + MMCIMASK0) | MCI_DATAENDMASK, base + MMCIMASK0); + mmci_complete_data_xfer(host); } return IRQ_HANDLED; @@ -1200,6 +1227,7 @@ static int __devinit mmci_probe(struct amba_device *dev, dev->irq[0], dev->irq[1]); mmci_dma_setup(host); + INIT_DELAYED_WORK(&host->dataend_timeout, mmci_dataend_timeout); mmc_add_host(mmc); @@ -1247,6 +1275,7 @@ static int __devexit mmci_remove(struct amba_device *dev) writel(0, host->base + MMCIDATACTRL); mmci_dma_release(host); + cancel_delayed_work(&host->dataend_timeout); 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 2164e8c..79156a0 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -153,7 +153,7 @@ #define MCI_IRQENABLE \ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \ - MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_STARTBITERRMASK) + MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_STARTBITERRMASK|MCI_DATAENDMASK) /* These interrupts are directed to IRQ1 when two IRQ lines are available */ #define MCI_IRQ1MASK \ @@ -198,6 +198,11 @@ struct mmci_host { unsigned int size; struct regulator *vcc; + /* sync of DATAEND irq */ + bool dataend; + bool dataend_timeout_active; + struct delayed_work dataend_timeout; + #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ struct dma_chan *dma_current;