OSDN Git Service

mmc: core: hibernation support for mmc cards
authorVijay Viswanath <vviswana@codeaurora.org>
Tue, 10 Apr 2018 07:31:16 +0000 (13:01 +0530)
committerGerrit - the friendly Code Review server <code-review@localhost>
Fri, 22 Jun 2018 17:55:19 +0000 (10:55 -0700)
eMMC is the storage device for hibernation image. As such it needs
to be working when hibernation image is created and written. Also for
loading hibernation image after reboot, eMMC driver needs to be
probed and running.

During reboot,  kernel comes up and does its own eMMC probe and sets
speed for eMMC. A late system call then checks for hibernation image.
If there is a hibernation image, the current kernel will stay only
temporarily.
Once the hibernation image is found and loaded, the temporary kernel
suspends the eMMC device and then gives control to kernel in
hibernation image.
The hibernation image then restores the eMMC device based on the eMMC
driver state saved just before creating hibernation image.
This gives rise to a possible missmatch between the HW state of eMMC
(Specifically the bus speed) when temporary kernel suspends it and the
state driver restores after hibernation restore.
So a synchornization has been done by making the eMMC run in max speed
during:
1. Just before hibernation image is created (So the driver state
in hibernation image will be for max speed mode).
2. After reboot, just before temporary kernel loads image and
suspends (so the mmc driver in temporary kernel will put the
eMMC in suspend while HW state is in highest speed mode.
This ensures that the HW state, mmc_resume post the hibernation
expects, will be same as the HW state actually present in the eMMC
at the point of hibernation restore.

This also ensures we save the hibernation image in max performance and
we restore the hibernation image also in max performance.

Change-Id: Ia9deceacade97ac89e4f25b40ce074d75bbb2bbc
Signed-off-by: Vijay Viswanath <vviswana@codeaurora.org>
drivers/mmc/core/core.c
drivers/mmc/core/mmc.c
include/linux/mmc/core.h
include/linux/mmc/host.h

index 547d18c..151643c 100644 (file)
@@ -4526,9 +4526,11 @@ int mmc_pm_notify(struct notifier_block *notify_block,
        int err = 0;
 
        switch (mode) {
+       case PM_RESTORE_PREPARE:
        case PM_HIBERNATION_PREPARE:
+               if (host->bus_ops && host->bus_ops->pre_hibernate)
+                       host->bus_ops->pre_hibernate(host);
        case PM_SUSPEND_PREPARE:
-       case PM_RESTORE_PREPARE:
                spin_lock_irqsave(&host->lock, flags);
                host->rescan_disable = 1;
                spin_unlock_irqrestore(&host->lock, flags);
@@ -4560,9 +4562,11 @@ int mmc_pm_notify(struct notifier_block *notify_block,
                host->pm_flags = 0;
                break;
 
-       case PM_POST_SUSPEND:
-       case PM_POST_HIBERNATION:
        case PM_POST_RESTORE:
+       case PM_POST_HIBERNATION:
+               if (host->bus_ops && host->bus_ops->post_hibernate)
+                       host->bus_ops->post_hibernate(host);
+       case PM_POST_SUSPEND:
 
                spin_lock_irqsave(&host->lock, flags);
                host->rescan_disable = 0;
index c8f85b3..999fdb9 100644 (file)
@@ -2953,6 +2953,73 @@ static int mmc_shutdown(struct mmc_host *host)
        return 0;
 }
 
+static int mmc_pre_hibernate(struct mmc_host *host)
+{
+       int ret = 0;
+
+       mmc_get_card(host->card);
+       host->cached_caps2 = host->caps2;
+
+       /*
+        * Increase usage_count of card and host device till
+        * hibernation is over. This will ensure they will not runtime suspend.
+        */
+       pm_runtime_get_noresume(mmc_dev(host));
+       pm_runtime_get_noresume(&host->card->dev);
+
+       if (!mmc_can_scale_clk(host))
+               goto out;
+       /*
+        * Suspend clock scaling and mask host capability so that
+        * we will run in max frequency during:
+        *      1. Hibernation preparation and image creation
+        *      2. After finding hibernation image during reboot
+        *      3. Once hibernation image is loaded and till hibernation
+        *      restore is complete.
+        */
+       if (host->clk_scaling.enable)
+               mmc_suspend_clk_scaling(host);
+       host->caps2 &= ~MMC_CAP2_CLK_SCALE;
+       host->clk_scaling.state = MMC_LOAD_HIGH;
+       ret = mmc_clk_update_freq(host, host->card->clk_scaling_highest,
+                               host->clk_scaling.state);
+       if (ret)
+               pr_err("%s: %s: Setting clk frequency to max failed: %d\n",
+                               mmc_hostname(host), __func__, ret);
+out:
+       mmc_host_clk_hold(host);
+       mmc_put_card(host->card);
+       return ret;
+}
+
+static int mmc_post_hibernate(struct mmc_host *host)
+{
+       int ret = 0;
+
+       mmc_get_card(host->card);
+       if (!(host->cached_caps2 & MMC_CAP2_CLK_SCALE))
+               goto enable_pm;
+       /* Enable the clock scaling and set the host capability */
+       host->caps2 |= MMC_CAP2_CLK_SCALE;
+       if (!host->clk_scaling.enable)
+               ret = mmc_resume_clk_scaling(host);
+       if (ret)
+               pr_err("%s: %s: Resuming clk scaling failed: %d\n",
+                               mmc_hostname(host), __func__, ret);
+enable_pm:
+       /*
+        * Reduce usage count of card and host device so that they may
+        * runtime suspend.
+        */
+       pm_runtime_put_noidle(&host->card->dev);
+       pm_runtime_put_noidle(mmc_dev(host));
+
+       mmc_host_clk_release(host);
+
+       mmc_put_card(host->card);
+       return ret;
+}
+
 static const struct mmc_bus_ops mmc_ops = {
        .remove = mmc_remove,
        .detect = mmc_detect,
@@ -2964,6 +3031,8 @@ static const struct mmc_bus_ops mmc_ops = {
        .change_bus_speed = mmc_change_bus_speed,
        .reset = mmc_reset,
        .shutdown = mmc_shutdown,
+       .pre_hibernate = mmc_pre_hibernate,
+       .post_hibernate = mmc_post_hibernate
 };
 
 /*
index 862d8d1..ef3e628 100644 (file)
@@ -131,6 +131,8 @@ struct mmc_bus_ops {
        int (*shutdown)(struct mmc_host *);
        int (*reset)(struct mmc_host *);
        int (*change_bus_speed)(struct mmc_host *, unsigned long *);
+       int (*pre_hibernate)(struct mmc_host *);
+       int (*post_hibernate)(struct mmc_host *);
 };
 
 struct mmc_card;
index f564303..89e19dd 100644 (file)
@@ -419,6 +419,7 @@ struct mmc_host {
 #define MMC_CAP_HW_RESET       (1 << 31)       /* Hardware reset */
 
        u32                     caps2;          /* More host capabilities */
+       u32                     cached_caps2;
 
 #define MMC_CAP2_BOOTPART_NOACC        (1 << 0)        /* Boot partition no access */
 #define MMC_CAP2_FULL_PWR_CYCLE        (1 << 2)        /* Can do full power cycle */