[WATCHDOG] Add support for the built-int RDC R-321x SoC watchdog
authorFlorian Fainelli <florian.fainelli@telecomint.eu>
Mon, 25 Feb 2008 11:59:26 +0000 (12:59 +0100)
committerWim Van Sebroeck <wim@iguana.be>
Tue, 26 Aug 2008 20:20:05 +0000 (20:20 +0000)
This patch adds support for the built-in RDC R-321x SoC watchdog.

Signed-off-by: Florian Fainelli <florian.fainelli@telecomint.eu>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/rdc321x_wdt.c [new file with mode: 0644]

index a8e80f2eb8ae94e7ae424d6555c3bf3b9da4a5a2..a1f47c7d7cd65c103373dda53679dcaa1f5df907 100644 (file)
@@ -465,6 +465,16 @@ config PC87413_WDT
 
          Most people will say N.
 
+config RDC321X_WDT
+       tristate "RDC R-321x SoC watchdog"
+       depends on X86_RDC321X
+       help
+         This is the driver for the built in hardware watchdog
+         in the RDC R-321x SoC.
+
+         To compile this driver as a module, choose M here: the
+         module will be called rdc321x_wdt.
+
 config 60XX_WDT
        tristate "SBC-60XX Watchdog Timer"
        depends on X86
index 1b5c0118cfe611675446a338cc205e4a2bfb55a5..a88be6fdadfc49d3c799ce21ed96647eebd7284f 100644 (file)
@@ -75,6 +75,7 @@ obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
+obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o
 obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
 obj-$(CONFIG_SBC8360_WDT) += sbc8360.o
 obj-$(CONFIG_SBC7240_WDT) += sbc7240_wdt.o
diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c
new file mode 100644 (file)
index 0000000..9108efa
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * RDC321x watchdog driver
+ *
+ * Copyright (C) 2007 Florian Fainelli <florian@openwrt.org>
+ *
+ * This driver is highly inspired from the cpu5_wdt driver
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <asm/mach-rdc321x/rdc321x_defs.h>
+
+#define RDC_WDT_MASK   0x80000000 /* Mask */
+#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_CLS_TMR    0x80003844 /* Clear timer */
+
+#define RDC_WDT_INTERVAL       (HZ/10+1)
+
+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;
+} rdc321x_wdt_device;
+
+/* generic helper functions */
+
+static void rdc321x_wdt_trigger(unsigned long unused)
+{
+       unsigned long flags;
+
+       if (rdc321x_wdt_device.running)
+               ticks--;
+
+       /* keep watchdog alive */
+       spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+       outl(RDC_WDT_EN | inl(RDC3210_CFGREG_DATA),
+               RDC3210_CFGREG_DATA);
+       spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+
+       /* 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);
+       }
+
+}
+
+static void rdc321x_wdt_reset(void)
+{
+       ticks = rdc321x_wdt_device.default_ticks;
+}
+
+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);
+               outl(RDC_CLS_TMR, RDC3210_CFGREG_ADDR);
+
+               /* Enable watchdog and set the timeout to 81.92 us */
+               outl(RDC_WDT_EN | RDC_WDT_CNT, RDC3210_CFGREG_DATA);
+               spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+
+               mod_timer(&rdc321x_wdt_device.timer,
+                               jiffies + RDC_WDT_INTERVAL);
+       }
+
+       /* if process dies, counter is not decremented */
+       rdc321x_wdt_device.running++;
+}
+
+static int rdc321x_wdt_stop(void)
+{
+       if (rdc321x_wdt_device.running)
+               rdc321x_wdt_device.running = 0;
+
+       ticks = rdc321x_wdt_device.default_ticks;
+
+       return -EIO;
+}
+
+/* filesystem operations */
+static int rdc321x_wdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
+               return -EBUSY;
+
+       return nonseekable_open(inode, file);
+}
+
+static int rdc321x_wdt_release(struct inode *inode, struct file *file)
+{
+       clear_bit(0, &rdc321x_wdt_device.inuse);
+       return 0;
+}
+
+static int rdc321x_wdt_ioctl(struct inode *inode, struct file *file,
+                               unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       unsigned int value;
+       static struct watchdog_info ident = {
+               .options = WDIOF_CARDRESET,
+               .identity = "RDC321x WDT",
+       };
+       unsigned long flags;
+
+       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);
+               value = inl(RDC3210_CFGREG_DATA);
+               spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+               if (copy_to_user(argp, &value, sizeof(int)))
+                       return -EFAULT;
+               break;
+       case WDIOC_GETSUPPORT:
+               if (copy_to_user(argp, &ident, sizeof(ident)))
+                       return -EFAULT;
+               break;
+       case WDIOC_SETOPTIONS:
+               if (copy_from_user(&value, argp, sizeof(int)))
+                       return -EFAULT;
+               switch (value) {
+               case WDIOS_ENABLECARD:
+                       rdc321x_wdt_start();
+                       break;
+               case WDIOS_DISABLECARD:
+                       return rdc321x_wdt_stop();
+               default:
+                       return -EINVAL;
+               }
+               break;
+       default:
+               return -ENOTTY;
+       }
+       return 0;
+}
+
+static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
+                               size_t count, loff_t *ppos)
+{
+       if (!count)
+               return -EIO;
+
+       rdc321x_wdt_reset();
+
+       return count;
+}
+
+static const struct file_operations rdc321x_wdt_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .ioctl          = rdc321x_wdt_ioctl,
+       .open           = rdc321x_wdt_open,
+       .write          = rdc321x_wdt_write,
+       .release        = rdc321x_wdt_release,
+};
+
+static struct miscdevice rdc321x_wdt_misc = {
+       .minor  = WATCHDOG_MINOR,
+       .name   = "watchdog",
+       .fops   = &rdc321x_wdt_fops,
+};
+
+static int __devinit rdc321x_wdt_probe(struct platform_device *pdev)
+{
+       int err;
+
+       err = misc_register(&rdc321x_wdt_misc);
+       if (err < 0) {
+               printk(KERN_ERR PFX "watchdog misc_register failed\n");
+               return err;
+       }
+
+       spin_lock_init(&rdc321x_wdt_device.lock);
+
+       /* Reset the watchdog */
+       outl(RDC_WDT_RST, RDC3210_CFGREG_DATA);
+
+       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;
+
+       printk(KERN_INFO PFX "watchdog init success\n");
+
+       return 0;
+}
+
+static int rdc321x_wdt_remove(struct platform_device *pdev)
+{
+       if (rdc321x_wdt_device.queue) {
+               rdc321x_wdt_device.queue = 0;
+               wait_for_completion(&rdc321x_wdt_device.stop);
+       }
+
+       misc_deregister(&rdc321x_wdt_misc);
+
+       return 0;
+}
+
+static struct platform_driver rdc321x_wdt_driver = {
+       .probe = rdc321x_wdt_probe,
+       .remove = rdc321x_wdt_remove,
+       .driver = {
+               .owner = THIS_MODULE,
+               .name = "rdc321x-wdt",
+       },
+};
+
+static int __init rdc321x_wdt_init(void)
+{
+       return platform_driver_register(&rdc321x_wdt_driver);
+}
+
+static void __exit rdc321x_wdt_exit(void)
+{
+       platform_driver_unregister(&rdc321x_wdt_driver);
+}
+
+module_init(rdc321x_wdt_init);
+module_exit(rdc321x_wdt_exit);
+
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("RDC321x watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);