[U-Boot] [PATCH 0/5] mmc: Tegra2: DMA related fixes

The first two patches in this set are basic bug fixes for the Tegra2 MMC driver. The third patch is cleanup. The fourth patch adds some cache management utility functions to the generic MMC code. And the final patch adds bounce buffer management, cache flusing, and cache invalidation to the Tegra2 MMC driver. This bounce buffer allows the driver to function correctly even when faced with incorrectly aligned buffers.
My other patch sets attempt to fix as many unaligned buffers as I could find. But there are likely more to deal with and it is preferable that the MMC driver continue to function in the face of unaligned buffers, even if it slows down due to memcpy overhead.
An alternative to the fifth patch would be to attempt to add this bounce buffer management to the generic MMC layer itself. This would benefit all architectures, but would require more testing and work. It seems better to enable it here first and then move it to the generic code. Once a generic solution is in place this code can be reverted. It might even make sense to put the bounce buffer code all the way up in the block interface layer. But my hope is that we can hash that all out independently of this patch. :)
Anton Staaf (5): mmc: Tegra2: Support DMA restarts at buffer boundaries mmc: Tegra2: Add data transfer completion timeout mmc: Tegra2: Factor out mmc_wait_inhibit functionality mmc: Create dcache flush and invalidate convenience methods mmc: Tegra2: Enable dcache support by bouncing unaligned requests.
drivers/mmc/mmc.c | 23 +++++ drivers/mmc/tegra2_mmc.c | 202 +++++++++++++++++++++++++++++++++++++++++----- drivers/mmc/tegra2_mmc.h | 14 +++ include/mmc.h | 10 +++ 4 files changed, 227 insertions(+), 22 deletions(-)

Currently if a DMA buffer straddles a buffer alignment boundary (512KiB) then the DMA engine will pause and generate a DMA interrupt. Since the DMA interrupt is not enabled it will hang the MMC driver.
This patch adds support for restarting the DMA transfer. The SYSTEM_ADDRESS register contains the next address that would have been read/written when a boundary is hit. So we can read that and write it back. The write triggers the resumption of the transfer.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com --- drivers/mmc/tegra2_mmc.c | 15 +++++++++++---- 1 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 8b6f829..195f89d 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -256,9 +256,15 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, __func__, mask); return -1; } else if (mask & (1 << 3)) { - /* DMA Interrupt */ + /* + * DMA Interrupt, restart the transfer where + * it was interrupted. + */ + unsigned int address = readl(&host->reg->sysad); + debug("DMA end\n"); - break; + writel((1 << 3), &host->reg->norintsts); + writel(address, &host->reg->sysad); } else if (mask & (1 << 1)) { /* Transfer Complete */ debug("r/w is done\n"); @@ -442,12 +448,13 @@ static int mmc_core_init(struct mmc *mmc) * NORMAL Interrupt Status Enable Register init * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable + * [3] ENSTADMAINT : DMA Interrupt Status Enable * [1] ENSTASTANSCMPLT : Transfre Complete Status Enable * [0] ENSTACMDCMPLT : Command Complete Status Enable - */ + */ mask = readl(&host->reg->norintstsen); mask &= ~(0xffff); - mask |= (1 << 5) | (1 << 4) | (1 << 1) | (1 << 0); + mask |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 1) | (1 << 0); writel(mask, &host->reg->norintstsen);
/*

On Thu, Oct 13, 2011 at 2:57 PM, Anton Staaf robotboy@chromium.org wrote:
Currently if a DMA buffer straddles a buffer alignment boundary (512KiB) then the DMA engine will pause and generate a DMA interrupt. Since the DMA interrupt is not enabled it will hang the MMC driver.
This patch adds support for restarting the DMA transfer. The SYSTEM_ADDRESS register contains the next address that would have been read/written when a boundary is hit. So we can read that and write it back. The write triggers the resumption of the transfer.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com
Adding Albert to CC.
-Anton
drivers/mmc/tegra2_mmc.c | 15 +++++++++++---- 1 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 8b6f829..195f89d 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -256,9 +256,15 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, __func__, mask); return -1; } else if (mask & (1 << 3)) {
- /* DMA Interrupt */
- /*
- * DMA Interrupt, restart the transfer where
- * it was interrupted.
- */
- unsigned int address = readl(&host->reg->sysad);
debug("DMA end\n");
- break;
- writel((1 << 3), &host->reg->norintsts);
- writel(address, &host->reg->sysad);
} else if (mask & (1 << 1)) { /* Transfer Complete */ debug("r/w is done\n"); @@ -442,12 +448,13 @@ static int mmc_core_init(struct mmc *mmc) * NORMAL Interrupt Status Enable Register init * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable
- * [3] ENSTADMAINT : DMA Interrupt Status Enable
* [1] ENSTASTANSCMPLT : Transfre Complete Status Enable * [0] ENSTACMDCMPLT : Command Complete Status Enable
- */
- */
mask = readl(&host->reg->norintstsen); mask &= ~(0xffff);
- mask |= (1 << 5) | (1 << 4) | (1 << 1) | (1 << 0);
- mask |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 1) | (1 << 0);
writel(mask, &host->reg->norintstsen);
/*
1.7.3.1

On Thu, Oct 13, 2011 at 4:57 PM, Anton Staaf robotboy@chromium.org wrote:
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 8b6f829..195f89d 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -256,9 +256,15 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, __func__, mask); return -1; } else if (mask & (1 << 3)) {
[...]
- writel((1 << 3), &host->reg->norintsts);
- writel(address, &host->reg->sysad);
} else if (mask & (1 << 1)) { /* Transfer Complete */ debug("r/w is done\n"); @@ -442,12 +448,13 @@ static int mmc_core_init(struct mmc *mmc) * NORMAL Interrupt Status Enable Register init * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable
- * [3] ENSTADMAINT : DMA Interrupt Status Enable
* [1] ENSTASTANSCMPLT : Transfre Complete Status Enable * [0] ENSTACMDCMPLT : Command Complete Status Enable
- */
- */
mask = readl(&host->reg->norintstsen); mask &= ~(0xffff);
- mask |= (1 << 5) | (1 << 4) | (1 << 1) | (1 << 0);
- mask |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 1) | (1 << 0);
I'm pretty sure that a similar patch resulted in a request from Wolfgang to clean up the magic numbers in the code. Commenting the numbers above is helpful, but still very prone to maintenance issues. Why not just set mask:
mask |= (ENSTABUFRDRDY | ENSTABUFWTRDY | ENSTADMAINT | ENSTASTANSCMPLT | ENSTACMDCMPLT);
If all of the uses of those masks were replaced like that, we'd have much greater confidence that a random current or future typo wouldn't break the driver. It would also make it much simpler to identify other places where that bit was being set/checked.
Please fix, and resubmit.
Andy

