Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 142 additions & 32 deletions drivers/watchdog/sbsa_gwdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/suspend.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h>
#include <asm/arch_timer.h>
Expand Down Expand Up @@ -88,6 +90,13 @@
* indicate whether to adjust wdd->timeout to avoid a race with WS0
* @refresh_base: Virtual address of the watchdog refresh frame
* @control_base: Virtual address of the watchdog control frame
* @lock: Serializes the watchdog ops against the system sleep hooks
* @hw_armed: The watchdog is logically running (started by firmware,
* early_enable or userspace); the hardware follows it except
* while a system sleep transition is in progress
* @pm_stopped: A system sleep transition stopped the hardware and owes
* a restart at PM_POST_*
* @pm_nb: PM notifier stopping the watchdog across system sleep
*/
struct sbsa_gwdt {
struct watchdog_device wdd;
Expand All @@ -96,6 +105,10 @@ struct sbsa_gwdt {
bool need_ws0_race_workaround;
void __iomem *refresh_base;
void __iomem *control_base;
spinlock_t lock; /* hw_armed, pm_stopped */
bool hw_armed;
bool pm_stopped;
struct notifier_block pm_nb;
};

#define DEFAULT_TIMEOUT 10 /* seconds */
Expand Down Expand Up @@ -250,22 +263,48 @@ static void sbsa_gwdt_get_version(struct watchdog_device *wdd)
!action && (impl == SBSA_GWDT_IMPL_MEDIATEK);
}

static void sbsa_gwdt_hw_start(struct sbsa_gwdt *gwdt)
{
/* writing WCS will cause an explicit watchdog refresh */
writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS);
}

static void sbsa_gwdt_hw_stop(struct sbsa_gwdt *gwdt)
{
/* Simply write 0 to WCS to clean WCS_EN bit */
writel(0, gwdt->control_base + SBSA_GWDT_WCS);
}

static int sbsa_gwdt_start(struct watchdog_device *wdd)
{
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
unsigned long flags;

/* writing WCS will cause an explicit watchdog refresh */
writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS);
spin_lock_irqsave(&gwdt->lock, flags);
gwdt->hw_armed = true;
/*
* While a system sleep transition is in progress nobody can refresh
* the watchdog: leave the hardware stopped and let the PM_POST_*
* notifier arm it once everything has resumed.
*/
if (!gwdt->pm_stopped)
sbsa_gwdt_hw_start(gwdt);
spin_unlock_irqrestore(&gwdt->lock, flags);

return 0;
}

static int sbsa_gwdt_stop(struct watchdog_device *wdd)
{
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
unsigned long flags;

/* Simply write 0 to WCS to clean WCS_EN bit */
writel(0, gwdt->control_base + SBSA_GWDT_WCS);
spin_lock_irqsave(&gwdt->lock, flags);
gwdt->hw_armed = false;
/* A stop during the transition also cancels the pending restart. */
gwdt->pm_stopped = false;
sbsa_gwdt_hw_stop(gwdt);
spin_unlock_irqrestore(&gwdt->lock, flags);

return 0;
}
Expand Down Expand Up @@ -294,6 +333,86 @@ static const struct watchdog_ops sbsa_gwdt_ops = {
.get_timeleft = sbsa_gwdt_get_timeleft,
};

/*
* Per-device suspend/resume callbacks alone would stop the watchdog only
* once this device itself is suspended, one of the last steps of suspend
* entry, and restart it during device resume, before tasks are thawed. A
* watchdog running from boot (early_enable) would therefore be armed, with
* nobody refreshing it, through task freezing and every other device's
* suspend callback on the way down, and again from device resume until
* userspace runs on the way up; anything stalling past the timeout in
* either window resets the system.
*
* Own the transition from a PM notifier instead: stop at the *_PREPARE
* events, before anything is frozen, and restart at PM_POST_*, after
* everything has resumed. The driver state (hw_armed, pm_stopped) is kept
* under a lock shared with the watchdog ops so that a userspace stop or
* magic close after thaw cannot race the restart, and a start requested
* while the transition is in progress is deferred to PM_POST_*. A
* suspend-only device callback remains as the final guard for a device
* whose probe overlapped the *_PREPARE event; it has no resume
* counterpart, so nothing re-arms the hardware before PM_POST_*.
*/
static void sbsa_gwdt_sleep_stop(struct sbsa_gwdt *gwdt)
{
unsigned long flags;

spin_lock_irqsave(&gwdt->lock, flags);
if (gwdt->hw_armed && !gwdt->pm_stopped) {
sbsa_gwdt_hw_stop(gwdt);
gwdt->pm_stopped = true;
}
spin_unlock_irqrestore(&gwdt->lock, flags);
}

