/* * tegra_wdt_t18x.c: watchdog driver for NVIDIA tegra internal watchdog * * Copyright (c) 2012-2019, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 struct tegra_wdt_t18x_soc { bool unmask_hw_irq; }; 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_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) 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 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; } #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); 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", S_IRUGO | S_IWUSR, root, twdt_t18x, &dump_regs_fops); if (IS_ERR_OR_NULL(retval)) goto clean; retval = debugfs_create_file("disable_dbg_reset", S_IRUGO | S_IWUSR, root, twdt_t18x, &disable_dbg_reset_fops); if (IS_ERR_OR_NULL(retval)) goto clean; retval = debugfs_create_file("disable_por_reset", S_IRUGO | S_IWUSR, root, twdt_t18x, &disable_por_reset_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 */ wdt_id = WATCHDOG_INDEX(res_wdt->start); 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. */ timer_id = TIMER_INDEX(res_tmr->start); 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,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 t18x_wdt_silicon = { .unmask_hw_irq = true, }; static const struct tegra_wdt_t18x_soc t18x_wdt_sim = { .unmask_hw_irq = false, }; static const struct of_device_id tegra_wdt_t18x_match[] = { { .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");