[RFC PATCH v2 6/6] mmc: core: add manual resume capability
Vincent Yang
vincent.yang.fujitsu at gmail.com
Fri Jun 27 21:00:21 EST 2014
2014-06-27 17:40 GMT+08:00 Ulf Hansson <ulf.hansson at linaro.org>:
>
>
> On 27 juni 2014 04:23:42 CEST, Vincent Yang <vincent.yang.fujitsu at gmail.com> wrote:
>>2014-06-26 17:42 GMT+08:00 Ulf Hansson <ulf.hansson at linaro.org>:
>>>
>>>
>>> On 26 juni 2014 08:23:32 CEST, Vincent Yang
>><vincent.yang.fujitsu at gmail.com> wrote:
>>>>This patch adds manual resume for some embedded platforms with rootfs
>>>>stored in SD card. It references CONFIG_MMC_BLOCK_DEFERRED_RESUME in
>>>>kernel 3.10. It lets host controller driver to manually handle resume
>>>>by itself.
>>>>
>>>>[symptom]
>>>>This issue is found on mb86s7x platforms with rootfs stored in SD
>>card.
>>>>It failed to resume form STR suspend mode because SD card cannot be
>>>>ready
>>>>in time. It take longer time (e.g., 600ms) to be ready for access.
>>>>The error log looks like below:
>>>>
>>>>root at localhost:~# echo mem > /sys/power/state
>>>>[ 30.441974] SUSPEND
>>>>
>>>>SCB Firmware : Category 01 Version 02.03 Rev. 00_
>>>> Config : (no configuration)
>>>>root at localhost:~# [ 30.702976] Buffer I/O error on device
>>mmcblk1p2,
>>>>logical block 31349
>>>>[ 30.709678] Buffer I/O error on device mmcblk1p2, logical block
>>>>168073
>>>>[ 30.716220] Buffer I/O error on device mmcblk1p2, logical block
>>>>168074
>>>>[ 30.722759] Buffer I/O error on device mmcblk1p2, logical block
>>>>168075
>>>>[ 30.729456] Buffer I/O error on device mmcblk1p2, logical block
>>>>31349
>>>>[ 30.735916] Buffer I/O error on device mmcblk1p2, logical block
>>>>31350
>>>>[ 30.742370] Buffer I/O error on device mmcblk1p2, logical block
>>>>31351
>>>>[ 30.749025] Buffer I/O error on device mmcblk1p2, logical block
>>>>168075
>>>>[ 30.755657] Buffer I/O error on device mmcblk1p2, logical block
>>>>31351
>>>>[ 30.763130] Aborting journal on device mmcblk1p2-8.
>>>>[ 30.768060] JBD2: Error -5 detected when updating journal
>>superblock
>>>>for mmcblk1p2-8.
>>>>[ 30.776085] EXT4-fs error (device mmcblk1p2):
>>>>ext4_journal_check_start:56: Detected aborted journal
>>>>[ 30.785259] EXT4-fs (mmcblk1p2): Remounting filesystem read-only
>>>>[ 31.370716] EXT4-fs error (device mmcblk1p2):
>>ext4_find_entry:1309:
>>>>inode #2490369: comm udevd: reading directory lblock 0
>>>>[ 31.382485] EXT4-fs error (device mmcblk1p2):
>>ext4_find_entry:1309:
>>>>inode #1048577: comm udevd: reading directory lblock 0
>>>>
>>>>[analysis]
>>>>In system resume path, mmc_sd_resume() is failed with error code -123
>>>>because at that time SD card is still not ready on mb86s7x platforms.
>>>
>>> So why does it fail? It shouldn't!
>>>
>>> I get the impression that you are solving this in the wrong way.
>>
>>Hi Uffe,
>>On mb86s7x EVB, power for SD card is completely removed when entering
>>STR suspend mode (i.e., echo mem > /sys/power/state). When system
>>starts to resume, it turns on the power for SD card again. However, It
>>take
>>longer time (e.g., 600ms) to get the power ready.
>>This is why mmc_sd_resume() failed with error code -123. At that time
>>point,
>>power for SD card is not ready yet.
>>
>>At first we fixed it by a simple method of using
>>SDHCI_QUIRK_DELAY_AFTER_POWER:
>>
>>diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>>index 169e17d..ed28896 100644
>>--- a/drivers/mmc/host/sdhci.c
>>+++ b/drivers/mmc/host/sdhci.c
>>@@ -1283,7 +1283,7 @@ static void sdhci_set_power(struct sdhci_host
>>*host, unsigned char mode,
>> * they can apply clock after applying power
>> */
>> if (host->quirks & SDHCI_QUIRK_DELAY_AFTER_POWER)
>>- mdelay(10);
>>+ mdelay(600);
>> }
>
> If you can't model the power to the card through a regulator, this is what you need to do.
Hi Uffe,
Yes, I got it.
>
>>
>> if (host->vmmc) {
>>
>>However, we found it blocks the system resume path. It can slow down
>>system resume and also booting.
>>Therefore, we would like to resume SD card manually and asynchronously
>>by host controller driver itself. Thus system resume path is not
>>blocked.
>
> That is accomplished by using MMC_CAP_RUNTIME_RESUME.
Yes, I got it.
Thanks a lot for your review and help.
I will update it in next version.
Best regards,
Vincent Yang
>
> Kind regards
> Uffe
>
>
>>Thanks a lot for your review!
>>
>>
>>Best regards,
>>Vincent Yang
>>
>>>
>>> Kind regards
>>> Uffe
>>>
>>>>
>>>>[solution]
>>>>In order to not blocking system resume path, this patch just sets a
>>>>flag
>>>>MMC_BUSRESUME_MANUAL_RESUME when this error happened, and then host
>>>>controller
>>>>driver can understand it by this flag. Then host controller driver
>>have
>>>>to
>>>>resume SD card manually and asynchronously.
>>>>
>>>>Signed-off-by: Vincent Yang <Vincent.Yang at tw.fujitsu.com>
>>>>---
>>>> drivers/mmc/core/core.c | 4 ++
>>>> drivers/mmc/core/sd.c | 4 ++
>>>>drivers/mmc/host/sdhci_f_sdh30.c | 89
>>>>++++++++++++++++++++++++++++++++++++++++
>>>> include/linux/mmc/host.h | 14 +++++++
>>>> 4 files changed, 111 insertions(+)
>>>>
>>>>diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
>>>>index 764af63..51fce49 100644
>>>>--- a/drivers/mmc/core/core.c
>>>>+++ b/drivers/mmc/core/core.c
>>>>@@ -2648,6 +2648,10 @@ int mmc_pm_notify(struct notifier_block
>>>>*notify_block,
>>>> case PM_POST_RESTORE:
>>>>
>>>> spin_lock_irqsave(&host->lock, flags);
>>>>+ if (mmc_bus_manual_resume(host)) {
>>>>+ spin_unlock_irqrestore(&host->lock, flags);
>>>>+ break;
>>>>+ }
>>>> host->rescan_disable = 0;
>>>> spin_unlock_irqrestore(&host->lock, flags);
>>>> _mmc_detect_change(host, 0, false);
>>>>diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
>>>>index 0c44510..859390d 100644
>>>>--- a/drivers/mmc/core/sd.c
>>>>+++ b/drivers/mmc/core/sd.c
>>>>@@ -1133,6 +1133,10 @@ static int mmc_sd_resume(struct mmc_host
>>*host)
>>>>
>>>> if (!(host->caps & MMC_CAP_RUNTIME_RESUME)) {
>>>> err = _mmc_sd_resume(host);
>>>>+ if ((host->caps2 & MMC_CAP2_MANUAL_RESUME) && err)
>>>>+ mmc_set_bus_resume_policy(host, 1);
>>>>+ else
>>>>+ mmc_set_bus_resume_policy(host, 0);
>>>> pm_runtime_set_active(&host->card->dev);
>>>> pm_runtime_mark_last_busy(&host->card->dev);
>>>> }
>>>>diff --git a/drivers/mmc/host/sdhci_f_sdh30.c
>>>>b/drivers/mmc/host/sdhci_f_sdh30.c
>>>>index 6fae509..67bcff2 100644
>>>>--- a/drivers/mmc/host/sdhci_f_sdh30.c
>>>>+++ b/drivers/mmc/host/sdhci_f_sdh30.c
>>>>@@ -30,6 +30,12 @@
>>>> #include "../core/core.h"
>>>>
>>>> #define DRIVER_NAME "f_sdh30"
>>>>+#define RESUME_WAIT_COUNT 100
>>>>+#define RESUME_WAIT_TIME 50
>>>>+#define RESUME_WAIT_JIFFIES msecs_to_jiffies(RESUME_DETECT_TIME)
>>>>+#define RESUME_DETECT_COUNT 16
>>>>+#define RESUME_DETECT_TIME 50
>>>>+#define RESUME_DETECT_JIFFIES msecs_to_jiffies(RESUME_DETECT_TIME)
>>>>
>>>>
>>>> struct f_sdhost_priv {
>>>>@@ -38,8 +44,59 @@ struct f_sdhost_priv {
>>>> int gpio_select_1v8;
>>>> u32 vendor_hs200;
>>>> struct device *dev;
>>>>+ unsigned int quirks; /* Deviations from spec. */
>>>>+
>>>>+/* retry to detect mmc device when resume */
>>>>+#define F_SDH30_QUIRK_RESUME_DETECT_RETRY (1<<0)
>>>>+
>>>>+ struct workqueue_struct *resume_detect_wq;
>>>>+ struct delayed_work resume_detect_work;
>>>>+ unsigned int resume_detect_count;
>>>>+ unsigned int resume_wait_count;
>>>> };
>>>>
>>>>+static void sdhci_f_sdh30_resume_detect_work_func(struct work_struct
>>>>*work)
>>>>+{
>>>>+ struct f_sdhost_priv *priv = container_of(work, struct
>>f_sdhost_priv,
>>>>+ resume_detect_work.work);
>>>>+ struct sdhci_host *host = dev_get_drvdata(priv->dev);
>>>>+ int err = 0;
>>>>+
>>>>+ if (mmc_bus_manual_resume(host->mmc)) {
>>>>+ pm_runtime_disable(&host->mmc->card->dev);
>>>>+ mmc_card_set_suspended(host->mmc->card);
>>>>+ err = host->mmc->bus_ops->resume(host->mmc);
>>>>+ if (priv->resume_detect_count-- && err)
>>>>+ queue_delayed_work(priv->resume_detect_wq,
>>>>+ &priv->resume_detect_work,
>>>>+ RESUME_DETECT_JIFFIES);
>>>>+ else
>>>>+ pr_debug("%s: resume detection done (count:%d,
>>wait:%d, err:%d)\n",
>>>>+ mmc_hostname(host->mmc),
>>>>+ priv->resume_detect_count,
>>>>+ priv->resume_wait_count, err);
>>>>+ } else {
>>>>+ if (priv->resume_wait_count--)
>>>>+ queue_delayed_work(priv->resume_detect_wq,
>>>>+ &priv->resume_detect_work,
>>>>+ RESUME_WAIT_JIFFIES);
>>>>+ else
>>>>+ pr_debug("%s: resume done\n",
>>mmc_hostname(host->mmc));
>>>>+ }
>>>>+}
>>>>+
>>>>+static void sdhci_f_sdh30_resume_detect(struct mmc_host *mmc,
>>>>+ int detect, int wait)
>>>>+{
>>>>+ struct sdhci_host *host = mmc_priv(mmc);
>>>>+ struct f_sdhost_priv *priv = sdhci_priv(host);
>>>>+
>>>>+ priv->resume_detect_count = detect;
>>>>+ priv->resume_wait_count = wait;
>>>>+ queue_delayed_work(priv->resume_detect_wq,
>>>>+ &priv->resume_detect_work, 0);
>>>>+}
>>>>+
>>>> void sdhci_f_sdh30_soft_voltage_switch(struct sdhci_host *host)
>>>> {
>>>> struct f_sdhost_priv *priv = sdhci_priv(host);
>>>>@@ -146,6 +203,12 @@ static int sdhci_f_sdh30_probe(struct
>>>>platform_device *pdev)
>>>> }
>>>> }
>>>>
>>>>+ if (of_find_property(pdev->dev.of_node, "resume-detect-retry",
>>NULL))
>>>>{
>>>>+ dev_info(dev, "Applying resume detect retry quirk\n");
>>>>+ priv->quirks |= F_SDH30_QUIRK_RESUME_DETECT_RETRY;
>>>>+ host->mmc->caps2 |= MMC_CAP2_MANUAL_RESUME;
>>>>+ }
>>>>+
>>>> ret = mmc_of_parse_voltage(pdev->dev.of_node,
>>&host->ocr_mask);
>>>> if (ret) {
>>>> dev_err(dev, "%s: parse voltage error\n", __func__);
>>>>@@ -197,6 +260,18 @@ static int sdhci_f_sdh30_probe(struct
>>>>platform_device *pdev)
>>>> }
>>>> }
>>>>
>>>>+ /* Init workqueue */
>>>>+ if (priv->quirks & F_SDH30_QUIRK_RESUME_DETECT_RETRY) {
>>>>+ priv->resume_detect_wq =
>>create_workqueue("sdhci_f_sdh30");
>>>>+ if (priv->resume_detect_wq == NULL) {
>>>>+ ret = -ENOMEM;
>>>>+ dev_err(dev, "Failed to create resume
>>detection workqueue\n");
>>>>+ goto err_init_wq;
>>>>+ }
>>>>+ INIT_DELAYED_WORK(&priv->resume_detect_work,
>>>>+
>>sdhci_f_sdh30_resume_detect_work_func);
>>>>+ }
>>>>+
>>>> ret = sdhci_add_host(host);
>>>> if (ret) {
>>>> dev_err(dev, "%s: host add error\n", __func__);
>>>>@@ -229,6 +304,9 @@ static int sdhci_f_sdh30_probe(struct
>>>>platform_device *pdev)
>>>> return 0;
>>>>
>>>> err_add_host:
>>>>+ if (priv->quirks & F_SDH30_QUIRK_RESUME_DETECT_RETRY)
>>>>+ destroy_workqueue(priv->resume_detect_wq);
>>>>+err_init_wq:
>>>> if (gpio_is_valid(priv->gpio_select_1v8)) {
>>>> gpio_direction_output(priv->gpio_select_1v8, 1);
>>>> gpio_free(priv->gpio_select_1v8);
>>>>@@ -268,6 +346,9 @@ static int sdhci_f_sdh30_remove(struct
>>>>platform_device *pdev)
>>>> gpio_free(priv->gpio_select_1v8);
>>>> }
>>>>
>>>>+ if (priv->quirks & F_SDH30_QUIRK_RESUME_DETECT_RETRY)
>>>>+ destroy_workqueue(priv->resume_detect_wq);
>>>>+
>>>> sdhci_free_host(host);
>>>> platform_set_drvdata(pdev, NULL);
>>>>
>>>>@@ -285,7 +366,15 @@ static int sdhci_f_sdh30_suspend(struct device
>>>>*dev)
>>>> static int sdhci_f_sdh30_resume(struct device *dev)
>>>> {
>>>> struct sdhci_host *host = dev_get_drvdata(dev);
>>>>+ struct f_sdhost_priv *priv = sdhci_priv(host);
>>>>
>>>>+ if (priv->quirks & F_SDH30_QUIRK_RESUME_DETECT_RETRY) {
>>>>+ pr_debug("%s: start resume detect\n",
>>>>+ mmc_hostname(host->mmc));
>>>>+ sdhci_f_sdh30_resume_detect(host->mmc,
>>>>+ RESUME_DETECT_COUNT,
>>>>+ RESUME_WAIT_COUNT);
>>>>+ }
>>>> return sdhci_resume_host(host);
>>>> }
>>>> #endif
>>>>diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
>>>>index 7960424..55221dd 100644
>>>>--- a/include/linux/mmc/host.h
>>>>+++ b/include/linux/mmc/host.h
>>>>@@ -283,6 +283,7 @@ struct mmc_host {
>>>> #define MMC_CAP2_HS400 (MMC_CAP2_HS400_1_8V | \
>>>> MMC_CAP2_HS400_1_2V)
>>>> #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17)
>>>>+#define MMC_CAP2_MANUAL_RESUME (1 << 18) /* Resume manually
>>when
>>>>error */
>>>>
>>>> mmc_pm_flag_t pm_caps; /* supported pm
>>features */
>>>>
>>>>@@ -338,6 +339,9 @@ struct mmc_host {
>>>> const struct mmc_bus_ops *bus_ops; /* current bus driver
>>*/
>>>> unsigned int bus_refs; /* reference counter
>>*/
>>>>
>>>>+ unsigned int bus_resume_flags;
>>>>+#define MMC_BUSRESUME_MANUAL_RESUME (1 << 0)
>>>>+
>>>> unsigned int sdio_irqs;
>>>> struct task_struct *sdio_irq_thread;
>>>> bool sdio_irq_pending;
>>>>@@ -384,6 +388,16 @@ static inline void *mmc_priv(struct mmc_host
>>>>*host)
>>>> #define mmc_dev(x) ((x)->parent)
>>>> #define mmc_classdev(x) (&(x)->class_dev)
>>>> #define mmc_hostname(x) (dev_name(&(x)->class_dev))
>>>>+#define mmc_bus_manual_resume(host) ((host)->bus_resume_flags &
>> \
>>>>+ MMC_BUSRESUME_MANUAL_RESUME)
>>>>+
>>>>+static inline void mmc_set_bus_resume_policy(struct mmc_host *host,
>>>>int manual)
>>>>+{
>>>>+ if (manual)
>>>>+ host->bus_resume_flags |= MMC_BUSRESUME_MANUAL_RESUME;
>>>>+ else
>>>>+ host->bus_resume_flags &=
>>~MMC_BUSRESUME_MANUAL_RESUME;
>>>>+}
>>>>
>>>> int mmc_power_save_host(struct mmc_host *host);
>>>> int mmc_power_restore_host(struct mmc_host *host);
>>>
>
More information about the Linuxppc-dev
mailing list