aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog')
-rw-r--r--drivers/watchdog/at91sam9_wdt.h37
-rw-r--r--drivers/watchdog/dw_wdt.c376
-rw-r--r--drivers/watchdog/of_xilinx_wdt.c433
-rw-r--r--drivers/watchdog/watchdog_core.c111
-rw-r--r--drivers/watchdog/watchdog_dev.c395
-rw-r--r--drivers/watchdog/watchdog_dev.h33
6 files changed, 1385 insertions, 0 deletions
diff --git a/drivers/watchdog/at91sam9_wdt.h b/drivers/watchdog/at91sam9_wdt.h
new file mode 100644
index 0000000..757f9ca
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.h
@@ -0,0 +1,37 @@
+/*
+ * drivers/watchdog/at91sam9_wdt.h
+ *
+ * Copyright (C) 2007 Andrew Victor
+ * Copyright (C) 2007 Atmel Corporation.
+ *
+ * Watchdog Timer (WDT) - System peripherals regsters.
+ * Based on AT91SAM9261 datasheet revision D.
+ *
+ * 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.
+ */
+
+#ifndef AT91_WDT_H
+#define AT91_WDT_H
+
+#define AT91_WDT_CR (AT91_WDT + 0x00) /* Watchdog Control Register */
+#define AT91_WDT_WDRSTT (1 << 0) /* Restart */
+#define AT91_WDT_KEY (0xa5 << 24) /* KEY Password */
+
+#define AT91_WDT_MR (AT91_WDT + 0x04) /* Watchdog Mode Register */
+#define AT91_WDT_WDV (0xfff << 0) /* Counter Value */
+#define AT91_WDT_WDFIEN (1 << 12) /* Fault Interrupt Enable */
+#define AT91_WDT_WDRSTEN (1 << 13) /* Reset Processor */
+#define AT91_WDT_WDRPROC (1 << 14) /* Timer Restart */
+#define AT91_WDT_WDDIS (1 << 15) /* Watchdog Disable */
+#define AT91_WDT_WDD (0xfff << 16) /* Delta Value */
+#define AT91_WDT_WDDBGHLT (1 << 28) /* Debug Halt */
+#define AT91_WDT_WDIDLEHLT (1 << 29) /* Idle Halt */
+
+#define AT91_WDT_SR (AT91_WDT + 0x08) /* Watchdog Status Register */
+#define AT91_WDT_WDUNF (1 << 0) /* Watchdog Underflow */
+#define AT91_WDT_WDERR (1 << 1) /* Watchdog Error */
+
+#endif
diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
new file mode 100644
index 0000000..f10f8c0
--- /dev/null
+++ b/drivers/watchdog/dw_wdt.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2010-2011 Picochip Ltd., Jamie Iles
+ * http://www.picochip.com
+ *
+ * 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 file implements a driver for the Synopsys DesignWare watchdog device
+ * in the many ARM subsystems. The watchdog has 16 different timeout periods
+ * and these are a function of the input clock frequency.
+ *
+ * The DesignWare watchdog cannot be stopped once it has been started so we
+ * use a software timer to implement a ping that will keep the watchdog alive.
+ * If we receive an expected close for the watchdog then we keep the timer
+ * running, otherwise the timer is stopped and the watchdog will expire.
+ */
+#define pr_fmt(fmt) "dw_wdt: " fmt
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+#define WDOG_CONTROL_REG_OFFSET 0x00
+#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
+#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04
+#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
+#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
+#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
+
+/* The maximum TOP (timeout period) value that can be set in the watchdog. */
+#define DW_WDT_MAX_TOP 15
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+ "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+#define WDT_TIMEOUT (HZ / 2)
+
+static struct {
+ spinlock_t lock;
+ void __iomem *regs;
+ struct clk *clk;
+ unsigned long in_use;
+ unsigned long next_heartbeat;
+ struct timer_list timer;
+ int expect_close;
+} dw_wdt;
+
+static inline int dw_wdt_is_enabled(void)
+{
+ return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) &
+ WDOG_CONTROL_REG_WDT_EN_MASK;
+}
+
+static inline int dw_wdt_top_in_seconds(unsigned top)
+{
+ /*
+ * There are 16 possible timeout values in 0..15 where the number of
+ * cycles is 2 ^ (16 + i) and the watchdog counts down.
+ */
+ return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk);
+}
+
+static int dw_wdt_get_top(void)
+{
+ int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
+
+ return dw_wdt_top_in_seconds(top);
+}
+
+static inline void dw_wdt_set_next_heartbeat(void)
+{
+ dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ;
+}
+
+static int dw_wdt_set_top(unsigned top_s)
+{
+ int i, top_val = DW_WDT_MAX_TOP;
+
+ /*
+ * Iterate over the timeout values until we find the closest match. We
+ * always look for >=.
+ */
+ for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
+ if (dw_wdt_top_in_seconds(i) >= top_s) {
+ top_val = i;
+ break;
+ }
+
+ /* Set the new value in the watchdog. */
+ writel(top_val, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+
+ dw_wdt_set_next_heartbeat();
+
+ return dw_wdt_top_in_seconds(top_val);
+}
+
+static void dw_wdt_keepalive(void)
+{
+ writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
+ WDOG_COUNTER_RESTART_REG_OFFSET);
+}
+
+static void dw_wdt_ping(unsigned long data)
+{
+ if (time_before(jiffies, dw_wdt.next_heartbeat) ||
+ (!nowayout && !dw_wdt.in_use)) {
+ dw_wdt_keepalive();
+ mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
+ } else
+ pr_crit("keepalive missed, machine will reset\n");
+}
+
+static int dw_wdt_open(struct inode *inode, struct file *filp)
+{
+ if (test_and_set_bit(0, &dw_wdt.in_use))
+ return -EBUSY;
+
+ /* Make sure we don't get unloaded. */
+ __module_get(THIS_MODULE);
+
+ spin_lock(&dw_wdt.lock);
+ if (!dw_wdt_is_enabled()) {
+ /*
+ * The watchdog is not currently enabled. Set the timeout to
+ * the maximum and then start it.
+ */
+ dw_wdt_set_top(DW_WDT_MAX_TOP);
+ writel(WDOG_CONTROL_REG_WDT_EN_MASK,
+ dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
+ }
+
+ dw_wdt_set_next_heartbeat();
+
+ spin_unlock(&dw_wdt.lock);
+
+ return nonseekable_open(inode, filp);
+}
+
+ssize_t dw_wdt_write(struct file *filp, const char __user *buf, size_t len,
+ loff_t *offset)
+{
+ if (!len)
+ return 0;
+
+ if (!nowayout) {
+ size_t i;
+
+ dw_wdt.expect_close = 0;
+
+ for (i = 0; i < len; ++i) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+
+ if (c == 'V') {
+ dw_wdt.expect_close = 1;
+ break;
+ }
+ }
+ }
+
+ dw_wdt_set_next_heartbeat();
+ mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
+
+ return len;
+}
+
+static u32 dw_wdt_time_left(void)
+{
+ return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
+ clk_get_rate(dw_wdt.clk);
+}
+
+static const struct watchdog_info dw_wdt_ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .identity = "Synopsys DesignWare Watchdog",
+};
+
+static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ unsigned long val;
+ int timeout;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user((struct watchdog_info *)arg, &dw_wdt_ident,
+ sizeof(dw_wdt_ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, (int *)arg);
+
+ case WDIOC_KEEPALIVE:
+ dw_wdt_set_next_heartbeat();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ timeout = dw_wdt_set_top(val);
+ return put_user(timeout , (int __user *)arg);
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(dw_wdt_get_top(), (int __user *)arg);
+
+ case WDIOC_GETTIMELEFT:
+ /* Get the time left until expiry. */
+ if (get_user(val, (int __user *)arg))
+ return -EFAULT;
+ return put_user(dw_wdt_time_left(), (int __user *)arg);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int dw_wdt_release(struct inode *inode, struct file *filp)
+{
+ clear_bit(0, &dw_wdt.in_use);
+
+ if (!dw_wdt.expect_close) {
+ del_timer(&dw_wdt.timer);
+
+ if (!nowayout)
+ pr_crit("unexpected close, system will reboot soon\n");
+ else
+ pr_crit("watchdog cannot be disabled, system will reboot soon\n");
+ }
+
+ dw_wdt.expect_close = 0;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int dw_wdt_suspend(struct device *dev)
+{
+ clk_disable(dw_wdt.clk);
+
+ return 0;
+}
+
+static int dw_wdt_resume(struct device *dev)
+{
+ int err = clk_enable(dw_wdt.clk);
+
+ if (err)
+ return err;
+
+ dw_wdt_keepalive();
+
+ return 0;
+}
+
+static const struct dev_pm_ops dw_wdt_pm_ops = {
+ .suspend = dw_wdt_suspend,
+ .resume = dw_wdt_resume,
+};
+#endif /* CONFIG_PM */
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = dw_wdt_open,
+ .write = dw_wdt_write,
+ .unlocked_ioctl = dw_wdt_ioctl,
+ .release = dw_wdt_release
+};
+
+static struct miscdevice dw_wdt_miscdev = {
+ .fops = &wdt_fops,
+ .name = "watchdog",
+ .minor = WATCHDOG_MINOR,
+};
+
+static int __devinit dw_wdt_drv_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!mem)
+ return -EINVAL;
+
+ if (!devm_request_mem_region(&pdev->dev, mem->start, resource_size(mem),
+ "dw_wdt"))
+ return -ENOMEM;
+
+ dw_wdt.regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
+ if (!dw_wdt.regs)
+ return -ENOMEM;
+
+ dw_wdt.clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dw_wdt.clk))
+ return PTR_ERR(dw_wdt.clk);
+
+ ret = clk_enable(dw_wdt.clk);
+ if (ret)
+ goto out_put_clk;
+
+ spin_lock_init(&dw_wdt.lock);
+
+ ret = misc_register(&dw_wdt_miscdev);
+ if (ret)
+ goto out_disable_clk;
+
+ dw_wdt_set_next_heartbeat();
+ setup_timer(&dw_wdt.timer, dw_wdt_ping, 0);
+ mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
+
+ return 0;
+
+out_disable_clk:
+ clk_disable(dw_wdt.clk);
+out_put_clk:
+ clk_put(dw_wdt.clk);
+
+ return ret;
+}
+
+static int __devexit dw_wdt_drv_remove(struct platform_device *pdev)
+{
+ misc_deregister(&dw_wdt_miscdev);
+
+ clk_disable(dw_wdt.clk);
+ clk_put(dw_wdt.clk);
+
+ return 0;
+}
+
+static struct platform_driver dw_wdt_driver = {
+ .probe = dw_wdt_drv_probe,
+ .remove = __devexit_p(dw_wdt_drv_remove),
+ .driver = {
+ .name = "dw_wdt",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &dw_wdt_pm_ops,
+#endif /* CONFIG_PM */
+ },
+};
+
+static int __init dw_wdt_watchdog_init(void)
+{
+ return platform_driver_register(&dw_wdt_driver);
+}
+module_init(dw_wdt_watchdog_init);
+
+static void __exit dw_wdt_watchdog_exit(void)
+{
+ platform_driver_unregister(&dw_wdt_driver);
+}
+module_exit(dw_wdt_watchdog_exit);
+
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/of_xilinx_wdt.c b/drivers/watchdog/of_xilinx_wdt.c
new file mode 100644
index 0000000..4ec741a
--- /dev/null
+++ b/drivers/watchdog/of_xilinx_wdt.c
@@ -0,0 +1,433 @@
+/*
+* of_xilinx_wdt.c 1.01 A Watchdog Device Driver for Xilinx xps_timebase_wdt
+*
+* (C) Copyright 2011 (Alejandro Cabrera <aldaya@gmail.com>)
+*
+* -----------------------
+*
+* 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.
+*
+* -----------------------
+* 30-May-2011 Alejandro Cabrera <aldaya@gmail.com>
+* - If "xlnx,wdt-enable-once" wasn't found on device tree the
+* module will use CONFIG_WATCHDOG_NOWAYOUT
+* - If the device tree parameters ("clock-frequency" and
+* "xlnx,wdt-interval") wasn't found the driver won't
+* know the wdt reset interval
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+/* Register offsets for the Wdt device */
+#define XWT_TWCSR0_OFFSET 0x0 /* Control/Status Register0 */
+#define XWT_TWCSR1_OFFSET 0x4 /* Control/Status Register1 */
+#define XWT_TBR_OFFSET 0x8 /* Timebase Register Offset */
+
+/* Control/Status Register Masks */
+#define XWT_CSR0_WRS_MASK 0x00000008 /* Reset status */
+#define XWT_CSR0_WDS_MASK 0x00000004 /* Timer state */
+#define XWT_CSR0_EWDT1_MASK 0x00000002 /* Enable bit 1 */
+
+/* Control/Status Register 0/1 bits */
+#define XWT_CSRX_EWDT2_MASK 0x00000001 /* Enable bit 2 */
+
+/* SelfTest constants */
+#define XWT_MAX_SELFTEST_LOOP_COUNT 0x00010000
+#define XWT_TIMER_FAILED 0xFFFFFFFF
+
+#define WATCHDOG_NAME "Xilinx Watchdog"
+#define PFX WATCHDOG_NAME ": "
+
+struct xwdt_device {
+ struct resource res;
+ void __iomem *base;
+ u32 nowayout;
+ u32 wdt_interval;
+ u32 boot_status;
+};
+
+static struct xwdt_device xdev;
+
+static u32 timeout;
+static u32 control_status_reg;
+static u8 expect_close;
+static u8 no_timeout;
+static unsigned long driver_open;
+
+static DEFINE_SPINLOCK(spinlock);
+
+static void xwdt_start(void)
+{
+ spin_lock(&spinlock);
+
+ /* Clean previous status and enable the watchdog timer */
+ control_status_reg = ioread32(xdev.base + XWT_TWCSR0_OFFSET);
+ control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK);
+
+ iowrite32((control_status_reg | XWT_CSR0_EWDT1_MASK),
+ xdev.base + XWT_TWCSR0_OFFSET);
+
+ iowrite32(XWT_CSRX_EWDT2_MASK, xdev.base + XWT_TWCSR1_OFFSET);
+
+ spin_unlock(&spinlock);
+}
+
+static void xwdt_stop(void)
+{
+ spin_lock(&spinlock);
+
+ control_status_reg = ioread32(xdev.base + XWT_TWCSR0_OFFSET);
+
+ iowrite32((control_status_reg & ~XWT_CSR0_EWDT1_MASK),
+ xdev.base + XWT_TWCSR0_OFFSET);
+
+ iowrite32(0, xdev.base + XWT_TWCSR1_OFFSET);
+
+ spin_unlock(&spinlock);
+ printk(KERN_INFO PFX "Stopped!\n");
+}
+
+static void xwdt_keepalive(void)
+{
+ spin_lock(&spinlock);
+
+ control_status_reg = ioread32(xdev.base + XWT_TWCSR0_OFFSET);
+ control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK);
+ iowrite32(control_status_reg, xdev.base + XWT_TWCSR0_OFFSET);
+
+ spin_unlock(&spinlock);
+}
+
+static void xwdt_get_status(int *status)
+{
+ int new_status;
+
+ spin_lock(&spinlock);
+
+ control_status_reg = ioread32(xdev.base + XWT_TWCSR0_OFFSET);
+ new_status = ((control_status_reg &
+ (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK)) != 0);
+ spin_unlock(&spinlock);
+
+ *status = 0;
+ if (new_status & 1)
+ *status |= WDIOF_CARDRESET;
+}
+
+static u32 xwdt_selftest(void)
+{
+ int i;
+ u32 timer_value1;
+ u32 timer_value2;
+
+ spin_lock(&spinlock);
+
+ timer_value1 = ioread32(xdev.base + XWT_TBR_OFFSET);
+ timer_value2 = ioread32(xdev.base + XWT_TBR_OFFSET);
+
+ for (i = 0;
+ ((i <= XWT_MAX_SELFTEST_LOOP_COUNT) &&
+ (timer_value2 == timer_value1)); i++) {
+ timer_value2 = ioread32(xdev.base + XWT_TBR_OFFSET);
+ }
+
+ spin_unlock(&spinlock);
+
+ if (timer_value2 != timer_value1)
+ return ~XWT_TIMER_FAILED;
+ else
+ return XWT_TIMER_FAILED;
+}
+
+static int xwdt_open(struct inode *inode, struct file *file)
+{
+ /* Only one process can handle the wdt at a time */
+ if (test_and_set_bit(0, &driver_open))
+ return -EBUSY;
+
+ /* Make sure that the module are always loaded...*/
+ if (xdev.nowayout)
+ __module_get(THIS_MODULE);
+
+ xwdt_start();
+ printk(KERN_INFO PFX "Started...\n");
+
+ return nonseekable_open(inode, file);
+}
+
+static int xwdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ xwdt_stop();
+ } else {
+ printk(KERN_CRIT PFX
+ "Unexpected close, not stopping watchdog!\n");
+ xwdt_keepalive();
+ }
+
+ clear_bit(0, &driver_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * xwdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we don't define content meaning.
+ */
+static ssize_t xwdt_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!xdev.nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ xwdt_keepalive();
+ }
+ return len;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+};
+
+/*
+ * xwdt_ioctl:
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+static long xwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int status;
+
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ uarg.i = (int __user *)arg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(xdev.boot_status, uarg.i);
+
+ case WDIOC_GETSTATUS:
+ xwdt_get_status(&status);
+ return put_user(status, uarg.i);
+
+ case WDIOC_KEEPALIVE:
+ xwdt_keepalive();
+ return 0;
+
+ case WDIOC_GETTIMEOUT:
+ if (no_timeout)
+ return -ENOTTY;
+ else
+ return put_user(timeout, uarg.i);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations xwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = xwdt_write,
+ .open = xwdt_open,
+ .release = xwdt_release,
+ .unlocked_ioctl = xwdt_ioctl,
+};
+
+static struct miscdevice xwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &xwdt_fops,
+};
+
+static int __devinit xwdt_probe(struct platform_device *pdev)
+{
+ int rc;
+ u32 *tmptr;
+ u32 *pfreq;
+
+ no_timeout = 0;
+
+ pfreq = (u32 *)of_get_property(pdev->dev.of_node->parent,
+ "clock-frequency", NULL);
+
+ if (pfreq == NULL) {
+ printk(KERN_WARNING PFX
+ "The watchdog clock frequency cannot be obtained!\n");
+ no_timeout = 1;
+ }
+
+ rc = of_address_to_resource(pdev->dev.of_node, 0, &xdev.res);
+ if (rc) {
+ printk(KERN_WARNING PFX "invalid address!\n");
+ return rc;
+ }
+
+ tmptr = (u32 *)of_get_property(pdev->dev.of_node,
+ "xlnx,wdt-interval", NULL);
+ if (tmptr == NULL) {
+ printk(KERN_WARNING PFX "Parameter \"xlnx,wdt-interval\""
+ " not found in device tree!\n");
+ no_timeout = 1;
+ } else {
+ xdev.wdt_interval = *tmptr;
+ }
+
+ tmptr = (u32 *)of_get_property(pdev->dev.of_node,
+ "xlnx,wdt-enable-once", NULL);
+ if (tmptr == NULL) {
+ printk(KERN_WARNING PFX "Parameter \"xlnx,wdt-enable-once\""
+ " not found in device tree!\n");
+ xdev.nowayout = WATCHDOG_NOWAYOUT;
+ }
+
+/*
+ * Twice of the 2^wdt_interval / freq because the first wdt overflow is
+ * ignored (interrupt), reset is only generated at second wdt overflow
+ */
+ if (!no_timeout)
+ timeout = 2 * ((1<<xdev.wdt_interval) / *pfreq);
+
+ if (!request_mem_region(xdev.res.start,
+ xdev.res.end - xdev.res.start + 1, WATCHDOG_NAME)) {
+ rc = -ENXIO;
+ printk(KERN_ERR PFX "memory request failure!\n");
+ goto err_out;
+ }
+
+ xdev.base = ioremap(xdev.res.start, xdev.res.end - xdev.res.start + 1);
+ if (xdev.base == NULL) {
+ rc = -ENOMEM;
+ printk(KERN_ERR PFX "ioremap failure!\n");
+ goto release_mem;
+ }
+
+ rc = xwdt_selftest();
+ if (rc == XWT_TIMER_FAILED) {
+ printk(KERN_ERR PFX "SelfTest routine error!\n");
+ goto unmap_io;
+ }
+
+ xwdt_get_status(&xdev.boot_status);
+
+ rc = misc_register(&xwdt_miscdev);
+ if (rc) {
+ printk(KERN_ERR PFX
+ "cannot register miscdev on minor=%d (err=%d)\n",
+ xwdt_miscdev.minor, rc);
+ goto unmap_io;
+ }
+
+ if (no_timeout)
+ printk(KERN_INFO PFX
+ "driver loaded (timeout=? sec, nowayout=%d)\n",
+ xdev.nowayout);
+ else
+ printk(KERN_INFO PFX
+ "driver loaded (timeout=%d sec, nowayout=%d)\n",
+ timeout, xdev.nowayout);
+
+ expect_close = 0;
+ clear_bit(0, &driver_open);
+
+ return 0;
+
+unmap_io:
+ iounmap(xdev.base);
+release_mem:
+ release_mem_region(xdev.res.start, resource_size(&xdev.res));
+err_out:
+ return rc;
+}
+
+static int __devexit xwdt_remove(struct platform_device *dev)
+{
+ misc_deregister(&xwdt_miscdev);
+ iounmap(xdev.base);
+ release_mem_region(xdev.res.start, resource_size(&xdev.res));
+
+ return 0;
+}
+
+/* Match table for of_platform binding */
+static struct of_device_id __devinitdata xwdt_of_match[] = {
+ { .compatible = "xlnx,xps-timebase-wdt-1.01.a", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xwdt_of_match);
+
+static struct platform_driver xwdt_driver = {
+ .probe = xwdt_probe,
+ .remove = __devexit_p(xwdt_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = WATCHDOG_NAME,
+ .of_match_table = xwdt_of_match,
+ },
+};
+
+static int __init xwdt_init(void)
+{
+ return platform_driver_register(&xwdt_driver);
+}
+
+static void __exit xwdt_exit(void)
+{
+ platform_driver_unregister(&xwdt_driver);
+}
+
+module_init(xwdt_init);
+module_exit(xwdt_exit);
+
+MODULE_AUTHOR("Alejandro Cabrera <aldaya@gmail.com>");
+MODULE_DESCRIPTION("Xilinx Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
new file mode 100644
index 0000000..cfa1a15
--- /dev/null
+++ b/drivers/watchdog/watchdog_core.c
@@ -0,0 +1,111 @@
+/*
+ * watchdog_core.c
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * 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.
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For EXPORT_SYMBOL/module stuff/... */
+#include <linux/types.h> /* For standard types */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/watchdog.h> /* For watchdog specific items */
+#include <linux/init.h> /* For __init/__exit/... */
+
+#include "watchdog_dev.h" /* For watchdog_dev_register/... */
+
+/**
+ * watchdog_register_device() - register a watchdog device
+ * @wdd: watchdog device
+ *
+ * Register a watchdog device with the kernel so that the
+ * watchdog timer can be accessed from userspace.
+ *
+ * A zero is returned on success and a negative errno code for
+ * failure.
+ */
+int watchdog_register_device(struct watchdog_device *wdd)
+{
+ int ret;
+
+ if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
+ return -EINVAL;
+
+ /* Mandatory operations need to be supported */
+ if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
+ return -EINVAL;
+
+ /*
+ * Check that we have valid min and max timeout values, if
+ * not reset them both to 0 (=not used or unknown)
+ */
+ if (wdd->min_timeout > wdd->max_timeout) {
+ pr_info("Invalid min and max timeout values, resetting to 0!\n");
+ wdd->min_timeout = 0;
+ wdd->max_timeout = 0;
+ }
+
+ /*
+ * Note: now that all watchdog_device data has been verified, we
+ * will not check this anymore in other functions. If data gets
+ * corrupted in a later stage then we expect a kernel panic!
+ */
+
+ /* We only support 1 watchdog device via the /dev/watchdog interface */
+ ret = watchdog_dev_register(wdd);
+ if (ret) {
+ pr_err("error registering /dev/watchdog (err=%d).\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(watchdog_register_device);
+
+/**
+ * watchdog_unregister_device() - unregister a watchdog device
+ * @wdd: watchdog device to unregister
+ *
+ * Unregister a watchdog device that was previously successfully
+ * registered with watchdog_register_device().
+ */
+void watchdog_unregister_device(struct watchdog_device *wdd)
+{
+ int ret;
+
+ if (wdd == NULL)
+ return;
+
+ ret = watchdog_dev_unregister(wdd);
+ if (ret)
+ pr_err("error unregistering /dev/watchdog (err=%d).\n", ret);
+}
+EXPORT_SYMBOL_GPL(watchdog_unregister_device);
+
+MODULE_AUTHOR("Alan Cox <alan@lxorguk.ukuu.org.uk>");
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("WatchDog Timer Driver Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
new file mode 100644
index 0000000..1199da0
--- /dev/null
+++ b/drivers/watchdog/watchdog_dev.c
@@ -0,0 +1,395 @@
+/*
+ * watchdog_dev.c
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * This part of the generic code takes care of the following
+ * misc device: /dev/watchdog.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * 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.
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For module stuff/... */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/watchdog.h> /* For watchdog specific items */
+#include <linux/miscdevice.h> /* For handling misc devices */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+
+/* make sure we only register one /dev/watchdog device */
+static unsigned long watchdog_dev_busy;
+/* the watchdog device behind /dev/watchdog */
+static struct watchdog_device *wdd;
+
+/*
+ * watchdog_ping: ping the watchdog.
+ * @wddev: the watchdog device to ping
+ *
+ * If the watchdog has no own ping operation then it needs to be
+ * restarted via the start operation. This wrapper function does
+ * exactly that.
+ * We only ping when the watchdog device is running.
+ */
+
+static int watchdog_ping(struct watchdog_device *wddev)
+{
+ if (test_bit(WDOG_ACTIVE, &wddev->status)) {
+ if (wddev->ops->ping)
+ return wddev->ops->ping(wddev); /* ping the watchdog */
+ else
+ return wddev->ops->start(wddev); /* restart watchdog */
+ }
+ return 0;
+}
+
+/*
+ * watchdog_start: wrapper to start the watchdog.
+ * @wddev: the watchdog device to start
+ *
+ * Start the watchdog if it is not active and mark it active.
+ * This function returns zero on success or a negative errno code for
+ * failure.
+ */
+
+static int watchdog_start(struct watchdog_device *wddev)
+{
+ int err;
+
+ if (!test_bit(WDOG_ACTIVE, &wddev->status)) {
+ err = wddev->ops->start(wddev);
+ if (err < 0)
+ return err;
+
+ set_bit(WDOG_ACTIVE, &wddev->status);
+ }
+ return 0;
+}
+
+/*
+ * watchdog_stop: wrapper to stop the watchdog.
+ * @wddev: the watchdog device to stop
+ *
+ * Stop the watchdog if it is still active and unmark it active.
+ * This function returns zero on success or a negative errno code for
+ * failure.
+ * If the 'nowayout' feature was set, the watchdog cannot be stopped.
+ */
+
+static int watchdog_stop(struct watchdog_device *wddev)
+{
+ int err = -EBUSY;
+
+ if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) {
+ pr_info("%s: nowayout prevents watchdog to be stopped!\n",
+ wddev->info->identity);
+ return err;
+ }
+
+ if (test_bit(WDOG_ACTIVE, &wddev->status)) {
+ err = wddev->ops->stop(wddev);
+ if (err < 0)
+ return err;
+
+ clear_bit(WDOG_ACTIVE, &wddev->status);
+ }
+ return 0;
+}
+
+/*
+ * watchdog_write: writes to the watchdog.
+ * @file: file from VFS
+ * @data: user address of data
+ * @len: length of data
+ * @ppos: pointer to the file offset
+ *
+ * A write to a watchdog device is defined as a keepalive ping.
+ * Writing the magic 'V' sequence allows the next close to turn
+ * off the watchdog (if 'nowayout' is not set).
+ */
+
+static ssize_t watchdog_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ size_t i;
+ char c;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * Note: just in case someone wrote the magic character
+ * five months ago...
+ */
+ clear_bit(WDOG_ALLOW_RELEASE, &wdd->status);
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ set_bit(WDOG_ALLOW_RELEASE, &wdd->status);
+ }
+
+ /* someone wrote to us, so we send the watchdog a keepalive ping */
+ watchdog_ping(wdd);
+
+ return len;
+}
+
+/*
+ * watchdog_ioctl: handle the different ioctl's for the watchdog device.
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static long watchdog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ unsigned int val;
+ int err;
+
+ if (wdd->ops->ioctl) {
+ err = wdd->ops->ioctl(wdd, cmd, arg);
+ if (err != -ENOIOCTLCMD)
+ return err;
+ }
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, wdd->info,
+ sizeof(struct watchdog_info)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ val = wdd->ops->status ? wdd->ops->status(wdd) : 0;
+ return put_user(val, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(wdd->bootstatus, p);
+ case WDIOC_SETOPTIONS:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val & WDIOS_DISABLECARD) {
+ err = watchdog_stop(wdd);
+ if (err < 0)
+ return err;
+ }
+ if (val & WDIOS_ENABLECARD) {
+ err = watchdog_start(wdd);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+ case WDIOC_KEEPALIVE:
+ if (!(wdd->info->options & WDIOF_KEEPALIVEPING))
+ return -EOPNOTSUPP;
+ watchdog_ping(wdd);
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if ((wdd->ops->set_timeout == NULL) ||
+ !(wdd->info->options & WDIOF_SETTIMEOUT))
+ return -EOPNOTSUPP;
+ if (get_user(val, p))
+ return -EFAULT;
+ if ((wdd->max_timeout != 0) &&
+ (val < wdd->min_timeout || val > wdd->max_timeout))
+ return -EINVAL;
+ err = wdd->ops->set_timeout(wdd, val);
+ if (err < 0)
+ return err;
+ wdd->timeout = val;
+ /* If the watchdog is active then we send a keepalive ping
+ * to make sure that the watchdog keep's running (and if
+ * possible that it takes the new timeout) */
+ watchdog_ping(wdd);
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ /* timeout == 0 means that we don't know the timeout */
+ if (wdd->timeout == 0)
+ return -EOPNOTSUPP;
+ return put_user(wdd->timeout, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * watchdog_open: open the /dev/watchdog device.
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * When the /dev/watchdog device gets opened, we start the watchdog.
+ * Watch out: the /dev/watchdog device is single open, so we make sure
+ * it can only be opened once.
+ */
+
+static int watchdog_open(struct inode *inode, struct file *file)
+{
+ int err = -EBUSY;
+
+ /* the watchdog is single open! */
+ if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status))
+ return -EBUSY;
+
+ /*
+ * If the /dev/watchdog device is open, we don't want the module
+ * to be unloaded.
+ */
+ if (!try_module_get(wdd->ops->owner))
+ goto out;
+
+ err = watchdog_start(wdd);
+ if (err < 0)
+ goto out_mod;
+
+ /* dev/watchdog is a virtual (and thus non-seekable) filesystem */
+ return nonseekable_open(inode, file);
+
+out_mod:
+ module_put(wdd->ops->owner);
+out:
+ clear_bit(WDOG_DEV_OPEN, &wdd->status);
+ return err;
+}
+
+/*
+ * watchdog_release: release the /dev/watchdog device.
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * This is the code for when /dev/watchdog gets closed. We will only
+ * stop the watchdog when we have received the magic char (and nowayout
+ * was not set), else the watchdog will keep running.
+ */
+
+static int watchdog_release(struct inode *inode, struct file *file)
+{
+ int err = -EBUSY;
+
+ /*
+ * We only stop the watchdog if we received the magic character
+ * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then
+ * watchdog_stop will fail.
+ */
+ if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) ||
+ !(wdd->info->options & WDIOF_MAGICCLOSE))
+ err = watchdog_stop(wdd);
+
+ /* If the watchdog was not stopped, send a keepalive ping */
+ if (err < 0) {
+ pr_crit("%s: watchdog did not stop!\n", wdd->info->identity);
+ watchdog_ping(wdd);
+ }
+
+ /* Allow the owner module to be unloaded again */
+ module_put(wdd->ops->owner);
+
+ /* make sure that /dev/watchdog can be re-opened */
+ clear_bit(WDOG_DEV_OPEN, &wdd->status);
+
+ return 0;
+}
+
+static const struct file_operations watchdog_fops = {
+ .owner = THIS_MODULE,
+ .write = watchdog_write,
+ .unlocked_ioctl = watchdog_ioctl,
+ .open = watchdog_open,
+ .release = watchdog_release,
+};
+
+static struct miscdevice watchdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &watchdog_fops,
+};
+
+/*
+ * watchdog_dev_register:
+ * @watchdog: watchdog device
+ *
+ * Register a watchdog device as /dev/watchdog. /dev/watchdog
+ * is actually a miscdevice and thus we set it up like that.
+ */
+
+int watchdog_dev_register(struct watchdog_device *watchdog)
+{
+ int err;
+
+ /* Only one device can register for /dev/watchdog */
+ if (test_and_set_bit(0, &watchdog_dev_busy)) {
+ pr_err("only one watchdog can use /dev/watchdog.\n");
+ return -EBUSY;
+ }
+
+ wdd = watchdog;
+
+ err = misc_register(&watchdog_miscdev);
+ if (err != 0) {
+ pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
+ watchdog->info->identity, WATCHDOG_MINOR, err);
+ goto out;
+ }
+
+ return 0;
+
+out:
+ wdd = NULL;
+ clear_bit(0, &watchdog_dev_busy);
+ return err;
+}
+
+/*
+ * watchdog_dev_unregister:
+ * @watchdog: watchdog device
+ *
+ * Deregister the /dev/watchdog device.
+ */
+
+int watchdog_dev_unregister(struct watchdog_device *watchdog)
+{
+ /* Check that a watchdog device was registered in the past */
+ if (!test_bit(0, &watchdog_dev_busy) || !wdd)
+ return -ENODEV;
+
+ /* We can only unregister the watchdog device that was registered */
+ if (watchdog != wdd) {
+ pr_err("%s: watchdog was not registered as /dev/watchdog.\n",
+ watchdog->info->identity);
+ return -ENODEV;
+ }
+
+ misc_deregister(&watchdog_miscdev);
+ wdd = NULL;
+ clear_bit(0, &watchdog_dev_busy);
+ return 0;
+}
diff --git a/drivers/watchdog/watchdog_dev.h b/drivers/watchdog/watchdog_dev.h
new file mode 100644
index 0000000..bc7612b
--- /dev/null
+++ b/drivers/watchdog/watchdog_dev.h
@@ -0,0 +1,33 @@
+/*
+ * watchdog_core.h
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * 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.
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+/*
+ * Functions/procedures to be called by the core
+ */
+int watchdog_dev_register(struct watchdog_device *);
+int watchdog_dev_unregister(struct watchdog_device *);