mmc: fix all hangs related to mmc/sd card insert/removal during suspend/resume
[pandora-kernel.git] / drivers / mmc / core / core.c
index 569e94d..83240fa 100644 (file)
@@ -1057,6 +1057,17 @@ void mmc_rescan(struct work_struct *work)
                container_of(work, struct mmc_host, detect.work);
        u32 ocr;
        int err;
+       unsigned long flags;
+
+       spin_lock_irqsave(&host->lock, flags);
+
+       if (host->rescan_disable) {
+               spin_unlock_irqrestore(&host->lock, flags);
+               return;
+       }
+
+       spin_unlock_irqrestore(&host->lock, flags);
+
 
        mmc_bus_get(host);
 
@@ -1099,8 +1110,15 @@ void mmc_rescan(struct work_struct *work)
         */
        err = mmc_send_io_op_cond(host, 0, &ocr);
        if (!err) {
-               if (mmc_attach_sdio(host, ocr))
-                       mmc_power_off(host);
+               if (mmc_attach_sdio(host, ocr)) {
+                       mmc_claim_host(host);
+                       /* try SDMEM (but not MMC) even if SDIO is broken */
+                       if (mmc_send_app_op_cond(host, 0, &ocr))
+                               goto out_fail;
+
+                       if (mmc_attach_sd(host, ocr))
+                               mmc_power_off(host);
+               }
                goto out;
        }
 
@@ -1124,6 +1142,7 @@ void mmc_rescan(struct work_struct *work)
                goto out;
        }
 
+out_fail:
        mmc_release_host(host);
        mmc_power_off(host);
 
@@ -1266,19 +1285,6 @@ int mmc_suspend_host(struct mmc_host *host)
        if (host->bus_ops && !host->bus_dead) {
                if (host->bus_ops->suspend)
                        err = host->bus_ops->suspend(host);
-               if (err == -ENOSYS || !host->bus_ops->resume) {
-                       /*
-                        * We simply "remove" the card in this case.
-                        * It will be redetected on resume.
-                        */
-                       if (host->bus_ops->remove)
-                               host->bus_ops->remove(host);
-                       mmc_claim_host(host);
-                       mmc_detach_bus(host);
-                       mmc_release_host(host);
-                       host->pm_flags = 0;
-                       err = 0;
-               }
        }
        mmc_bus_put(host);
 
@@ -1310,28 +1316,61 @@ int mmc_resume_host(struct mmc_host *host)
                        printk(KERN_WARNING "%s: error %d during resume "
                                            "(card was removed?)\n",
                                            mmc_hostname(host), err);
-                       if (host->bus_ops->remove)
-                               host->bus_ops->remove(host);
-                       mmc_claim_host(host);
-                       mmc_detach_bus(host);
-                       mmc_release_host(host);
-                       /* no need to bother upper layers */
                        err = 0;
                }
        }
        mmc_bus_put(host);
 
-       /*
-        * We add a slight delay here so that resume can progress
-        * in parallel.
-        */
-       mmc_detect_change(host, 1);
-
        return err;
 }
-
 EXPORT_SYMBOL(mmc_resume_host);
 
+/* Do the card removal on suspend if card is assumed removeable
+ * Do that in pm notifier while userspace isn't yet frozen, so we will be able
+   to sync the card.
+*/
+int mmc_pm_notify(struct notifier_block *notify_block,
+                                       unsigned long mode, void *unused)
+{
+       struct mmc_host *host = container_of(
+               notify_block, struct mmc_host, pm_notify);
+       unsigned long flags;
+
+
+       switch (mode) {
+       case PM_HIBERNATION_PREPARE:
+       case PM_SUSPEND_PREPARE:
+
+               spin_lock_irqsave(&host->lock, flags);
+               host->rescan_disable = 1;
+               spin_unlock_irqrestore(&host->lock, flags);
+               cancel_delayed_work_sync(&host->detect);
+
+               if (!host->bus_ops || host->bus_ops->suspend)
+                       break;
+
+               mmc_claim_host(host);
+
+               if (host->bus_ops->remove)
+                       host->bus_ops->remove(host);
+
+               mmc_detach_bus(host);
+               mmc_release_host(host);
+               host->pm_flags = 0;
+               break;
+
+       case PM_POST_SUSPEND:
+       case PM_POST_HIBERNATION:
+
+               spin_lock_irqsave(&host->lock, flags);
+               host->rescan_disable = 0;
+               spin_unlock_irqrestore(&host->lock, flags);
+               mmc_detect_change(host, 0);
+
+       }
+
+       return 0;
+}
 #endif
 
 static int __init mmc_init(void)