@@ -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);
@@ -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);
}
@@ -29,6 +29,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)
/* F_SDH30 extended Controller registers */
#define F_SDH30_AHB_CONFIG 0x100
@@ -61,8 +67,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);
@@ -173,6 +230,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__);
@@ -225,6 +288,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);
+ }
+
platform_set_drvdata(pdev, host);
#ifdef CONFIG_PM_RUNTIME
@@ -263,6 +338,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);
@@ -302,6 +380,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);
@@ -319,7 +400,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
@@ -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);