omap_hsmmc: protect the card when the cover is open
authorAdrian Hunter <adrian.hunter@nokia.com>
Tue, 22 Sep 2009 23:45:01 +0000 (16:45 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 23 Sep 2009 14:39:36 +0000 (07:39 -0700)
Depending on the manufacturer, there is a small possibility that removing
a card while it is being written to, can render the card permanently
unusable.  To prevent that, the card is made inaccessible when the cover
is open.

Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Ian Molton <ian@mnementh.co.uk>
Cc: "Roberto A. Foglietta" <roberto.foglietta@gmail.com>
Cc: Jarkko Lavinen <jarkko.lavinen@nokia.com>
Cc: Denis Karpov <ext-denis.2.karpov@nokia.com>
Cc: Pierre Ossman <pierre@ossman.eu>
Cc: Philip Langdale <philipl@overt.org>
Cc: "Madhusudhan" <madhu.cr@ti.com>
Cc: <linux-mmc@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/mmc/host/omap_hsmmc.c

index 20746e4..a20fafe 100644 (file)
@@ -167,6 +167,8 @@ struct omap_hsmmc_host {
        int                     context_loss;
        int                     dpm_state;
        int                     vdd;
+       int                     protect_card;
+       int                     reqs_blocked;
 
        struct  omap_mmc_platform_data  *pdata;
 };
@@ -351,6 +353,9 @@ static void send_init_stream(struct omap_hsmmc_host *host)
        int reg = 0;
        unsigned long timeout;
 
+       if (host->protect_card)
+               return;
+
        disable_irq(host->irq);
        OMAP_HSMMC_WRITE(host->base, CON,
                OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
@@ -786,6 +791,30 @@ err:
        return ret;
 }
 
+/* Protect the card while the cover is open */
+static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host)
+{
+       if (!mmc_slot(host).get_cover_state)
+               return;
+
+       host->reqs_blocked = 0;
+       if (mmc_slot(host).get_cover_state(host->dev, host->slot_id)) {
+               if (host->protect_card) {
+                       printk(KERN_INFO "%s: cover is closed, "
+                                        "card is now accessible\n",
+                                        mmc_hostname(host->mmc));
+                       host->protect_card = 0;
+               }
+       } else {
+               if (!host->protect_card) {
+                       printk(KERN_INFO "%s: cover is open, "
+                                        "card is now inaccessible\n",
+                                        mmc_hostname(host->mmc));
+                       host->protect_card = 1;
+               }
+       }
+}
+
 /*
  * Work Item to notify the core about card insertion/removal
  */
@@ -803,8 +832,10 @@ static void omap_hsmmc_detect(struct work_struct *work)
 
        if (slot->card_detect)
                carddetect = slot->card_detect(slot->card_detect_irq);
-       else
+       else {
+               omap_hsmmc_protect_card(host);
                carddetect = -ENOSYS;
+       }
 
        if (carddetect) {
                mmc_detect_change(host->mmc, (HZ * 200) / 1000);
@@ -1040,8 +1071,32 @@ static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
         * interrupts, but not if we are already in interrupt context i.e.
         * retries.
         */
-       if (!in_interrupt())
+       if (!in_interrupt()) {
                spin_lock_irqsave(&host->irq_lock, host->flags);
+               /*
+                * Protect the card from I/O if there is a possibility
+                * it can be removed.
+                */
+               if (host->protect_card) {
+                       if (host->reqs_blocked < 3) {
+                               /*
+                                * Ensure the controller is left in a consistent
+                                * state by resetting the command and data state
+                                * machines.
+                                */
+                               omap_hsmmc_reset_controller_fsm(host, SRD);
+                               omap_hsmmc_reset_controller_fsm(host, SRC);
+                               host->reqs_blocked += 1;
+                       }
+                       req->cmd->error = -EBADF;
+                       if (req->data)
+                               req->data->error = -EBADF;
+                       spin_unlock_irqrestore(&host->irq_lock, host->flags);
+                       mmc_request_done(mmc, req);
+                       return;
+               } else if (host->reqs_blocked)
+                       host->reqs_blocked = 0;
+       }
        WARN_ON(host->mrq != NULL);
        host->mrq = req;
        err = omap_hsmmc_prepare_data(host, req);
@@ -1732,6 +1787,8 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev)
 
        mmc_host_lazy_disable(host->mmc);
 
+       omap_hsmmc_protect_card(host);
+
        mmc_add_host(mmc);
 
        if (mmc_slot(host).name != NULL) {
@@ -1897,6 +1954,8 @@ static int omap_hsmmc_resume(struct platform_device *pdev)
                                        "Unmask interrupt failed\n");
                }
 
+               omap_hsmmc_protect_card(host);
+
                /* Notify the core to resume the host */
                ret = mmc_resume_host(host->mmc);
                if (ret == 0)