nfsd4: shutdown callbacks on expiry
[safe/jmp/linux-2.6] / drivers / watchdog / at32ap700x_wdt.c
index 54a5161..6873376 100644 (file)
@@ -6,6 +6,19 @@
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
+ *
+ *
+ * Errata: WDT Clear is blocked after WDT Reset
+ *
+ * A watchdog timer event will, after reset, block writes to the WDT_CLEAR
+ * register, preventing the program to clear the next Watchdog Timer Reset.
+ *
+ * If you still want to use the WDT after a WDT reset a small code can be
+ * insterted at the startup checking the AVR32_PM.rcause register for WDT reset
+ * and use a GPIO pin to reset the system. This method requires that one of the
+ * GPIO pins are available and connected externally to the RESET_N pin. After
+ * the GPIO pin has pulled down the reset line the GPIO will be reset and leave
+ * the pin tristated with pullup.
  */
 
 #include <linux/init.h>
@@ -44,6 +57,13 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
 
 #define WDT_CLR                        0x04
 
+#define WDT_RCAUSE             0x10
+#define WDT_RCAUSE_POR            0
+#define WDT_RCAUSE_EXT            2
+#define WDT_RCAUSE_WDT            3
+#define WDT_RCAUSE_JTAG                   4
+#define WDT_RCAUSE_SERP                   5
+
 #define WDT_BIT(name)          (1 << WDT_##name)
 #define WDT_BF(name, value)    ((value) << WDT_##name)
 
@@ -56,6 +76,7 @@ struct wdt_at32ap700x {
        void __iomem            *regs;
        spinlock_t              io_lock;
        int                     timeout;
+       int                     boot_status;
        unsigned long           users;
        struct miscdevice       miscdev;
 };
@@ -126,7 +147,7 @@ static int at32_wdt_close(struct inode *inode, struct file *file)
                at32_wdt_stop();
        } else {
                dev_dbg(wdt->miscdev.parent,
-                       "Unexpected close, not stopping watchdog!\n");
+                       "unexpected close, not stopping watchdog!\n");
                at32_wdt_pat();
        }
        clear_bit(1, &wdt->users);
@@ -154,7 +175,34 @@ static int at32_wdt_settimeout(int time)
        return 0;
 }
 
-static struct watchdog_info at32_wdt_info = {
+/*
+ * Get the watchdog status.
+ */
+static int at32_wdt_get_status(void)
+{
+       int rcause;
+       int status = 0;
+
+       rcause = wdt_readl(wdt, RCAUSE);
+
+       switch (rcause) {
+       case WDT_BIT(RCAUSE_EXT):
+               status = WDIOF_EXTERN1;
+               break;
+       case WDT_BIT(RCAUSE_WDT):
+               status = WDIOF_CARDRESET;
+               break;
+       case WDT_BIT(RCAUSE_POR):  /* fall through */
+       case WDT_BIT(RCAUSE_JTAG): /* fall through */
+       case WDT_BIT(RCAUSE_SERP): /* fall through */
+       default:
+               break;
+       }
+
+       return status;
+}
+
+static const struct watchdog_info at32_wdt_info = {
        .identity       = "at32ap700x watchdog",
        .options        = WDIOF_SETTIMEOUT |
                          WDIOF_KEEPALIVEPING |
@@ -164,8 +212,8 @@ static struct watchdog_info at32_wdt_info = {
 /*
  * Handle commands from user-space.
  */
-static int at32_wdt_ioctl(struct inode *inode, struct file *file,
-               unsigned int cmd, unsigned long arg)
+static long at32_wdt_ioctl(struct file *file,
+                               unsigned int cmd, unsigned long arg)
 {
        int ret = -ENOTTY;
        int time;
@@ -173,30 +221,15 @@ static int at32_wdt_ioctl(struct inode *inode, struct file *file,
        int __user *p = argp;
 
        switch (cmd) {
-       case WDIOC_KEEPALIVE:
-               at32_wdt_pat();
-               ret = 0;
-               break;
        case WDIOC_GETSUPPORT:
                ret = copy_to_user(argp, &at32_wdt_info,
                                sizeof(at32_wdt_info)) ? -EFAULT : 0;
                break;
-       case WDIOC_SETTIMEOUT:
-               ret = get_user(time, p);
-               if (ret)
-                       break;
-               ret = at32_wdt_settimeout(time);
-               if (ret)
-                       break;
-               /* Enable new time value */
-               at32_wdt_start();
-               /* fall through */
-       case WDIOC_GETTIMEOUT:
-               ret = put_user(wdt->timeout, p);
+       case WDIOC_GETSTATUS:
+               ret = put_user(0, p);
                break;
-       case WDIOC_GETSTATUS: /* fall through */
        case WDIOC_GETBOOTSTATUS:
-               ret = put_user(0, p);
+               ret = put_user(wdt->boot_status, p);
                break;
        case WDIOC_SETOPTIONS:
                ret = get_user(time, p);
@@ -208,6 +241,23 @@ static int at32_wdt_ioctl(struct inode *inode, struct file *file,
                        at32_wdt_start();
                ret = 0;
                break;
+       case WDIOC_KEEPALIVE:
+               at32_wdt_pat();
+               ret = 0;
+               break;
+       case WDIOC_SETTIMEOUT:
+               ret = get_user(time, p);
+               if (ret)
+                       break;
+               ret = at32_wdt_settimeout(time);
+               if (ret)
+                       break;
+               /* Enable new time value */
+               at32_wdt_start();
+               /* fall through */
+       case WDIOC_GETTIMEOUT:
+               ret = put_user(wdt->timeout, p);
+               break;
        }
 
        return ret;
@@ -233,7 +283,7 @@ static ssize_t at32_wdt_write(struct file *file, const char __user *data,
                         */
                        for (i = 0; i != len; i++) {
                                char c;
-                               if (get_user(c, data+i))
+                               if (get_user(c, data + i))
                                        return -EFAULT;
                                if (c == 'V')
                                        expect_release = 42;
@@ -248,7 +298,7 @@ static ssize_t at32_wdt_write(struct file *file, const char __user *data,
 static const struct file_operations at32_wdt_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
-       .ioctl          = at32_wdt_ioctl,
+       .unlocked_ioctl = at32_wdt_ioctl,
        .open           = at32_wdt_open,
        .release        = at32_wdt_close,
        .write          = at32_wdt_write,
@@ -276,14 +326,25 @@ static int __init at32_wdt_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
-       wdt->regs = ioremap(regs->start, regs->end - regs->start + 1);
+       wdt->regs = ioremap(regs->start, resource_size(regs));
        if (!wdt->regs) {
                ret = -ENOMEM;
                dev_dbg(&pdev->dev, "could not map I/O memory\n");
                goto err_free;
        }
+
        spin_lock_init(&wdt->io_lock);
-       wdt->users = 0;
+       wdt->boot_status = at32_wdt_get_status();
+
+       /* Work-around for watchdog silicon errata. */
+       if (wdt->boot_status & WDIOF_CARDRESET) {
+               dev_info(&pdev->dev, "CPU must be reset with external "
+                               "reset or POR due to silicon errata.\n");
+               ret = -EIO;
+               goto err_iounmap;
+       } else {
+               wdt->users = 0;
+       }
        wdt->miscdev.minor = WATCHDOG_MINOR;
        wdt->miscdev.name = "watchdog";
        wdt->miscdev.fops = &at32_wdt_fops;
@@ -330,7 +391,6 @@ static int __exit at32_wdt_remove(struct platform_device *pdev)
                wdt = NULL;
                platform_set_drvdata(pdev, NULL);
        }
-
        return 0;
 }
 
@@ -357,6 +417,9 @@ static int at32_wdt_resume(struct platform_device *pdev)
 #define at32_wdt_resume NULL
 #endif
 
+/* work with hotplug and coldplug */
+MODULE_ALIAS("platform:at32_wdt");
+
 static struct platform_driver at32_wdt_driver = {
        .remove         = __exit_p(at32_wdt_remove),
        .suspend        = at32_wdt_suspend,