On Wed, Nov 2, 2011 at 6:08 PM, Andy Fleming afleming@gmail.com wrote:
On Thu, Oct 13, 2011 at 4:57 PM, Anton Staaf robotboy@chromium.org wrote:
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 8b6f829..195f89d 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -256,9 +256,15 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, __func__, mask); return -1; } else if (mask & (1 << 3)) {
[...]
- writel((1 << 3), &host->reg->norintsts);
- writel(address, &host->reg->sysad);
} else if (mask & (1 << 1)) { /* Transfer Complete */ debug("r/w is done\n"); @@ -442,12 +448,13 @@ static int mmc_core_init(struct mmc *mmc) * NORMAL Interrupt Status Enable Register init * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable
- * [3] ENSTADMAINT : DMA Interrupt Status Enable
* [1] ENSTASTANSCMPLT : Transfre Complete Status Enable * [0] ENSTACMDCMPLT : Command Complete Status Enable
- */
- */
mask = readl(&host->reg->norintstsen); mask &= ~(0xffff);
- mask |= (1 << 5) | (1 << 4) | (1 << 1) | (1 << 0);
- mask |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 1) | (1 << 0);
I'm pretty sure that a similar patch resulted in a request from Wolfgang to clean up the magic numbers in the code. Commenting the numbers above is helpful, but still very prone to maintenance issues. Why not just set mask:
mask |= (ENSTABUFRDRDY | ENSTABUFWTRDY | ENSTADMAINT | ENSTASTANSCMPLT | ENSTACMDCMPLT);
If all of the uses of those masks were replaced like that, we'd have much greater confidence that a random current or future typo wouldn't break the driver. It would also make it much simpler to identify other places where that bit was being set/checked.
Please fix, and resubmit.
Will do.
Thanks, Anton
Andy

