Sdio device may trigger interrupt after its function suspended
while system not suspended yet.

This patch handle above cases by below two methods:
1. For multiple sdio_funcs, abort suspend if interrupt happen after
its func suspended while some other funcs not suspended yet.
2. If interrupt happens after all sdio_funcs suspended, wakeup the
system.

Signed-off-by: Jialing Fu <j...@marvell.com>
Signed-off-by: Kevin Liu <kl...@marvell.com>
---
 drivers/mmc/core/sdio.c       |   57 ++++++++++++++++++++++++++++++++++------
 drivers/mmc/core/sdio_irq.c   |    5 +++
 include/linux/mmc/host.h      |    5 +++
 include/linux/mmc/sdio_func.h |    6 ++++
 4 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index 81140b9..d9b4a11 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -888,6 +888,14 @@ out:
        }
 }
 
+void mmc_sdio_irq_wakeup(struct mmc_host *host)
+{
+       pr_warning("%s: submit a wakeup event\n",
+                      mmc_hostname(host));
+       /* set the process time to 3 seconds */
+       pm_wakeup_event(mmc_dev(host), 3000);
+}
+
 /*
  * SDIO suspend.  We need to suspend all functions separately.
  * Therefore all registered functions must have drivers with suspend
@@ -897,8 +905,17 @@ static int mmc_sdio_suspend(struct mmc_host *host)
 {
        int i, err = 0;
 
+       atomic_set(&host->sdio_suspend_abort, 0);
        for (i = 0; i < host->card->sdio_funcs; i++) {
                struct sdio_func *func = host->card->sdio_func[i];
+
+               /* Abort suspend if irq come after its sdio_func suspended */
+               if (i && atomic_cmpxchg(&host->sdio_suspend_abort, 1, 0)) {
+                       atomic_set(&host->sdio_suspend_abort, 0);
+                       err = -EBUSY;
+                       break;
+               }
+
                if (func && sdio_func_present(func) && func->dev.driver) {
                        const struct dev_pm_ops *pmops = func->dev.driver->pm;
                        if (!pmops || !pmops->suspend || !pmops->resume) {
@@ -906,15 +923,20 @@ static int mmc_sdio_suspend(struct mmc_host *host)
                                err = -ENOSYS;
                        } else
                                err = pmops->suspend(&func->dev);
+
                        if (err)
                                break;
+                       else
+                               func->func_status = FUNC_SUSPENDED;
                }
        }
+
        while (err && --i >= 0) {
                struct sdio_func *func = host->card->sdio_func[i];
                if (func && sdio_func_present(func) && func->dev.driver) {
                        const struct dev_pm_ops *pmops = func->dev.driver->pm;
                        pmops->resume(&func->dev);
+                       func->func_status = FUNC_RESUMED;
                }
        }
 
@@ -924,17 +946,30 @@ static int mmc_sdio_suspend(struct mmc_host *host)
                mmc_release_host(host);
        }
 
-       /*
-       * If sdio host can wakeup system, its interrupt will _NOT_ be disabled
-       * during suspending. So the card interrupt may occur after host has
-       * suspended. Claim the host here to avoid sdio irq thread handling the
-       * pending interrupt while sdio host suspended. The pending interrupt
-       * will be handled after the host released in resume when sdio host has
-       * been resumed.
-       */
-       if (!err && device_may_wakeup(mmc_dev(host)))
+       if (!err && device_may_wakeup(mmc_dev(host))) {
+               /*
+                * All interrupts after this will wakeup system
+                */
+               atomic_set(&host->sdio_suspended, 1);
+
+               /*
+                * If sdio host can wakeup system, its interrupt will _NOT_ be 
disabled
+                * during suspending. So the card interrupt may occur after 
host has
+                * suspended. Claim the host here to avoid sdio irq thread 
handling the
+                * pending interrupt while sdio host suspended. The pending 
interrupt
+                * will be handled after the host released in resume when sdio 
host has
+                * been resumed.
+                */
                mmc_claim_host(host);
 
+               /*
+                * Double check whether sdio IRQ happens after all func
+                * suspended. If this case occur, wakeup system.
+                */
+               if (atomic_cmpxchg(&host->sdio_suspend_abort, 1, 0))
+                       mmc_sdio_irq_wakeup(host);
+       }
+
        return err;
 }
 
