/* * drivers/watchdog/tegra_wdt.c * * watchdog driver for NVIDIA tegra internal watchdog * * Copyright (c) 2012-2013, 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. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_TEGRA_FIQ_DEBUGGER #include #endif #include /* minimum and maximum watchdog trigger periods, in seconds */ #define MIN_WDT_PERIOD 5 #define MAX_WDT_PERIOD 1000 /* Assign Timer 7 to Timer 10 for WDT0 to WDT3, respectively */ #define TMR_SRC_START 7 enum tegra_wdt_status { WDT_DISABLED = 1 << 0, WDT_ENABLED = 1 << 1, WDT_ENABLED_AT_PROBE = 1 << 2, }; struct tegra_wdt { struct miscdevice miscdev; struct notifier_block notifier; struct resource *res_src; struct resource *res_wdt; struct resource *res_int_base; unsigned long users; void __iomem *wdt_source; void __iomem *wdt_timer; void __iomem *int_base; int irq; int tmrsrc; int timeout; int status; }; /* * For spinlock lockup detection to work, the heartbeat should be 2*lockup * for cases where the spinlock disabled irqs. */ static int heartbeat = 120; /* must be greater than MIN_WDT_PERIOD and lower than MAX_WDT_PERIOD */ #if defined(CONFIG_ARCH_TEGRA_2x_SOC) #define TIMER_PTV 0x0 #define TIMER_EN (1 << 31) #define TIMER_PERIODIC (1 << 30) #define TIMER_PCR 0x4 #define TIMER_PCR_INTR (1 << 30) #define WDT_EN (1 << 5) #define WDT_SEL_TMR1 (0 << 4) #define WDT_SYS_RST (1 << 2) static void tegra_wdt_enable(struct tegra_wdt *wdt) { u32 val; /* since the watchdog reset occurs when a second interrupt * is asserted before the first is processed, program the * timer period to one-half of the watchdog period */ val = wdt->timeout * 1000000ul / 2; val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->wdt_timer + TIMER_PTV); val = WDT_EN | WDT_SEL_TMR1 | WDT_SYS_RST; writel(val, wdt->wdt_source); } static void tegra_wdt_disable(struct tegra_wdt *wdt) { writel(0, wdt->wdt_source); writel(0, wdt->wdt_timer + TIMER_PTV); } static inline void tegra_wdt_ping(struct tegra_wdt *wdt) { return; } static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { struct tegra_wdt *wdt = dev_id; writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); return IRQ_HANDLED; } #elif defined(CONFIG_ARCH_TEGRA_3x_SOC) || defined(CONFIG_ARCH_TEGRA_11x_SOC) #define TIMER_PTV 0 #define TIMER_EN (1 << 31) #define TIMER_PERIODIC (1 << 30) #define TIMER_PCR 0x4 #define TIMER_PCR_INTR (1 << 30) #define WDT_CFG (0) #define WDT_CFG_PERIOD (1 << 4) #define WDT_CFG_INT_EN (1 << 12) #define WDT_CFG_FIQ_INT_EN (1 << 13) #define WDT_CFG_SYS_RST_EN (1 << 14) #define WDT_CFG_PMC2CAR_RST_EN (1 << 15) #define WDT_STATUS (4) #define WDT_INTR_STAT (1 << 1) #define WDT_CMD (8) #define WDT_CMD_START_COUNTER (1 << 0) #define WDT_CMD_DISABLE_COUNTER (1 << 1) #define WDT_UNLOCK (0xC) #define WDT_UNLOCK_PATTERN (0xC45A << 0) #define ICTLR_IEP_CLASS 0x2C #define MAX_NR_CPU_WDT 0x4 #define PMC_RST_STATUS 0x1b4 struct tegra_wdt *tegra_wdt[MAX_NR_CPU_WDT]; static inline void tegra_wdt_ping(struct tegra_wdt *wdt) { writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); } #ifdef CONFIG_TEGRA_FIQ_DEBUGGER static void tegra_wdt_int_priority(struct tegra_wdt *wdt) { unsigned val = 0; if (!wdt->int_base) return; val = readl(wdt->int_base + ICTLR_IEP_CLASS); val &= ~(1 << (INT_WDT_CPU & 31)); writel(val, wdt->int_base + ICTLR_IEP_CLASS); } #endif static void tegra_wdt_enable(struct tegra_wdt *wdt) { u32 val; writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); val = (wdt->timeout * 1000000ul) / 4; val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->wdt_timer + TIMER_PTV); /* Interrupt handler is not required for user space * WDT accesses, since the caller is responsible to ping the * WDT to reset the counter before expiration, through ioctls. * SYS_RST_EN doesnt work as there is no external reset * from Tegra. */ val = wdt->tmrsrc | WDT_CFG_PERIOD | /*WDT_CFG_INT_EN |*/ /*WDT_CFG_SYS_RST_EN |*/ WDT_CFG_PMC2CAR_RST_EN; #ifdef CONFIG_TEGRA_FIQ_DEBUGGER val |= WDT_CFG_FIQ_INT_EN; #endif writel(val, wdt->wdt_source + WDT_CFG); writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); } static void tegra_wdt_disable(struct tegra_wdt *wdt) { writel(WDT_UNLOCK_PATTERN, wdt->wdt_source + WDT_UNLOCK); writel(WDT_CMD_DISABLE_COUNTER, wdt->wdt_source + WDT_CMD); writel(0, wdt->wdt_timer + TIMER_PTV); } static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { unsigned i, status; for (i = 0; i < MAX_NR_CPU_WDT; i++) { if (tegra_wdt[i] == NULL) continue; status = readl(tegra_wdt[i]->wdt_source + WDT_STATUS); if ((tegra_wdt[i]->status & WDT_ENABLED) && (status & WDT_INTR_STAT)) tegra_wdt_ping(tegra_wdt[i]); } return IRQ_HANDLED; } #endif static int tegra_wdt_notify(struct notifier_block *this, unsigned long code, void *dev) { struct tegra_wdt *wdt = container_of(this, struct tegra_wdt, notifier); if (code == SYS_DOWN || code == SYS_HALT) tegra_wdt_disable(wdt); return NOTIFY_DONE; } static int tegra_wdt_open(struct inode *inode, struct file *file) { struct miscdevice *mdev = file->private_data; struct tegra_wdt *wdt = container_of(mdev, struct tegra_wdt, miscdev); if (test_and_set_bit(1, &wdt->users)) return -EBUSY; wdt->status |= WDT_ENABLED; wdt->timeout = heartbeat; tegra_wdt_enable(wdt); file->private_data = wdt; return nonseekable_open(inode, file); } static int tegra_wdt_release(struct inode *inode, struct file *file) { struct tegra_wdt *wdt = file->private_data; if (wdt->status == WDT_ENABLED) { #ifndef CONFIG_WATCHDOG_NOWAYOUT tegra_wdt_disable(wdt); wdt->status = WDT_DISABLED; #endif } wdt->users = 0; return 0; } static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct tegra_wdt *wdt = file->private_data; static DEFINE_SPINLOCK(lock); int new_timeout; int option; static const struct watchdog_info ident = { .identity = "Tegra Watchdog", .options = WDIOF_SETTIMEOUT, .firmware_version = 0, }; switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user((struct watchdog_info __user *)arg, &ident, sizeof(ident)); case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, (int __user *)arg); case WDIOC_KEEPALIVE: spin_lock(&lock); tegra_wdt_ping(wdt); spin_unlock(&lock); return 0; case WDIOC_SETTIMEOUT: if (get_user(new_timeout, (int __user *)arg)) return -EFAULT; spin_lock(&lock); tegra_wdt_disable(wdt); wdt->timeout = clamp(new_timeout, MIN_WDT_PERIOD, MAX_WDT_PERIOD); tegra_wdt_enable(wdt); spin_unlock(&lock); case WDIOC_GETTIMEOUT: return put_user(wdt->timeout, (int __user *)arg); case WDIOC_SETOPTIONS: #ifndef CONFIG_WATCHDOG_NOWAYOUT if (get_user(option, (int __user *)arg)) return -EFAULT; spin_lock(&lock); if (option & WDIOS_DISABLECARD) { wdt->status &= ~WDT_ENABLED; wdt->status |= WDT_DISABLED; tegra_wdt_disable(wdt); } else if (option & WDIOS_ENABLECARD) { tegra_wdt_enable(wdt); wdt->status |= WDT_ENABLED; wdt->status &= ~WDT_DISABLED; } else { spin_unlock(&lock); return -EINVAL; } spin_unlock(&lock); return 0; #else return -EINVAL; #endif } return -ENOTTY; } static ssize_t tegra_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { return len; } static void tegra_wdt_log_reset_reason(struct platform_device *pdev, struct tegra_wdt *wdt) { #if defined(CONFIG_ARCH_TEGRA_3x_SOC) || defined(CONFIG_ARCH_TEGRA_11x_SOC) /* * There are two pathes to make the WDT reset: * (a) WDT -> PMC -> CAR * ^ * | * v * PMIC * * (b) WDT -> CAR * * Path (a) is enabled by WDT_CFG_PMC2CAR_RST_EN bit in the WDT * configuration register, as it will reset the CAR module, and we * cannot read back the reset reason from the CAR module. However, we * can read back the reaset reason from the PMC module. * * Path (b) is enabled by the WDT_CFG_SYS_RST_EN bit, and we can * read back the reset reason from the CAR moudle. However, this reset * path will not reset the peripherals which might be the hard hang * source. We will not use this path. */ u32 val; void __iomem *pmc_base; #define RESET_STR(REASON) "last reset is due to "#REASON"\n" char *reset_reason[] = { RESET_STR(power on reset), RESET_STR(watchdog timeout), RESET_STR(sensor), RESET_STR(software reset), RESET_STR(deep sleep reset), }; /* report reset reason only once */ if (pdev->id > 0) return; pmc_base = IO_ADDRESS(TEGRA_PMC_BASE); val = readl(pmc_base + PMC_RST_STATUS) & 0x7; if (val >= ARRAY_SIZE(reset_reason)) dev_info(&pdev->dev, "last reset value is invalid 0x%x\n", val); else dev_info(&pdev->dev, reset_reason[val]); #else u32 val; val = readl(wdt->wdt_source); if (val & BIT(12)) dev_info(&pdev->dev, "last reset due to watchdog timeout\n"); #endif } static const struct file_operations tegra_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = tegra_wdt_write, .unlocked_ioctl = tegra_wdt_ioctl, .open = tegra_wdt_open, .release = tegra_wdt_release, }; static int tegra_wdt_probe(struct platform_device *pdev) { struct resource *res_src, *res_wdt, *res_irq; struct resource *res_int_base = NULL; struct tegra_wdt *wdt; int ret = 0; if ((pdev->id < -1) || (pdev->id > 0)) { dev_err(&pdev->dev, "Only support IDs -1 and 0\n"); return -ENODEV; } res_src = platform_get_resource(pdev, IORESOURCE_MEM, 0); res_wdt = platform_get_resource(pdev, IORESOURCE_MEM, 1); res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res_src || !res_wdt || (!pdev->id && !res_irq)) { dev_err(&pdev->dev, "incorrect resources\n"); return -ENOENT; } #ifdef CONFIG_TEGRA_FIQ_DEBUGGER res_int_base = platform_get_resource(pdev, IORESOURCE_MEM, 2); if (!pdev->id && !res_int_base) { dev_err(&pdev->dev, "FIQ_DBG: INT base not defined\n"); return -ENOENT; } #endif if (pdev->id == -1 && !res_irq) { dev_err(&pdev->dev, "incorrect irq\n"); return -ENOENT; } wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); if (!wdt) { dev_err(&pdev->dev, "out of memory\n"); return -ENOMEM; } wdt->irq = -1; wdt->miscdev.parent = &pdev->dev; if (pdev->id == -1) { wdt->miscdev.minor = WATCHDOG_MINOR; wdt->miscdev.name = "watchdog"; } else { wdt->miscdev.minor = MISC_DYNAMIC_MINOR; if (pdev->id == 0) wdt->miscdev.name = "watchdog0"; else if (pdev->id == 1) wdt->miscdev.name = "watchdog1"; else if (pdev->id == 2) wdt->miscdev.name = "watchdog2"; else if (pdev->id == 3) wdt->miscdev.name = "watchdog3"; } wdt->miscdev.fops = &tegra_wdt_fops; wdt->notifier.notifier_call = tegra_wdt_notify; res_src = request_mem_region(res_src->start, resource_size(res_src), pdev->name); res_wdt = request_mem_region(res_wdt->start, resource_size(res_wdt), pdev->name); if (!res_src || !res_wdt) { dev_err(&pdev->dev, "unable to request memory resources\n"); ret = -EBUSY; goto fail; } wdt->wdt_source = ioremap(res_src->start, resource_size(res_src)); wdt->wdt_timer = ioremap(res_wdt->start, resource_size(res_wdt)); /* tmrsrc will be used to set WDT_CFG */ wdt->tmrsrc = (TMR_SRC_START + pdev->id) % 10; if (!wdt->wdt_source || !wdt->wdt_timer) { dev_err(&pdev->dev, "unable to map registers\n"); ret = -ENOMEM; goto fail; } tegra_wdt_log_reset_reason(pdev, wdt); tegra_wdt_disable(wdt); writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); if (res_irq != NULL) { #ifdef CONFIG_TEGRA_FIQ_DEBUGGER /* FIQ debugger enables FIQ priority for INT_WDT_CPU. * But that will disable IRQ on WDT expiration. * Reset the priority back to IRQ on INT_WDT_CPU so * that tegra_wdt_interrupt gets its chance to restart the * counter before expiration. */ res_int_base = request_mem_region(res_int_base->start, resource_size(res_int_base), pdev->name); if (!res_int_base) goto fail; wdt->int_base = ioremap(res_int_base->start, resource_size(res_int_base)); if (!wdt->int_base) goto fail; tegra_wdt_int_priority(wdt); #endif ret = request_irq(res_irq->start, tegra_wdt_interrupt, IRQF_DISABLED, dev_name(&pdev->dev), wdt); if (ret) { dev_err(&pdev->dev, "unable to configure IRQ\n"); goto fail; } wdt->irq = res_irq->start; } wdt->res_src = res_src; wdt->res_wdt = res_wdt; wdt->res_int_base = res_int_base; wdt->status = WDT_DISABLED; ret = register_reboot_notifier(&wdt->notifier); if (ret) { dev_err(&pdev->dev, "cannot register reboot notifier\n"); goto fail; } ret = misc_register(&wdt->miscdev); if (ret) { dev_err(&pdev->dev, "failed to register misc device\n"); unregister_reboot_notifier(&wdt->notifier); goto fail; } platform_set_drvdata(pdev, wdt); #ifndef CONFIG_ARCH_TEGRA_2x_SOC #ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_ON_PROBE /* Init and enable watchdog on WDT0 with timer 8 during probe */ if (!(pdev->id)) { u32 val = 0; wdt->status = WDT_ENABLED | WDT_ENABLED_AT_PROBE; wdt->timeout = heartbeat; tegra_wdt_enable(wdt); val = readl(wdt->wdt_source + WDT_CFG); val |= WDT_CFG_INT_EN; writel(val, wdt->wdt_source + WDT_CFG); pr_info("WDT heartbeat enabled on probe\n"); } #endif tegra_wdt[pdev->id] = wdt; #endif pr_info("%s done\n", __func__); return 0; fail: if (wdt->irq != -1) free_irq(wdt->irq, wdt); if (wdt->wdt_source) iounmap(wdt->wdt_source); if (wdt->wdt_timer) iounmap(wdt->wdt_timer); if (wdt->int_base) iounmap(wdt->int_base); if (res_src) release_mem_region(res_src->start, resource_size(res_src)); if (res_wdt) release_mem_region(res_wdt->start, resource_size(res_wdt)); if (res_int_base) release_mem_region(res_int_base->start, resource_size(res_int_base)); kfree(wdt); return ret; } static int tegra_wdt_remove(struct platform_device *pdev) { struct tegra_wdt *wdt = platform_get_drvdata(pdev); tegra_wdt_disable(wdt); unregister_reboot_notifier(&wdt->notifier); misc_deregister(&wdt->miscdev); if (wdt->irq != -1) free_irq(wdt->irq, wdt); iounmap(wdt->wdt_source); iounmap(wdt->wdt_timer); if (wdt->int_base) iounmap(wdt->int_base); release_mem_region(wdt->res_src->start, resource_size(wdt->res_src)); release_mem_region(wdt->res_wdt->start, resource_size(wdt->res_wdt)); if (wdt->res_int_base) release_mem_region(wdt->res_int_base->start, resource_size(wdt->res_int_base)); kfree(wdt); platform_set_drvdata(pdev, NULL); return 0; } #ifdef CONFIG_PM static int tegra_wdt_suspend(struct platform_device *pdev, pm_message_t state) { struct tegra_wdt *wdt = platform_get_drvdata(pdev); tegra_wdt_disable(wdt); return 0; } static int tegra_wdt_resume(struct platform_device *pdev) { struct tegra_wdt *wdt = platform_get_drvdata(pdev); if (wdt->status & WDT_ENABLED) tegra_wdt_enable(wdt); #ifndef CONFIG_ARCH_TEGRA_2x_SOC /* Enable interrupt for WDT3 heartbeat watchdog */ if (wdt->status & WDT_ENABLED_AT_PROBE) { u32 val = 0; val = readl(wdt->wdt_source + WDT_CFG); val |= WDT_CFG_INT_EN; writel(val, wdt->wdt_source + WDT_CFG); pr_info("WDT heartbeat enabled on probe\n"); } #endif return 0; } #endif static struct platform_driver tegra_wdt_driver = { .probe = tegra_wdt_probe, .remove = tegra_wdt_remove, #ifdef CONFIG_PM .suspend = tegra_wdt_suspend, .resume = tegra_wdt_resume, #endif .driver = { .owner = THIS_MODULE, .name = "tegra_wdt", }, }; static int __init tegra_wdt_init(void) { return platform_driver_register(&tegra_wdt_driver); } static void __exit tegra_wdt_exit(void) { platform_driver_unregister(&tegra_wdt_driver); } module_init(tegra_wdt_init); module_exit(tegra_wdt_exit); MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra Watchdog Driver"); module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:tegra_wdt");