Currently when no expected completion condition occures in the mmc_send_cmd while loop that is waiting for a data transfer to complete the MMC driver just hangs.
This patch adds an arbitrary 2 second timeout. If nothing we recognize occures within 2 seconds some diagnostic information is printed and we fail out.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com --- drivers/mmc/tegra2_mmc.c | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 195f89d..27564b0 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -246,6 +246,8 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, }
if (data) { + unsigned long start = get_timer(0); + while (1) { mask = readl(&host->reg->norintsts);
@@ -269,6 +271,18 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, /* Transfer Complete */ debug("r/w is done\n"); break; + } else if (get_timer(start) > 2000UL) { + writel(mask, &host->reg->norintsts); + printf("%s: MMC Timeout\n" + " Interrupt status 0x%08x\n" + " Interrupt status enable 0x%08x\n" + " Interrupt signal enable 0x%08x\n" + " Present status 0x%08x\n", + __func__, mask, + readl(&host->reg->norintstsen), + readl(&host->reg->norintsigen), + readl(&host->reg->prnsts)); + return -1; } } writel(mask, &host->reg->norintsts);

On Thu, Oct 13, 2011 at 2:57 PM, Anton Staaf robotboy@chromium.org wrote:
Currently when no expected completion condition occures in the mmc_send_cmd while loop that is waiting for a data transfer to complete the MMC driver just hangs.
This patch adds an arbitrary 2 second timeout. If nothing we recognize occures within 2 seconds some diagnostic information is printed and we fail out.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com
Adding Albert to CC.
-Anton
drivers/mmc/tegra2_mmc.c | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 195f89d..27564b0 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -246,6 +246,8 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, }
if (data) {
- unsigned long start = get_timer(0);
while (1) { mask = readl(&host->reg->norintsts);
@@ -269,6 +271,18 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, /* Transfer Complete */ debug("r/w is done\n"); break;
- } else if (get_timer(start) > 2000UL) {
- writel(mask, &host->reg->norintsts);
- printf("%s: MMC Timeout\n"
- " Interrupt status 0x%08x\n"
- " Interrupt status enable 0x%08x\n"
- " Interrupt signal enable 0x%08x\n"
- " Present status 0x%08x\n",
- __func__, mask,
- readl(&host->reg->norintstsen),
- readl(&host->reg->norintsigen),
- readl(&host->reg->prnsts));
- return -1;
} } writel(mask, &host->reg->norintsts); -- 1.7.3.1

This is a well encapsulated section of mmc_send_cmd, by moving it to it's own function it increases the readability of mmc_send_cmd.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com --- drivers/mmc/tegra2_mmc.c | 41 ++++++++++++++++++++++++++--------------- 1 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 27564b0..141429e 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -102,18 +102,12 @@ static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data) writew(mode, &host->reg->trnmod); }
-static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, - struct mmc_data *data) +static int mmc_wait_inhibit(struct mmc_host *host, + struct mmc_cmd *cmd, + struct mmc_data *data, + unsigned int timeout) { - struct mmc_host *host = (struct mmc_host *)mmc->priv; - int flags, i; - unsigned int timeout; - unsigned int mask; - unsigned int retry = 0x100000; - debug(" mmc_send_cmd called\n"); - - /* Wait max 10 ms */ - timeout = 10; + unsigned int mask = 0;
/* * PRNSTS @@ -121,15 +115,13 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, * CMDINHCMD[0] : Command Inhibit (CMD) */ mask = (1 << 0); - if ((data != NULL) || (cmd->resp_type & MMC_RSP_BUSY)) - mask |= (1 << 1);
/* * We shouldn't wait for data inhibit for stop commands, even * though they might use busy signaling */ - if (data) - mask &= ~(1 << 1); + if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY)) + mask |= (1 << 1);
while (readl(&host->reg->prnsts) & mask) { if (timeout == 0) { @@ -140,6 +132,25 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, udelay(1000); }
+ return 0; +} + +static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc_host *host = (struct mmc_host *)mmc->priv; + int flags, i; + int result; + unsigned int timeout; + unsigned int mask; + unsigned int retry = 0x100000; + debug(" mmc_send_cmd called\n"); + + result = mmc_wait_inhibit(host, cmd, data, 10 /* ms */); + + if (result < 0) + return result; + if (data) mmc_prepare_data(host, data);

