Commit eeb005cd authored by Vladimir Bashkirtsev's avatar Vladimir Bashkirtsev

Added patch for NVIDIA Tegra

parent 99bfdd14
diff -uNr linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194.dtsi linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194.dtsi
--- linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194.dtsi 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194.dtsi 2025-12-27 00:12:00.459267441 +0000
@@ -443,6 +443,21 @@
};
};
+ wdt: watchdog@30c0000 {
+ compatible = "nvidia,tegra-wdt-t18x";
+ reg = <0x030c0000 0x10000>, /* WDT0 */
+ <0x03020000 0x10000>, /* TMR0 */
+ <0x03010000 0x10000>; /* TKE */
+ interrupts = <0 7 0x4 0 8 0x4>; /* TKE shared int */
+ nvidia,watchdog-index = <0>;
+ nvidia,timer-index = <7>;
+ nvidia,enable-on-init;
+ nvidia,extend-watchdog-suspend;
+ timeout-sec = <120>;
+ nvidia,disable-debug-reset;
+ status = "disabled";
+ };
+
uarta: serial@3100000 {
compatible = "nvidia,tegra194-uart", "nvidia,tegra20-uart";
reg = <0x03100000 0x40>;
diff -uNr linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194-p3668-0001.dtsi linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194-p3668-0001.dtsi
--- linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194-p3668-0001.dtsi 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194-p3668-0001.dtsi 2025-12-26 11:20:27.075770000 +0000
@@ -7,6 +7,7 @@
aliases {
mmc0 = "/bus@0/mmc@3460000";
+ ethernet0 = "/bus@0/ethernet@2490000";
};
bus@0 {
@@ -19,5 +20,30 @@
vqmmc-supply = <&vdd_1v8ls>;
vmmc-supply = <&vdd_emmc_3v3>;
};
+
+ ethernet@2490000 {
+ status = "okay";
+ compatible = "nvidia,tegra194-eqos",
+ "nvidia,tegra186-eqos",
+ "snps,dwc-qos-ethernet-4.10";
+
+ phy-reset-gpios = <&gpio TEGRA194_MAIN_GPIO(R, 1) GPIO_ACTIVE_LOW>;
+ phy-handle = <&phy>;
+ phy-mode = "rgmii-id";
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ phy: phy@0 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x0>;
+ interrupt-parent = <&gpio>;
+ interrupts = <TEGRA194_MAIN_GPIO(G, 4) IRQ_TYPE_LEVEL_LOW>;
+ #phy-cells = <0>;
+ };
+ };
+ };
+
};
};
diff -uNr linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194-p3668.dtsi linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194-p3668.dtsi
--- linux-5.12.10/arch/arm64/boot/dts/nvidia/tegra194-p3668.dtsi 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/arch/arm64/boot/dts/nvidia/tegra194-p3668.dtsi 2025-12-27 00:10:51.206459714 +0000
@@ -50,6 +50,10 @@
status = "okay";
};
+ watchdog@30c0000 {
+ status = "okay";
+ };
+
serial@3100000 {
status = "okay";
};
diff -uNr linux-5.12.10/drivers/soc/tegra/pmc.c linux-5.12.10-nvidia/drivers/soc/tegra/pmc.c
--- linux-5.12.10/drivers/soc/tegra/pmc.c 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/drivers/soc/tegra/pmc.c 2025-12-24 10:43:14.305214834 +0000
@@ -147,6 +147,8 @@
#define GPU_RG_CNTRL 0x2d4
+#define PMC_IMPL_HALT_IN_FIQ_MASK BIT(28)
+
/* Tegra186 and later */
#define WAKE_AOWAKE_CNTRL(x) (0x000 + ((x) << 2))
#define WAKE_AOWAKE_CNTRL_LEVEL (1 << 3)
@@ -258,6 +260,7 @@
unsigned int rst_source_mask;
unsigned int rst_level_shift;
unsigned int rst_level_mask;
+ unsigned int ramdump_ctl_status;
};
struct tegra_wake_event {
@@ -461,6 +464,16 @@
}
}
+static void tegra_pmc_register_update(int offset,
+ unsigned long mask, unsigned long val)
+{
+ u32 pmc_reg;
+
+ pmc_reg = tegra_pmc_readl(pmc, offset);
+ pmc_reg = (pmc_reg & ~mask) | (val & mask);
+ tegra_pmc_writel(pmc, pmc_reg, offset);
+}
+
static u32 tegra_pmc_scratch_readl(struct tegra_pmc *pmc, unsigned long offset)
{
if (pmc->tz_only)
@@ -1944,6 +1957,25 @@
}
}
+bool tegra_pmc_is_halt_in_fiq(void)
+{
+ return !!(PMC_IMPL_HALT_IN_FIQ_MASK &
+ tegra_pmc_readl(pmc, pmc->soc->regs->ramdump_ctl_status));
+}
+EXPORT_SYMBOL(tegra_pmc_is_halt_in_fiq);
+
+static void tegra_pmc_halt_in_fiq_init(struct tegra_pmc *pmc)
+{
+ struct device_node *np = pmc->dev->of_node;
+
+ if (!of_property_read_bool(np, "nvidia,enable-halt-in-fiq"))
+ return;
+
+ tegra_pmc_register_update(pmc->soc->regs->ramdump_ctl_status,
+ PMC_IMPL_HALT_IN_FIQ_MASK,
+ PMC_IMPL_HALT_IN_FIQ_MASK);
+}
+
static int tegra_pmc_irq_translate(struct irq_domain *domain,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
@@ -2584,6 +2616,8 @@
tegra_pmc_init_tsense_reset(pmc);
+ tegra_pmc_halt_in_fiq_init(pmc);
+
tegra_pmc_reset_sysfs_init(pmc);
if (IS_ENABLED(CONFIG_DEBUG_FS)) {
@@ -3181,6 +3215,7 @@
.rst_source_mask = 0x3c,
.rst_level_shift = 0x0,
.rst_level_mask = 0x3,
+ .ramdump_ctl_status = 0x10c,
};
static void tegra186_pmc_setup_irq_polarity(struct tegra_pmc *pmc,
@@ -3347,6 +3382,7 @@
.rst_source_mask = 0x7c,
.rst_level_shift = 0x0,
.rst_level_mask = 0x3,
+ .ramdump_ctl_status = 0x10c,
};
static const char * const tegra194_reset_sources[] = {
@@ -3420,6 +3456,7 @@
.rst_source_mask = 0xfc,
.rst_level_shift = 0x0,
.rst_level_mask = 0x3,
+ .ramdump_ctl_status = 0x10c,
};
static const char * const tegra234_reset_sources[] = {
diff -uNr linux-5.12.10/drivers/watchdog/Kconfig linux-5.12.10-nvidia/drivers/watchdog/Kconfig
--- linux-5.12.10/drivers/watchdog/Kconfig 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/drivers/watchdog/Kconfig 2025-12-24 10:43:14.309214923 +0000
@@ -789,6 +789,16 @@
To compile this driver as a module, choose M here: the
module will be called tegra_wdt.
+config TEGRA18X_WATCHDOG
+ tristate "Tegra T18x watchdog"
+ depends on (ARCH_TEGRA_18x_SOC || ARCH_TEGRA_186_SOC)
+ help
+ Say Y here to include support for the watchdog timer
+ embedded in NVIDIA Tegra SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tegra18x_wdt.
+
config QCOM_WDT
tristate "QCOM watchdog"
depends on HAS_IOMEM
diff -uNr linux-5.12.10/drivers/watchdog/Makefile linux-5.12.10-nvidia/drivers/watchdog/Makefile
--- linux-5.12.10/drivers/watchdog/Makefile 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/drivers/watchdog/Makefile 2025-12-24 10:43:14.309214923 +0000
@@ -76,6 +76,7 @@
obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o
obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
+obj-$(CONFIG_TEGRA18X_WATCHDOG) += tegra_wdt_t18x.o
obj-$(CONFIG_MESON_GXBB_WATCHDOG) += meson_gxbb_wdt.o
obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
diff -uNr linux-5.12.10/drivers/watchdog/tegra_wdt_t18x.c linux-5.12.10-nvidia/drivers/watchdog/tegra_wdt_t18x.c
--- linux-5.12.10/drivers/watchdog/tegra_wdt_t18x.c 1970-01-01 00:00:00.000000000 +0000
+++ linux-5.12.10-nvidia/drivers/watchdog/tegra_wdt_t18x.c 2025-12-24 10:43:14.309214923 +0000
@@ -0,0 +1,856 @@
+/*
+ * tegra_wdt_t18x.c: watchdog driver for NVIDIA tegra internal watchdog
+ *
+ * Copyright (c) 2012-2022, NVIDIA CORPORATION. All rights reserved.
+ * Based on:
+ * drivers/watchdog/softdog.c and
+ * drivers/watchdog/omap_wdt.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/syscore_ops.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/version.h>
+#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE
+#include <soc/tegra/chip-id.h>
+#else
+#include <soc/tegra/fuse.h>
+#endif
+#include <soc/tegra/pmc.h>
+
+/* The total expiry count of Tegra WDTs supported by HW */
+#define EXPIRY_COUNT 5
+
+/*
+ * minimum and maximum Timer PTV in seconds
+ * MAX_TMR_PTV defines maximum value in seconds which can be fed into
+ * TOP_TKE_TMR_PTV which is of size 29 bits
+ * MAX_TMR_PTV = (1 << 29) / USEC_PER_SEC;
+ */
+#define MIN_TMR_PTV 1
+#define MAX_TMR_PTV 536
+
+/*
+ * To detect lockup condition, the heartbeat should be EXPIRY_COUNT*lockup.
+ * It may be taken over later by timeout value requested by application.
+ * Must be greater than MIN_TMR_PTV and lower than MAX_TMR_PTV*active_count.
+ */
+#define HEARTBEAT 120
+
+/* Watchdog configured to this time before reset during shutdown */
+#define SHUTDOWN_TIMEOUT 150
+
+/* Bit numbers for status flags */
+#define WDT_ENABLED 0
+#define WDT_ENABLED_ON_INIT 1
+#define WDT_ENABLED_USERSPACE 2
+
+#define TIMER_INDEX_T186 0
+#define TIMER_INDEX_T234 1
+
+struct tegra_wdt_t18x_soc {
+ bool unmask_hw_irq;
+ int timer_index;
+};
+
+struct tegra_wdt_t18x {
+ struct device *dev;
+ struct watchdog_device wdt;
+ unsigned long users;
+ void __iomem *wdt_source;
+ void __iomem *wdt_timer;
+ void __iomem *wdt_tke;
+ struct dentry *root;
+ const struct tegra_wdt_t18x_soc *soc;
+ u32 config;
+ int irq;
+ int hwirq;
+ unsigned long status;
+ bool enable_on_init;
+ int active_count;
+ int shutdown_timeout;
+ int wdt_index;
+ int tmr_index;
+ bool extended_suspend;
+ bool config_locked;
+};
+
+/*
+ * Global variable to store wdt pointer required by nvdumper and pmic to
+ * change wdt state
+ */
+static struct tegra_wdt_t18x *t18x_wdt;
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static bool default_disable;
+module_param(default_disable, bool, 0644);
+MODULE_PARM_DESC(default_disable, "Default state of watchdog");
+
+static struct syscore_ops tegra_wdt_t18x_syscore_ops;
+
+static struct tegra_wdt_t18x *to_tegra_wdt_t18x(struct watchdog_device *wdt)
+{
+ return container_of(wdt, struct tegra_wdt_t18x, wdt);
+}
+
+#define TOP_TKE_TKEIE_BASE 0x100
+#define TOP_TKE_TKEIE(i) (0x100 + 4 * (i))
+#define TOP_TKE_TKEIE_WDT_MASK(i) (1 << (16 + 4 * (i)))
+#define TOP_TKE_TMR_PTV 0
+#define TOP_TKE_TMR_EN BIT(31)
+#define TOP_TKE_TMR_PERIODIC BIT(30)
+#define TOP_TKE_TMR_PCR 0x4
+#define TOP_TKE_TMR_PCR_INTR BIT(30)
+#define WDT_CFG (0)
+#define WDT_CFG_PERIOD BIT(4)
+#define WDT_CFG_INT_EN BIT(12)
+#define WDT_CFG_FINT_EN BIT(13)
+#define WDT_CFG_REMOTE_INT_EN BIT(14)
+#define WDT_CFG_DBG_RST_EN BIT(15)
+#define WDT_CFG_SYS_PORST_EN BIT(16)
+#define WDT_CFG_DISALLOW_FREEZE BIT(25)
+#define WDT_CFG_ERR_THRESHOLD (7 << 20)
+#define WDT_STATUS (0x4)
+#define WDT_INTR_STAT BIT(1)
+#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 << 0)
+#define WDT_SKIP (0x10)
+#define WDT_SKIP_VAL(i, val) (((val) & 0x7) << (4 * (i)))
+#define TIMER_INDEX(add) ((((add) >> 16) & 0xF) - 0x2)
+#define WATCHDOG_INDEX(add) ((((add) >> 16) & 0xF) - 0xc)
+#define T234_TIMER_INDEX(add) ((((add) >> 16) & 0xF) - 0x9)
+#define T234_WATCHDOG_INDEX(add) ((((add) >> 16) & 0xF) - 0x9)
+
+static int __tegra_wdt_t18x_ping(struct tegra_wdt_t18x *twdt_t18x)
+{
+ u32 val;
+
+ /* Disable timer, load the timeout value and restart. */
+ writel(WDT_UNLOCK_PATTERN, twdt_t18x->wdt_source + WDT_UNLOCK);
+ writel(WDT_CMD_DISABLE_COUNTER, twdt_t18x->wdt_source + WDT_CMD);
+
+ writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);
+
+ val = (twdt_t18x->wdt.timeout * USEC_PER_SEC) / twdt_t18x->active_count;
+ val |= (TOP_TKE_TMR_EN | TOP_TKE_TMR_PERIODIC);
+ writel(val, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);
+
+ writel(WDT_CMD_START_COUNTER, twdt_t18x->wdt_source + WDT_CMD);
+
+ dev_dbg(twdt_t18x->dev, "Watchdog%d: wdt cleared\n", twdt_t18x->wdt.id);
+
+ return 0;
+}
+
+static irqreturn_t tegra_wdt_t18x_isr(int irq, void *data)
+{
+ struct tegra_wdt_t18x *twdt_t18x = data;
+
+ __tegra_wdt_t18x_ping(twdt_t18x);
+
+ return IRQ_HANDLED;
+}
+
+static void tegra_wdt_t18x_ref(struct watchdog_device *wdt)
+{
+ struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);
+
+ if (twdt_t18x->irq <= 0)
+ return;
+
+ /* Remove the interrupt handler if userspace is taking over WDT. */
+ if (!test_and_set_bit(WDT_ENABLED_USERSPACE, &twdt_t18x->status) &&
+ test_bit(WDT_ENABLED_ON_INIT, &twdt_t18x->status))
+ disable_irq(twdt_t18x->irq);
+}
+
+static inline int tegra_wdt_t18x_skip(struct tegra_wdt_t18x *twdt_t18x)
+{
+ u32 val = 0;
+ int skip_count = 0;
+ bool remote_skip = !(twdt_t18x->config & WDT_CFG_REMOTE_INT_EN);
+ bool dbg_skip = !(twdt_t18x->config & WDT_CFG_DBG_RST_EN);
+
+ if (remote_skip) {
+ if (dbg_skip) {
+ val |= WDT_SKIP_VAL(2, 2);
+ skip_count += 2;
+ } else {
+ val |= WDT_SKIP_VAL(2, 1);
+ skip_count += 1;
+ }
+ } else {
+ if (dbg_skip) {
+ val |= WDT_SKIP_VAL(3, 1);
+ skip_count += 1;
+ }
+ }
+
+ if (val)
+ writel(val, twdt_t18x->wdt_source + WDT_SKIP);
+
+ return skip_count;
+}
+
+static int __tegra_wdt_t18x_enable(struct tegra_wdt_t18x *twdt_t18x)
+{
+ u32 val;
+
+ /* Unmask IRQ. This has to be called after every WDT power gate */
+ if (twdt_t18x->soc->unmask_hw_irq)
+ writel(TOP_TKE_TKEIE_WDT_MASK(twdt_t18x->wdt_index),
+ twdt_t18x->wdt_tke + TOP_TKE_TKEIE(twdt_t18x->hwirq));
+
+ /* Update skip configuration and active expiry count */
+ twdt_t18x->active_count = EXPIRY_COUNT - tegra_wdt_t18x_skip(twdt_t18x);
+ if (twdt_t18x->active_count < 1)
+ twdt_t18x->active_count = 1;
+
+ writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);
+ /*
+ * The timeout needs to be divided by active expiry count here so as
+ * to keep the ultimate watchdog reset timeout the same as the program
+ * timeout requested by application. The program timeout should make
+ * sure WDT FIQ will never be asserted in a valid use case.
+ */
+ val = (twdt_t18x->wdt.timeout * USEC_PER_SEC) / twdt_t18x->active_count;
+ val |= (TOP_TKE_TMR_EN | TOP_TKE_TMR_PERIODIC);
+ writel(val, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);
+
+ if (!twdt_t18x->config_locked)
+ writel(twdt_t18x->config, twdt_t18x->wdt_source + WDT_CFG);
+ writel(WDT_CMD_START_COUNTER, twdt_t18x->wdt_source + WDT_CMD);
+ set_bit(WDT_ENABLED, &twdt_t18x->status);
+
+ return 0;
+}
+
+static int __tegra_wdt_t18x_disable(struct tegra_wdt_t18x *twdt_t18x)
+{
+ writel(WDT_UNLOCK_PATTERN, twdt_t18x->wdt_source + WDT_UNLOCK);
+ writel(WDT_CMD_DISABLE_COUNTER, twdt_t18x->wdt_source + WDT_CMD);
+
+ writel(0, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);
+
+ clear_bit(WDT_ENABLED, &twdt_t18x->status);
+
+ return 0;
+}
+
+static int tegra_wdt_t18x_enable(struct watchdog_device *wdt)
+{
+ struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);
+
+ return __tegra_wdt_t18x_enable(twdt_t18x);
+}
+
+static int tegra_wdt_t18x_disable(struct watchdog_device *wdt)
+{
+ struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);
+
+ return __tegra_wdt_t18x_disable(twdt_t18x);
+}
+
+static int tegra_wdt_t18x_ping(struct watchdog_device *wdt)
+{
+ struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);
+
+ tegra_wdt_t18x_ref(wdt);
+
+ return __tegra_wdt_t18x_ping(twdt_t18x);
+}
+
+static int tegra_wdt_t18x_set_timeout(struct watchdog_device *wdt,
+ unsigned int timeout)
+{
+ struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);
+
+ tegra_wdt_t18x_disable(wdt);
+ wdt->timeout = timeout;
+ tegra_wdt_t18x_enable(wdt);
+
+ dev_info(twdt_t18x->dev, "Watchdog(%d): wdt timeout set to %u sec\n",
+ wdt->id, timeout);
+ return 0;
+}
+
+static const struct watchdog_info tegra_wdt_t18x_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Tegra WDT",
+ .firmware_version = 1,
+};
+
+static const struct watchdog_ops tegra_wdt_t18x_ops = {
+ .owner = THIS_MODULE,
+ .start = tegra_wdt_t18x_enable,
+ .stop = tegra_wdt_t18x_disable,
+ .ping = tegra_wdt_t18x_ping,
+ .set_timeout = tegra_wdt_t18x_set_timeout,
+};
+
+#ifdef CONFIG_DEBUG_FS
+#define FILE_PERMISSIONS 644
+#define COUNTER_ENABLED 0x1
+
+static int dump_registers_show(struct seq_file *s, void *unused)
+{
+ struct tegra_wdt_t18x *twdt_t18x = s->private;
+
+ seq_printf(s, "Timer config register \t\t0x%08x\n",
+ readl(twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV));
+ seq_printf(s, "Timer status register \t\t0x%08x\n",
+ readl(twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR));
+ seq_printf(s, "Watchdog config register \t0x%08x\n",
+ readl(twdt_t18x->wdt_source + WDT_CFG));
+ seq_printf(s, "Watchdog status register \t0x%08x\n",
+ readl(twdt_t18x->wdt_source + WDT_STATUS));
+ seq_printf(s, "Watchdog command register \t0x%08x\n",
+ readl(twdt_t18x->wdt_source + WDT_CMD));
+ seq_printf(s, "Watchdog skip register \t\t0x%08x\n",
+ readl(twdt_t18x->wdt_source + WDT_SKIP));
+
+ return 0;
+}
+
+static int disable_dbg_reset_show(struct seq_file *s, void *unused)
+{
+ struct tegra_wdt_t18x *twdt_t18x = s->private;
+
+ seq_printf(s, "%d\n",
+ (twdt_t18x->config & WDT_CFG_DBG_RST_EN) ? 0 : 1);
+
+ return 0;
+}
+
+static int disable_por_reset_show(struct seq_file *s, void *unused)
+{
+ struct tegra_wdt_t18x *twdt_t18x = s->private;
+
+ seq_printf(s, "%d\n",
+ (twdt_t18x->config & WDT_CFG_SYS_PORST_EN) ? 0 : 1);
+
+ return 0;
+}
+
+static int disable_wdt_show(void *data, u64 *val)
+{
+ struct tegra_wdt_t18x *twdt_t18x = data;
+ *val = (twdt_t18x->status & COUNTER_ENABLED) ? 0 : 1;
+ return 0;
+}
+
+static int disable_wdt_store(void *data, u64 val)
+{
+ struct tegra_wdt_t18x *twdt_t18x = data;
+
+ if (val) {
+ if (COUNTER_ENABLED & twdt_t18x->status)
+ __tegra_wdt_t18x_disable(twdt_t18x);
+ } else {
+ if (!(COUNTER_ENABLED & twdt_t18x->status))
+ __tegra_wdt_t18x_enable(twdt_t18x);
+ }
+
+ return 0;
+}
+
+#define SIMPLE_FOPS(_name, _show) \
+static int dbg_open_##_name(struct inode *inode, struct file *file) \
+{ \
+ return single_open(file, _show, inode->i_private); \
+} \
+static const struct file_operations _name##_fops = { \
+ .open = dbg_open_##_name, \
+ .read = seq_read, \
+ .llseek = seq_lseek, \
+ .release = single_release, \
+}
+
+SIMPLE_FOPS(dump_regs, dump_registers_show);
+SIMPLE_FOPS(disable_dbg_reset, disable_dbg_reset_show);
+SIMPLE_FOPS(disable_por_reset, disable_por_reset_show);
+DEFINE_SIMPLE_ATTRIBUTE(disable_wdt_fops, disable_wdt_show,
+ disable_wdt_store, "%lld\n");
+
+static void tegra_wdt_t18x_debugfs_init(struct tegra_wdt_t18x *twdt_t18x)
+{
+ struct dentry *root;
+ struct dentry *retval;
+ struct platform_device *pdev = to_platform_device(twdt_t18x->dev);
+
+ root = debugfs_create_dir(pdev->name, NULL);
+ if (IS_ERR_OR_NULL(root))
+ goto clean;
+
+ retval = debugfs_create_file("dump_regs", FILE_PERMISSIONS,
+ root, twdt_t18x, &dump_regs_fops);
+ if (IS_ERR_OR_NULL(retval))
+ goto clean;
+
+ retval = debugfs_create_file("disable_dbg_reset", FILE_PERMISSIONS,
+ root, twdt_t18x,
+ &disable_dbg_reset_fops);
+ if (IS_ERR_OR_NULL(retval))
+ goto clean;
+
+ retval = debugfs_create_file("disable_por_reset", FILE_PERMISSIONS,
+ root, twdt_t18x,
+ &disable_por_reset_fops);
+ if (IS_ERR_OR_NULL(retval))
+ goto clean;
+
+ retval = debugfs_create_file("disable_wdt", FILE_PERMISSIONS,
+ root, twdt_t18x,
+ &disable_wdt_fops);
+ if (IS_ERR_OR_NULL(retval))
+ goto clean;
+
+ twdt_t18x->root = root;
+
+ return;
+clean:
+ dev_warn(twdt_t18x->dev, "Failed to create debugfs!\n");
+ debugfs_remove_recursive(root);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+static void tegra_wdt_t18x_debugfs_init(struct tegra_wdt_t18x *twdt_t18x)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static int tegra_wdt_t18x_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res_wdt, *res_tmr, *res_tke;
+ struct tegra_wdt_t18x *twdt_t18x;
+ struct device_node *np = pdev->dev.of_node;
+ struct of_phandle_args oirq;
+ int timer_id, wdt_id;
+ int skip_count = 0;
+ u32 pval = 0;
+ int ret;
+
+ twdt_t18x = devm_kzalloc(&pdev->dev, sizeof(*twdt_t18x), GFP_KERNEL);
+ if (!twdt_t18x)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(np, "nvidia,shutdown-timeout", &pval);
+ twdt_t18x->shutdown_timeout = (ret) ? SHUTDOWN_TIMEOUT : pval;
+
+ twdt_t18x->soc = of_device_get_match_data(&pdev->dev);
+ if (!twdt_t18x->soc) {
+ dev_err(&pdev->dev, "Unable to find soc data\n");
+ return -EINVAL;
+ }
+
+ twdt_t18x->dev = &pdev->dev;
+ twdt_t18x->wdt.info = &tegra_wdt_t18x_info;
+ twdt_t18x->wdt.ops = &tegra_wdt_t18x_ops;
+
+ ret = of_property_read_u32(np, "nvidia,expiry-count", &pval);
+ if (!ret)
+ dev_info(twdt_t18x->dev, "Expiry count is deprecated\n");
+
+ watchdog_set_nowayout(&twdt_t18x->wdt, nowayout);
+
+ twdt_t18x->irq = platform_get_irq(pdev, 0);
+ if (twdt_t18x->irq < 0) {
+ dev_err(&pdev->dev, "Interrupt is not available\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Find the IRQ number from the perspective of the interrupt
+ * controller. This is different than Linux's IRQ number
+ */
+ ret = of_irq_parse_one(np, 0, &oirq);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to parse IRQ: %d\n", ret);
+ return -EINVAL;
+ }
+
+ /* The second entry is the IRQ */
+ twdt_t18x->hwirq = oirq.args[1];
+
+ twdt_t18x->enable_on_init = of_property_read_bool(np,
+ "nvidia,enable-on-init");
+
+ twdt_t18x->extended_suspend = of_property_read_bool(np,
+ "nvidia,extend-watchdog-suspend");
+
+ t18x_wdt = twdt_t18x;
+ platform_set_drvdata(pdev, twdt_t18x);
+
+ res_wdt = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ res_tmr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ res_tke = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (!res_wdt || !res_tmr || !res_tke) {
+ dev_err(&pdev->dev, "Insufficient resources\n");
+ return -ENOENT;
+ }
+
+ /* WDT ID from base address */
+ switch (twdt_t18x->soc->timer_index) {
+ case TIMER_INDEX_T234:
+ wdt_id = T234_WATCHDOG_INDEX(res_wdt->start);
+ break;
+ default:
+ wdt_id = WATCHDOG_INDEX(res_wdt->start);
+ break;
+ }
+
+ ret = of_property_read_u32(np, "nvidia,watchdog-index", &pval);
+ if (!ret) {
+ twdt_t18x->wdt_index = pval;
+
+ /* If WDT index provided then address must be for base */
+ if (wdt_id && (wdt_id != twdt_t18x->wdt_index)) {
+ dev_err(dev, "WDT base address must be for WDT0\n");
+ return -EINVAL;
+ }
+
+ /* Adjust address for WDT */
+ ret = adjust_resource(res_wdt, res_wdt->start + pval * 0x1000,
+ res_wdt->end - res_wdt->start);
+ if (ret < 0) {
+ dev_err(dev, "Cannot adjust address of WDT: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /* Watchdog index in list of wdts under top_tke */
+ twdt_t18x->wdt_index = wdt_id;
+ }
+
+ twdt_t18x->wdt_source = devm_ioremap_resource(&pdev->dev, res_wdt);
+ if (IS_ERR(twdt_t18x->wdt_source)) {
+ dev_err(&pdev->dev, "Cannot request memregion/iomap res_wdt\n");
+ return PTR_ERR(twdt_t18x->wdt_source);
+ }
+
+ twdt_t18x->config = readl(twdt_t18x->wdt_source + WDT_CFG);
+ twdt_t18x->config_locked = !!(twdt_t18x->config & WDT_CFG_INT_EN);
+
+ /*
+ * Get Timer index,
+ * First from BL, if locked else
+ * from DT property else
+ * From base address.
+ */
+ if (twdt_t18x->config_locked) {
+ twdt_t18x->tmr_index = twdt_t18x->config & 0xF;
+ } else {
+ ret = of_property_read_u32(np, "nvidia,timer-index", &pval);
+ if (!ret)
+ twdt_t18x->tmr_index = pval;
+ else
+ twdt_t18x->tmr_index = TIMER_INDEX(res_tmr->start);
+ }
+
+ /*
+ * If timer ID from address different then timer index
+ * then adjust address.
+ */
+ switch (twdt_t18x->soc->timer_index) {
+ case TIMER_INDEX_T234:
+ timer_id = T234_TIMER_INDEX(res_tmr->start);
+ break;
+ default:
+ timer_id = TIMER_INDEX(res_tmr->start);
+ break;
+ }
+
+ if (timer_id != twdt_t18x->tmr_index) {
+ if (timer_id) {
+ dev_err(dev, "Timer base address must be Timer0\n");
+ return -EINVAL;
+ }
+
+ ret = adjust_resource(res_tmr, res_tmr->start +
+ twdt_t18x->tmr_index * 0x10000,
+ res_tmr->end - res_tmr->start);
+ if (ret < 0) {
+ dev_err(dev, "Cannot adjust address of TMR:%d\n", ret);
+ return ret;
+ }
+ }
+
+ twdt_t18x->wdt_timer = devm_ioremap_resource(&pdev->dev, res_tmr);
+ if (IS_ERR(twdt_t18x->wdt_timer)) {
+ dev_err(&pdev->dev, "Cannot request memregion/iomap res_tmr\n");
+ return PTR_ERR(twdt_t18x->wdt_timer);
+ }
+
+ twdt_t18x->wdt_tke = devm_ioremap_resource(&pdev->dev, res_tke);
+ if (IS_ERR(twdt_t18x->wdt_tke)) {
+ dev_err(&pdev->dev, "Cannot request memregion/iomap res_tke\n");
+ return PTR_ERR(twdt_t18x->wdt_tke);
+ }
+
+ if (!twdt_t18x->config_locked) {
+ /* Configure timer source and period */
+ twdt_t18x->config = twdt_t18x->tmr_index;
+ twdt_t18x->config |= WDT_CFG_PERIOD;
+
+ /* Enable local interrupt for WDT petting */
+ twdt_t18x->config |= WDT_CFG_INT_EN;
+
+ /*
+ * 'ErrorThreshold' field @ TKE_TOP_WDT1_WDTCR_0 decides the
+ * indication to HSM. The WDT logic asserts an error signal to
+ * HSM when ExpirationLevel >= ErrorThreshold. Retain the POR
+ * value to avoid nuisance trigger to HSM.
+ */
+ twdt_t18x->config |= WDT_CFG_ERR_THRESHOLD;
+
+ /* Enable local FIQ and remote interrupt for debug dump */
+ twdt_t18x->config |= WDT_CFG_FINT_EN;
+
+ if (of_property_read_bool(np, "nvidia,disallow-wdt-freeze"))
+ twdt_t18x->config |= WDT_CFG_DISALLOW_FREEZE;
+
+ if (!of_property_read_bool(np,
+ "nvidia,disable-remote-interrupt"))
+ twdt_t18x->config |= WDT_CFG_REMOTE_INT_EN;
+ else
+ skip_count++;
+
+ /*
+ * Debug and POR reset events should be enabled by default.
+ * Disable only if explicitly indicated in device tree or
+ * HALT_IN_FIQ is set, so as to allow external debugger to poke.
+ */
+ if (!tegra_pmc_is_halt_in_fiq()) {
+ if (!of_property_read_bool(
+ np, "nvidia,disable-debug-reset"))
+ twdt_t18x->config |= WDT_CFG_DBG_RST_EN;
+ else
+ skip_count++;
+
+ if (!of_property_read_bool(
+ np, "nvidia,disable-por-reset"))
+ twdt_t18x->config |= WDT_CFG_SYS_PORST_EN;
+ }
+ }
+
+ twdt_t18x->wdt.min_timeout = MIN_TMR_PTV;
+ twdt_t18x->wdt.max_timeout = MAX_TMR_PTV * (EXPIRY_COUNT - skip_count);
+ ret = watchdog_init_timeout(&twdt_t18x->wdt, 0, &pdev->dev);
+ if (ret < 0)
+ twdt_t18x->wdt.timeout = HEARTBEAT;
+
+ tegra_wdt_t18x_disable(&twdt_t18x->wdt);
+ writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);
+
+ ret = devm_request_threaded_irq(&pdev->dev, twdt_t18x->irq,
+ NULL, tegra_wdt_t18x_isr,
+ IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+ dev_name(&pdev->dev), twdt_t18x);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register irq %d err %d\n",
+ twdt_t18x->irq, ret);
+ return ret;
+ }
+
+ if (twdt_t18x->enable_on_init) {
+ tegra_wdt_t18x_enable(&twdt_t18x->wdt);
+ set_bit(WDOG_ACTIVE, &twdt_t18x->wdt.status);
+ set_bit(WDT_ENABLED_ON_INIT, &twdt_t18x->status);
+ dev_info(twdt_t18x->dev, "Tegra WDT init timeout = %u sec\n",
+ twdt_t18x->wdt.timeout);
+ }
+
+ tegra_wdt_t18x_debugfs_init(twdt_t18x);
+
+ if (twdt_t18x->extended_suspend)
+ register_syscore_ops(&tegra_wdt_t18x_syscore_ops);
+
+ ret = devm_watchdog_register_device(&pdev->dev, &twdt_t18x->wdt);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register watchdog device\n");
+ goto cleanup;
+ }
+
+ dev_info(&pdev->dev, "Registered successfully\n");
+
+ return 0;
+
+cleanup:
+ __tegra_wdt_t18x_disable(twdt_t18x);
+
+ if (twdt_t18x->extended_suspend)
+ unregister_syscore_ops(&tegra_wdt_t18x_syscore_ops);
+
+ debugfs_remove_recursive(twdt_t18x->root);
+
+ return ret;
+}
+
+static void tegra_wdt_t18x_shutdown(struct platform_device *pdev)
+{
+ struct tegra_wdt_t18x *twdt_t18x = platform_get_drvdata(pdev);
+
+ if (twdt_t18x->shutdown_timeout) {
+ twdt_t18x->wdt.timeout = twdt_t18x->shutdown_timeout;
+ __tegra_wdt_t18x_ping(twdt_t18x);
+ return;
+ }
+
+ __tegra_wdt_t18x_disable(twdt_t18x);
+}
+
+static int tegra_wdt_t18x_remove(struct platform_device *pdev)
+{
+ struct tegra_wdt_t18x *twdt_t18x = platform_get_drvdata(pdev);
+
+ t18x_wdt = NULL;
+ __tegra_wdt_t18x_disable(twdt_t18x);
+
+ if (twdt_t18x->extended_suspend)
+ unregister_syscore_ops(&tegra_wdt_t18x_syscore_ops);
+
+ debugfs_remove_recursive(twdt_t18x->root);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra_wdt_t18x_suspend(struct device *dev)
+{
+ struct tegra_wdt_t18x *twdt_t18x = dev_get_drvdata(dev);
+
+ if (twdt_t18x->extended_suspend)
+ __tegra_wdt_t18x_ping(twdt_t18x);
+ else
+ __tegra_wdt_t18x_disable(twdt_t18x);
+
+ return 0;
+}
+
+static int tegra_wdt_t18x_resume(struct device *dev)
+{
+ struct tegra_wdt_t18x *twdt_t18x = dev_get_drvdata(dev);
+
+ if (watchdog_active(&twdt_t18x->wdt)) {
+ if (twdt_t18x->extended_suspend)
+ __tegra_wdt_t18x_ping(twdt_t18x);
+ else
+ __tegra_wdt_t18x_enable(twdt_t18x);
+ } else {
+ if (twdt_t18x->extended_suspend)
+ __tegra_wdt_t18x_disable(twdt_t18x);
+ }
+
+ return 0;
+}
+
+static int tegra_wdt_t18x_syscore_suspend(void)
+{
+ if (t18x_wdt && t18x_wdt->extended_suspend)
+ __tegra_wdt_t18x_disable(t18x_wdt);
+
+ return 0;
+}
+
+static void tegra_wdt_t18x_syscore_resume(void)
+{
+ if (t18x_wdt && t18x_wdt->extended_suspend)
+ __tegra_wdt_t18x_enable(t18x_wdt);
+}
+#else
+static int tegra_wdt_t18x_syscore_suspend(void)
+{
+ return 0;
+}
+
+static void tegra_wdt_t18x_syscore_resume(void) { }
+#endif
+
+static struct syscore_ops tegra_wdt_t18x_syscore_ops = {
+ .suspend = tegra_wdt_t18x_syscore_suspend,
+ .resume = tegra_wdt_t18x_syscore_resume,
+};
+
+static const struct dev_pm_ops tegra_wdt_t18x_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tegra_wdt_t18x_suspend, tegra_wdt_t18x_resume)
+};
+
+static const struct tegra_wdt_t18x_soc t234_wdt_silicon = {
+ .unmask_hw_irq = true,
+ .timer_index = TIMER_INDEX_T234,
+};
+
+static const struct tegra_wdt_t18x_soc t18x_wdt_silicon = {
+ .unmask_hw_irq = true,
+ .timer_index = TIMER_INDEX_T186,
+};
+
+static const struct tegra_wdt_t18x_soc t18x_wdt_sim = {
+ .unmask_hw_irq = false,
+ .timer_index = TIMER_INDEX_T186,
+};
+
+static const struct of_device_id tegra_wdt_t18x_match[] = {
+ { .compatible = "nvidia,tegra-wdt-t234", .data = &t234_wdt_silicon},
+ { .compatible = "nvidia,tegra-wdt-t19x", .data = &t18x_wdt_silicon},
+ { .compatible = "nvidia,tegra-wdt-t18x", .data = &t18x_wdt_silicon},
+ { .compatible = "nvidia,tegra-wdt-t18x-linsim", .data = &t18x_wdt_sim},
+ {}
+};
+MODULE_DEVICE_TABLE(of, tegra_wdt_t18x_match);
+
+static struct platform_driver tegra_wdt_t18x_driver = {
+ .probe = tegra_wdt_t18x_probe,
+ .remove = tegra_wdt_t18x_remove,
+ .shutdown = tegra_wdt_t18x_shutdown,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "tegra_wdt_t18x",
+ .pm = &tegra_wdt_t18x_pm_ops,
+ .of_match_table = of_match_ptr(tegra_wdt_t18x_match),
+ },
+};
+
+static int __init tegra_wdt_t18x_init(void)
+{
+ return platform_driver_register(&tegra_wdt_t18x_driver);
+}
+
+static void __exit tegra_wdt_t18x_exit(void)
+{
+ platform_driver_unregister(&tegra_wdt_t18x_driver);
+}
+
+subsys_initcall(tegra_wdt_t18x_init);
+module_exit(tegra_wdt_t18x_exit);
+
+MODULE_AUTHOR("NVIDIA Corporation");
+MODULE_DESCRIPTION("Tegra Watchdog Driver");
+MODULE_LICENSE("GPL v2");
diff -uNr linux-5.12.10/include/soc/tegra/pmc.h linux-5.12.10-nvidia/include/soc/tegra/pmc.h
--- linux-5.12.10/include/soc/tegra/pmc.h 2021-06-10 11:41:49.000000000 +0000
+++ linux-5.12.10-nvidia/include/soc/tegra/pmc.h 2025-12-24 10:43:14.313215012 +0000
@@ -238,4 +238,6 @@
}
#endif
+bool tegra_pmc_is_halt_in_fiq(void);
+
#endif /* __SOC_TEGRA_PMC_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment