[rdc] resync 2.6.32 support with changes in 2.6.30
[openwrt/svn-archive/archive.git] / target / linux / rdc / patches-2.6.32 / 110-rdc321x_watchdog_fix.patch
diff --git a/target/linux/rdc/patches-2.6.32/110-rdc321x_watchdog_fix.patch b/target/linux/rdc/patches-2.6.32/110-rdc321x_watchdog_fix.patch
new file mode 100644 (file)
index 0000000..ce4fd1f
--- /dev/null
@@ -0,0 +1,315 @@
+Index: linux-2.6.32.10/drivers/watchdog/rdc321x_wdt.c
+===================================================================
+--- linux-2.6.32.10.orig/drivers/watchdog/rdc321x_wdt.c        2010-04-28 11:39:28.000000000 +0200
++++ linux-2.6.32.10/drivers/watchdog/rdc321x_wdt.c     2010-04-28 11:40:11.000000000 +0200
+@@ -36,111 +36,99 @@
+ #include <linux/watchdog.h>
+ #include <linux/io.h>
+ #include <linux/uaccess.h>
++#include <linux/pci.h>
++#include <linux/delay.h>
+ #include <linux/mfd/rdc321x.h>
+-#define RDC_WDT_MASK  0x80000000 /* Mask */
++#define RDC321X_WDT_REG 0x00000044
++
+ #define RDC_WDT_EN    0x00800000 /* Enable bit */
+-#define RDC_WDT_WTI   0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
+-#define RDC_WDT_RST   0x00100000 /* Reset bit */
+-#define RDC_WDT_WIF   0x00040000 /* WDT IRQ Flag */
+-#define RDC_WDT_IRT   0x00000100 /* IRQ Routing table */
+-#define RDC_WDT_CNT   0x00000001 /* WDT count */
++#define RDC_WDT_WDTIRQ        0x00400000 /* Create WDT IRQ before CPU reset */
++#define RDC_WDT_NMIIRQ        0x00200000 /* Create NMI IRQ before CPU reset */
++#define RDC_WDT_RST   0x00100000 /* Reset wdt */
++#define RDC_WDT_NIF   0x00080000 /* NMI interrupt occured */
++#define RDC_WDT_WIF   0x00040000 /* WDT interrupt occured */
++#define RDC_WDT_IRT   0x00000700 /* IRQ Routing table */
++#define RDC_WDT_CNT   0x0000007F /* WDT count */
+-#define RDC_CLS_TMR   0x80003844 /* Clear timer */
++/* default counter value (2.34 s) */
++#define RDC_WDT_DFLT_CNT 0x00000040
+-#define RDC_WDT_INTERVAL      (HZ/10+1)
++#define RDC_WDT_SETUP (RDC_WDT_EN | RDC_WDT_NMIIRQ | RDC_WDT_RST | RDC_WDT_DFLT_CNT)
+ static int ticks = 1000;
+ /* some device data */
+ static struct {
+-      struct completion stop;
+-      int running;
+       struct timer_list timer;
+-      int queue;
+-      int default_ticks;
+-      unsigned long inuse;
+-      spinlock_t lock;
++      int seconds_left;
++      int total_seconds;
++      bool inuse;
++      bool running;
++      bool close_expected;
++
+       struct pci_dev *sb_pdev;
+       int base_reg;
+ } rdc321x_wdt_device;
+-/* generic helper functions */
++static struct watchdog_info ident = {
++      .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
++      .identity = "RDC321x WDT",
++};
++
+-static void rdc321x_wdt_trigger(unsigned long unused)
++/* generic helper functions */
++static void rdc321x_wdt_timer(unsigned long unused)
+ {
+-      unsigned long flags;
+-      u32 val;
++        if (!rdc321x_wdt_device.running) {
++                pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
++                                              rdc321x_wdt_device.base_reg, 0);
++                return;
++        }
+-      if (rdc321x_wdt_device.running)
+-              ticks--;
++        rdc321x_wdt_device.seconds_left--;
+-      /* keep watchdog alive */
+-      spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+-      pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
+-                                      rdc321x_wdt_device.base_reg, &val);
+-      val |= RDC_WDT_EN;
+-      pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+-                                      rdc321x_wdt_device.base_reg, val);
+-      spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
++        if (rdc321x_wdt_device.seconds_left < 1)
++                return;
+-      /* requeue?? */
+-      if (rdc321x_wdt_device.queue && ticks)
+-              mod_timer(&rdc321x_wdt_device.timer,
+-                              jiffies + RDC_WDT_INTERVAL);
+-      else {
+-              /* ticks doesn't matter anyway */
+-              complete(&rdc321x_wdt_device.stop);
+-      }
++      pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
++                              rdc321x_wdt_device.base_reg, RDC_WDT_SETUP);
++        mod_timer(&rdc321x_wdt_device.timer, HZ * 2 + jiffies);
+ }
+ static void rdc321x_wdt_reset(void)
+ {
+-      ticks = rdc321x_wdt_device.default_ticks;
++      rdc321x_wdt_device.seconds_left = rdc321x_wdt_device.total_seconds;
+ }
+ static void rdc321x_wdt_start(void)
+ {
+-      unsigned long flags;
+-
+-      if (!rdc321x_wdt_device.queue) {
+-              rdc321x_wdt_device.queue = 1;
+-
+-              /* Clear the timer */
+-              spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+-              pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+-                              rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
+-
+-              /* Enable watchdog and set the timeout to 81.92 us */
+-              pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+-                                      rdc321x_wdt_device.base_reg,
+-                                      RDC_WDT_EN | RDC_WDT_CNT);
+-              spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
++      if (rdc321x_wdt_device.running)
++              return;
+-              mod_timer(&rdc321x_wdt_device.timer,
+-                              jiffies + RDC_WDT_INTERVAL);
+-      }
++      rdc321x_wdt_device.seconds_left = rdc321x_wdt_device.total_seconds;
++      rdc321x_wdt_device.running = true;
++      rdc321x_wdt_timer(0);
+-      /* if process dies, counter is not decremented */
+-      rdc321x_wdt_device.running++;
++      return;
+ }
+ static int rdc321x_wdt_stop(void)
+ {
+-      if (rdc321x_wdt_device.running)
+-              rdc321x_wdt_device.running = 0;
++      if (WATCHDOG_NOWAYOUT)
++              return -ENOSYS;
+-      ticks = rdc321x_wdt_device.default_ticks;
++      rdc321x_wdt_device.running = false;
+-      return -EIO;
++      return 0;
+ }
+ /* filesystem operations */
+ static int rdc321x_wdt_open(struct inode *inode, struct file *file)
+ {
+-      if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
++      if (xchg(&rdc321x_wdt_device.inuse, true))
+               return -EBUSY;
+       return nonseekable_open(inode, file);
+@@ -148,7 +136,16 @@
+ static int rdc321x_wdt_release(struct inode *inode, struct file *file)
+ {
+-      clear_bit(0, &rdc321x_wdt_device.inuse);
++      int ret;
++
++      if (rdc321x_wdt_device.close_expected) {
++              ret = rdc321x_wdt_stop();
++              if (ret)
++                      return ret;
++      }
++
++      rdc321x_wdt_device.inuse = false;
++
+       return 0;
+ }
+@@ -156,30 +153,29 @@
+                               unsigned long arg)
+ {
+       void __user *argp = (void __user *)arg;
+-      u32 value;
+-      static struct watchdog_info ident = {
+-              .options = WDIOF_CARDRESET,
+-              .identity = "RDC321x WDT",
+-      };
+-      unsigned long flags;
++      int value;
+       switch (cmd) {
+       case WDIOC_KEEPALIVE:
+               rdc321x_wdt_reset();
+               break;
+-      case WDIOC_GETSTATUS:
+-              /* Read the value from the DATA register */
+-              spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+-              pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
+-                                      rdc321x_wdt_device.base_reg, &value);
+-              spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+-              if (copy_to_user(argp, &value, sizeof(u32)))
+-                      return -EFAULT;
+-              break;
+       case WDIOC_GETSUPPORT:
+               if (copy_to_user(argp, &ident, sizeof(ident)))
+                       return -EFAULT;
+               break;
++      case WDIOC_SETTIMEOUT:
++              if (copy_from_user(&rdc321x_wdt_device.total_seconds, argp, sizeof(int)))
++                      return -EFAULT;
++              rdc321x_wdt_device.seconds_left = rdc321x_wdt_device.total_seconds;
++              break;
++      case WDIOC_GETTIMEOUT:
++              if (copy_to_user(argp, &rdc321x_wdt_device.total_seconds, sizeof(int)))
++                      return -EFAULT;
++              break;
++      case WDIOC_GETTIMELEFT:
++              if (copy_to_user(argp, &rdc321x_wdt_device.seconds_left, sizeof(int)))
++                      return -EFAULT;
++              break;
+       case WDIOC_SETOPTIONS:
+               if (copy_from_user(&value, argp, sizeof(int)))
+                       return -EFAULT;
+@@ -194,17 +190,34 @@
+               }
+               break;
+       default:
+-              return -ENOTTY;
++              return -EINVAL;
+       }
++
+       return 0;
+ }
+ static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
+                               size_t count, loff_t *ppos)
+ {
++      size_t i;
++
+       if (!count)
+               return -EIO;
++      rdc321x_wdt_device.close_expected = false;
++
++      for (i = 0; i != count; i++) {
++              char c;
++
++              if (get_user(c, buf + i))
++                      return -EFAULT;
++
++              if (c == 'V') {
++                      rdc321x_wdt_device.close_expected = true;
++                      break;
++              }
++      }
++
+       rdc321x_wdt_reset();
+       return count;
+@@ -246,27 +259,18 @@
+       rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
+       rdc321x_wdt_device.base_reg = r->start;
++      rdc321x_wdt_device.running = false;
++      rdc321x_wdt_device.close_expected = false;
++      rdc321x_wdt_device.inuse = 0;
++      setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_timer, 0);
++      rdc321x_wdt_device.total_seconds = 100;
++
+       err = misc_register(&rdc321x_wdt_misc);
+       if (err < 0) {
+               dev_err(&pdev->dev, "misc_register failed\n");
+               return err;
+       }
+-      spin_lock_init(&rdc321x_wdt_device.lock);
+-
+-      /* Reset the watchdog */
+-      pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+-                              rdc321x_wdt_device.base_reg, RDC_WDT_RST);
+-
+-      init_completion(&rdc321x_wdt_device.stop);
+-      rdc321x_wdt_device.queue = 0;
+-
+-      clear_bit(0, &rdc321x_wdt_device.inuse);
+-
+-      setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
+-
+-      rdc321x_wdt_device.default_ticks = ticks;
+-
+       dev_info(&pdev->dev, "watchdog init success\n");
+       return 0;
+@@ -274,10 +278,11 @@
+ static int __devexit rdc321x_wdt_remove(struct platform_device *pdev)
+ {
+-      if (rdc321x_wdt_device.queue) {
+-              rdc321x_wdt_device.queue = 0;
+-              wait_for_completion(&rdc321x_wdt_device.stop);
+-      }
++      if (rdc321x_wdt_device.inuse)
++              rdc321x_wdt_device.inuse = 0;
++
++      while (timer_pending(&rdc321x_wdt_device.timer))
++              msleep(100);
+       misc_deregister(&rdc321x_wdt_misc);