[WATCHDOG] Add support for the IDT RC32434 watchdog
authorFlorian Fainelli <florian.fainelli@telecomint.eu>
Mon, 25 Feb 2008 12:11:31 +0000 (13:11 +0100)
committerWim Van Sebroeck <wim@iguana.be>
Tue, 26 Aug 2008 20:20:20 +0000 (20:20 +0000)
Add driver for the IDT RC32434 SoC built-in 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/rc32434_wdt.c [new file with mode: 0644]

index a1f47c7d7cd65c103373dda53679dcaa1f5df907..c510367167009a1e57e88826bf6bbfb453357b9b 100644 (file)
@@ -643,6 +643,16 @@ config SBC_EPX_C3_WATCHDOG
 
 # MIPS Architecture
 
+config RC32434_WDT
+       tristate "IDT RC32434 SoC Watchdog Timer"
+       depends on MIKROTIK_RB532
+       help
+         Hardware driver for the IDT RC32434 SoC built-in
+         watchdog timer.
+
+         To compile this driver as a module, choose M here: the
+         module will be called rc32434_wdt.
+
 config INDYDOG
        tristate "Indy/I2 Hardware Watchdog"
        depends on SGI_HAS_INDYDOG
index a88be6fdadfc49d3c799ce21ed96647eebd7284f..e0ef123fbdeaa859c08b64660f16fb1a407c8afb 100644 (file)
@@ -95,6 +95,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
 # M68KNOMMU Architecture
 
 # MIPS Architecture
+obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o
 obj-$(CONFIG_INDYDOG) += indydog.o
 obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o
 obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o
diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c
new file mode 100644 (file)
index 0000000..6756bcb
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ *  IDT Interprise 79RC32434 watchdog driver
+ *
+ *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
+ *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
+ *
+ *  based on
+ *  SoftDog 0.05:      A Software Watchdog Device
+ *
+ *  (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *
+ *  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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/smp_lock.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+
+#include <asm/bootinfo.h>
+#include <asm/time.h>
+#include <asm/mach-rc32434/integ.h>
+
+#define MAX_TIMEOUT                    20
+#define RC32434_WDT_INTERVAL           (15 * HZ)
+
+#define VERSION "0.2"
+
+static struct {
+       struct completion stop;
+       int running;
+       struct timer_list timer;
+       int queue;
+       int default_ticks;
+       unsigned long inuse;
+} rc32434_wdt_device;
+
+static struct integ __iomem *wdt_reg;
+static int ticks = 100 * HZ;
+
+static int expect_close;
+static int timeout;
+
+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) ")");
+
+
+static void rc32434_wdt_start(void)
+{
+       u32 val;
+
+       if (!rc32434_wdt_device.inuse) {
+               writel(0, &wdt_reg->wtcount);
+
+               val = RC32434_ERR_WRE;
+               writel(readl(&wdt_reg->errcs) | val, &wdt_reg->errcs);
+
+               val = RC32434_WTC_EN;
+               writel(readl(&wdt_reg->wtc) | val, &wdt_reg->wtc);
+       }
+       rc32434_wdt_device.running++;
+}
+
+static void rc32434_wdt_stop(void)
+{
+       u32 val;
+
+       if (rc32434_wdt_device.running) {
+
+               val = ~RC32434_WTC_EN;
+               writel(readl(&wdt_reg->wtc) & val, &wdt_reg->wtc);
+
+               val = ~RC32434_ERR_WRE;
+               writel(readl(&wdt_reg->errcs) & val, &wdt_reg->errcs);
+
+               rc32434_wdt_device.running = 0;
+       }
+}
+
+static void rc32434_wdt_set(int new_timeout)
+{
+       u32 cmp = new_timeout * HZ;
+       u32 state, val;
+
+       timeout = new_timeout;
+       /*
+        * store and disable WTC
+        */
+       state = (u32)(readl(&wdt_reg->wtc) & RC32434_WTC_EN);
+       val = ~RC32434_WTC_EN;
+       writel(readl(&wdt_reg->wtc) & val, &wdt_reg->wtc);
+
+       writel(0, &wdt_reg->wtcount);
+       writel(cmp, &wdt_reg->wtcompare);
+
+       /*
+        * restore WTC
+        */
+
+       writel(readl(&wdt_reg->wtc) | state, &wdt_reg);
+}
+
+static void rc32434_wdt_reset(void)
+{
+       ticks = rc32434_wdt_device.default_ticks;
+}
+
+static void rc32434_wdt_update(unsigned long unused)
+{
+       if (rc32434_wdt_device.running)
+               ticks--;
+
+       writel(0, &wdt_reg->wtcount);
+
+       if (rc32434_wdt_device.queue && ticks)
+               mod_timer(&rc32434_wdt_device.timer,
+                       jiffies + RC32434_WDT_INTERVAL);
+       else
+               complete(&rc32434_wdt_device.stop);
+}
+
+static int rc32434_wdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
+               return -EBUSY;
+
+       if (nowayout)
+               __module_get(THIS_MODULE);
+
+       return nonseekable_open(inode, file);
+}
+
+static int rc32434_wdt_release(struct inode *inode, struct file *file)
+{
+       if (expect_close && nowayout == 0) {
+               rc32434_wdt_stop();
+               printk(KERN_INFO KBUILD_MODNAME ": disabling watchdog timer\n");
+               module_put(THIS_MODULE);
+       } else
+               printk(KERN_CRIT KBUILD_MODNAME
+                       ": device closed unexpectedly. WDT will not stop !\n");
+
+       clear_bit(0, &rc32434_wdt_device.inuse);
+       return 0;
+}
+
+static ssize_t rc32434_wdt_write(struct file *file, const char *data,
+                               size_t len, loff_t *ppos)
+{
+       if (len) {
+               if (!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, data + i))
+                                       return -EFAULT;
+                               if (c == 'V')
+                                       expect_close = 1;
+                       }
+               }
+               rc32434_wdt_update(0);
+               return len;
+       }
+       return 0;
+}
+
+static int rc32434_wdt_ioctl(struct inode *inode, struct file *file,
+       unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       int new_timeout;
+       unsigned int value;
+       static struct watchdog_info ident = {
+               .options =              WDIOF_SETTIMEOUT |
+                                       WDIOF_KEEPALIVEPING |
+                                       WDIOF_MAGICCLOSE,
+               .identity =             "RC32434_WDT Watchdog",
+       };
+       switch (cmd) {
+       case WDIOC_KEEPALIVE:
+               rc32434_wdt_reset();
+               break;
+       case WDIOC_GETSTATUS:
+       case WDIOC_GETBOOTSTATUS:
+               value = readl(&wdt_reg->wtcount);
+               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:
+                       rc32434_wdt_start();
+                       break;
+               case WDIOS_DISABLECARD:
+                       rc32434_wdt_stop();
+               default:
+                       return -EINVAL;
+               }
+               break;
+       case WDIOC_SETTIMEOUT:
+               if (copy_from_user(&new_timeout, argp, sizeof(int)))
+                       return -EFAULT;
+               if (new_timeout < 1)
+                       return -EINVAL;
+               if (new_timeout > MAX_TIMEOUT)
+                       return -EINVAL;
+               rc32434_wdt_set(new_timeout);
+       case WDIOC_GETTIMEOUT:
+               return copy_to_user(argp, &timeout, sizeof(int));
+       default:
+               return -ENOTTY;
+       }
+
+       return 0;
+}
+
+static struct file_operations rc32434_wdt_fops = {
+       .owner          = THIS_MODULE,
+       .llseek         = no_llseek,
+       .write          = rc32434_wdt_write,
+       .ioctl          = rc32434_wdt_ioctl,
+       .open           = rc32434_wdt_open,
+       .release        = rc32434_wdt_release,
+};
+
+static struct miscdevice rc32434_wdt_miscdev = {
+       .minor  = WATCHDOG_MINOR,
+       .name   = "watchdog",
+       .fops   = &rc32434_wdt_fops,
+};
+
+static char banner[] = KERN_INFO KBUILD_MODNAME
+               ": Watchdog Timer version " VERSION ", timer margin: %d sec\n";
+
+static int rc32434_wdt_probe(struct platform_device *pdev)
+{
+       int ret;
+       struct resource *r;
+
+       r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb500_wdt_res");
+       if (!r) {
+               printk(KERN_ERR KBUILD_MODNAME
+                       "failed to retrieve resources\n");
+               return -ENODEV;
+       }
+
+       wdt_reg = ioremap_nocache(r->start, r->end - r->start);
+       if (!wdt_reg) {
+               printk(KERN_ERR KBUILD_MODNAME
+                       "failed to remap I/O resources\n");
+               return -ENXIO;
+       }
+
+       ret = misc_register(&rc32434_wdt_miscdev);
+
+       if (ret < 0) {
+               printk(KERN_ERR KBUILD_MODNAME
+                       "failed to register watchdog device\n");
+               goto unmap;
+       }
+
+       init_completion(&rc32434_wdt_device.stop);
+       rc32434_wdt_device.queue = 0;
+
+       clear_bit(0, &rc32434_wdt_device.inuse);
+
+       setup_timer(&rc32434_wdt_device.timer, rc32434_wdt_update, 0L);
+
+       rc32434_wdt_device.default_ticks = ticks;
+
+       rc32434_wdt_start();
+
+       printk(banner, timeout);
+
+       return 0;
+
+unmap:
+       iounmap(wdt_reg);
+       return ret;
+}
+
+static int rc32434_wdt_remove(struct platform_device *pdev)
+{
+       if (rc32434_wdt_device.queue) {
+               rc32434_wdt_device.queue = 0;
+               wait_for_completion(&rc32434_wdt_device.stop);
+       }
+       misc_deregister(&rc32434_wdt_miscdev);
+
+       iounmap(wdt_reg);
+
+       return 0;
+}
+
+static struct platform_driver rc32434_wdt = {
+       .probe  = rc32434_wdt_probe,
+       .remove = rc32434_wdt_remove,
+       .driver = {
+               .name = "rc32434_wdt",
+       }
+};
+
+static int __init rc32434_wdt_init(void)
+{
+       return platform_driver_register(&rc32434_wdt);
+}
+
+static void __exit rc32434_wdt_exit(void)
+{
+       platform_driver_unregister(&rc32434_wdt);
+}
+
+module_init(rc32434_wdt_init);
+module_exit(rc32434_wdt_exit);
+
+MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
+               "Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);