sdhci: handle data interrupts during command
authorPierre Ossman <drzeus@drzeus.cx>
Sun, 12 Aug 2007 14:46:32 +0000 (16:46 +0200)
committerPierre Ossman <drzeus@drzeus.cx>
Thu, 23 Aug 2007 04:30:53 +0000 (06:30 +0200)
It is fully legal for a controller to start issuing data related
interrupts before it has signalled that the command has completed.
Make sure the driver actually can handle this.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index 2b327b40fa8124953e70d86f4f1d6c1c5336c8df..f8fc0a98b8c40d18274566afa6aeaae51e0a362d 100644 (file)
@@ -385,6 +385,9 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
        BUG_ON(data->blksz > host->mmc->max_blk_size);
        BUG_ON(data->blocks > 65535);
 
+       host->data = data;
+       host->data_early = 0;
+
        /* timeout in us */
        target_timeout = data->timeout_ns / 1000 +
                data->timeout_clks / host->clock;
@@ -443,11 +446,11 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
 {
        u16 mode;
 
-       WARN_ON(host->data);
-
        if (data == NULL)
                return;
 
+       WARN_ON(!host->data);
+
        mode = SDHCI_TRNS_BLK_CNT_EN;
        if (data->blocks > 1)
                mode |= SDHCI_TRNS_MULTI;
@@ -600,9 +603,10 @@ static void sdhci_finish_command(struct sdhci_host *host)
 
        host->cmd->error = MMC_ERR_NONE;
 
-       if (host->cmd->data)
-               host->data = host->cmd->data;
-       else
+       if (host->data && host->data_early)
+               sdhci_finish_data(host);
+
+       if (!host->cmd->data)
                tasklet_schedule(&host->finish_tasklet);
 
        host->cmd = NULL;
@@ -991,8 +995,18 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
                        writel(readl(host->ioaddr + SDHCI_DMA_ADDRESS),
                                host->ioaddr + SDHCI_DMA_ADDRESS);
 
-               if (intmask & SDHCI_INT_DATA_END)
-                       sdhci_finish_data(host);
+               if (intmask & SDHCI_INT_DATA_END) {
+                       if (host->cmd) {
+                               /*
+                                * Data managed to finish before the
+                                * command completed. Make sure we do
+                                * things in the proper order.
+                                */
+                               host->data_early = 1;
+                       } else {
+                               sdhci_finish_data(host);
+                       }
+               }
        }
 }
 
index d157776c11491d1f58d7c255acf8dd03d448a1f7..e28987d6d2ebf4f12d9ea180916d811571fc63e2 100644 (file)
@@ -182,6 +182,7 @@ struct sdhci_host {
        struct mmc_request      *mrq;           /* Current request */
        struct mmc_command      *cmd;           /* Current command */
        struct mmc_data         *data;          /* Current data request */
+       int                     data_early:1;   /* Data finished before cmd */
 
        struct scatterlist      *cur_sg;        /* We're working on this */
        int                     num_sg;         /* Entries left */