mmc: sdhci: fix interrupt storm from card detection
authorShawn Guo <shawn.guo@linaro.org>
Tue, 21 Jun 2011 14:41:48 +0000 (22:41 +0800)
committerChris Ball <cjb@laptop.org>
Wed, 20 Jul 2011 21:21:01 +0000 (17:21 -0400)
The issue was initially found by Eric Benard as below.

http://permalink.gmane.org/gmane.linux.ports.arm.kernel/108031

Not sure about other SDHCI based controller, but on Freescale eSDHC,
the SDHCI_INT_CARD_INSERT bits will be immediately set again when it
gets cleared, if a card is inserted. The driver need to mask the irq
to prevent interrupt storm which will freeze the system.  And the
SDHCI_INT_CARD_REMOVE gets the same situation.

The patch fixes the problem based on the initial idea from
Eric Benard.

Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Cc: Eric Benard <eric@eukrea.com>
Tested-by: Arnaud Patard <arnaud.patard@rtp-net.org>
Signed-off-by: Chris Ball <cjb@laptop.org>
drivers/mmc/host/sdhci.c

index 58d5436..aa25eae 100644 (file)
@@ -127,11 +127,15 @@ static void sdhci_mask_irqs(struct sdhci_host *host, u32 irqs)
 
 static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
 {
-       u32 irqs = SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT;
+       u32 present, irqs;
 
        if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
                return;
 
+       present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
+                             SDHCI_CARD_PRESENT;
+       irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT;
+
        if (enable)
                sdhci_unmask_irqs(host, irqs);
        else
@@ -2154,13 +2158,30 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
                mmc_hostname(host->mmc), intmask);
 
        if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
+               u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
+                             SDHCI_CARD_PRESENT;
+
+               /*
+                * There is a observation on i.mx esdhc.  INSERT bit will be
+                * immediately set again when it gets cleared, if a card is
+                * inserted.  We have to mask the irq to prevent interrupt
+                * storm which will freeze the system.  And the REMOVE gets
+                * the same situation.
+                *
+                * More testing are needed here to ensure it works for other
+                * platforms though.
+                */
+               sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT :
+                                               SDHCI_INT_CARD_REMOVE);
+               sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE :
+                                                 SDHCI_INT_CARD_INSERT);
+
                sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |
-                       SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);
+                            SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);
+               intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
                tasklet_schedule(&host->card_tasklet);
        }
 
-       intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
-
        if (intmask & SDHCI_INT_CMD_MASK) {
                sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK,
                        SDHCI_INT_STATUS);