diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index aa1b82961e69..21ffd1121706 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -1021,6 +1021,7 @@ out: mmc_release_host(host); host->pm_flags &= ~MMC_PM_KEEP_POWER; + host->pm_flags &= ~MMC_PM_WAKE_SDIO_IRQ; return err; } diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ae6149fa6c66..99c9357c4b05 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -133,6 +133,7 @@ #define INVALID_TUNING_PHASE -1 +#define sdhci_is_valid_gpio_wakeup_int(_h) ((_h)->sdiowakeup_irq >= 0) #define SDHCI_MSM_MIN_CLOCK 400000 #define CORE_FREQ_100MHZ (100 * 1000 * 1000) #define TCXO_FREQ 19200000 @@ -463,6 +464,9 @@ struct sdhci_msm_host { bool cqhci_offset_changed; bool reg_store; struct reset_control *core_reset; + int sdiowakeup_irq; + bool is_sdiowakeup_enabled; + bool sdio_pending_processing; }; static struct sdhci_msm_host *sdhci_slot[2]; @@ -1978,6 +1982,43 @@ static void sdhci_msm_check_power_status(struct sdhci_host *host, u32 req_type) __func__, req_type); } +/* + * Acquire spin-lock host->lock before calling this function + */ +static void sdhci_msm_cfg_sdiowakeup_gpio_irq(struct sdhci_host *host, + bool enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + + if (enable && !msm_host->is_sdiowakeup_enabled) + enable_irq(msm_host->sdiowakeup_irq); + else if (!enable && msm_host->is_sdiowakeup_enabled) + disable_irq_nosync(msm_host->sdiowakeup_irq); + else + dev_warn(&msm_host->pdev->dev, "%s: wakeup to config: %d curr: %d\n", + __func__, enable, msm_host->is_sdiowakeup_enabled); + msm_host->is_sdiowakeup_enabled = enable; +} + +static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data) +{ + struct sdhci_host *host = (struct sdhci_host *)data; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + + unsigned long flags; + + pr_debug("%s: irq (%d) received\n", __func__, irq); + + spin_lock_irqsave(&host->lock, flags); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + spin_unlock_irqrestore(&host->lock, flags); + msm_host->sdio_pending_processing = true; + + return IRQ_HANDLED; +} + static void sdhci_msm_dump_pwr_ctrl_regs(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -3555,6 +3596,33 @@ static void sdhci_msm_clkgate_bus_delayed_work(struct work_struct *work) sdhci_msm_bus_voting(host, false); } +static int sdhci_msm_ungate_clocks(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + int ret = 0; + + sdhci_msm_bus_voting(host, true); + ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), + msm_host->bulk_clks); + if (ret) { + dev_err(&msm_host->pdev->dev, "Failed to enable clocks %d\n", ret); + sdhci_msm_bus_voting(host, false); + return ret; + } + + sdhci_msm_registers_restore(host); + sdhci_msm_toggle_fifo_write_clk(host); + /* + * Whenever core-clock is gated dynamically, it's needed to + * restore the SDR DLL settings when the clock is ungated. + */ + if (msm_host->restore_dll_config && msm_host->clk_rate) + sdhci_msm_restore_sdr_dll_config(host); + + return ret; +} + /* Find cpu group qos from a given cpu */ static struct qos_cpu_group *cpu_to_group(struct sdhci_msm_qos_req *r, int cpu) { @@ -4161,6 +4229,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) const struct sdhci_msm_variant_info *var_info; struct device *dev = &pdev->dev; struct device_node *node = pdev->dev.of_node; + unsigned long flags; host = sdhci_pltfm_init(pdev, &sdhci_msm_pdata, sizeof(*msm_host)); if (IS_ERR(host)) @@ -4428,6 +4497,31 @@ static int sdhci_msm_probe(struct platform_device *pdev) ret = sdhci_add_host(host); if (ret) goto pm_runtime_disable; + + msm_host->sdiowakeup_irq = platform_get_irq_byname(pdev, + "sdiowakeup_irq"); + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + dev_info(&pdev->dev, "%s: sdiowakeup_irq = %d\n", __func__, + msm_host->sdiowakeup_irq); + msm_host->is_sdiowakeup_enabled = true; + ret = request_irq(msm_host->sdiowakeup_irq, + sdhci_msm_sdiowakeup_irq, + IRQF_SHARED | IRQF_TRIGGER_HIGH, + "sdhci-msm sdiowakeup", host); + if (ret) { + dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d\n", + __func__, msm_host->sdiowakeup_irq, ret); + msm_host->sdiowakeup_irq = -1; + msm_host->is_sdiowakeup_enabled = false; + goto vreg_deinit; + } else { + spin_lock_irqsave(&host->lock, flags); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + msm_host->sdio_pending_processing = false; + spin_unlock_irqrestore(&host->lock, flags); + } + } + sdhci_msm_set_regulator_caps(msm_host); #if defined(CONFIG_SDC_QTI) @@ -4511,6 +4605,53 @@ skip_removing_qos: return 0; } +static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + unsigned long flags; + int ret = 0; + + if (!(host->mmc->card && mmc_card_sdio(host->mmc->card) && + sdhci_is_valid_gpio_wakeup_int(msm_host) && + mmc_card_wake_sdio_irq(host->mmc))) { + msm_host->sdio_pending_processing = false; + return 1; + } + + spin_lock_irqsave(&host->lock, flags); + if (enable) { + /* configure DAT1 gpio if applicable */ + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + msm_host->sdio_pending_processing = false; + ret = enable_irq_wake(msm_host->sdiowakeup_irq); + if (!ret) + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, true); + goto out; + } else { + pr_err("%s: sdiowakeup_irq(%d) invalid\n", + mmc_hostname(host->mmc), enable); + } + } else { + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + ret = disable_irq_wake(msm_host->sdiowakeup_irq); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + msm_host->sdio_pending_processing = false; + } else { + pr_err("%s: sdiowakeup_irq(%d)invalid\n", + mmc_hostname(host->mmc), enable); + + } + } +out: + if (ret) + pr_err("%s: %s: %sable wakeup: failed: %d gpio: %d\n", + mmc_hostname(host->mmc), __func__, enable ? "en" : "dis", + ret, msm_host->sdiowakeup_irq); + spin_unlock_irqrestore(&host->lock, flags); + return ret; +} + static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); @@ -4523,10 +4664,32 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) sdhci_msm_unvote_qos_all(msm_host); skip_qos: + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + goto skip_clk_gating; + queue_delayed_work(msm_host->workq, &msm_host->clk_gating_work, msecs_to_jiffies(msm_host->clk_gating_delay)); +skip_clk_gating: + return 0; +} + +static int sdhci_msm_resume_early(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + int ret = 0; + + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + if (msm_host->is_sdiowakeup_enabled) + sdhci_msm_cfg_sdio_wakeup(host, false); + + ret = sdhci_msm_ungate_clocks(host); + if (ret) + return ret; + } return 0; } @@ -4538,27 +4701,17 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; int ret; + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + goto skip_clk_ungating; + ret = cancel_delayed_work_sync(&msm_host->clk_gating_work); if (!ret) { - sdhci_msm_bus_voting(host, true); - ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), - msm_host->bulk_clks); - if (ret) { - dev_err(dev, "Failed to enable clocks %d\n", ret); - sdhci_msm_bus_voting(host, false); + ret = sdhci_msm_ungate_clocks(host); + if (ret) return ret; - } - - sdhci_msm_registers_restore(host); - sdhci_msm_toggle_fifo_write_clk(host); - /* - * Whenever core-clock is gated dynamically, it's needed to - * restore the SDR DLL settings when the clock is ungated. - */ - if (msm_host->restore_dll_config && msm_host->clk_rate) - sdhci_msm_restore_sdr_dll_config(host); } +skip_clk_ungating: if (!qos_req) return 0; sdhci_msm_vote_pmqos(msm_host->mmc, @@ -4573,19 +4726,42 @@ static int sdhci_msm_suspend_late(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + sdhci_msm_cfg_sdio_wakeup(host, true); + + /* Start gating work asap for SDIO card in late suspend */ + queue_delayed_work(msm_host->workq, + &msm_host->clk_gating_work, 0); + } + if (flush_delayed_work(&msm_host->clk_gating_work)) dev_dbg(dev, "%s Waited for clk_gating_work to finish\n", __func__); return 0; } +static int sdhci_msm_suspend_noirq(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + int ret = 0; + + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + if (msm_host->sdio_pending_processing) + ret = -EBUSY; + + return ret; +} + static const struct dev_pm_ops sdhci_msm_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) - SET_LATE_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend_late, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend_late, sdhci_msm_resume_early) SET_RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume, NULL) + .suspend_noirq = sdhci_msm_suspend_noirq, }; static struct platform_driver sdhci_msm_driver = {