From: Artur Kowalski <arturkow2000@gmail.com>
To: Daniel Lezcano <daniel.lezcano@kernel.org>,
Thomas Gleixner <tglx@kernel.org>,
Thierry Reding <thierry.reding@kernel.org>,
Jonathan Hunter <jonathanh@nvidia.com>,
Wim Van Sebroeck <wim@linux-watchdog.org>,
Guenter Roeck <linux@roeck-us.net>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Philipp Zabel <p.zabel@pengutronix.de>
Cc: linux-kernel@vger.kernel.org, linux-tegra@vger.kernel.org,
linux-watchdog@vger.kernel.org, linux-doc@vger.kernel.org,
clamor95@gmail.com, Artur Kowalski <arturkow2000@gmail.com>
Subject: [PATCH 6/6] watchdog: tegra_wdt: Stop on Tegra20 VDE power off
Date: Tue, 10 Nov 2026 13:39:11 +0100 [thread overview]
Message-ID: <20261110-t20-for-upstream-v1-6-5923449951fe@gmail.com> (raw)
In-Reply-To: <20261110-t20-for-upstream-v1-0-5923449951fe@gmail.com>
A watchdog reset doesn't reset the PMC, and Tegra20's boot ROM hangs
after it if the VDE power partition is off. Unless booted with
tegra_pmc.vde_always_on=1, the PMC driver powers the partition off when
it registers it, and genpd powers it off whenever the VDE is idle. A
watchdog started by the bootloader can still catch hangs early during
boot, before that happens.
Register a PMC partition power off notifier to stop the watchdog, and
the watchdog core's pings, before the VDE partition is powered off for
the first time. If the partition is already off, a watchdog left
running by the bootloader is stopped instead of taken over. Afterwards
the watchdog refuses to start with -EBUSY, as a reset would hang, even
if the partition is powered on again. If the notifier can't be
registered, the driver warns and keeps the watchdog running.
Stopping is the default. With tegra_pmc.vde_always_on=1, the partition
is never powered off, so the watchdog stays usable. Booting with
tegra_wdt.stop_on_vde_off=0 also keeps it usable, but at the risk of the
system hanging instead of rebooting.
The watchdog operations and the notifier share a mutex, as the notifier
runs outside of the watchdog core's locking.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Artur Kowalski <arturkow2000@gmail.com>
---
Documentation/watchdog/watchdog-parameters.rst | 5 +
drivers/watchdog/tegra_wdt.c | 143 +++++++++++++++++++++++--
2 files changed, 137 insertions(+), 11 deletions(-)
diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst
index 348229ffcb43..3f7a1831c5cb 100644
--- a/Documentation/watchdog/watchdog-parameters.rst
+++ b/Documentation/watchdog/watchdog-parameters.rst
@@ -600,6 +600,11 @@ tegra_wdt:
nowayout:
Watchdog cannot be stopped once started
(default=kernel config parameter)
+ stop_on_vde_off:
+ Tegra20: stop the watchdog for good when the VDE partition is
+ powered off, as a reset would hang the boot ROM. Booting with
+ tegra_pmc.vde_always_on=1 keeps the partition, and with it the
+ watchdog, usable (default=1)
-------------------------------------------------
diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
index 7c2c21f90de5..d36310c50f78 100644
--- a/drivers/watchdog/tegra_wdt.c
+++ b/drivers/watchdog/tegra_wdt.c
@@ -7,6 +7,8 @@
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -14,6 +16,8 @@
#include <linux/stringify.h>
#include <linux/watchdog.h>
+#include <soc/tegra/pmc.h>
+
/* minimum and maximum watchdog trigger timeout, in seconds */
#define MIN_WDT_TIMEOUT 1
#define MAX_WDT_TIMEOUT 255
@@ -86,7 +90,12 @@ struct tegra_wdt {
struct watchdog_device wdd;
void __iomem *wdt_regs;
void __iomem *tmr_regs;
- void __iomem *rst_source; /* Tegra20 only */
+
+ /* Tegra20 only */
+ void __iomem *rst_source;
+ struct notifier_block powergate_nb;
+ struct mutex lock; /* hardware and vde_off */
+ bool vde_off;
};
struct tegra_wdt_soc {
@@ -109,6 +118,21 @@ MODULE_PARM_DESC(nowayout,
"Watchdog cannot be stopped once started (default="
__stringify(WATCHDOG_NOWAYOUT) ")");
+/*
+ * After a watchdog reset, Tegra20's boot ROM hangs if the VDE partition is
+ * powered off, which the PMC driver does when it registers the partition,
+ * unless booted with tegra_pmc.vde_always_on=1. By default, the watchdog is
+ * therefore only used until the partition is powered off for the first time,
+ * which still allows a watchdog started by the bootloader to catch hangs early
+ * during boot. tegra_pmc.vde_always_on=1 keeps the watchdog usable for good.
+ * Clearing this keeps it usable as well, but at the risk of the system
+ * hanging instead of rebooting.
+ */
+static bool stop_on_vde_off = true;
+module_param(stop_on_vde_off, bool, 0);
+MODULE_PARM_DESC(stop_on_vde_off,
+ "Tegra20: stop the watchdog for good when the VDE partition is powered off (default=1)");
+
static int tegra_wdt_start(struct watchdog_device *wdd)
{
struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
@@ -222,10 +246,9 @@ static int tegra30_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
return 0;
}
-static int tegra20_wdt_start(struct watchdog_device *wdd)
+static void __tegra20_wdt_start(struct tegra_wdt *wdt)
{
- struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
- u32 period = wdd->timeout * USEC_PER_SEC / 2;
+ u32 period = wdt->wdd.timeout * USEC_PER_SEC / 2;
u32 val;
writel(TIMER_EN | TIMER_PERIODIC | (period - 1),
@@ -241,13 +264,10 @@ static int tegra20_wdt_start(struct watchdog_device *wdd)
val &= ~T20_RST_SOURCE_WDT_MASK;
val |= T20_RST_SOURCE_WDT_EN | T20_RST_SOURCE_WDT_SYS_RST_EN;
writel(val, wdt->rst_source);
-
- return 0;
}
-static int tegra20_wdt_stop(struct watchdog_device *wdd)
+static void __tegra20_wdt_stop(struct tegra_wdt *wdt)
{
- struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
u32 val;
val = readl(wdt->rst_source);
@@ -256,6 +276,35 @@ static int tegra20_wdt_stop(struct watchdog_device *wdd)
writel(0, wdt->tmr_regs + TIMER_PTV);
writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
+}
+
+static int tegra20_wdt_start(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret = 0;
+
+ mutex_lock(&wdt->lock);
+
+ if (wdt->vde_off) {
+ dev_warn(wdd->parent,
+ "not starting, a reset would hang with VDE powered off\n");
+ ret = -EBUSY;
+ } else {
+ __tegra20_wdt_start(wdt);
+ }
+
+ mutex_unlock(&wdt->lock);
+
+ return ret;
+}
+
+static int tegra20_wdt_stop(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ mutex_lock(&wdt->lock);
+ __tegra20_wdt_stop(wdt);
+ mutex_unlock(&wdt->lock);
return 0;
}
@@ -264,15 +313,20 @@ static int tegra20_wdt_ping(struct watchdog_device *wdd)
{
struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ mutex_lock(&wdt->lock);
+
/*
* Clearing the interrupt alone doesn't restart the timer's period, so
* the reset could follow as soon as half the timeout after the ping.
* Rewriting the timer setting restarts the period. The interrupt is
- * cleared afterwards, so one fired in between doesn't count.
+ * cleared afterwards, so one fired in between doesn't count. While
+ * stopped, the timer setting reads back as 0, so this doesn't start it.
*/
writel(readl(wdt->tmr_regs + TIMER_PTV), wdt->tmr_regs + TIMER_PTV);
writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
+ mutex_unlock(&wdt->lock);
+
return 0;
}
@@ -296,6 +350,45 @@ static const struct watchdog_ops tegra20_wdt_ops = {
.set_timeout = tegra20_wdt_set_timeout,
};
+static int tegra20_wdt_powergate_notify(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct tegra_wdt *wdt = container_of(nb, struct tegra_wdt,
+ powergate_nb);
+ unsigned int id = *(unsigned int *)data;
+
+ if (event != TEGRA_PMC_POWERGATE_POWER_OFF ||
+ id != TEGRA_POWERGATE_VDEC)
+ return NOTIFY_DONE;
+
+ mutex_lock(&wdt->lock);
+
+ if (!wdt->vde_off) {
+ wdt->vde_off = true;
+ __tegra20_wdt_stop(wdt);
+
+ /*
+ * Stop the core's pings. If user space has the device open,
+ * its pings don't restart the watchdog either.
+ */
+ clear_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+
+ dev_info(wdt->wdd.parent,
+ "VDE powered off, watchdog stopped\n");
+ }
+
+ mutex_unlock(&wdt->lock);
+
+ return NOTIFY_OK;
+}
+
+static void tegra20_wdt_unregister_notifier(void *data)
+{
+ struct tegra_wdt *wdt = data;
+
+ tegra_pmc_powergate_unregister_notifier(&wdt->powergate_nb);
+}
+
static int tegra20_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
void __iomem *regs)
{
@@ -326,18 +419,46 @@ static int tegra20_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
wdt->tmr_regs = regs + T20_WDT_TIMER_BASE;
+ ret = devm_mutex_init(dev, &wdt->lock);
+ if (ret)
+ return ret;
+
+ /*
+ * If the VDE partition is already powered off, the notifier is called
+ * right away and stops a watchdog left running by the bootloader.
+ */
+ if (stop_on_vde_off) {
+ wdt->powergate_nb.notifier_call = tegra20_wdt_powergate_notify;
+
+ ret = tegra_pmc_powergate_register_notifier(&wdt->powergate_nb);
+ if (ret) {
+ dev_warn(dev, "failed to register VDE notifier: %d\n",
+ ret);
+ } else {
+ ret = devm_add_action_or_reset(dev,
+ tegra20_wdt_unregister_notifier, wdt);
+ if (ret)
+ return ret;
+ }
+ }
+
/*
* The bootloader may leave the watchdog running. Take it over with
* our own timeout and timer 1, and let the core ping it until user
* space opens the device.
*/
- if (readl(wdt->rst_source) & T20_RST_SOURCE_WDT_EN) {
+ mutex_lock(&wdt->lock);
+
+ if (!wdt->vde_off &&
+ (readl(wdt->rst_source) & T20_RST_SOURCE_WDT_EN)) {
dev_info(dev, "watchdog left running by the bootloader, taking it over (timeout %u sec)\n",
wdt->wdd.timeout);
- tegra20_wdt_start(&wdt->wdd);
+ __tegra20_wdt_start(wdt);
set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
}
+ mutex_unlock(&wdt->lock);
+
return 0;
}
--
2.56.0
prev parent reply other threads:[~2026-11-10 12:39 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 12:39 [PATCH 0/6] watchdog: tegra_wdt: Add Tegra20 support Artur Kowalski
2026-11-10 12:39 ` [PATCH 1/6] clocksource/drivers/timer-tegra: Keep Tegra20's watchdog timers free Artur Kowalski
2026-11-10 12:39 ` [PATCH 2/6] watchdog: tegra_wdt: Move SoC specific parts into match data Artur Kowalski
2026-11-10 12:39 ` [PATCH 3/6] watchdog: tegra_wdt: Support Tegra20 Artur Kowalski
2026-11-10 16:21 ` Krzysztof Kozlowski
2026-11-10 12:39 ` [PATCH 4/6] soc/tegra: pmc: Add an option to keep the VDE partition on Artur Kowalski
2026-11-10 12:39 ` [PATCH 5/6] soc/tegra: pmc: Add a partition power off notifier Artur Kowalski
2026-11-10 12:39 ` Artur Kowalski [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110-t20-for-upstream-v1-6-5923449951fe@gmail.com \
--to=arturkow2000@gmail.com \
--cc=clamor95@gmail.com \
--cc=corbet@lwn.net \
--cc=daniel.lezcano@kernel.org \
--cc=jonathanh@nvidia.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=linux-watchdog@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=p.zabel@pengutronix.de \
--cc=rdunlap@infradead.org \
--cc=skhan@linuxfoundation.org \
--cc=tglx@kernel.org \
--cc=thierry.reding@kernel.org \
--cc=wim@linux-watchdog.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®