watchdog: tegra: support Tegra20
Subsystems (2)
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.
---
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
@@ -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]>
пн, 9 лист. 2026 р. о 23:02 Artur Kowalski <[email protected]> пише:
Quoted 44 lines
The timer driver binds
tegra_wdtto 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-5Signed-off-by Artur Kowalski <[email protected]>---
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 commit510f276df2b9("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 withIS_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.