On Thu, Oct 13, 2011 at 2:57 PM, Anton Staaf robotboy@chromium.org wrote:
This is a well encapsulated section of mmc_send_cmd, by moving it to it's own function it increases the readability of mmc_send_cmd.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com
Adding Albert to CC.
-Anton
drivers/mmc/tegra2_mmc.c | 41 ++++++++++++++++++++++++++--------------- 1 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 27564b0..141429e 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -102,18 +102,12 @@ static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data) writew(mode, &host->reg->trnmod); }
-static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
- struct mmc_data *data)
+static int mmc_wait_inhibit(struct mmc_host *host,
- struct mmc_cmd *cmd,
- struct mmc_data *data,
- unsigned int timeout)
{
- struct mmc_host *host = (struct mmc_host *)mmc->priv;
- int flags, i;
- unsigned int timeout;
- unsigned int mask;
- unsigned int retry = 0x100000;
- debug(" mmc_send_cmd called\n");
- /* Wait max 10 ms */
- timeout = 10;
- unsigned int mask = 0;
/* * PRNSTS @@ -121,15 +115,13 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, * CMDINHCMD[0] : Command Inhibit (CMD) */ mask = (1 << 0);
- if ((data != NULL) || (cmd->resp_type & MMC_RSP_BUSY))
- mask |= (1 << 1);
/* * We shouldn't wait for data inhibit for stop commands, even * though they might use busy signaling */
- if (data)
- mask &= ~(1 << 1);
- if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY))
- mask |= (1 << 1);
while (readl(&host->reg->prnsts) & mask) { if (timeout == 0) { @@ -140,6 +132,25 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, udelay(1000); }
- return 0;
+}
+static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
- struct mmc_data *data)
+{
- struct mmc_host *host = (struct mmc_host *)mmc->priv;
- int flags, i;
- int result;
- unsigned int timeout;
- unsigned int mask;
- unsigned int retry = 0x100000;
- debug(" mmc_send_cmd called\n");
- result = mmc_wait_inhibit(host, cmd, data, 10 /* ms */);
- if (result < 0)
- return result;
if (data) mmc_prepare_data(host, data);
-- 1.7.3.1

With the enabling of data caches in U-Boot flushing and invalidating MMC buffers will need to be done in all MMC drivers. These utility functions simplify that task slightly.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Andy Fleming afleming@gmail.com --- drivers/mmc/mmc.c | 23 +++++++++++++++++++++++ include/mmc.h | 10 ++++++++++ 2 files changed, 33 insertions(+), 0 deletions(-)
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 391bc2b..5001bb1 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -47,6 +47,29 @@ int __board_mmc_getcd(u8 *cd, struct mmc *mmc) { int board_mmc_getcd(u8 *cd, struct mmc *mmc)__attribute__((weak, alias("__board_mmc_getcd")));
+/* + * Flush the data cache lines associated with the given mmc_data structs buffer. + */ +void mmc_dcache_flush(struct mmc_data *data) +{ + unsigned long start = (unsigned long) data->dest; + unsigned long stop = start + (data->blocksize * data->blocks); + + flush_dcache_range(start, stop); +} + +/* + * Invalidate the data cache lines associated with the given mmc_data structs + * buffer. + */ +void mmc_dcache_invalidate(struct mmc_data *data) +{ + unsigned long start = (unsigned long) data->dest; + unsigned long stop = start + (data->blocksize * data->blocks); + + invalidate_dcache_range(start, stop); +} + int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) { #ifdef CONFIG_MMC_TRACE diff --git a/include/mmc.h b/include/mmc.h index 53aff9b..94be2e1 100644 --- a/include/mmc.h +++ b/include/mmc.h @@ -315,6 +315,16 @@ int get_mmc_num(void); int board_mmc_getcd(u8 *cd, struct mmc *mmc); int mmc_switch_part(int dev_num, unsigned int part_num);
+/* + * Given an mmc_data struct these flush and invalidate the cache range covered + * by the mmc_data buffer. A flush should be done before starting a DMA + * transfer from the mmc_data struct. An invalidate should be done after a + * DMA transfer finishes that wrote to the memory referenced in the mmc_data + * struct. + */ +void mmc_dcache_flush(struct mmc_data *data); +void mmc_dcache_invalidate(struct mmc_data *data); + #ifdef CONFIG_GENERIC_MMC int atmel_mci_init(void *regs); #define mmc_host_is_spi(mmc) ((mmc)->host_caps & MMC_MODE_SPI)

When an unaligned buffer is used for DMA the first and last few bytes of the buffer would be clobbered by the dcache invalidate call that is required to make the contents of the buffer visible to the CPU post DMA. The U-Boot cache invalidation and flushing code checks for these sorts of alignment violations and only operates on the aligned portion and generates an error message to indicate that the buffer was not aligned correctly. This obviously can results in incorrect values at the beginning and end of a DMA trasfered buffer. Until all buffers passed to the MMC layer are correctly aligned the MMC driver code must deal with these unaligned situations if it wishes to function correctly.
This patch adds a DMA bounce buffer to the Tegra MMC driver code. This buffer is only used if an unaligned buffer is used for reading or writing. This solution requires an extra memcpy to or from the bounce buffer and has a high water mark in memory consumption.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com --- drivers/mmc/tegra2_mmc.c | 132 ++++++++++++++++++++++++++++++++++++++++++++- drivers/mmc/tegra2_mmc.h | 14 +++++ 2 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 141429e..523bc6a 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -3,6 +3,7 @@ * Minkyu Kang mk7.kang@samsung.com * Jaehoon Chung jh80.chung@samsung.com * Portions Copyright 2011 NVIDIA Corporation + * Portions Copyright (c) 2011 The Chromium OS Authors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,9 +21,12 @@ */
#include <common.h> +#include <linux/ctype.h> #include <mmc.h> #include <asm/io.h> #include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <malloc.h> #include "tegra2_mmc.h"
/* support 4 mmc hosts */ @@ -55,14 +59,105 @@ static inline struct tegra2_mmc *tegra2_get_base_mmc(int dev_index) return (struct tegra2_mmc *)(offset); }
-static void mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) +/* + * Ensure that the host's bounce buffer is large enough to hold the given + * mmc_data's buffer. On failure to reallocate the bounce buffer -1 is + * returned and the host is left in a consistent state. + */ +static int mmc_resize_bounce(struct mmc_host *host, struct mmc_data *data) +{ + uint new_bounce_size = data->blocks * data->blocksize; + + if (host->bounce) { + free(host->bounce); + + host->bounce_size = 0; + host->bounce = NULL; + } + + host->bounce = memalign(ARCH_DMA_MINALIGN, new_bounce_size); + + debug("mmc_resize_bounce: size(%d) bounce(%p)\n", + new_bounce_size, host->bounce); + + if (host->bounce == NULL) + return -1; + + host->bounce_size = new_bounce_size; + + return 0; +} + +/* + * Prepare the host's bounce buffer to proxy for the given mmc_data's buffer. + * If this is a write operation the contents of the mmc_data's buffer are + * copied to the bounce buffer. + */ +static int mmc_setup_bounce_data(struct mmc_host *host, struct mmc_data *data) +{ + size_t bytes = data->blocks * data->blocksize; + + if ((bytes > host->bounce_size) && (mmc_resize_bounce(host, data) < 0)) + return -1; + + host->bounce_data = *data; + host->bounce_data.dest = host->bounce; + + if (data->flags & MMC_DATA_WRITE) + memcpy(host->bounce_data.dest, data->src, bytes); + + return 0; +} + +/* + * Invalidate the cache lines covering the bounce buffer and copy the contents + * to the given mmc_data's buffer. This assumes that the hosts bounce buffer + * was initialized via mmc_setup_bounce_data. + */ +static void mmc_restore_bounce_data(struct mmc_host *host, + struct mmc_data *data) +{ + mmc_dcache_invalidate(&host->bounce_data); + + memcpy(data->dest, + host->bounce_data.src, + data->blocks * data->blocksize); +} + +static int mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) { unsigned char ctrl;
debug("data->dest: %08X, data->blocks: %u, data->blocksize: %u\n", (u32)data->dest, data->blocks, data->blocksize);
+ /* + * If the mmc_data's buffer is not aligned to a cache line boundary. + * We need to use an aligned bounce buffer because the cache + * invalidation code that would give us visibility into the read data + * after the DMA operation completes will flush the first and last few + * bytes of the unaligned buffer out to memory, overwriting the DMA'ed + * data. + */ + if (((uint)data->dest) & (ARCH_DMA_MINALIGN - 1)) { + printf("%s: Unaligned data, using slower bounce buffer\n", + __func__); + + if (mmc_setup_bounce_data(host, data) < 0) + return -1; + + data = &host->bounce_data; + } + + if (data->flags & MMC_DATA_WRITE) + mmc_dcache_flush(data); + + /* + * At this point the buffer referenced by data is guaranteed to be + * cache line size aligned. + */ writel((u32)data->dest, &host->reg->sysad); + /* * DMASEL[4:3] * 00 = Selects SDMA @@ -77,6 +172,24 @@ static void mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) /* We do not handle DMA boundaries, so set it to max (512 KiB) */ writew((7 << 12) | (data->blocksize & 0xFFF), &host->reg->blksize); writew(data->blocks, &host->reg->blkcnt); + + return 0; +} + +static void mmc_restore_data(struct mmc_host *host, struct mmc_data *data) +{ + /* + * If we performed a read then we need to invalidate the dcache lines + * that cover the DMA buffer. This might also require copying data + * back from the bounce buffer if the original mmc_data's buffer is + * unaligned. + */ + if (data->flags & MMC_DATA_READ) { + if (((uint)data->dest) & (ARCH_DMA_MINALIGN - 1)) + mmc_restore_bounce_data(host, data); + else + mmc_dcache_invalidate(data); + } }
static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data) @@ -151,8 +264,12 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, if (result < 0) return result;
- if (data) - mmc_prepare_data(host, data); + if (data) { + result = mmc_prepare_data(host, data); + + if (result < 0) + return result; + }
debug("cmd->arg: %08x\n", cmd->cmdarg); writel(cmd->cmdarg, &host->reg->argument); @@ -186,8 +303,10 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
if (cmd->resp_type & MMC_RSP_CRC) flags |= (1 << 3); + if (cmd->resp_type & MMC_RSP_OPCODE) flags |= (1 << 4); + if (data) flags |= (1 << 5);
@@ -300,6 +419,10 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, }
udelay(1000); + + if (data) + mmc_restore_data(host, data); + return 0; }
@@ -529,6 +652,9 @@ static int tegra2_mmc_initialize(int dev_index, int bus_width) mmc_host[dev_index].clock = 0; mmc_host[dev_index].reg = tegra2_get_base_mmc(dev_index); mmc_host[dev_index].base = (unsigned int)mmc_host[dev_index].reg; + mmc_host[dev_index].bounce = NULL; + mmc_host[dev_index].bounce_size = 0; + mmc_register(mmc);
return 0; diff --git a/drivers/mmc/tegra2_mmc.h b/drivers/mmc/tegra2_mmc.h index 4b80f9f..9da1368 100644 --- a/drivers/mmc/tegra2_mmc.h +++ b/drivers/mmc/tegra2_mmc.h @@ -2,6 +2,7 @@ * (C) Copyright 2009 SAMSUNG Electronics * Minkyu Kang mk7.kang@samsung.com * Portions Copyright (C) 2011 NVIDIA Corporation + * Portions Copyright (c) 2011 The Chromium OS Authors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +23,8 @@ #ifndef __TEGRA2_MMC_H_ #define __TEGRA2_MMC_H_
+#include <mmc.h> + #define TEGRA2_SDMMC1_BASE 0xC8000000 #define TEGRA2_SDMMC2_BASE 0xC8000200 #define TEGRA2_SDMMC3_BASE 0xC8000400 @@ -73,6 +76,17 @@ struct mmc_host { unsigned int version; /* SDHCI spec. version */ unsigned int clock; /* Current clock (MHz) */ unsigned int base; /* Base address, SDMMC1/2/3/4 */ + + /* + * We need a per-host bounce buffer that will be optionally used + * when the mmc_send_cmd function is called with an unaligned + * buffer. The bounce buffer will be allocated in that case and + * a copy to and from it will be used so that DMA destination and + * source pointers can be aligned. + */ + char *bounce; + uint bounce_size; + struct mmc_data bounce_data; };
int tegra2_mmc_init(int dev_index, int bus_width);

