From patchwork Thu Dec 20 10:06:15 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gabriele Mondada X-Patchwork-Id: 1906431 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by patchwork1.kernel.org (Postfix) with ESMTP id 72A793FCA5 for ; Sat, 22 Dec 2012 20:54:27 +0000 (UTC) Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1TmW1o-0001xo-Vv; Sat, 22 Dec 2012 20:50:41 +0000 Received: from ne-6.precidata.com ([157.26.74.6]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1Tld8b-0004j8-Cg for linux-arm-kernel@lists.infradead.org; Thu, 20 Dec 2012 10:14:03 +0000 Received: from dcne-sys-email.precidata.net (sys-email.precidata.net [127.1.0.15]) by ne-6.precidata.com (Postfix) with ESMTPS id 471511CCA; Thu, 20 Dec 2012 11:05:30 +0100 (CET) Received: from iMac-de-Gabriele.local (ne-5.precidata.com [157.26.74.5]) by dcne-sys-email.precidata.net (Postfix) with ESMTPSA id 3061F54F8E; Thu, 20 Dec 2012 11:05:30 +0100 (CET) Message-ID: <50D2E317.6000005@precidata.com> Date: Thu, 20 Dec 2012 11:06:15 +0100 From: Gabriele Mondada User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:17.0) Gecko/17.0 Thunderbird/17.0 MIME-Version: 1.0 To: linux-arm-kernel@lists.infradead.org Subject: LPC32xx and MMCI driver References: <50D2E156.20707@precidata.com> In-Reply-To: <50D2E156.20707@precidata.com> X-Forwarded-Message-Id: <50D2E156.20707@precidata.com> X-Bad-Reply: References and In-Reply-To but no 'Re:' in Subject. X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20121220_051402_084231_FE7F6B7E X-CRM114-Status: GOOD ( 31.09 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2 on merlin.infradead.org summary: Content analysis details: (-2.6 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record -0.7 RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] X-Mailman-Approved-At: Sat, 22 Dec 2012 15:44:39 -0500 Cc: stigge@antcom.de, Cedric Berger X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.14 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 Hi, Currently, LPC32xx plateform do not enable DMA on the mmci driver. This makes the driver useless because getting out data from a 64 bytes FIFO by interrupt is not fast enough (at standard SD-card data rate). DMA is not enabled because LPC32xx has a bug that prevent DMA to work properly with the MMC controller (silicon bug, I guess). NXP did a patch to workaround this, but it has not been commited on the main branch. The patch is for linux 2.6.39.2 and does not use dmaengine. So, I reworked this patch to make it compatible with the last kernel (3.7). Here it is. Have I any chance to see this patch be commited on the main branch? Thanks a lot, Gabriele diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index d1cc579..cdc8899 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -385,6 +385,7 @@ static void pl08x_start_next_txd(struct pl08x_dma_chan *plchan) while ((val & PL080_CONFIG_ACTIVE) || (val & PL080_CONFIG_ENABLE)) val = readl(phychan->base + PL080_CH_CONFIG); + /* TODO: why val and not txd->ccfg ? */ writel(val | PL080_CONFIG_ENABLE, phychan->base + PL080_CH_CONFIG); } @@ -1758,6 +1759,26 @@ static void pl08x_free_virtual_channels(struct dma_device *dmadev) } } +#ifdef CONFIG_ARCH_LPC32XX +/* + * This exported function is used by mmci driver to workaround a bug in the + * LPC32xx CPU. + */ +void pl08x_force_dma_burst(struct dma_chan *chan) +{ + struct pl08x_dma_chan *plchan = to_pl08x_chan(chan); + struct pl08x_driver_data *pl08x = plchan->host; + + dev_dbg(&pl08x->adev->dev, + "force burst signal=%d chan=%p plchan=%p\n", + plchan->signal, chan, plchan); + if (plchan->signal >= 0) + writel(1 << plchan->signal, pl08x->base + PL080_SOFT_BREQ); +} + +EXPORT_SYMBOL_GPL(pl08x_force_dma_burst); +#endif + #ifdef CONFIG_DEBUG_FS static const char *pl08x_state_str(enum pl08x_dma_chan_state state) { diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index edc3e9b..0890905 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -3,6 +3,7 @@ * * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. * Copyright (C) 2010 ST-Ericsson SA + * Copyright (C) 2012 Gabriele Mondada * * 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 @@ -42,6 +43,16 @@ #define DRIVER_NAME "mmci-pl18x" +#define COOKIE_PREP 0x00000001 +#define COOKIE_SINGLE 0x00000002 +#define COOKIE_ID_MASK 0xFF000000 +#define COOKIE_ID_SHIFT 24 +#define COOKIE_ID(n) (COOKIE_ID_MASK & ((n) << COOKIE_ID_SHIFT)) + +#ifdef CONFIG_ARCH_LPC32XX +#define DMA_TX_SIZE SZ_64K +#endif + static unsigned int fmax = 515633; /** @@ -132,6 +143,10 @@ static struct variant_data variant_ux500v2 = { .signal_direction = true, }; +#ifdef CONFIG_ARCH_LPC32XX +void pl08x_force_dma_burst(struct dma_chan *chan); +#endif + /* * This must be called with host->lock held */ @@ -266,14 +281,38 @@ static void __devinit mmci_dma_setup(struct mmci_host *host) struct mmci_platform_data *plat = host->plat; const char *rxname, *txname; dma_cap_mask_t mask; - +#ifdef CONFIG_ARCH_LPC32XX + int i; +#endif + if (!plat || !plat->dma_filter) { dev_info(mmc_dev(host->mmc), "no DMA platform data\n"); return; } - /* initialize pre request cookie */ - host->next_data.cookie = 1; +#ifdef CONFIG_ARCH_LPC32XX + /* + * The LPC32XX do not support sg on TX DMA. So + * a temporary bouncing buffer is used if more than 1 sg segment + * is passed in the data request. The bouncing buffer will get a + * contiguous copy of the TX data and it will be used instead. + */ + for (i=0; i<2; i++) { + host->dma_v_tx[i] = dma_alloc_coherent(mmc_dev(host->mmc), + DMA_TX_SIZE, &host->dma_p_tx[i], GFP_KERNEL); + if (host->dma_v_tx[i] == NULL) { + dev_err(mmc_dev(host->mmc), "error getting DMA region\n"); + return; + } + dev_info(mmc_dev(host->mmc), "DMA buffer: phy:%p, virt:%p\n", + (void *)host->dma_p_tx[i], host->dma_v_tx[i]); + } + + host->dma_v_tx_current = host->dma_v_tx[0]; + host->dma_p_tx_current = host->dma_p_tx[0]; + host->next_data.dma_v_tx = host->dma_v_tx[1]; + host->next_data.dma_p_tx = host->dma_p_tx[1]; +#endif /* Try to acquire a generic DMA engine slave channel */ dma_cap_zero(mask); @@ -343,12 +382,26 @@ static void __devinit mmci_dma_setup(struct mmci_host *host) static inline void mmci_dma_release(struct mmci_host *host) { struct mmci_platform_data *plat = host->plat; +#ifdef CONFIG_ARCH_LPC32XX + int i; +#endif if (host->dma_rx_channel) dma_release_channel(host->dma_rx_channel); if (host->dma_tx_channel && plat->dma_tx_param) dma_release_channel(host->dma_tx_channel); host->dma_rx_channel = host->dma_tx_channel = NULL; + +#ifdef CONFIG_ARCH_LPC32XX + for (i=0; i<2; i++) { + if (host->dma_v_tx[i] == NULL) + continue; + dma_free_coherent(mmc_dev(host->mmc), DMA_TX_SIZE, + host->dma_v_tx[i], host->dma_p_tx[i]); + host->dma_v_tx[i] = NULL; + host->dma_p_tx[i] = 0; + } +#endif } static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) @@ -384,8 +437,9 @@ static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) dir = DMA_FROM_DEVICE; } - if (!data->host_cookie) + if (data->host_cookie && !(data->host_cookie & COOKIE_SINGLE)) dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); + /* TODO: no data->host_cookie=0 here ? */ /* * Use of DMA with scatter-gather is impossible. @@ -421,6 +475,7 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, struct dma_async_tx_descriptor *desc; enum dma_data_direction buffer_dirn; int nr_sg; + bool single = false; /* Check if next job is already prepared */ if (data->host_cookie && !next && @@ -440,6 +495,9 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, conf.direction = DMA_MEM_TO_DEV; buffer_dirn = DMA_TO_DEVICE; chan = host->dma_tx_channel; +#ifdef CONFIG_ARCH_LPC32XX + conf.device_fc = true; +#endif } /* If there's no DMA channel, fall back to PIO */ @@ -451,13 +509,46 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, return -EINVAL; device = chan->device; - nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, buffer_dirn); - if (nr_sg == 0) - return -EINVAL; - dmaengine_slave_config(chan, &conf); - desc = dmaengine_prep_slave_sg(chan, data->sg, nr_sg, - conf.direction, DMA_CTRL_ACK); + +#ifdef CONFIG_ARCH_LPC32XX + if ((data->flags & MMC_DATA_WRITE) && (data->sg_len > 1)) + single = true; +#endif + + if (single) { + int i; + unsigned char *dst = next ? next->dma_v_tx : host->dma_v_tx_current; + size_t len = 0; + + dev_dbg(mmc_dev(host->mmc), "use bounce buffer\n"); + /* Move data to contiguous buffer first, then transfer it */ + for (i = 0; i < data->sg_len; i++) { + unsigned long flags; + struct scatterlist *sg = &data->sg[i]; + void *src; + + /* Map the current scatter buffer, copy data, and unmap */ + local_irq_save(flags); + src = (unsigned char *)kmap_atomic(sg_page(sg)) + sg->offset; + memcpy(dst + len, src, sg->length); + len += sg->length; + kunmap_atomic(src); + local_irq_restore(flags); + } + + desc = dmaengine_prep_slave_single(chan, + next ? next->dma_p_tx : host->dma_p_tx_current, + len, buffer_dirn, DMA_CTRL_ACK); + } else { + nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, buffer_dirn); + if (nr_sg == 0) + return -EINVAL; + + desc = dmaengine_prep_slave_sg(chan, data->sg, nr_sg, + conf.direction, DMA_CTRL_ACK); + } + if (!desc) goto unmap_exit; @@ -469,12 +560,20 @@ static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data, host->dma_desc_current = desc; } + data->host_cookie = COOKIE_PREP; + if (single) + data->host_cookie |= COOKIE_SINGLE; + if (next) + data->host_cookie |= COOKIE_ID(++next->cookie_cnt); + return 0; unmap_exit: if (!next) dmaengine_terminate_all(chan); - dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn); + if (!single) + dma_unmap_sg(device->dev, data->sg, data->sg_len, buffer_dirn); + data->host_cookie = 0; return -ENOMEM; } @@ -512,11 +611,16 @@ static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) static void mmci_get_next_data(struct mmci_host *host, struct mmc_data *data) { struct mmci_host_next *next = &host->next_data; +#ifdef CONFIG_ARCH_LPC32XX + void *tmp_v; + dma_addr_t tmp_p; +#endif - if (data->host_cookie && data->host_cookie != next->cookie) { - pr_warning("[%s] invalid cookie: data->host_cookie %d" - " host->next_data.cookie %d\n", - __func__, data->host_cookie, host->next_data.cookie); + if (data->host_cookie && ((data->host_cookie & COOKIE_ID_MASK) != + COOKIE_ID(next->cookie_cnt))) { + pr_warning("[%s] invalid cookie: data->host_cookie=0x%08x" + " host->next_data.cookie_cnt=0x%08x\n", + __func__, data->host_cookie, host->next_data.cookie_cnt); data->host_cookie = 0; } @@ -528,6 +632,15 @@ static void mmci_get_next_data(struct mmci_host *host, struct mmc_data *data) next->dma_desc = NULL; next->dma_chan = NULL; + +#ifdef CONFIG_ARCH_LPC32XX + tmp_v = host->next_data.dma_v_tx; + host->next_data.dma_v_tx = host->dma_v_tx_current; + host->dma_v_tx_current = tmp_v; + tmp_p = host->next_data.dma_p_tx; + host->next_data.dma_p_tx = host->dma_p_tx_current; + host->dma_p_tx_current = tmp_p; +#endif } static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq, @@ -551,7 +664,7 @@ static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq, if (mmci_dma_prep_data(host, data, nd)) data->host_cookie = 0; else - data->host_cookie = ++nd->cookie < 0 ? 1 : nd->cookie; + data->host_cookie = COOKIE_ID(++nd->cookie_cnt) | COOKIE_PREP; } } @@ -579,7 +692,7 @@ static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq, if (chan) { if (err) dmaengine_terminate_all(chan); - if (data->host_cookie) + if (data->host_cookie && !(data->host_cookie & COOKIE_SINGLE)) dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, dir); mrq->data->host_cookie = 0; @@ -767,6 +880,15 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, dev_err(mmc_dev(host->mmc), "stray MCI_DATABLOCKEND interrupt\n"); if (status & MCI_DATAEND || data->error) { +#ifdef CONFIG_ARCH_LPC32XX + /* + * On LPC32XX, there is a problem with the DMA flow control and + * the last burst transfer is not performed. So we force the + * transfer programmatically here. + */ + if (data->flags & MMC_DATA_READ) + pl08x_force_dma_burst(host->dma_rx_channel); +#endif if (dma_inprogress(host)) mmci_dma_unmap(host, data); mmci_stop_data(host); diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index d437ccf..6e1ea3f 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -159,7 +159,11 @@ struct dma_chan; struct mmci_host_next { struct dma_async_tx_descriptor *dma_desc; struct dma_chan *dma_chan; - s32 cookie; + s32 cookie_cnt; +#ifdef CONFIG_ARCH_LPC32XX + void *dma_v_tx; + dma_addr_t dma_p_tx; +#endif }; struct mmci_host { @@ -202,6 +206,12 @@ struct mmci_host { struct dma_chan *dma_tx_channel; struct dma_async_tx_descriptor *dma_desc_current; struct mmci_host_next next_data; +#ifdef CONFIG_ARCH_LPC32XX + void *dma_v_tx[2]; + dma_addr_t dma_p_tx[2]; + void *dma_v_tx_current; + dma_addr_t dma_p_tx_current; +#endif #define dma_inprogress(host) ((host)->dma_current) #else