@@ -967,6 +1002,8 @@ static int mmc_sdio_resume(struct mmc_host *host)
 
        if (!err && host->sdio_irqs)
                wake_up_process(host->sdio_irq_thread);
+
+       atomic_set(&host->sdio_suspended, 0);
        mmc_release_host(host);
 
        /*
@@ -982,8 +1019,10 @@ static int mmc_sdio_resume(struct mmc_host *host)
        for (i = 0; !err && i < host->card->sdio_funcs; i++) {
                struct sdio_func *func = host->card->sdio_func[i];
                if (func && sdio_func_present(func) && func->dev.driver) {
+
                        const struct dev_pm_ops *pmops = func->dev.driver->pm;
                        err = pmops->resume(&func->dev);
+                       func->func_status = FUNC_RESUMED;
                }
        }
 
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
index 3d8ceb4..cf03463 100644
--- a/drivers/mmc/core/sdio_irq.c
+++ b/drivers/mmc/core/sdio_irq.c
@@ -43,6 +43,9 @@ static int process_sdio_pending_irqs(struct mmc_host *host)
        func = card->sdio_single_irq;
        if (func && host->sdio_irq_pending) {
                func->irq_handler(func);
+               if (func->func_status == FUNC_SUSPENDED)
+                       atomic_set(&host->sdio_suspend_abort, 1);
+
                return 1;
        }
 
@@ -64,6 +67,8 @@ static int process_sdio_pending_irqs(struct mmc_host *host)
                                ret = -EINVAL;
                        } else if (func->irq_handler) {
                                func->irq_handler(func);
+                               if (func->func_status == FUNC_SUSPENDED)
+                                       atomic_set(&host->sdio_suspend_abort, 
1);
                                count++;
                        } else {
                                pr_warning("%s: pending IRQ with no handler\n",
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 61a10c1..72984ce 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -316,6 +316,8 @@ struct mmc_host {
        struct task_struct      *sdio_irq_thread;
        bool                    sdio_irq_pending;
        atomic_t                sdio_irq_thread_abort;
+       atomic_t                sdio_suspend_abort;
+       atomic_t                sdio_suspended;
 
        mmc_pm_flag_t           pm_flags;       /* requested pm features */
 
@@ -367,11 +369,14 @@ extern void mmc_detect_change(struct mmc_host *, unsigned 
long delay);
 extern void mmc_request_done(struct mmc_host *, struct mmc_request *);
 
 extern int mmc_cache_ctrl(struct mmc_host *, u8);
+extern void mmc_sdio_irq_wakeup(struct mmc_host *host);
 
 static inline void mmc_signal_sdio_irq(struct mmc_host *host)
 {
        host->ops->enable_sdio_irq(host, 0);
        host->sdio_irq_pending = true;
+       if (atomic_read(&host->sdio_suspended))
+               mmc_sdio_irq_wakeup(host);
        wake_up_process(host->sdio_irq_thread);
 }
 
diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h
index 50f0bc9..59f4c23 100644
--- a/include/linux/mmc/sdio_func.h
+++ b/include/linux/mmc/sdio_func.h
@@ -32,6 +32,11 @@ struct sdio_func_tuple {
        unsigned char data[0];
 };
 
+enum sdio_func_status {
+       FUNC_RESUMED = 0,       /* default value */
+       FUNC_SUSPENDED,
+};
+
 /*
  * SDIO function devices
  */
@@ -59,6 +64,7 @@ struct sdio_func {
        const char              **info;         /* info strings */
 
        struct sdio_func_tuple *tuples;
+       enum sdio_func_status   func_status;    /* SDIO function driver state */
 };
 
 #define sdio_func_present(f)   ((f)->state & SDIO_STATE_PRESENT)
-- 
1.7.0.4

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to