mmc: Unirqify bcm2835_sdhost and fix writes
authorAlexander Graf <agraf@suse.de>
Wed, 23 May 2018 20:24:51 +0000 (22:24 +0200)
committerAlexander Graf <agraf@suse.de>
Wed, 23 May 2018 20:31:42 +0000 (22:31 +0200)
The bcm2835 sdhost driver has a problem with "write multiple" commands.
It seems to boil down to the fact that the controller dislikes its FIFO
to get drained at the end of a block when a write multiple blocks command
is in flight.

The easy fix is to simply get rid of all the IRQ driven logic and make
the driver push as much data into the FIFO as it can. That way we never
drain and we never run into the problem.

Reported-by: Jan Leonhardt <jan@cyberdesigner.net>
Signed-off-by: Alexander Graf <agraf@suse.de>
drivers/mmc/bcm2835_sdhost.c

index 96428333b0abb2d28f3d845998fa2d44a70ce9ef..1ce019af579fb5b1e06bb04ac816733678bb51cf 100644 (file)
@@ -163,7 +163,6 @@ struct bcm2835_host {
        int                     clock;          /* Current clock speed */
        unsigned int            max_clk;        /* Max possible freq */
        unsigned int            blocks;         /* remaining PIO blocks */
-       int                     irq;            /* Device IRQ */
 
        u32                     ns_per_fifo_word;
 
@@ -173,14 +172,7 @@ struct bcm2835_host {
 
        struct mmc_cmd  *cmd;           /* Current command */
        struct mmc_data         *data;          /* Current data request */
-       bool                    data_complete:1;/* Data finished before cmd */
        bool                    use_busy:1;     /* Wait for busy interrupt */
-       bool                    wait_data_complete:1;   /* Wait for data */
-
-       /* for threaded irq handler */
-       bool                    irq_block;
-       bool                    irq_busy;
-       bool                    irq_data;
 
        struct udevice          *dev;
        struct mmc              *mmc;
@@ -240,17 +232,9 @@ static void bcm2835_reset_internal(struct bcm2835_host *host)
        writel(host->cdiv, host->ioaddr + SDCDIV);
 }
 
-static int bcm2835_finish_command(struct bcm2835_host *host);
-
-static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
+static int bcm2835_wait_transfer_complete(struct bcm2835_host *host)
 {
-       int timediff;
-       u32 alternate_idle;
-
-       alternate_idle = (host->data->flags & MMC_DATA_READ) ?
-               SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1;
-
-       timediff = 0;
+       int timediff = 0;
 
        while (1) {
                u32 edm, fsm;
@@ -261,7 +245,10 @@ static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
                if ((fsm == SDEDM_FSM_IDENTMODE) ||
                    (fsm == SDEDM_FSM_DATAMODE))
                        break;
-               if (fsm == alternate_idle) {
+
+               if ((fsm == SDEDM_FSM_READWAIT) ||
+                   (fsm == SDEDM_FSM_WRITESTART1) ||
+                   (fsm == SDEDM_FSM_READDATA)) {
                        writel(edm | SDEDM_FORCE_DATA_MODE,
                               host->ioaddr + SDEDM);
                        break;
@@ -273,9 +260,11 @@ static void bcm2835_wait_transfer_complete(struct bcm2835_host *host)
                                "wait_transfer_complete - still waiting after %d retries\n",
                                timediff);
                        bcm2835_dumpregs(host);
-                       return;
+                       return -ETIMEDOUT;
                }
        }
+
+       return 0;
 }
 
 static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read)
