Skip to contentSkip to conversation

watchdog: tegra: support Tegra20

PATCH Superseded Artur Kowalski 2 participants 2 messages Nov 9 – Nov 10

Subsystems (2)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 watchdog: tegra: support Tegra20

The timer driver binds tegra_wdt to Tegra20's timer node as well, but the driver programs timer 5 and watchdog 0, which only exist on Tegra30 and later. On Tegra20 they lie past the end of the timer block, so starting the watchdog writes to unrelated registers.

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, enabled through the WDT bits of RST_SOURCE. Use timer 1 for it (U-Boot itself only uses the microsecond counter): run it periodic with half of the timeout as 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.

Assisted-by Claude:claude-opus-5-5

---
Add watchdog support for Tegra20. This makes it possible to recover from hangs in U-Boot or early during Linux boot.

Linux doesn't handle a running Tegra20 watchdog yet. Its timer driver takes over TIMER1 for CPU0's clock event device, which can reset the system early during boot.

There is also a Tegra20 quirk that affects any watchdog reset while Linux runs: the watchdog resets the system through the clock and reset controller, which doesn't reset the PMC, so the power partitions keep their state. Linux powers the VDE partition off when its PMC driver registers it, and whenever the VDE is idle. If the watchdog fires while the partition is off, the boot ROM hangs and the device stays dead until it is powered off. Linux already works around the same hang for reboots (commit 8f0c714ad9be ("media: staging: tegra-vde: Turn ON power domain on shutdown") and commit 510f276df2b9 ("mfd: tps6586x: Register restart handler")). The Linux patches that will follow stop the watchdog before the VDE partition is powered off.

For these reasons I haven't enabled the watchdog in any defconfig.

checkpatch suggests IS_ENABLED() instead of the #if blocks. They select whole functions and a struct member, as a U-Boot binary supports a single SoC generation and the two variants share no register layout. I'm happy to restructure it with IS_ENABLED() if preferred.

The patch was developed with the help of Claude Code. I have reviewed it and tested it on an ASUS TF101 (Tegra20). Tegra30 and later are untested, but their code path is unchanged.

---
 drivers/watchdog/tegra_wdt.c | 102 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 97 insertions(+), 5 deletions(-)
drivers/watchdog/tegra_wdt.c +97 -5
index adc30da579a..6dc3a3e0b1d 100644
@@ -10,13 +10,105 @@
 #include <wdt.h>
 #include <hang.h>
 #include <asm/io.h>
+#include <asm/arch/tegra.h>
+#include <asm/arch-tegra/clk_rst.h>
 #include <watchdog.h>
 
 /* Timer registers */
 #define TIMER_PTV			0x0
 #define TIMER_EN			BIT(31)
 #define TIMER_PERIODIC			BIT(30)
+#define TIMER_PTV_MAX			GENMASK(28, 0)
+#define TIMER_PCR			0x4
+#define TIMER_PCR_INTR_CLR		BIT(30)
 
