This commit provides support for Tegra watchdog functionality. The WATCHDOG index 0 in conjunction with TIMER 5 has been used. as the same setup is used in the Linux kernel driver.
Signed-off-by: Lukasz Majewski <lu...@nabladev.com> --- Changes for v2: - Remove () from defines and use hex lower case numbers - Move check for timeout range validity before first register write - Remove "nvidia,tegra30-timer" device compatibility check --- drivers/watchdog/Kconfig | 7 +++ drivers/watchdog/Makefile | 1 + drivers/watchdog/tegra_wdt.c | 118 +++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 drivers/watchdog/tegra_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 9e149a75e81..a10baed7232 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -454,6 +454,13 @@ config WDT_TANGIER Intel Tangier SoC. If you're using a board with Intel Tangier SoC, say Y here. +config WDT_TEGRA + bool "Tegra watchdog" + depends on WDT && ARCH_TEGRA + help + Select this to enable support for the watchdog timer + embedded in NVIDIA Tegra SoCs. + config WDT_ARM_SMC bool "ARM SMC watchdog timer support" depends on WDT && ARM_SMCCC diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index d52d17e1c90..02e2674f8af 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_WDT_STARFIVE) += starfive_wdt.o obj-$(CONFIG_WDT_STM32MP) += stm32mp_wdt.o obj-$(CONFIG_WDT_SUNXI) += sunxi_wdt.o obj-$(CONFIG_WDT_TANGIER) += tangier_wdt.o +obj-$(CONFIG_WDT_TEGRA) += tegra_wdt.o obj-$(CONFIG_WDT_XILINX) += xilinx_wwdt.o obj-$(CONFIG_WDT_ADI) += adi_wdt.o obj-$(CONFIG_WDT_QCOM) += qcom-wdt.o diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c new file mode 100644 index 00000000000..345c83fc8a2 --- /dev/null +++ b/drivers/watchdog/tegra_wdt.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NVIDIA Tegra Watchdog driver + * + * Copyright (C) 2025 NABLA Software Engineering + * Lukasz Majewski, NABLA Software Engineering, lu...@nabladev.com + */ + +#include <dm.h> +#include <wdt.h> +#include <hang.h> +#include <asm/io.h> +#include <watchdog.h> + +/* Timer registers */ +#define TIMER_PTV 0x0 +#define TIMER_EN BIT(31) +#define TIMER_PERIODIC BIT(30) + +/* WDT registers */ +#define WDT_CFG 0x0 +#define WDT_CFG_PERIOD_SHIFT 4 +#define WDT_CFG_PERIOD_MASK GENMASK(7, 0) +#define WDT_CFG_INT_EN BIT(12) +#define WDT_CFG_PMC2CAR_RST_EN BIT(15) +#define WDT_CMD 0x8 +#define WDT_CMD_START_COUNTER BIT(0) +#define WDT_CMD_DISABLE_COUNTER BIT(1) +#define WDT_UNLOCK 0xc +#define WDT_UNLOCK_PATTERN 0xc45a + +/* Use watchdog ID 0 */ +#define WDT0_BASE 0x100 + +/* Use Timer 5 as WDT counter */ +#define WDT_TIM5_BASE 0x60 +#define WDT_TIM5_ID 5 + +struct tegra_wdt_priv { + void __iomem *wdt_base; + void __iomem *tim_base; +}; + +static int tegra_wdt_reset(struct udevice *dev) +{ + struct tegra_wdt_priv *priv = dev_get_priv(dev); + + writel(WDT_CMD_START_COUNTER, priv->wdt_base + WDT_CMD); + + return 0; +} + +static int tegra_wdt_start(struct udevice *dev, u64 timeout, ulong flags) +{ + struct tegra_wdt_priv *priv = dev_get_priv(dev); + u32 timeout_sec = timeout / 1000; + + /* Support for timeout from 1 to 255 seconds */ + if (timeout_sec < 1 || timeout_sec > 255) + return -EINVAL; + + /* + * Timer for WDT has a fixed 1MHz clock, so for 1 second period one + * shall write 1000000ul. + * + * On Tegra the watchdog reset actually occurs on the 4th expiration + * of this counter, so we set the period to 1/4. + */ + writel(TIMER_EN | TIMER_PERIODIC | (1000000ul / 4), + priv->tim_base + TIMER_PTV); + + writel(WDT_CFG_PMC2CAR_RST_EN | (timeout_sec << WDT_CFG_PERIOD_SHIFT) | + WDT_TIM5_ID, priv->wdt_base + WDT_CFG); + + writel(WDT_CMD_START_COUNTER, priv->wdt_base + WDT_CMD); + + return 0; +} + +static int tegra_wdt_stop(struct udevice *dev) +{ + struct tegra_wdt_priv *priv = dev_get_priv(dev); + + writel(WDT_UNLOCK_PATTERN, priv->wdt_base + WDT_UNLOCK); + writel(WDT_CMD_DISABLE_COUNTER, priv->wdt_base + WDT_CMD); + writel(0, priv->tim_base + TIMER_PTV); + + return 0; +} + +static int tegra_wdt_probe(struct udevice *dev) +{ + struct tegra_wdt_priv *priv = dev_get_priv(dev); + void __iomem *base; + + base = dev_read_addr_ptr(dev); + if (!base) + return -ENOENT; + + priv->wdt_base = base + WDT0_BASE; + priv->tim_base = base + WDT_TIM5_BASE; + + return 0; +} + +static const struct wdt_ops tegra_wdt_ops = { + .start = tegra_wdt_start, + .stop = tegra_wdt_stop, + .reset = tegra_wdt_reset, +}; + +U_BOOT_DRIVER(tegra_wdt) = { + .name = "tegra_wdt", + .id = UCLASS_WDT, + .priv_auto = sizeof(struct tegra_wdt_priv), + .probe = tegra_wdt_probe, + .ops = &tegra_wdt_ops, +}; -- 2.39.5