mmc: core: add wakeup functionality to sdio cards
This patch initializes wakeup source if the detected card
is a sdio card and enables the wakeup capability.
Platform drivers would have to invoke:
* pm_wakeup_event on this card device to signal a wakeup
* corresponding pm_relax have to be invoked
CRs-Fixed: 585481
Change-Id: Ic8d5c98073e8ed3f676eb42fc0ce1f13a11cb40f
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index b36faff..667da01 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -421,6 +421,12 @@
else if (!mmc_card_sdio(card) && mmc_use_core_runtime_pm(card->host))
pm_runtime_enable(&card->dev);
+ if (mmc_card_sdio(card)) {
+ ret = device_init_wakeup(&card->dev, true);
+ if (ret)
+ pr_err("%s: %s: failed to init wakeup: %d\n",
+ mmc_hostname(card->host), __func__, ret);
+ }
ret = device_add(&card->dev);
if (ret)
return ret;
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index c7fa876..c082f77 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -77,16 +77,40 @@
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);
int ret = 0;
+ unsigned long flags;
if (!mmc_use_core_pm(host))
return 0;
+ spin_lock_irqsave(&host->clk_lock, flags);
+ /*
+ * let the driver know that suspend is in progress and must
+ * be aborted on receiving a sdio card interrupt
+ */
+ host->dev_status = DEV_SUSPENDING;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
if (!pm_runtime_suspended(dev)) {
ret = mmc_suspend_host(host);
if (ret < 0)
pr_err("%s: %s: failed: ret: %d\n", mmc_hostname(host),
__func__, ret);
}
+ /*
+ * If SDIO function driver doesn't want to power off the card,
+ * atleast turn off clocks to allow deep sleep.
+ */
+ if (!ret && host->card && mmc_card_sdio(host->card) &&
+ host->ios.clock) {
+ spin_lock_irqsave(&host->clk_lock, flags);
+ host->clk_old = host->ios.clock;
+ host->ios.clock = 0;
+ host->clk_gated = true;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ mmc_set_ios(host);
+ }
+ spin_lock_irqsave(&host->clk_lock, flags);
+ host->dev_status = DEV_SUSPENDED;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
return ret;
}
@@ -104,6 +128,7 @@
pr_err("%s: %s: failed: ret: %d\n", mmc_hostname(host),
__func__, ret);
}
+ host->dev_status = DEV_RESUMED;
return ret;
}
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
index 3d8ceb4..2b48f77 100644
--- a/drivers/mmc/core/sdio_irq.c
+++ b/drivers/mmc/core/sdio_irq.c
@@ -85,6 +85,7 @@
struct sched_param param = { .sched_priority = 1 };
unsigned long period, idle_period;
int ret;
+ bool ws;
sched_setscheduler(current, SCHED_FIFO, ¶m);
@@ -118,6 +119,17 @@
ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort);
if (ret)
break;
+ ws = false;
+ /*
+ * prevent suspend if it has started when scheduled;
+ * 100 msec (approx. value) should be enough for the system to
+ * resume and attend to the card's request
+ */
+ if ((host->dev_status == DEV_SUSPENDING) ||
+ (host->dev_status == DEV_SUSPENDED)) {
+ pm_wakeup_event(&host->card->dev, 100);
+ ws = true;
+ }
ret = process_sdio_pending_irqs(host);
host->sdio_irq_pending = false;
mmc_release_host(host);
@@ -154,6 +166,12 @@
host->ops->enable_sdio_irq(host, 1);
mmc_host_clk_release(host);
}
+ /*
+ * function drivers would have processed the event from card
+ * unless suspended, hence release wake source
+ */
+ if (ws && (host->dev_status == DEV_RESUMED))
+ pm_relax(&host->card->dev);
if (!kthread_should_stop())
schedule_timeout(period);
set_current_state(TASK_RUNNING);
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index bff056d..e53c350 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -197,6 +197,12 @@
void *handler_priv;
};
+enum dev_state {
+ DEV_SUSPENDING = 1,
+ DEV_SUSPENDED,
+ DEV_RESUMED,
+};
+
struct mmc_host {
struct device *parent;
struct device class_dev;
@@ -421,6 +427,7 @@
struct delayed_work work;
enum mmc_load state;
} clk_scaling;
+ enum dev_state dev_status;
unsigned long private[0] ____cacheline_aligned;
};