mmc: sdhci: Add CQE support
authorAdrian Hunter <adrian.hunter@intel.com>
Mon, 20 Mar 2017 17:50:47 +0000 (19:50 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Mon, 24 Apr 2017 19:41:36 +0000 (21:41 +0200)
Add an interrupt hook and helper functions for enabling, disabling and
delivering interrupts to a CQE.

Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Tested-by: Ludovic Desroches <ludovic.desroches@microchip.com>
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h

index 176e07f..a971fcf 100644 (file)
@@ -252,6 +252,8 @@ static void sdhci_init(struct sdhci_host *host, int soft)
 
        sdhci_set_default_irqs(host);
 
+       host->cqe_on = false;
+
        if (soft) {
                /* force clock reconfiguration */
                host->clock = 0;
@@ -2672,13 +2674,19 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
        }
 
        do {
+               DBG("IRQ status 0x%08x\n", intmask);
+
+               if (host->ops->irq) {
+                       intmask = host->ops->irq(host, intmask);
+                       if (!intmask)
+                               goto cont;
+               }
+
                /* Clear selected interrupts. */
                mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
                                  SDHCI_INT_BUS_POWER);
                sdhci_writel(host, mask, SDHCI_INT_STATUS);
 
-               DBG("IRQ status 0x%08x\n", intmask);
-
                if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
                        u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
                                      SDHCI_CARD_PRESENT;
@@ -2738,7 +2746,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
                        unexpected |= intmask;
                        sdhci_writel(host, intmask, SDHCI_INT_STATUS);
                }
-
+cont:
                if (result == IRQ_NONE)
                        result = IRQ_HANDLED;
 
@@ -2967,6 +2975,119 @@ EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host);
 
 /*****************************************************************************\
  *                                                                           *
+ * Command Queue Engine (CQE) helpers                                        *
+ *                                                                           *
+\*****************************************************************************/
+
+void sdhci_cqe_enable(struct mmc_host *mmc)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+       unsigned long flags;
+       u8 ctrl;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+       ctrl &= ~SDHCI_CTRL_DMA_MASK;
+       if (host->flags & SDHCI_USE_64_BIT_DMA)
+               ctrl |= SDHCI_CTRL_ADMA64;
+       else
+               ctrl |= SDHCI_CTRL_ADMA32;
+       sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+
+       sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 512),
+                    SDHCI_BLOCK_SIZE);
+
+       /* Set maximum timeout */
+       sdhci_writeb(host, 0xE, SDHCI_TIMEOUT_CONTROL);
+
+       host->ier = host->cqe_ier;
+
+       sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+       sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+
+       host->cqe_on = true;
+
+       pr_debug("%s: sdhci: CQE on, IRQ mask %#x, IRQ status %#x\n",
+                mmc_hostname(mmc), host->ier,
+                sdhci_readl(host, SDHCI_INT_STATUS));
+
+       mmiowb();
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+EXPORT_SYMBOL_GPL(sdhci_cqe_enable);
+
+void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery)
+{
+       struct sdhci_host *host = mmc_priv(mmc);
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       sdhci_set_default_irqs(host);
+
+       host->cqe_on = false;
+
+       if (recovery) {
+               sdhci_do_reset(host, SDHCI_RESET_CMD);
+               sdhci_do_reset(host, SDHCI_RESET_DATA);
+       }
+
+       pr_debug("%s: sdhci: CQE off, IRQ mask %#x, IRQ status %#x\n",
+                mmc_hostname(mmc), host->ier,
+                sdhci_readl(host, SDHCI_INT_STATUS));
+
+       mmiowb();
+       spin_unlock_irqrestore(&host->lock, flags);
+}
+EXPORT_SYMBOL_GPL(sdhci_cqe_disable);
+
+bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
+                  int *data_error)
+{
+       u32 mask;
+
+       if (!host->cqe_on)
+               return false;
+
+       if (intmask & (SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC))
+               *cmd_error = -EILSEQ;
+       else if (intmask & SDHCI_INT_TIMEOUT)
+               *cmd_error = -ETIMEDOUT;
+       else
+               *cmd_error = 0;
+
+       if (intmask & (SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC))
+               *data_error = -EILSEQ;
+       else if (intmask & SDHCI_INT_DATA_TIMEOUT)
+               *data_error = -ETIMEDOUT;
+       else if (intmask & SDHCI_INT_ADMA_ERROR)
+               *data_error = -EIO;
+       else
+               *data_error = 0;
+
+       /* Clear selected interrupts. */
+       mask = intmask & host->cqe_ier;
+       sdhci_writel(host, mask, SDHCI_INT_STATUS);
+
+       if (intmask & SDHCI_INT_BUS_POWER)
+               pr_err("%s: Card is consuming too much power!\n",
+                      mmc_hostname(host->mmc));
+
+       intmask &= ~(host->cqe_ier | SDHCI_INT_ERROR);
+       if (intmask) {
+               sdhci_writel(host, intmask, SDHCI_INT_STATUS);
+               pr_err("%s: CQE: Unexpected interrupt 0x%08x.\n",
+                      mmc_hostname(host->mmc), intmask);
+               sdhci_dumpregs(host);
+       }
+
+       return true;
+}
+EXPORT_SYMBOL_GPL(sdhci_cqe_irq);
+
+/*****************************************************************************\
+ *                                                                           *
  * Device allocation/registration                                            *
  *                                                                           *
 \*****************************************************************************/