@@ -322,6 +311,9 @@ static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read)
                              fsm_state != SDEDM_FSM_READCRC)) ||
                            (!is_read &&
                             (fsm_state != SDEDM_FSM_WRITEDATA &&
+                             fsm_state != SDEDM_FSM_WRITEWAIT1 &&
+                             fsm_state != SDEDM_FSM_WRITEWAIT2 &&
+                             fsm_state != SDEDM_FSM_WRITECRC &&
                              fsm_state != SDEDM_FSM_WRITESTART1 &&
                              fsm_state != SDEDM_FSM_WRITESTART2))) {
                                hsts = readl(host->ioaddr + SDHSTS);
@@ -358,9 +350,8 @@ static int bcm2835_transfer_pio(struct bcm2835_host *host)
 
        is_read = (host->data->flags & MMC_DATA_READ) != 0;
        ret = bcm2835_transfer_block_pio(host, is_read);
-
-       if (host->wait_data_complete)
-               bcm2835_wait_transfer_complete(host);
+       if (ret)
+               return ret;
 
        sdhsts = readl(host->ioaddr + SDHSTS);
        if (sdhsts & (SDHSTS_CRC16_ERROR |
@@ -379,21 +370,8 @@ static int bcm2835_transfer_pio(struct bcm2835_host *host)
        return ret;
 }
 
-static void bcm2835_set_transfer_irqs(struct bcm2835_host *host)
-{
-       u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN |
-               SDHCFG_BUSY_IRPT_EN;
-
-       host->hcfg = (host->hcfg & ~all_irqs) |
-               SDHCFG_DATA_IRPT_EN |
-               SDHCFG_BUSY_IRPT_EN;
-
-       writel(host->hcfg, host->ioaddr + SDHCFG);
-}
-
-static
-void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
-                         struct mmc_data *data)
+static void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
+                                struct mmc_data *data)
 {
        WARN_ON(host->data);
 
@@ -401,14 +379,9 @@ void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd,
        if (!data)
                return;
 
-       host->wait_data_complete = cmd->cmdidx != MMC_CMD_READ_MULTIPLE_BLOCK;
-       host->data_complete = false;
-
        /* Use PIO */
        host->blocks = data->blocks;
 
-       bcm2835_set_transfer_irqs(host);
-
        writel(data->blocksize, host->ioaddr + SDHBCT);
        writel(data->blocks, host->ioaddr + SDHBLC);
 }
@@ -483,36 +456,6 @@ static int bcm2835_send_command(struct bcm2835_host *host, struct mmc_cmd *cmd,
        return 0;
 }
 
-static int bcm2835_transfer_complete(struct bcm2835_host *host)
-{
-       int ret = 0;
-
-       WARN_ON(!host->data_complete);
-
-       host->data = NULL;
-
-       return ret;
-}
-
-static void bcm2835_finish_data(struct bcm2835_host *host)
-{
-       host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
-       writel(host->hcfg, host->ioaddr + SDHCFG);
-
-       host->data_complete = true;
-
-       if (host->cmd) {
-               /* Data managed to finish before the
-                * command completed. Make sure we do
-                * things in the proper order.
-                */
-               dev_dbg(dev, "Finished early - HSTS %08x\n",
-                       readl(host->ioaddr + SDHSTS));
-       } else {
-               bcm2835_transfer_complete(host);
-       }
-}
-
 static int bcm2835_finish_command(struct bcm2835_host *host)
 {
        struct mmc_cmd *cmd = host->cmd;
@@ -562,8 +505,6 @@ static int bcm2835_finish_command(struct bcm2835_host *host)
 
        /* Processed actual command. */
        host->cmd = NULL;
-       if (host->data && host->data_complete)
-               ret = bcm2835_transfer_complete(host);
 
        return ret;
 }
@@ -608,159 +549,44 @@ static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask)
        return ret;
 }
 
-static void bcm2835_busy_irq(struct bcm2835_host *host)
-{
-       if (WARN_ON(!host->cmd)) {
-               bcm2835_dumpregs(host);
-               return;
-       }
-
-       if (WARN_ON(!host->use_busy)) {
-               bcm2835_dumpregs(host);
-               return;
-       }
-       host->use_busy = false;
-
-       bcm2835_finish_command(host);
-}
-
-static void bcm2835_data_irq(struct bcm2835_host *host, u32 intmask)
+static int bcm2835_transmit(struct bcm2835_host *host)
 {
+       u32 intmask = readl(host->ioaddr + SDHSTS);
        int ret;
 
-       /*
-        * There are no dedicated data/space available interrupt
-        * status bits, so it is necessary to use the single shared
-        * data/space available FIFO status bits. It is therefore not
-        * an error to get here when there is no data transfer in
-        * progress.
-        */
-       if (!host->data)
-               return;
-
+       /* Check for errors */
        ret = bcm2835_check_data_error(host, intmask);
        if (ret)
-               goto finished;
-
-       if (host->data->flags & MMC_DATA_WRITE) {
-               /* Use the block interrupt for writes after the first block */
-               host->hcfg &= ~(SDHCFG_DATA_IRPT_EN);
-               host->hcfg |= SDHCFG_BLOCK_IRPT_EN;
-               writel(host->hcfg, host->ioaddr + SDHCFG);
-               bcm2835_transfer_pio(host);
-       } else {
-               bcm2835_transfer_pio(host);
-               host->blocks--;
-               if ((host->blocks == 0))
-                       goto finished;
-       }
-       return;
+               return ret;
 
-finished:
-       host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
-       writel(host->hcfg, host->ioaddr + SDHCFG);
-}
-
-static void bcm2835_data_threaded_irq(struct bcm2835_host *host)
-{
-       if (!host->data)
-               return;
-       if ((host->blocks == 0))
-               bcm2835_finish_data(host);
-}
-
-static void bcm2835_block_irq(struct bcm2835_host *host)
-{
-       if (WARN_ON(!host->data)) {
-               bcm2835_dumpregs(host);
-               return;
-       }
-
-       WARN_ON(!host->blocks);
-       if ((--host->blocks == 0))
-               bcm2835_finish_data(host);
-       else
-               bcm2835_transfer_pio(host);
-}
+       ret = bcm2835_check_cmd_error(host, intmask);
+       if (ret)
+               return ret;
 