static void sbsa_gwdt_sleep_restart(struct sbsa_gwdt *gwdt)
{
unsigned long flags;

spin_lock_irqsave(&gwdt->lock, flags);
if (gwdt->pm_stopped) {
gwdt->pm_stopped = false;
if (gwdt->hw_armed)
sbsa_gwdt_hw_start(gwdt);
}
spin_unlock_irqrestore(&gwdt->lock, flags);
}

static int sbsa_gwdt_pm_notify(struct notifier_block *nb, unsigned long mode,
void *data)
{
struct sbsa_gwdt *gwdt = container_of(nb, struct sbsa_gwdt, pm_nb);

switch (mode) {
case PM_SUSPEND_PREPARE:
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
sbsa_gwdt_sleep_stop(gwdt);
break;
case PM_POST_SUSPEND:
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
sbsa_gwdt_sleep_restart(gwdt);
break;
}

return NOTIFY_DONE;
}

static void sbsa_gwdt_unregister_pm_notifier(void *data)
{
unregister_pm_notifier(data);
}

static int sbsa_gwdt_suspend(struct device *dev)
{
sbsa_gwdt_sleep_stop(dev_get_drvdata(dev));

return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(sbsa_gwdt_pm_ops, sbsa_gwdt_suspend, NULL);

static int sbsa_gwdt_probe(struct platform_device *pdev)
{
void __iomem *rf_base, *cf_base;
Expand Down Expand Up @@ -325,6 +444,21 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
gwdt->clk = arch_timer_get_cntfrq();
gwdt->refresh_base = rf_base;
gwdt->control_base = cf_base;
spin_lock_init(&gwdt->lock);

/*
* Register the sleep hook before anything can arm the watchdog, and
* treat its failure as fatal: without it a running watchdog would
* survive into system sleep with nobody refreshing it.
*/
gwdt->pm_nb.notifier_call = sbsa_gwdt_pm_notify;
ret = register_pm_notifier(&gwdt->pm_nb);
if (!ret)
ret = devm_add_action_or_reset(dev,
sbsa_gwdt_unregister_pm_notifier,
&gwdt->pm_nb);
if (ret)
return dev_err_probe(dev, ret, "Failed to register PM notifier\n");

wdd = &gwdt->wdd;
wdd->parent = dev;
Expand Down Expand Up @@ -354,8 +488,10 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
dev_warn(dev, "System reset by WDT.\n");
wdd->bootstatus |= WDIOF_CARDRESET;
}
if (status & SBSA_GWDT_WCS_EN)
if (status & SBSA_GWDT_WCS_EN) {
set_bit(WDOG_HW_RUNNING, &wdd->status);
gwdt->hw_armed = true;
}

if (action) {
irq = platform_get_irq(pdev, 0);
Expand Down Expand Up @@ -414,32 +550,6 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
return 0;
}

/* Disable watchdog if it is active during suspend */
static int __maybe_unused sbsa_gwdt_suspend(struct device *dev)
{
struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);

if (watchdog_hw_running(&gwdt->wdd))
sbsa_gwdt_stop(&gwdt->wdd);

return 0;
}

/* Enable watchdog if necessary */
static int __maybe_unused sbsa_gwdt_resume(struct device *dev)
{
struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);

if (watchdog_hw_running(&gwdt->wdd))
sbsa_gwdt_start(&gwdt->wdd);

return 0;
}

static const struct dev_pm_ops sbsa_gwdt_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(sbsa_gwdt_suspend, sbsa_gwdt_resume)
};

static const struct of_device_id sbsa_gwdt_of_match[] = {
{ .compatible = "arm,sbsa-gwdt", },
{},
Expand All @@ -455,7 +565,7 @@ MODULE_DEVICE_TABLE(platform, sbsa_gwdt_pdev_match);
static struct platform_driver sbsa_gwdt_driver = {
.driver = {
.name = DRV_NAME,
.pm = &sbsa_gwdt_pm_ops,
.pm = pm_sleep_ptr(&sbsa_gwdt_pm_ops),
.of_match_table = sbsa_gwdt_of_match,
},
.probe = sbsa_gwdt_probe,
Expand Down
Loading