From 80ef5179fecf62ec3904d3f426d9ebd33f4d51b9 Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Mon, 25 Apr 2022 06:52:02 +0000 Subject: [PATCH] watchdog: Add NVIDIA Tegra watchdog timer driver Add NVIDIA Tegra watchdog timer driver. Bug 3583627 Change-Id: Iaa4c54c990eac7e74adfce342f22df413349f863 Signed-off-by: Laxman Dewangan Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2702335 GVS: Gerrit_Virtual_Submit --- drivers/watchdog/Makefile | 1 + drivers/watchdog/watchdog-tegra-t18x.c | 824 +++++++++++++++++++++++++ 2 files changed, 825 insertions(+) create mode 100644 drivers/watchdog/watchdog-tegra-t18x.c diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 671a113d..d046a03e 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -2,3 +2,4 @@ # Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. obj-m += softdog-platform.o +obj-m += watchdog-tegra-t18x.o diff --git a/drivers/watchdog/watchdog-tegra-t18x.c b/drivers/watchdog/watchdog-tegra-t18x.c new file mode 100644 index 00000000..d2ce8ac7 --- /dev/null +++ b/drivers/watchdog/watchdog-tegra-t18x.c @@ -0,0 +1,824 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2022 NVIDIA CORPORATION. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +#include +#define PMC_HALT_IN_FIQ() tegra_pmc_is_halt_in_fiq() +#else +#define PMC_HALT_IN_FIQ() (0) +#endif + +/* 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); + 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 (!PMC_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), + }, +}; + +module_platform_driver(tegra_wdt_t18x_driver); + +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_DESCRIPTION("Tegra Watchdog Driver"); +MODULE_LICENSE("GPL v2");