| From 157c99c5a2956a9ab1ae12de0136a2d8a1b1a307 Mon Sep 17 00:00:00 2001 |
| From: Daniel Drake <drake@endlessm.com> |
| Date: Tue, 26 Mar 2019 15:04:14 +0800 |
| Subject: mmc: alcor: don't write data before command has completed |
| |
| From: Daniel Drake <drake@endlessm.com> |
| |
| commit 157c99c5a2956a9ab1ae12de0136a2d8a1b1a307 upstream. |
| |
| The alcor driver is setting up data transfer and submitting the associated |
| MMC command at the same time. While this works most of the time, it |
| occasionally causes problems upon write. |
| |
| In the working case, after setting up the data transfer and submitting |
| the MMC command, an interrupt comes in a moment later with CMD_END and |
| WRITE_BUF_RDY bits set. The data transfer then happens without problem. |
| |
| However, on occasion, the interrupt that arrives at that point only |
| has WRITE_BUF_RDY set. The hardware notifies that it's ready to write |
| data, but the associated MMC command is still running. Regardless, the |
| driver was proceeding to write data immediately, and that would then cause |
| another interrupt indicating data CRC error, and the write would fail. |
| |
| Additionally, the transfer setup function alcor_trigger_data_transfer() |
| was being called 3 times for each write operation, which was confusing |
| and may be contributing to this issue. |
| |
| Solve this by tweaking the driver behaviour to follow the sequence observed |
| in the original ampe_stor vendor driver: |
| 1. When starting request handling, write 0 to DATA_XFER_CTRL |
| 2. Submit the command |
| 3. Wait for CMD_END interrupt and then trigger data transfer |
| 4. For the PIO case, trigger the next step of the data transfer only |
| upon the following DATA_END interrupt, which occurs after the block has |
| been written. |
| |
| I confirmed that the read path still works (DMA & PIO) and also now |
| presents more consistency with the operations performed by ampe_stor. |
| |
| Signed-off-by: Daniel Drake <drake@endlessm.com> |
| Fixes: c5413ad815a6 ("mmc: add new Alcor Micro Cardreader SD/MMC driver") |
| Cc: stable@vger.kernel.org |
| Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/mmc/host/alcor.c | 34 +++++++++++++--------------------- |
| 1 file changed, 13 insertions(+), 21 deletions(-) |
| |
| --- a/drivers/mmc/host/alcor.c |
| +++ b/drivers/mmc/host/alcor.c |
| @@ -48,7 +48,6 @@ struct alcor_sdmmc_host { |
| struct mmc_command *cmd; |
| struct mmc_data *data; |
| unsigned int dma_on:1; |
| - unsigned int early_data:1; |
| |
| struct mutex cmd_mutex; |
| |
| @@ -144,8 +143,7 @@ static void alcor_data_set_dma(struct al |
| host->sg_count--; |
| } |
| |
| -static void alcor_trigger_data_transfer(struct alcor_sdmmc_host *host, |
| - bool early) |
| +static void alcor_trigger_data_transfer(struct alcor_sdmmc_host *host) |
| { |
| struct alcor_pci_priv *priv = host->alcor_pci; |
| struct mmc_data *data = host->data; |
| @@ -155,13 +153,6 @@ static void alcor_trigger_data_transfer( |
| ctrl |= AU6601_DATA_WRITE; |
| |
| if (data->host_cookie == COOKIE_MAPPED) { |
| - if (host->early_data) { |
| - host->early_data = false; |
| - return; |
| - } |
| - |
| - host->early_data = early; |
| - |
| alcor_data_set_dma(host); |
| ctrl |= AU6601_DATA_DMA_MODE; |
| host->dma_on = 1; |
| @@ -231,6 +222,7 @@ static void alcor_prepare_sg_miter(struc |
| static void alcor_prepare_data(struct alcor_sdmmc_host *host, |
| struct mmc_command *cmd) |
| { |
| + struct alcor_pci_priv *priv = host->alcor_pci; |
| struct mmc_data *data = cmd->data; |
| |
| if (!data) |
| @@ -248,7 +240,7 @@ static void alcor_prepare_data(struct al |
| if (data->host_cookie != COOKIE_MAPPED) |
| alcor_prepare_sg_miter(host); |
| |
| - alcor_trigger_data_transfer(host, true); |
| + alcor_write8(priv, 0, AU6601_DATA_XFER_CTRL); |
| } |
| |
| static void alcor_send_cmd(struct alcor_sdmmc_host *host, |
| @@ -435,7 +427,7 @@ static int alcor_cmd_irq_done(struct alc |
| if (!host->data) |
| return false; |
| |
| - alcor_trigger_data_transfer(host, false); |
| + alcor_trigger_data_transfer(host); |
| host->cmd = NULL; |
| return true; |
| } |
| @@ -456,7 +448,7 @@ static void alcor_cmd_irq_thread(struct |
| if (!host->data) |
| alcor_request_complete(host, 1); |
| else |
| - alcor_trigger_data_transfer(host, false); |
| + alcor_trigger_data_transfer(host); |
| host->cmd = NULL; |
| } |
| |
| @@ -487,15 +479,9 @@ static int alcor_data_irq_done(struct al |
| break; |
| case AU6601_INT_READ_BUF_RDY: |
| alcor_trf_block_pio(host, true); |
| - if (!host->blocks) |
| - break; |
| - alcor_trigger_data_transfer(host, false); |
| return 1; |
| case AU6601_INT_WRITE_BUF_RDY: |
| alcor_trf_block_pio(host, false); |
| - if (!host->blocks) |
| - break; |
| - alcor_trigger_data_transfer(host, false); |
| return 1; |
| case AU6601_INT_DMA_END: |
| if (!host->sg_count) |
| @@ -508,8 +494,14 @@ static int alcor_data_irq_done(struct al |
| break; |
| } |
| |
| - if (intmask & AU6601_INT_DATA_END) |
| - return 0; |
| + if (intmask & AU6601_INT_DATA_END) { |
| + if (!host->dma_on && host->blocks) { |
| + alcor_trigger_data_transfer(host); |
| + return 1; |
| + } else { |
| + return 0; |
| + } |
| + } |
| |
| return 1; |
| } |