aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <drzeus@drzeus.cx>2008-04-16 19:13:13 +0200
committerPierre Ossman <drzeus@drzeus.cx>2008-07-15 14:14:40 +0200
commit1e72859e3ae16346d4007024b20d2d4ef387dcc3 (patch)
tree5fc8319ce14b0770546bbbf9a72c90abaf019317
parent4489428ab5a49a6f443d9aa17f1d891417787d7b (diff)
sdhci: handle hot-remove
Gracefully handle when the device is suddenly removed. Do a test read and avoid any further access if that read returns -1. Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
-rw-r--r--drivers/mmc/host/sdhci-pci.c21
-rw-r--r--drivers/mmc/host/sdhci.c48
-rw-r--r--drivers/mmc/host/sdhci.h3
3 files changed, 58 insertions, 14 deletions
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
index 5dcb4958e47..8554466e0f4 100644
--- a/drivers/mmc/host/sdhci-pci.c
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -47,7 +47,7 @@ struct sdhci_pci_fixes {
int (*probe)(struct sdhci_pci_chip*);
int (*probe_slot)(struct sdhci_pci_slot*);
- void (*remove_slot)(struct sdhci_pci_slot*);
+ void (*remove_slot)(struct sdhci_pci_slot*, int);
int (*suspend)(struct sdhci_pci_chip*,
pm_message_t);
@@ -209,8 +209,11 @@ static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
return 0;
}
-static void jmicron_remove_slot(struct sdhci_pci_slot *slot)
+static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)
{
+ if (dead)
+ return;
+
if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
jmicron_enable_mmc(slot->host, 0);
}
@@ -540,7 +543,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
remove:
if (chip->fixes && chip->fixes->remove_slot)
- chip->fixes->remove_slot(slot);
+ chip->fixes->remove_slot(slot, 0);
unmap:
iounmap(host->ioaddr);
@@ -554,10 +557,18 @@ release:
static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
{
- sdhci_remove_host(slot->host);
+ int dead;
+ u32 scratch;
+
+ dead = 0;
+ scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
+ sdhci_remove_host(slot->host, dead);
if (slot->chip->fixes && slot->chip->fixes->remove_slot)
- slot->chip->fixes->remove_slot(slot);
+ slot->chip->fixes->remove_slot(slot, dead);
pci_release_region(slot->chip->pdev, slot->pci_bar);
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 95b081a9967..0ab582e77ac 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -712,7 +712,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
host->mrq = mrq;
- if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+ if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
+ || (host->flags & SDHCI_DEVICE_DEAD)) {
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
} else
@@ -732,6 +733,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
spin_lock_irqsave(&host->lock, flags);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
/*
* Reset the chip on each power off.
* Should clear out any weird states.
@@ -770,6 +774,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
@@ -784,7 +789,10 @@ static int sdhci_get_ro(struct mmc_host *mmc)
spin_lock_irqsave(&host->lock, flags);
- present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ present = 0;
+ else
+ present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
spin_unlock_irqrestore(&host->lock, flags);
@@ -801,6 +809,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
spin_lock_irqsave(&host->lock, flags);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
ier = readl(host->ioaddr + SDHCI_INT_ENABLE);
ier &= ~SDHCI_INT_CARD_INT;
@@ -810,6 +821,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
writel(ier, host->ioaddr + SDHCI_INT_ENABLE);
writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE);
+out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
@@ -875,10 +887,11 @@ static void sdhci_tasklet_finish(unsigned long param)
* The controller needs a reset of internal state machines
* upon error conditions.
*/
- if (mrq->cmd->error ||
- (mrq->data && (mrq->data->error ||
- (mrq->data->stop && mrq->data->stop->error))) ||
- (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) {
+ if (!(host->flags & SDHCI_DEVICE_DEAD) &&
+ (mrq->cmd->error ||
+ (mrq->data && (mrq->data->error ||
+ (mrq->data->stop && mrq->data->stop->error))) ||
+ (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
/* Some controllers need this kick or reset won't work here */
if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
@@ -1378,15 +1391,34 @@ untasklet:
EXPORT_SYMBOL_GPL(sdhci_add_host);
-void sdhci_remove_host(struct sdhci_host *host)
+void sdhci_remove_host(struct sdhci_host *host, int dead)
{
+ unsigned long flags;
+
+ if (dead) {
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->flags |= SDHCI_DEVICE_DEAD;
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Controller removed during "
+ " transfer!\n", mmc_hostname(host->mmc));
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+
mmc_remove_host(host->mmc);
#ifdef CONFIG_LEDS_CLASS
led_classdev_unregister(&host->led);
#endif
- sdhci_reset(host, SDHCI_RESET_ALL);
+ if (!dead)
+ sdhci_reset(host, SDHCI_RESET_ALL);
free_irq(host->irq, host);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 7ce12f3e745..7c302515a6a 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -198,6 +198,7 @@ struct sdhci_host {
int flags; /* Host attributes */
#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */
#define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */
+#define SDHCI_DEVICE_DEAD (1<<2) /* Device unresponsive */
unsigned int max_clk; /* Max possible freq (MHz) */
unsigned int timeout_clk; /* Timeout freq (KHz) */
@@ -239,7 +240,7 @@ static inline void *sdhci_priv(struct sdhci_host *host)
}
extern int sdhci_add_host(struct sdhci_host *host);
-extern void sdhci_remove_host(struct sdhci_host *host);
+extern void sdhci_remove_host(struct sdhci_host *host, int dead);
#ifdef CONFIG_PM
extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);