@@ -2990,6 +3111,9 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev,
 
        host->flags = SDHCI_SIGNALING_330;
 
+       host->cqe_ier     = SDHCI_CQE_INT_MASK;
+       host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK;
+
        return host;
 }
 
index 477b3f5..35b41da 100644 (file)
 #define  SDHCI_INT_CARD_REMOVE 0x00000080
 #define  SDHCI_INT_CARD_INT    0x00000100
 #define  SDHCI_INT_RETUNE      0x00001000
+#define  SDHCI_INT_CQE         0x00004000
 #define  SDHCI_INT_ERROR       0x00008000
 #define  SDHCI_INT_TIMEOUT     0x00010000
 #define  SDHCI_INT_CRC         0x00020000
                SDHCI_INT_BLK_GAP)
 #define SDHCI_INT_ALL_MASK     ((unsigned int)-1)
 
+#define SDHCI_CQE_INT_ERR_MASK ( \
+       SDHCI_INT_ADMA_ERROR | SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | \
+       SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | \
+       SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT)
+
+#define SDHCI_CQE_INT_MASK (SDHCI_CQE_INT_ERR_MASK | SDHCI_INT_CQE)
+
 #define SDHCI_ACMD12_ERR       0x3C
 
 #define SDHCI_HOST_CONTROL2            0x3E
@@ -518,6 +526,10 @@ struct sdhci_host {
        /* cached registers */
        u32                     ier;
 
+       bool                    cqe_on;         /* CQE is operating */
+       u32                     cqe_ier;        /* CQE interrupt mask */
+       u32                     cqe_err_ier;    /* CQE error interrupt mask */
+
        wait_queue_head_t       buf_ready_int;  /* Waitqueue for Buffer Read Ready interrupt */
        unsigned int            tuning_done;    /* Condition flag set when CMD19 succeeds */
 
@@ -544,6 +556,8 @@ struct sdhci_ops {
        void    (*set_power)(struct sdhci_host *host, unsigned char mode,
                             unsigned short vdd);
 
+       u32             (*irq)(struct sdhci_host *host, u32 intmask);
+
        int             (*enable_dma)(struct sdhci_host *host);
        unsigned int    (*get_max_clock)(struct sdhci_host *host);
        unsigned int    (*get_min_clock)(struct sdhci_host *host);
@@ -697,6 +711,11 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host);
 int sdhci_runtime_resume_host(struct sdhci_host *host);
 #endif
 
+void sdhci_cqe_enable(struct mmc_host *mmc);
+void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery);
+bool sdhci_cqe_irq(struct sdhci_host *host, u32 intmask, int *cmd_error,
+                  int *data_error);
+
 void sdhci_dumpregs(struct sdhci_host *host);
 
 #endif /* __SDHCI_HW_H */