diff --git a/drivers/soc/qcom/qcom_soc_wdt.c b/drivers/soc/qcom/qcom_soc_wdt.c index b2c66f92a26d..1814d4e8d5b1 100644 --- a/drivers/soc/qcom/qcom_soc_wdt.c +++ b/drivers/soc/qcom/qcom_soc_wdt.c @@ -105,6 +105,13 @@ static int qcom_soc_wdt_probe(struct platform_device *pdev) return qcom_wdt_register(pdev, wdog_dd, "msm-watchdog"); } +static const struct dev_pm_ops qcom_soc_dev_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend_late = qcom_wdt_pet_suspend, + .resume_early = qcom_wdt_pet_resume, +#endif +}; + static const struct of_device_id qcom_soc_match_table[] = { { .compatible = "qcom,msm-watchdog" }, {} @@ -115,6 +122,7 @@ static struct platform_driver qcom_soc_wdt_driver = { .remove = qcom_wdt_remove, .driver = { .name = "msm_watchdog", + .pm = &qcom_soc_dev_pm_ops, .of_match_table = qcom_soc_match_table, }, }; diff --git a/drivers/soc/qcom/qcom_wdt_core.c b/drivers/soc/qcom/qcom_wdt_core.c index 165bd279442f..8177539beac3 100644 --- a/drivers/soc/qcom/qcom_wdt_core.c +++ b/drivers/soc/qcom/qcom_wdt_core.c @@ -89,6 +89,45 @@ static void qcom_wdt_resume(void) wdog_data->last_pet = sched_clock(); return; } + +int qcom_wdt_pet_suspend(struct device *dev) +{ + struct msm_watchdog_data *wdog_dd = + (struct msm_watchdog_data *)dev_get_drvdata(dev); + + if (!wdog_dd) + return 0; + + wdog_dd->ops->reset_wdt(wdog_dd); + wdog_dd->last_pet = sched_clock(); + spin_lock(&wdog_dd->freeze_lock); + wdog_dd->freeze_in_progress = true; + spin_unlock(&wdog_dd->freeze_lock); + del_timer_sync(&wdog_dd->pet_timer); + return 0; +} +EXPORT_SYMBOL(qcom_wdt_pet_suspend); + +int qcom_wdt_pet_resume(struct device *dev) +{ + struct msm_watchdog_data *wdog_dd = + (struct msm_watchdog_data *)dev_get_drvdata(dev); + unsigned long delay_time = 0; + + if (!wdog_dd) + return 0; + + delay_time = msecs_to_jiffies(wdog_dd->pet_time); + wdog_dd->ops->reset_wdt(wdog_dd); + wdog_dd->last_pet = sched_clock(); + spin_lock(&wdog_dd->freeze_lock); + wdog_dd->pet_timer.expires = jiffies + delay_time; + add_timer(&wdog_dd->pet_timer); + wdog_dd->freeze_in_progress = false; + spin_unlock(&wdog_dd->freeze_lock); + return 0; +} +EXPORT_SYMBOL(qcom_wdt_pet_resume); #endif static struct syscore_ops qcom_wdt_syscore_ops = { @@ -339,8 +378,13 @@ static __ref int qcom_wdt_kthread(void *arg) /* Check again before scheduling * Could have been changed on other cpu */ - if (!kthread_should_stop()) - mod_timer(&wdog_dd->pet_timer, jiffies + delay_time); + if (!kthread_should_stop()) { + spin_lock(&wdog_dd->freeze_lock); + if (!wdog_dd->freeze_in_progress) + mod_timer(&wdog_dd->pet_timer, + jiffies + delay_time); + spin_unlock(&wdog_dd->freeze_lock); + } } return 0; } @@ -473,9 +517,9 @@ static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd, int ret; ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq, - qcom_wdt_bark_handler, - IRQF_TRIGGER_RISING, - "apps_wdog_bark", wdog_dd); + qcom_wdt_bark_handler, + IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, + "apps_wdog_bark", wdog_dd); if (ret) { dev_err(wdog_dd->dev, "failed to request bark irq\n"); return -EINVAL; @@ -495,6 +539,8 @@ static int qcom_wdt_init(struct msm_watchdog_data *wdog_dd, wdog_dd->timer_expired = false; wdog_dd->user_pet_complete = true; wdog_dd->user_pet_enabled = false; + spin_lock_init(&wdog_dd->freeze_lock); + wdog_dd->freeze_in_progress = false; wake_up_process(wdog_dd->watchdog_task); timer_setup(&wdog_dd->pet_timer, qcom_wdt_pet_task_wakeup, 0); wdog_dd->pet_timer.expires = jiffies + delay_time; diff --git a/drivers/virt/haven/hh_virt_wdt.c b/drivers/virt/haven/hh_virt_wdt.c index 51c1b6c2eeb1..be25ae6c7ed4 100644 --- a/drivers/virt/haven/hh_virt_wdt.c +++ b/drivers/virt/haven/hh_virt_wdt.c @@ -261,6 +261,13 @@ static int hh_wdt_probe(struct platform_device *pdev) return qcom_wdt_register(pdev, wdog_dd, "hh-watchdog"); } +static const struct dev_pm_ops hh_wdt_dev_pm_ops = { +#ifdef CONFIG_PM_SLEEP + .suspend_late = qcom_wdt_pet_suspend, + .resume_early = qcom_wdt_pet_resume, +#endif +}; + static const struct of_device_id hh_wdt_match_table[] = { { .compatible = "qcom,hh-watchdog" }, {} @@ -271,6 +278,7 @@ static struct platform_driver hh_wdt_driver = { .remove = qcom_wdt_remove, .driver = { .name = "hh-watchdog", + .pm = &hh_wdt_dev_pm_ops, .of_match_table = hh_wdt_match_table, }, }; diff --git a/include/soc/qcom/watchdog.h b/include/soc/qcom/watchdog.h index 7b60f053ce1e..603aa02d86b6 100644 --- a/include/soc/qcom/watchdog.h +++ b/include/soc/qcom/watchdog.h @@ -123,12 +123,16 @@ struct msm_watchdog_data { unsigned long long ping_start[NR_CPUS]; unsigned long long ping_end[NR_CPUS]; int cpu_idle_pc_state[NR_CPUS]; + bool freeze_in_progress; + spinlock_t freeze_lock; }; extern void qcom_wdt_trigger_bite(void); int qcom_wdt_register(struct platform_device *pdev, struct msm_watchdog_data *wdog_dd, char *wdog_dd_name); +int qcom_wdt_pet_suspend(struct device *dev); +int qcom_wdt_pet_resume(struct device *dev); int qcom_wdt_remove(struct platform_device *pdev); #else static inline void qcom_wdt_trigger_bite(void) { }