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 3/6] watchdog: tegra_wdt: Support Tegra20
Date: Tue, 10 Nov 2026 13:39:08 +0100 [thread overview]
Message-ID: <20261110-t20-for-upstream-v1-3-5923449951fe@gmail.com> (raw)
In-Reply-To: <20261110-t20-for-upstream-v1-0-5923449951fe@gmail.com>
Tegra20 has no watchdog block. Instead, the clock and reset controller
can reset the system when the interrupt of TIMER1 or TIMER2 fires while
the previous one is still pending, enabled through the WDT bits of its
RST_SOURCE register. This relies on the timer driver leaving TIMER1-2
to the watchdog on Tegra20.
Use TIMER1 for it, running periodically with half of the timeout as its
period. Clearing the interrupt alone doesn't restart the period, which
would leave between half and all of the timeout after a ping, so a ping
also rewrites the timer setting, read back from the timer, to restart
it. The timer has no readable interrupt status, so the time left can't
be reported.
RST_SOURCE belongs to the clock and reset controller, whose driver
doesn't use it, so map only this register, without requesting the
controller's region.
A bootloader may leave the watchdog running. Take it over with the
driver's timeout and let the watchdog core ping it until user space
opens the device. Since it then runs without user space having started
it, stop it for suspend and restart it on resume whenever it runs, not
only when user space started it.
A watchdog reset leaves the PMC alone, and the boot ROM hangs if the VDE
power partition is off at that point. A following patch stops the
watchdog before the partition is powered off.
Tegra30 and later list "nvidia,tegra20-timer" as a fallback compatible
string, but they keep matching "nvidia,tegra30-timer", which is more
specific.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Artur Kowalski <arturkow2000@gmail.com>
---
drivers/watchdog/tegra_wdt.c | 164 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 162 insertions(+), 2 deletions(-)
diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
index bb6233320579..7c2c21f90de5 100644
--- a/drivers/watchdog/tegra_wdt.c
+++ b/drivers/watchdog/tegra_wdt.c
@@ -8,6 +8,7 @@
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/stringify.h>
@@ -57,11 +58,35 @@
#define TIMER_PTV 0x0
#define TIMER_EN (1 << 31)
#define TIMER_PERIODIC (1 << 30)
+#define TIMER_PTV_MAX GENMASK(28, 0)
+#define TIMER_PCR 0x4
+#define TIMER_PCR_INTR_CLR (1 << 30)
+
+/*
+ * Tegra20 has no watchdog block. Instead, the clock and reset controller can
+ * reset the system when the interrupt of timer 1 or 2 fires while the previous
+ * one is still pending. This is controlled by its RST_SOURCE register.
+ */
+#define T20_WDT_TIMER_BASE 0x0
+#define T20_RST_SOURCE 0x0
+#define T20_RST_SOURCE_WDT_EN (1 << 5)
+#define T20_RST_SOURCE_WDT_SEL_TMR2 (1 << 4)
+#define T20_RST_SOURCE_WDT_SYS_RST_EN (1 << 2)
+#define T20_RST_SOURCE_WDT_MASK (T20_RST_SOURCE_WDT_EN | \
+ T20_RST_SOURCE_WDT_SEL_TMR2 | \
+ T20_RST_SOURCE_WDT_SYS_RST_EN)
+
+/*
+ * The reset occurs on the second expiry of the timer, so its period is half
+ * of the timeout. The timer has a fixed 1 MHz clock and counts PTV + 1 ticks.
+ */
+#define T20_MAX_WDT_TIMEOUT ((TIMER_PTV_MAX + 1ull) * 2 / USEC_PER_SEC)
struct tegra_wdt {
struct watchdog_device wdd;
void __iomem *wdt_regs;
void __iomem *tmr_regs;
+ void __iomem *rst_source; /* Tegra20 only */
};
struct tegra_wdt_soc {
@@ -197,6 +222,131 @@ static int tegra30_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
return 0;
}
+static int tegra20_wdt_start(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 period = wdd->timeout * USEC_PER_SEC / 2;
+ u32 val;
+
+ writel(TIMER_EN | TIMER_PERIODIC | (period - 1),
+ wdt->tmr_regs + TIMER_PTV);
+
+ /*
+ * Clear an interrupt left pending by an earlier use of the timer,
+ * which would otherwise reset the system on the first expiry.
+ */
+ writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
+
+ val = readl(wdt->rst_source);
+ 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)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 val;
+
+ val = readl(wdt->rst_source);
+ val &= ~T20_RST_SOURCE_WDT_MASK;
+ writel(val, wdt->rst_source);
+
+ writel(0, wdt->tmr_regs + TIMER_PTV);
+ writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
+
+ return 0;
+}
+
+static int tegra20_wdt_ping(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ /*
+ * 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.
+ */
+ writel(readl(wdt->tmr_regs + TIMER_PTV), wdt->tmr_regs + TIMER_PTV);
+ writel(TIMER_PCR_INTR_CLR, wdt->tmr_regs + TIMER_PCR);
+
+ return 0;
+}
+
+static int tegra20_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+
+ /* starting again reprograms the running watchdog */
+ if (watchdog_active(wdd))
+ return tegra20_wdt_start(wdd);
+
+ return 0;
+}
+
+static const struct watchdog_ops tegra20_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = tegra20_wdt_start,
+ .stop = tegra20_wdt_stop,
+ .ping = tegra20_wdt_ping,
+ .set_timeout = tegra20_wdt_set_timeout,
+};
+
+static int tegra20_wdt_init(struct platform_device *pdev, struct tegra_wdt *wdt,
+ void __iomem *regs)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np;
+ struct resource res;
+ int ret;
+
+ /*
+ * RST_SOURCE belongs to the clock and reset controller, whose driver
+ * doesn't use it. Map only this register, without requesting it, as
+ * the region belongs to the clock and reset controller.
+ */
+ np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-car");
+ if (!np)
+ return dev_err_probe(dev, -ENODEV,
+ "clock and reset controller not found\n");
+
+ ret = of_address_to_resource(np, 0, &res);
+ of_node_put(np);
+ if (ret)
+ return ret;
+
+ wdt->rst_source = devm_ioremap(dev, res.start + T20_RST_SOURCE,
+ sizeof(u32));
+ if (!wdt->rst_source)
+ return -ENOMEM;
+
+ wdt->tmr_regs = regs + T20_WDT_TIMER_BASE;
+
+ /*
+ * 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) {
+ dev_info(dev, "watchdog left running by the bootloader, taking it over (timeout %u sec)\n",
+ wdt->wdd.timeout);
+ tegra20_wdt_start(&wdt->wdd);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+
+ return 0;
+}
+
+static const struct tegra_wdt_soc tegra20_wdt_soc = {
+ .ops = &tegra20_wdt_ops,
+ .max_timeout = T20_MAX_WDT_TIMEOUT,
+ .init = tegra20_wdt_init,
+};
+
static const struct tegra_wdt_soc tegra30_wdt_soc = {
.ops = &tegra_wdt_ops,
.max_timeout = MAX_WDT_TIMEOUT,
@@ -258,11 +408,16 @@ static int tegra_wdt_probe(struct platform_device *pdev)
return 0;
}
+/*
+ * The watchdog may also run without user space having opened it, when it was
+ * taken over from the bootloader, so check whether the hardware runs. Calling
+ * the stop operation directly leaves WDOG_HW_RUNNING set for the resume.
+ */
static int tegra_wdt_suspend(struct device *dev)
{
struct tegra_wdt *wdt = dev_get_drvdata(dev);
- if (watchdog_active(&wdt->wdd))
+ if (watchdog_hw_running(&wdt->wdd))
wdt->wdd.ops->stop(&wdt->wdd);
return 0;
@@ -272,14 +427,19 @@ static int tegra_wdt_resume(struct device *dev)
{
struct tegra_wdt *wdt = dev_get_drvdata(dev);
- if (watchdog_active(&wdt->wdd))
+ if (watchdog_hw_running(&wdt->wdd))
wdt->wdd.ops->start(&wdt->wdd);
return 0;
}
+/*
+ * Tegra30 and later list "nvidia,tegra20-timer" as well, but match their more
+ * specific compatible string.
+ */
static const struct of_device_id tegra_wdt_of_match[] = {
{ .compatible = "nvidia,tegra30-timer", .data = &tegra30_wdt_soc },
+ { .compatible = "nvidia,tegra20-timer", .data = &tegra20_wdt_soc },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_wdt_of_match);
--
2.56.0
next 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 ` Artur Kowalski [this message]
2026-11-10 16:21 ` [PATCH 3/6] watchdog: tegra_wdt: Support Tegra20 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 ` [PATCH 6/6] watchdog: tegra_wdt: Stop on Tegra20 VDE power off Artur Kowalski
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-3-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®