+struct tegra_wdt_priv {
+#if !defined(CONFIG_TEGRA20)
+	void __iomem *wdt_base;
+#endif
+	void __iomem *tmr_base;
+};
+
+#if defined(CONFIG_TEGRA20)
+/*
+ * Tegra20 has no watchdog block. Instead the clock and reset controller can
+ * reset the system when the interrupt of timer 1 or 2 is asserted while the
+ * previous one is still pending.
+ */
+#define T20_WDT_EN			BIT(5)
+#define T20_WDT_SEL_TMR2		BIT(4)
+#define T20_WDT_SYS_RST_EN		BIT(2)
+#define T20_WDT_MASK			(T20_WDT_EN | T20_WDT_SEL_TMR2 | \
+					 T20_WDT_SYS_RST_EN)
+
+/* Use Timer 1 as WDT counter */
+#define T20_WDT_TIMER_BASE		0x0
+
+static int tegra_wdt_reset(struct udevice *dev)
+{
+	struct tegra_wdt_priv *priv = dev_get_priv(dev);
+
+	/*
+	 * Clearing the interrupt alone doesn't restart the timer, so the
+	 * reset could follow as soon as half the timeout after the ping.
+	 * Rewriting the timer setting restarts its period. The register reads
+	 * back the setting (in periodic mode, the timer never clears TIMER_EN),
+	 * and while stopped it holds 0, so pinging doesn't start the timer.
+	 */
+	writel(readl(priv->tmr_base + TIMER_PTV), priv->tmr_base + TIMER_PTV);
+	/*
+	 * Clear the pending interrupt. Doing it after the restart is safe:
+	 * an interrupt from the new period that fired in between is just
+	 * cleared as part of this ping.
+	 */
+	writel(TIMER_PCR_INTR_CLR, priv->tmr_base + TIMER_PCR);
+
+	return 0;
+}
+
+static int tegra_wdt_start(struct udevice *dev, u64 timeout, ulong flags)
+{
+	struct clk_rst_ctlr *clkrst = (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE;
+	struct tegra_wdt_priv *priv = dev_get_priv(dev);
+	u64 period;
+
+	/*
+	 * The reset occurs on the second expiry of the timer after a ping,
+	 * so the timer period is half of the timeout. The timer has a fixed
+	 * 1 MHz clock and counts PTV + 1 ticks.
+	 */
+	period = timeout * 1000 / 2;
+	if (period < 1 || period > TIMER_PTV_MAX + 1ull)
+		return -EINVAL;
+
+	writel(TIMER_EN | TIMER_PERIODIC | (u32)(period - 1),
+	       priv->tmr_base + TIMER_PTV);
+	/*
+	 * Clear an interrupt left pending from earlier use of the timer,
+	 * which would otherwise reset the system on the first expiry, after
+	 * half the timeout. If the new timer has already expired, this counts
+	 * as a ping, which is harmless right after starting.
+	 */
+	writel(TIMER_PCR_INTR_CLR, priv->tmr_base + TIMER_PCR);
+
+	clrsetbits_le32(&clkrst->crc_rst_src, T20_WDT_MASK,
+			T20_WDT_EN | T20_WDT_SYS_RST_EN);
+
+	return 0;
+}
+
+static int tegra_wdt_stop(struct udevice *dev)
+{
+	struct clk_rst_ctlr *clkrst = (struct clk_rst_ctlr *)NV_PA_CLK_RST_BASE;
+	struct tegra_wdt_priv *priv = dev_get_priv(dev);
+
+	clrbits_le32(&clkrst->crc_rst_src, T20_WDT_MASK);
+	writel(0, priv->tmr_base + TIMER_PTV);
+	writel(TIMER_PCR_INTR_CLR, priv->tmr_base + TIMER_PCR);
+
+	return 0;
+}
+#else
 /* WDT registers */
 #define WDT_CFG				0x0
 #define WDT_CFG_PERIOD_SHIFT		4
@@ -36,11 +128,6 @@
 #define WDT_TIMER_BASE			0x60
 #define WDT_TIMER_ID			5
 
-struct tegra_wdt_priv {
-	void __iomem *wdt_base;
-	void __iomem *tmr_base;
-};
-
 static int tegra_wdt_reset(struct udevice *dev)
 {
 	struct tegra_wdt_priv *priv = dev_get_priv(dev);
@@ -87,6 +174,7 @@ static int tegra_wdt_stop(struct udevice *dev)
 
 	return 0;
 }
+#endif
 
 static int tegra_wdt_probe(struct udevice *dev)
 {
@@ -97,8 +185,12 @@ static int tegra_wdt_probe(struct udevice *dev)
 	if (!base)
 		return -ENOENT;
 
+#if defined(CONFIG_TEGRA20)
+	priv->tmr_base = base + T20_WDT_TIMER_BASE;
+#else
 	priv->wdt_base = base + WDT_BASE;
 	priv->tmr_base = base + WDT_TIMER_BASE;
+#endif
 
 	return 0;
 }

---
base-commit: 2f797312a4e2c5d104786dbcde9f7db8e355ee6e
change-id: 20261108-wdt-tegra20-8d66e6b6b0dd

Best regards,

Signature · 1 line
Artur Kowalski <[email protected]>
#1 lore raw

пн, 9 лист. 2026 р. о 23:02 Artur Kowalski <[email protected]> пише:

Quoted 44 lines

The timer driver binds tegra_wdt to Tegra20's timer node as well, but the driver programs timer 5 and watchdog 0, which only exist on Tegra30 and later. On Tegra20 they lie past the end of the timer block, so starting the watchdog writes to unrelated registers.

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, enabled through the WDT bits of RST_SOURCE. Use timer 1 for it (U-Boot itself only uses the microsecond counter): run it periodic with half of the timeout as 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.

Assisted-by Claude:claude-opus-5-5

---
Add watchdog support for Tegra20. This makes it possible to recover from hangs in U-Boot or early during Linux boot.

Linux doesn't handle a running Tegra20 watchdog yet. Its timer driver takes over TIMER1 for CPU0's clock event device, which can reset the system early during boot.

There is also a Tegra20 quirk that affects any watchdog reset while Linux runs: the watchdog resets the system through the clock and reset controller, which doesn't reset the PMC, so the power partitions keep their state. Linux powers the VDE partition off when its PMC driver registers it, and whenever the VDE is idle. If the watchdog fires while the partition is off, the boot ROM hangs and the device stays dead until it is powered off. Linux already works around the same hang for reboots (commit 8f0c714ad9be ("media: staging: tegra-vde: Turn ON power domain on shutdown") and commit 510f276df2b9 ("mfd: tps6586x: Register restart handler")). The Linux patches that will follow stop the watchdog before the VDE partition is powered off.

For these reasons I haven't enabled the watchdog in any defconfig.

checkpatch suggests IS_ENABLED() instead of the #if blocks. They select whole functions and a struct member, as a U-Boot binary supports a single SoC generation and the two variants share no register layout. I'm happy to restructure it with IS_ENABLED() if preferred.

Quoted 7 lines

The patch was developed with the help of Claude Code. I have reviewed it and tested it on an ASUS TF101 (Tegra20). Tegra30 and later are untested, but their code path is unchanged.

---
 drivers/watchdog/tegra_wdt.c | 102 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 97 insertions(+), 5 deletions(-)

Looks like you are implementing a fairly different stuff then existing driver. You can create a separate driver and make it specific for tegra20 and exclusive with tegra_wdt (preserving the driver name) and just adjusting timer binding.

If you have any questions, do not hesitate to ask and please cc me into your linux kernel patchset. I have tegra20 device with issues in poweroff/reset.