summaryrefslogtreecommitdiff
path: root/drivers/watchdog/tegra_wdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog/tegra_wdt.c')
-rw-r--r--drivers/watchdog/tegra_wdt.c676
1 files changed, 676 insertions, 0 deletions
diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
new file mode 100644
index 000000000000..3e58a984d9c2
--- /dev/null
+++ b/drivers/watchdog/tegra_wdt.c
@@ -0,0 +1,676 @@
+/*
+ * drivers/watchdog/tegra_wdt.c
+ *
+ * watchdog driver for NVIDIA tegra internal watchdog
+ *
+ * Copyright (c) 2012-2014, NVIDIA Corporation. All rights reserved.
+ * Copyright (c) 2013, Toradex AG
+ *
+ * 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 <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#ifdef CONFIG_TEGRA_FIQ_DEBUGGER
+#include <mach/irqs.h>
+#endif
+
+/* 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
+/*
+ * For spinlock lockup detection to work, the heartbeat should be 2*lockup
+ * for cases where the spinlock disabled irqs.
+ * Must be between MIN_WDT_PERIOD and MAX_WDT_PERIOD
+ */
+#define WDT_DEFAULT_TIME 60
+
+static int heartbeat = WDT_DEFAULT_TIME;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds (default="
+ __MODULE_STRING(WDT_DEFAULT_TIME));
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+#endif
+
+enum tegra_wdt_status {
+ WDT_DISABLED = 1 << 0,
+ WDT_ENABLED = 1 << 1,
+ WDT_KERNEL_HEARTBEAT = 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 tmrsrc;
+ int timeout;
+ int status;
+ int way_out_ok;
+ struct tasklet_struct tasklet;
+ int irq;
+ int irq_counter;
+};
+
+#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)
+{
+ /* Reset timer */
+ tegra_wdt_enable(wdt);
+
+ /* Reenable IRQ in case an interrupt already happend */
+ if (wdt->irq_counter) {
+ writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR);
+ wdt->irq_counter = 0;
+ enable_irq(wdt->irq);
+ }
+
+ return;
+}
+
+static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id)
+{
+ struct tegra_wdt *wdt = dev_id;
+
+ wdt->irq_counter++;
+
+ if (wdt->status & WDT_KERNEL_HEARTBEAT) {
+ tegra_wdt_ping(wdt);
+ } else {
+ /* If not in heartbeat mode, disable IRQs to avoid IRQ storm
+ * We don't acknowledge the interrupt here since the user
+ * did not reload the timer in time. Upon next interrupt
+ * the system will reset...
+ */
+ tasklet_schedule(&wdt->tasklet);
+ disable_irq_nosync(irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void tegra_wdt_do_tasklet(unsigned long data)
+{
+ struct tegra_wdt *wdt = (struct tegra_wdt *)data;
+ dev_info(wdt->miscdev.parent, "Watchdog interrupt received, system "
+ "will reset soon if no ping arrives\n");
+}
+
+#elif defined(CONFIG_ARCH_TEGRA_3x_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_STATUS_EXPIR_COUNTER (3 << 12)
+#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
+
+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);
+
+ writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD);
+
+ /* Interrupt handler is not required for user space since
+ * a warning in a fourth of time don't make sense. Then
+ * the interrupt line is also shared, so it can't be disabled
+ * if one watchdog is about to expire... (interrupt storm)
+ * 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);
+}
+
+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 void tegra_wdt_interrupt_instance(struct tegra_wdt *wdt)
+{
+ WARN_ON_ONCE(!(wdt->status & WDT_KERNEL_HEARTBEAT));
+
+ if (wdt->status & WDT_KERNEL_HEARTBEAT) {
+ tegra_wdt_ping(wdt);
+ }
+}
+
+static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id)
+{
+ struct tegra_wdt *wdt = NULL;
+ unsigned i, status;
+
+ for (i = 0; i < MAX_NR_CPU_WDT; i++) {
+ wdt = tegra_wdt[i];
+ if (wdt == NULL)
+ continue;
+ status = readl(wdt->wdt_source + WDT_STATUS);
+ if ((wdt->status & WDT_ENABLED) &&
+ (status & WDT_INTR_STAT)) {
+ tegra_wdt_interrupt_instance(wdt);
+ }
+ }
+
+ 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;
+
+ /* Reset no way out, we need a new magic again */
+ wdt->way_out_ok = 0;
+
+ 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 && !nowayout) {
+ if (wdt->way_out_ok) {
+ tegra_wdt_disable(wdt);
+ wdt->status = WDT_DISABLED;
+ } else {
+ dev_info(wdt->miscdev.parent, "No Magic Close "
+ "received, watchdog not disabled!\n");
+ }
+ } else if (nowayout) {
+ dev_info(wdt->miscdev.parent, "No way out is "
+ "enabled, watchdog not disabled!\n");
+ }
+ clear_bit(1, &wdt->users);
+ 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:
+ if (get_user(option, (int __user *)arg))
+ return -EFAULT;
+ spin_lock(&lock);
+ if (option & WDIOS_DISABLECARD && !nowayout) {
+ 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;
+ }
+ return -ENOTTY;
+}
+
+static ssize_t tegra_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ struct tegra_wdt *wdt = file->private_data;
+
+ /* check if way-out char was written as last data */
+ char c;
+ if (len) {
+ tegra_wdt_ping(wdt);
+
+ if (get_user(c, data + len - 1))
+ return -EFAULT;
+ else
+ wdt->way_out_ok = (('V' == c) ? 1 : 0);
+ }
+
+ return len;
+}
+
+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;
+#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ int is_heartbeat_wdt = 1;
+#else
+ int is_heartbeat_wdt = !(pdev->id);
+#endif
+#endif
+
+ if (pdev->id < -1 && pdev->id > 3) {
+ dev_err(&pdev->dev, "only IDs 3:0 supported\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->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;
+ }
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ /* Watchdog of Tegra 3 are not at the reset controller regs */
+ if (readl(wdt->wdt_source) & BIT(12))
+ dev_info(&pdev->dev, "last reset due to watchdog timeout\n");
+#endif
+
+ tegra_wdt_disable(wdt);
+ writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR);
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+ writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD);
+#endif
+
+ 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
+ wdt->irq = -1;
+ 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);
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ tasklet_init(&wdt->tasklet, tegra_wdt_do_tasklet, (unsigned long)wdt);
+#endif
+
+#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT
+ /* Enable first watchdog during probe */
+ if (is_heartbeat_wdt) {
+ wdt->status = WDT_ENABLED | WDT_KERNEL_HEARTBEAT;
+ wdt->timeout = heartbeat;
+ set_bit(1, &wdt->users);
+ tegra_wdt_enable(wdt);
+ pr_info("WDT kernel heartbeat enabled on probe\n");
+ }
+#endif
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+ tegra_wdt[pdev->id] = wdt;
+#endif
+
+ dev_info(&pdev->dev, "%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);
+
+ return 0;
+}
+#endif
+
+static struct platform_driver tegra_wdt_driver = {
+ .probe = tegra_wdt_probe,
+ .remove = __devexit_p(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_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS("platform:tegra_wdt");