-static irqreturn_t bcm2835_irq(int irq, void *dev_id)
-{
-       irqreturn_t result = IRQ_NONE;
-       struct bcm2835_host *host = dev_id;
-       u32 intmask;
-
-       intmask = readl(host->ioaddr + SDHSTS);
-
-       writel(SDHSTS_BUSY_IRPT |
-              SDHSTS_BLOCK_IRPT |
-              SDHSTS_SDIO_IRPT |
-              SDHSTS_DATA_FLAG,
-              host->ioaddr + SDHSTS);
-
-       if (intmask & SDHSTS_BLOCK_IRPT) {
-               bcm2835_check_data_error(host, intmask);
-               host->irq_block = true;
-               result = IRQ_WAKE_THREAD;
+       /* Handle wait for busy end */
+       if (host->use_busy && (intmask & SDHSTS_BUSY_IRPT)) {
+               writel(SDHSTS_BUSY_IRPT, host->ioaddr + SDHSTS);
+               host->use_busy = false;
+               bcm2835_finish_command(host);
        }
 
-       if (intmask & SDHSTS_BUSY_IRPT) {
-               if (!bcm2835_check_cmd_error(host, intmask)) {
-                       host->irq_busy = true;
-                       result = IRQ_WAKE_THREAD;
-               } else {
-                       result = IRQ_HANDLED;
+       /* Handle PIO data transfer */
+       if (host->data) {
+               ret = bcm2835_transfer_pio(host);
+               if (ret)
+                       return ret;
+               host->blocks--;
+               if (host->blocks == 0) {
+                       /* Wait for command to complete for real */
+                       ret = bcm2835_wait_transfer_complete(host);
+                       if (ret)
+                               return ret;
+                       /* Transfer complete */
+                       host->data = NULL;
                }
        }
 
-       /* There is no true data interrupt status bit, so it is
-        * necessary to qualify the data flag with the interrupt
-        * enable bit.
-        */
-       if ((intmask & SDHSTS_DATA_FLAG) &&
-           (host->hcfg & SDHCFG_DATA_IRPT_EN)) {
-               bcm2835_data_irq(host, intmask);
-               host->irq_data = true;
-               result = IRQ_WAKE_THREAD;
-       }
-
-       return result;
-}
-
-static irqreturn_t bcm2835_threaded_irq(int irq, void *dev_id)
-{
-       struct bcm2835_host *host = dev_id;
-
-       if (host->irq_block) {
-               host->irq_block = false;
-               bcm2835_block_irq(host);
-       }
-
-       if (host->irq_busy) {
-               host->irq_busy = false;
-               bcm2835_busy_irq(host);
-       }
-
-       if (host->irq_data) {
-               host->irq_data = false;
-               bcm2835_data_threaded_irq(host);
-       }
-
-       return IRQ_HANDLED;
-}
-
-static void bcm2835_irq_poll(struct bcm2835_host *host)
-{
-       u32 intmask;
-
-       while (1) {
-               intmask = readl(host->ioaddr + SDHSTS);
-               if (intmask & (SDHSTS_BUSY_IRPT | SDHSTS_BLOCK_IRPT |
-                              SDHSTS_SDIO_IRPT | SDHSTS_DATA_FLAG)) {
-                       bcm2835_irq(0, host);
-                       bcm2835_threaded_irq(0, host);
-                       return;
-               }
-       }
+       return 0;
 }
 
 static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
@@ -864,8 +690,11 @@ static int bcm2835_send_cmd(struct udevice *dev, struct mmc_cmd *cmd,
        }
 
        /* Wait for completion of busy signal or data transfer */
-       while (host->use_busy || host->data)
-               bcm2835_irq_poll(host);
+       while (host->use_busy || host->data) {
+               ret = bcm2835_transmit(host);
+               if (ret)
+                       break;
+       }
 
        return ret;
 }