On Thu, Oct 13, 2011 at 2:57 PM, Anton Staaf robotboy@chromium.org wrote:
When an unaligned buffer is used for DMA the first and last few bytes of the buffer would be clobbered by the dcache invalidate call that is required to make the contents of the buffer visible to the CPU post DMA. The U-Boot cache invalidation and flushing code checks for these sorts of alignment violations and only operates on the aligned portion and generates an error message to indicate that the buffer was not aligned correctly. This obviously can results in incorrect values at the beginning and end of a DMA trasfered buffer. Until all buffers passed to the MMC layer are correctly aligned the MMC driver code must deal with these unaligned situations if it wishes to function correctly.
This patch adds a DMA bounce buffer to the Tegra MMC driver code. This buffer is only used if an unaligned buffer is used for reading or writing. This solution requires an extra memcpy to or from the bounce buffer and has a high water mark in memory consumption.
I forgot to mention, this patch requires that the ARCH_DMA_MINALIGN patchset be applied to your tree first.
Signed-off-by: Anton Staaf robotboy@chromium.org Cc: Tom Warren twarren@nvidia.com Cc: Stephen Warren swarren@nvidia.com
drivers/mmc/tegra2_mmc.c | 132 ++++++++++++++++++++++++++++++++++++++++++++- drivers/mmc/tegra2_mmc.h | 14 +++++ 2 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/drivers/mmc/tegra2_mmc.c b/drivers/mmc/tegra2_mmc.c index 141429e..523bc6a 100644 --- a/drivers/mmc/tegra2_mmc.c +++ b/drivers/mmc/tegra2_mmc.c @@ -3,6 +3,7 @@ * Minkyu Kang mk7.kang@samsung.com * Jaehoon Chung jh80.chung@samsung.com * Portions Copyright 2011 NVIDIA Corporation
- Portions Copyright (c) 2011 The Chromium OS Authors
* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,9 +21,12 @@ */
#include <common.h> +#include <linux/ctype.h> #include <mmc.h> #include <asm/io.h> #include <asm/arch/clk_rst.h> +#include <asm/arch/clock.h> +#include <malloc.h> #include "tegra2_mmc.h"
/* support 4 mmc hosts */ @@ -55,14 +59,105 @@ static inline struct tegra2_mmc *tegra2_get_base_mmc(int dev_index) return (struct tegra2_mmc *)(offset); }
-static void mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) +/*
- Ensure that the host's bounce buffer is large enough to hold the given
- mmc_data's buffer. On failure to reallocate the bounce buffer -1 is
- returned and the host is left in a consistent state.
- */
+static int mmc_resize_bounce(struct mmc_host *host, struct mmc_data *data) +{
- uint new_bounce_size = data->blocks * data->blocksize;
- if (host->bounce) {
- free(host->bounce);
- host->bounce_size = 0;
- host->bounce = NULL;
- }
- host->bounce = memalign(ARCH_DMA_MINALIGN, new_bounce_size);
- debug("mmc_resize_bounce: size(%d) bounce(%p)\n",
- new_bounce_size, host->bounce);
- if (host->bounce == NULL)
- return -1;
- host->bounce_size = new_bounce_size;
- return 0;
+}
+/*
- Prepare the host's bounce buffer to proxy for the given mmc_data's buffer.
- If this is a write operation the contents of the mmc_data's buffer are
- copied to the bounce buffer.
- */
+static int mmc_setup_bounce_data(struct mmc_host *host, struct mmc_data *data) +{
- size_t bytes = data->blocks * data->blocksize;
- if ((bytes > host->bounce_size) && (mmc_resize_bounce(host, data) < 0))
- return -1;
- host->bounce_data = *data;
- host->bounce_data.dest = host->bounce;
- if (data->flags & MMC_DATA_WRITE)
- memcpy(host->bounce_data.dest, data->src, bytes);
- return 0;
+}
+/*
- Invalidate the cache lines covering the bounce buffer and copy the contents
- to the given mmc_data's buffer. This assumes that the hosts bounce buffer
- was initialized via mmc_setup_bounce_data.
- */
+static void mmc_restore_bounce_data(struct mmc_host *host,
- struct mmc_data *data)
+{
- mmc_dcache_invalidate(&host->bounce_data);
- memcpy(data->dest,
- host->bounce_data.src,
- data->blocks * data->blocksize);
+}
+static int mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) { unsigned char ctrl;
debug("data->dest: %08X, data->blocks: %u, data->blocksize: %u\n", (u32)data->dest, data->blocks, data->blocksize);
- /*
- * If the mmc_data's buffer is not aligned to a cache line boundary.
- * We need to use an aligned bounce buffer because the cache
- * invalidation code that would give us visibility into the read data
- * after the DMA operation completes will flush the first and last few
- * bytes of the unaligned buffer out to memory, overwriting the DMA'ed
- * data.
- */
- if (((uint)data->dest) & (ARCH_DMA_MINALIGN - 1)) {
- printf("%s: Unaligned data, using slower bounce buffer\n",
- __func__);
- if (mmc_setup_bounce_data(host, data) < 0)
- return -1;
- data = &host->bounce_data;
- }
- if (data->flags & MMC_DATA_WRITE)
- mmc_dcache_flush(data);
- /*
- * At this point the buffer referenced by data is guaranteed to be
- * cache line size aligned.
- */
writel((u32)data->dest, &host->reg->sysad);
/* * DMASEL[4:3] * 00 = Selects SDMA @@ -77,6 +172,24 @@ static void mmc_prepare_data(struct mmc_host *host, struct mmc_data *data) /* We do not handle DMA boundaries, so set it to max (512 KiB) */ writew((7 << 12) | (data->blocksize & 0xFFF), &host->reg->blksize); writew(data->blocks, &host->reg->blkcnt);
- return 0;
+}
+static void mmc_restore_data(struct mmc_host *host, struct mmc_data *data) +{
- /*
- * If we performed a read then we need to invalidate the dcache lines
- * that cover the DMA buffer. This might also require copying data
- * back from the bounce buffer if the original mmc_data's buffer is
- * unaligned.
- */
- if (data->flags & MMC_DATA_READ) {
- if (((uint)data->dest) & (ARCH_DMA_MINALIGN - 1))
- mmc_restore_bounce_data(host, data);
- else
- mmc_dcache_invalidate(data);
- }
}
static void mmc_set_transfer_mode(struct mmc_host *host, struct mmc_data *data) @@ -151,8 +264,12 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, if (result < 0) return result;
- if (data)
- mmc_prepare_data(host, data);
- if (data) {
- result = mmc_prepare_data(host, data);
- if (result < 0)
- return result;
- }
debug("cmd->arg: %08x\n", cmd->cmdarg); writel(cmd->cmdarg, &host->reg->argument); @@ -186,8 +303,10 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
if (cmd->resp_type & MMC_RSP_CRC) flags |= (1 << 3);
if (cmd->resp_type & MMC_RSP_OPCODE) flags |= (1 << 4);
if (data) flags |= (1 << 5);
@@ -300,6 +419,10 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, }
udelay(1000);
- if (data)
- mmc_restore_data(host, data);
return 0; }
@@ -529,6 +652,9 @@ static int tegra2_mmc_initialize(int dev_index, int bus_width) mmc_host[dev_index].clock = 0; mmc_host[dev_index].reg = tegra2_get_base_mmc(dev_index); mmc_host[dev_index].base = (unsigned int)mmc_host[dev_index].reg;
- mmc_host[dev_index].bounce = NULL;
- mmc_host[dev_index].bounce_size = 0;
mmc_register(mmc);
return 0; diff --git a/drivers/mmc/tegra2_mmc.h b/drivers/mmc/tegra2_mmc.h index 4b80f9f..9da1368 100644 --- a/drivers/mmc/tegra2_mmc.h +++ b/drivers/mmc/tegra2_mmc.h @@ -2,6 +2,7 @@ * (C) Copyright 2009 SAMSUNG Electronics * Minkyu Kang mk7.kang@samsung.com * Portions Copyright (C) 2011 NVIDIA Corporation
- Portions Copyright (c) 2011 The Chromium OS Authors
* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +23,8 @@ #ifndef __TEGRA2_MMC_H_ #define __TEGRA2_MMC_H_
+#include <mmc.h>
#define TEGRA2_SDMMC1_BASE 0xC8000000 #define TEGRA2_SDMMC2_BASE 0xC8000200 #define TEGRA2_SDMMC3_BASE 0xC8000400 @@ -73,6 +76,17 @@ struct mmc_host { unsigned int version; /* SDHCI spec. version */ unsigned int clock; /* Current clock (MHz) */ unsigned int base; /* Base address, SDMMC1/2/3/4 */
- /*
- * We need a per-host bounce buffer that will be optionally used
- * when the mmc_send_cmd function is called with an unaligned
- * buffer. The bounce buffer will be allocated in that case and
- * a copy to and from it will be used so that DMA destination and
- * source pointers can be aligned.
- */
- char *bounce;
- uint bounce_size;
- struct mmc_data bounce_data;
};
int tegra2_mmc_init(int dev_index, int bus_width);
1.7.3.1
participants (2)
-
Andy Fleming
-
Anton Staaf