* [PATCH 1/4] clocksource/drivers/timer-tegra186: Fix support for multiple watchdog instances
2026-05-07 15:45 [PATCH 0/4] Add support for Kernel WDT Kartik Rajput
@ 2026-05-07 15:45 ` Kartik Rajput
2026-05-07 15:45 ` [PATCH 2/4] clocksource/drivers/timer-tegra186: Correct num_wdts for Tegra186 and Tegra234 Kartik Rajput
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Kartik Rajput @ 2026-05-07 15:45 UTC (permalink / raw)
To: daniel.lezcano, tglx, wim, linux, thierry.reding, jonathanh,
kkartik, linux-watchdog, linux-kernel, linux-tegra
Cc: stable
Tegra SoCs support multiple watchdogs; currently only one (WDT0) is
used. When multiple watchdogs are registered, tegra186_wdt_enable()
overwrites the TKEIE(x) register, discarding any existing watchdog
interrupt enable bits. As a result, enabling one watchdog inadvertently
disables interrupts for the others.
Fix this by preserving the existing TKEIE(x) value and updating it
using a read-modify-write sequence.
Fixes: 42cee19a9f83 ("clocksource: Add Tegra186 timers support")
Cc: stable@vger.kernel.org
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
drivers/clocksource/timer-tegra186.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index 355558893e5f..bfe16d2d5104 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -149,7 +149,8 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
u32 value;
/* unmask hardware IRQ, this may have been lost across powergate */
- value = TKEIE_WDT_MASK(wdt->index, 1);
+ value = readl(tegra->regs + TKEIE(wdt->tmr->hwirq));
+ value |= TKEIE_WDT_MASK(wdt->index, 1);
writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq));
/* clear interrupt */
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/4] clocksource/drivers/timer-tegra186: Correct num_wdts for Tegra186 and Tegra234
2026-05-07 15:45 [PATCH 0/4] Add support for Kernel WDT Kartik Rajput
2026-05-07 15:45 ` [PATCH 1/4] clocksource/drivers/timer-tegra186: Fix support for multiple watchdog instances Kartik Rajput
@ 2026-05-07 15:45 ` Kartik Rajput
2026-05-07 15:45 ` [PATCH 3/4] clocksource/drivers/timer-tegra186: Register all accessible watchdog timers Kartik Rajput
2026-05-07 15:45 ` [PATCH 4/4] clocksource/drivers/timer-tegra186: Reserve and service a kernel watchdog Kartik Rajput
3 siblings, 0 replies; 5+ messages in thread
From: Kartik Rajput @ 2026-05-07 15:45 UTC (permalink / raw)
To: daniel.lezcano, tglx, wim, linux, thierry.reding, jonathanh,
kkartik, linux-watchdog, linux-kernel, linux-tegra
On Tegra186 and Tegra234, WDT2 is connected to the Audio Processing
Engine (APE) and cannot be accessed from Linux. Only WDT0 and WDT1
are accessible to Linux.
Update num_wdts from 3 to 2 for both Tegra186 and Tegra234 to reflect
the actual number of watchdogs available to Linux.
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
drivers/clocksource/timer-tegra186.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index bfe16d2d5104..fd82a73ab2d2 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -511,12 +511,12 @@ static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend,
static const struct tegra186_timer_soc tegra186_timer = {
.num_timers = 10,
- .num_wdts = 3,
+ .num_wdts = 2,
};
static const struct tegra186_timer_soc tegra234_timer = {
.num_timers = 16,
- .num_wdts = 3,
+ .num_wdts = 2,
};
static const struct of_device_id tegra186_timer_of_match[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/4] clocksource/drivers/timer-tegra186: Register all accessible watchdog timers
2026-05-07 15:45 [PATCH 0/4] Add support for Kernel WDT Kartik Rajput
2026-05-07 15:45 ` [PATCH 1/4] clocksource/drivers/timer-tegra186: Fix support for multiple watchdog instances Kartik Rajput
2026-05-07 15:45 ` [PATCH 2/4] clocksource/drivers/timer-tegra186: Correct num_wdts for Tegra186 and Tegra234 Kartik Rajput
@ 2026-05-07 15:45 ` Kartik Rajput
2026-05-07 15:45 ` [PATCH 4/4] clocksource/drivers/timer-tegra186: Reserve and service a kernel watchdog Kartik Rajput
3 siblings, 0 replies; 5+ messages in thread
From: Kartik Rajput @ 2026-05-07 15:45 UTC (permalink / raw)
To: daniel.lezcano, tglx, wim, linux, thierry.reding, jonathanh,
kkartik, linux-watchdog, linux-kernel, linux-tegra
Tegra186+ SoCs expose multiple watchdog timers, but the driver only
registers WDT(0).
Iterate over num_wdts and, for each WDT, check the SCR (firewall) registers
in the TKE block to determine whether Linux has read and write access.
Register the watchdogs that are accessible.
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
drivers/clocksource/timer-tegra186.c | 61 +++++++++++++++++++++++-----
1 file changed, 50 insertions(+), 11 deletions(-)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index fd82a73ab2d2..dd1d1a0dd63e 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -57,6 +57,13 @@
#define WDTUR 0x00c
#define WDTUR_UNLOCK_PATTERN 0x0000c45a
+/* WDT security configuration registers */
+#define WDTSCR(x) (0xf02c + (x) * 4)
+#define WDTSCR_SEC_WEN BIT(28)
+#define WDTSCR_SEC_REN BIT(27)
+#define WDTSCR_SEC_G1W BIT(9)
+#define WDTSCR_SEC_G1R BIT(1)
+
struct tegra186_timer_soc {
unsigned int num_timers;
unsigned int num_wdts;
@@ -89,7 +96,7 @@ struct tegra186_timer {
struct device *dev;
void __iomem *regs;
- struct tegra186_wdt *wdt;
+ struct tegra186_wdt **wdts;
struct clocksource usec;
struct clocksource tsc;
struct clocksource osc;
@@ -298,6 +305,23 @@ static const struct watchdog_ops tegra186_wdt_ops = {
.get_timeleft = tegra186_wdt_get_timeleft,
};
+static bool tegra186_wdt_is_accessible(struct tegra186_timer *tegra, unsigned int index)
+{
+ u32 value;
+
+ value = readl_relaxed(tegra->regs + WDTSCR(index));
+
+ /* Check OS write access if write blocking is enabled. */
+ if ((value & WDTSCR_SEC_WEN) && !(value & WDTSCR_SEC_G1W))
+ return false;
+
+ /* Check OS read access if read blocking is enabled. */
+ if ((value & WDTSCR_SEC_REN) && !(value & WDTSCR_SEC_G1R))
+ return false;
+
+ return true;
+}
+
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
unsigned int index)
{
@@ -424,6 +448,7 @@ static int tegra186_timer_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tegra186_timer *tegra;
+ unsigned int i;
int err;
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL);
@@ -442,12 +467,20 @@ static int tegra186_timer_probe(struct platform_device *pdev)
if (err < 0)
return err;
- /* create a watchdog using a preconfigured timer */
- tegra->wdt = tegra186_wdt_create(tegra, 0);
- if (IS_ERR(tegra->wdt)) {
- err = PTR_ERR(tegra->wdt);
- dev_err(dev, "failed to create WDT: %d\n", err);
- return err;
+ tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL);
+ if (!tegra->wdts)
+ return -ENOMEM;
+
+ for (i = 0; i < tegra->soc->num_wdts; i++) {
+ if (!tegra186_wdt_is_accessible(tegra, i)) {
+ dev_warn(dev, "WDT%u is not accessible\n", i);
+ continue;
+ }
+
+ tegra->wdts[i] = tegra186_wdt_create(tegra, i);
+ if (IS_ERR(tegra->wdts[i]))
+ return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]),
+ "failed to create WDT%u\n", i);
}
err = tegra186_timer_tsc_init(tegra);
@@ -489,9 +522,12 @@ static void tegra186_timer_remove(struct platform_device *pdev)
static int __maybe_unused tegra186_timer_suspend(struct device *dev)
{
struct tegra186_timer *tegra = dev_get_drvdata(dev);
+ unsigned int i;
- if (watchdog_active(&tegra->wdt->base))
- tegra186_wdt_disable(tegra->wdt);
+ for (i = 0; i < tegra->soc->num_wdts; i++) {
+ if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base))
+ tegra186_wdt_disable(tegra->wdts[i]);
+ }
return 0;
}
@@ -499,9 +535,12 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev)
static int __maybe_unused tegra186_timer_resume(struct device *dev)
{
struct tegra186_timer *tegra = dev_get_drvdata(dev);
+ unsigned int i;
- if (watchdog_active(&tegra->wdt->base))
- tegra186_wdt_enable(tegra->wdt);
+ for (i = 0; i < tegra->soc->num_wdts; i++) {
+ if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base))
+ tegra186_wdt_enable(tegra->wdts[i]);
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 4/4] clocksource/drivers/timer-tegra186: Reserve and service a kernel watchdog
2026-05-07 15:45 [PATCH 0/4] Add support for Kernel WDT Kartik Rajput
` (2 preceding siblings ...)
2026-05-07 15:45 ` [PATCH 3/4] clocksource/drivers/timer-tegra186: Register all accessible watchdog timers Kartik Rajput
@ 2026-05-07 15:45 ` Kartik Rajput
3 siblings, 0 replies; 5+ messages in thread
From: Kartik Rajput @ 2026-05-07 15:45 UTC (permalink / raw)
To: daniel.lezcano, tglx, wim, linux, thierry.reding, jonathanh,
kkartik, linux-watchdog, linux-kernel, linux-tegra
Tegra SoCs supports multiple watchdog timers. If the kernel crashes or
hangs before userspace enables a watchdog, the system cannot recover and
may remain bricked, e.g. after a failed OTA update. The driver currently
leaves all watchdogs disabled until userspace configures them.
Reserve first available watchdog as a kernel-only watchdog for Tegra186
and Tegra234. Arm it during probe (120s timeout) and keep it alive in
the driver IRQ handler. Do not register it to userspace. Other available
watchdogs remain exposed to userspace. This guarantees the system can
reset itself in case of a hang or crash even when userspace never starts.
Signed-off-by: Kartik Rajput <kkartik@nvidia.com>
---
drivers/clocksource/timer-tegra186.c | 62 ++++++++++++++++++++++++----
1 file changed, 54 insertions(+), 8 deletions(-)
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c
index dd1d1a0dd63e..78600ddeb1c6 100644
--- a/drivers/clocksource/timer-tegra186.c
+++ b/drivers/clocksource/timer-tegra186.c
@@ -57,6 +57,8 @@
#define WDTUR 0x00c
#define WDTUR_UNLOCK_PATTERN 0x0000c45a
+#define TEGRA186_KERNEL_WDT_TIMEOUT 120
+
/* WDT security configuration registers */
#define WDTSCR(x) (0xf02c + (x) * 4)
#define WDTSCR_SEC_WEN BIT(28)
@@ -82,6 +84,7 @@ struct tegra186_wdt {
void __iomem *regs;
unsigned int index;
bool locked;
+ bool is_kernel_wdt;
struct tegra186_tmr *tmr;
};
@@ -182,6 +185,10 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt)
value &= ~WDTCR_PERIOD_MASK;
value |= WDTCR_PERIOD(1);
+ /* enable local interrupt for kernel watchdog */
+ if (wdt->is_kernel_wdt)
+ value |= WDTCR_LOCAL_INT_ENABLE;
+
/* enable system POR reset */
value |= WDTCR_SYSTEM_POR_RESET_ENABLE;
@@ -219,6 +226,16 @@ static int tegra186_wdt_ping(struct watchdog_device *wdd)
return 0;
}
+static irqreturn_t tegra186_wdt_irq(int irq, void *data)
+{
+ struct tegra186_wdt *wdt = data;
+
+ tegra186_wdt_disable(wdt);
+ tegra186_wdt_enable(wdt);
+
+ return IRQ_HANDLED;
+}
+
static int tegra186_wdt_set_timeout(struct watchdog_device *wdd,
unsigned int timeout)
{
@@ -361,10 +378,6 @@ static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,
if (err < 0)
return ERR_PTR(err);
- err = devm_watchdog_register_device(tegra->dev, &wdt->base);
- if (err < 0)
- return ERR_PTR(err);
-
return wdt;
}
@@ -446,9 +459,11 @@ static int tegra186_timer_usec_init(struct tegra186_timer *tegra)
static int tegra186_timer_probe(struct platform_device *pdev)
{
+ struct tegra186_wdt *kernel_wdt = NULL;
struct device *dev = &pdev->dev;
struct tegra186_timer *tegra;
unsigned int i;
+ int irq;
int err;
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL);
@@ -467,6 +482,8 @@ static int tegra186_timer_probe(struct platform_device *pdev)
if (err < 0)
return err;
+ irq = err;
+
tegra->wdts = devm_kcalloc(dev, tegra->soc->num_wdts, sizeof(*tegra->wdts), GFP_KERNEL);
if (!tegra->wdts)
return -ENOMEM;
@@ -481,6 +498,17 @@ static int tegra186_timer_probe(struct platform_device *pdev)
if (IS_ERR(tegra->wdts[i]))
return dev_err_probe(dev, PTR_ERR(tegra->wdts[i]),
"failed to create WDT%u\n", i);
+
+ /* Reserve the first accessible WDT for the Kernel. */
+ if (!kernel_wdt) {
+ kernel_wdt = tegra->wdts[i];
+ kernel_wdt->is_kernel_wdt = true;
+ } else {
+ err = devm_watchdog_register_device(dev, &tegra->wdts[i]->base);
+ if (err < 0)
+ return dev_err_probe(dev, err,
+ "failed to register WDT%u\n", i);
+ }
}
err = tegra186_timer_tsc_init(tegra);
@@ -501,8 +529,22 @@ static int tegra186_timer_probe(struct platform_device *pdev)
goto unregister_osc;
}
+ if (kernel_wdt) {
+ err = devm_request_irq(dev, irq, tegra186_wdt_irq, 0,
+ dev_name(dev), kernel_wdt);
+ if (err < 0) {
+ dev_err(dev, "failed to request kernel WDT IRQ: %d\n", err);
+ goto unregister_usec;
+ }
+
+ tegra186_wdt_set_timeout(&kernel_wdt->base, TEGRA186_KERNEL_WDT_TIMEOUT);
+ tegra186_wdt_enable(kernel_wdt);
+ }
+
return 0;
+unregister_usec:
+ clocksource_unregister(&tegra->usec);
unregister_osc:
clocksource_unregister(&tegra->osc);
unregister_tsc:
@@ -525,8 +567,10 @@ static int __maybe_unused tegra186_timer_suspend(struct device *dev)
unsigned int i;
for (i = 0; i < tegra->soc->num_wdts; i++) {
- if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base))
- tegra186_wdt_disable(tegra->wdts[i]);
+ struct tegra186_wdt *wdt = tegra->wdts[i];
+
+ if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
+ tegra186_wdt_disable(wdt);
}
return 0;
@@ -538,8 +582,10 @@ static int __maybe_unused tegra186_timer_resume(struct device *dev)
unsigned int i;
for (i = 0; i < tegra->soc->num_wdts; i++) {
- if (tegra->wdts[i] && watchdog_active(&tegra->wdts[i]->base))
- tegra186_wdt_enable(tegra->wdts[i]);
+ struct tegra186_wdt *wdt = tegra->wdts[i];
+
+ if (wdt && (wdt->is_kernel_wdt || watchdog_active(&wdt->base)))
+ tegra186_wdt_enable(wdt);
}
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread