]> git.hungrycats.org Git - linux/commitdiff
[WATCHDOG] Add several new watchdog drivers from 2.4
authorDave Jones <davej@codemonkey.org.uk>
Fri, 10 Jan 2003 00:33:54 +0000 (23:33 -0100)
committerDave Jones <davej@tetrachloride.(none)>
Fri, 10 Jan 2003 00:33:54 +0000 (23:33 -0100)
drivers/char/watchdog/Kconfig
drivers/char/watchdog/Makefile
drivers/char/watchdog/alim7101_wdt.c [new file with mode: 0644]
drivers/char/watchdog/indydog.c [new file with mode: 0644]
drivers/char/watchdog/sc1200wdt.c [new file with mode: 0644]
drivers/char/watchdog/sc520_wdt.c [new file with mode: 0644]
drivers/char/watchdog/wafer5823wdt.c [new file with mode: 0644]

index d329cdc5dfb9ebda206eb92d8bdf4a3a0be90e49..7825533927bccd74957d8fd411c34261bc991a26 100644 (file)
@@ -283,4 +283,24 @@ config MACHZ_WDT
          The module is called machzwd.o.  If you want to compile it as a
          module, say M here and read <file:Documentation/modules.txt>.
 
+config W83877F_WDT
+       tristate "W83877F Computer Watchdog"
+       depends on WATCHDOG
+
+config SC520_WDT
+       tristate "AMD Elan SC520 processor Watchdog"
+       depends on WATCHDOG
+
+config ALIM7101_WDT
+       tristate "ALi M7101 PMU Computer Watchdog"
+       depends on WATCHDOG
+
+config SC1200_WDT
+       tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog"
+       depends on WATCHDOG
+
+config WAFER_WDT
+       tristate "ICP Wafer 5823 Single Board Computer Watchdog"
+       depends on WATCHDOG
+
 endmenu
index cdd69eb962ade7bd1852652abea7d54f30424e46..51d904e418f4f4a27a83e4e553452d66a80731dc 100644 (file)
@@ -23,3 +23,8 @@ obj-$(CONFIG_MACHZ_WDT) += machzwd.o
 obj-$(CONFIG_SH_WDT) += shwdt.o
 obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o
+obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
+obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
+obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
+obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o
diff --git a/drivers/char/watchdog/alim7101_wdt.c b/drivers/char/watchdog/alim7101_wdt.c
new file mode 100644 (file)
index 0000000..71928de
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ *     ALi M7101 PMU Computer Watchdog Timer driver for Linux 2.4.x
+ *
+ *     Based on w83877f_wdt.c by Scott Jennings <management@oro.net>
+ *     and the Cobalt kernel WDT timer driver by Tim Hockin
+ *                                           <thockin@cobaltnet.com>
+ *
+ *     (c)2002 Steve Hill <steve@navaho.co.uk>
+ * 
+ *  Theory of operation:
+ *  A Watchdog Timer (WDT) is a hardware circuit that can 
+ *  reset the computer system in case of a software fault.
+ *  You probably knew that already.
+ *
+ *  Usually a userspace daemon will notify the kernel WDT driver
+ *  via the /proc/watchdog special device file that userspace is
+ *  still alive, at regular intervals.  When such a notification
+ *  occurs, the driver will usually tell the hardware watchdog
+ *  that everything is in order, and that the watchdog should wait
+ *  for yet another little while to reset the system.
+ *  If userspace fails (RAM error, kernel bug, whatever), the
+ *  notifications cease to occur, and the hardware watchdog will
+ *  reset the system (causing a reboot) after the timeout occurs.
+ *
+ *  This WDT driver is different from most other Linux WDT
+ *  drivers in that the driver will ping the watchdog by itself,
+ *  because this particular WDT has a very short timeout (1.6
+ *  seconds) and it would be insane to count on any userspace
+ *  daemon always getting scheduled within that time frame.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#define OUR_NAME "alim7101_wdt"
+
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+#define ALI_7101_WDT    0x92
+#define ALI_WDT_ARM     0x01
+
+/*
+ * We're going to use a 1 second timeout.
+ * If we reset the watchdog every ~250ms we should be safe.  */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WDT_HEARTBEAT (HZ * 30)
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static int wdt_expect_close;
+static struct pci_dev *alim7101_pmu;
+
+/*
+ *     Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+       /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+        * we agree to ping the WDT 
+        */
+       char    tmp;
+
+       if(time_before(jiffies, next_heartbeat)) 
+       {
+               /* Ping the WDT (this is actually a disarm/arm sequence) */
+               pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
+               pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+               pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+       } else {
+               printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n");
+       }
+       /* Re-set the timer interval */
+       timer.expires = jiffies + WDT_INTERVAL;
+       add_timer(&timer);
+}
+
+/* 
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+       char    tmp;
+
+       pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
+       if (writeval == WDT_ENABLE)
+               pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+       else
+               pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+}
+
+static void wdt_startup(void)
+{
+       next_heartbeat = jiffies + WDT_HEARTBEAT;
+
+       /* We must enable before we kick off the timer in case the timer
+          occurs as we ping it */
+
+       wdt_change(WDT_ENABLE);
+
+       /* Start the timer */
+       timer.expires = jiffies + WDT_INTERVAL; 
+       add_timer(&timer);
+
+
+       printk(OUR_NAME ": Watchdog timer is now enabled.\n");  
+}
+
+static void wdt_turnoff(void)
+{
+       /* Stop the timer */
+       del_timer_sync(&timer);
+       wdt_change(WDT_DISABLE);
+       printk(OUR_NAME ": Watchdog timer is now disabled...\n");
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
+{
+       /* We can't seek */
+       if(ppos != &file->f_pos)
+               return -ESPIPE;
+
+       /* See if we got the magic character */
+       if(count) 
+       {
+               size_t ofs;
+
+               /* note: just in case someone wrote the magic character
+                * five months ago... */
+               wdt_expect_close = 0;
+
+               /* now scan */
+               for(ofs = 0; ofs != count; ofs++)
+               {
+                       char c;
+                       if(get_user(c, buf+ofs))
+                               return -EFAULT;
+                       if(c == 'V')
+                               wdt_expect_close = 1;
+               }
+               /* someone wrote to us, we should restart timer */
+               next_heartbeat = jiffies + WDT_HEARTBEAT;
+               return 1;
+       };
+       return 0;
+}
+
+static ssize_t fop_read(struct file * file, char * buf, size_t count, loff_t * ppos)
+{
+       /* No can do */
+       return -EINVAL;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+       /* Just in case we're already talking to someone... */
+       if(test_and_set_bit(0, &wdt_is_open))
+               return -EBUSY;
+       /* Good, fire up the show */
+       wdt_startup();
+       return 0;
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+#ifdef CONFIG_WDT_NOWAYOUT
+       if(wdt_expect_close)
+               wdt_turnoff();
+       else {
+               printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n");
+       }
+#else
+       wdt_turnoff();
+#endif
+       clear_bit(0, &wdt_is_open);
+       return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+       static struct watchdog_info ident=
+       {
+               0,
+               1,
+               "ALiM7101"
+       };
+       
+       switch(cmd)
+       {
+               case WDIOC_GETSUPPORT:
+                       return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
+               case WDIOC_KEEPALIVE:
+                       next_heartbeat = jiffies + WDT_HEARTBEAT;
+                       return 0;
+               default:
+                       return -ENOTTY;
+       }
+}
+
+static struct file_operations wdt_fops = {
+       .owner=         THIS_MODULE,
+       .llseek=                no_llseek,
+       .read=          fop_read,
+       .write=         fop_write,
+       .open=          fop_open,
+       .release=       fop_close,
+       .ioctl=         fop_ioctl
+};
+
+static struct miscdevice wdt_miscdev = {
+       .minor=WATCHDOG_MINOR,
+       .name="watchdog",
+       .fops=&wdt_fops
+};
+
+/*
+ *     Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+       if (code==SYS_DOWN || code==SYS_HALT) wdt_turnoff();
+       if (code==SYS_RESTART) {
+               /*
+                * Cobalt devices have no way of rebooting themselves other than
+                * getting the watchdog to pull reset, so we restart the watchdog on
+                * reboot with no heartbeat
+                */
+               wdt_change(WDT_ENABLE);
+               printk(OUR_NAME ": Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n");
+       };
+       return NOTIFY_DONE;
+}
+/*
+ *     The WDT needs to learn about soft shutdowns in order to
+ *     turn the timebomb registers off. 
+ */
+static struct notifier_block wdt_notifier=
+{
+       wdt_notify_sys,
+       0,
+       0
+};
+
+static void __exit alim7101_wdt_unload(void)
+{
+       wdt_turnoff();
+       /* Deregister */
+       misc_deregister(&wdt_miscdev);
+       unregister_reboot_notifier(&wdt_notifier);
+}
+
+static int __init alim7101_wdt_init(void)
+{
+       int rc = -EBUSY;
+       struct pci_dev *ali1543_south;
+       char tmp;
+
+       printk(KERN_INFO OUR_NAME ": Steve Hill <steve@navaho.co.uk>.\n");
+       alim7101_pmu = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,NULL);
+       if (!alim7101_pmu) {
+               printk(KERN_INFO OUR_NAME ": ALi M7101 PMU not present - WDT not set\n");
+               return -EBUSY;
+       };
+       
+       /* Set the WDT in the PMU to 1 second */
+       pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
+
+       ali1543_south = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL);
+       if (!ali1543_south) {
+               printk(KERN_INFO OUR_NAME ": ALi 1543 South-Bridge not present - WDT not set\n");
+               return -EBUSY;
+       };
+       pci_read_config_byte(ali1543_south, 0x5e, &tmp);
+       if ((tmp & 0x1e) != 0x12) {
+               printk(KERN_INFO OUR_NAME ": ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
+               return -EBUSY;
+       };
+
+       init_timer(&timer);
+       timer.function = wdt_timer_ping;
+       timer.data = 1;
+               
+       rc = misc_register(&wdt_miscdev);
+       if (rc)
+               return rc;
+
+       rc = register_reboot_notifier(&wdt_notifier);
+       if (rc) {
+               misc_deregister(&wdt_miscdev);
+               return rc;
+       };
+       
+       printk(KERN_INFO OUR_NAME ": WDT driver for ALi M7101 initialised.\n");
+       return 0;
+}
+
+module_init(alim7101_wdt_init);
+module_exit(alim7101_wdt_unload);
+
+EXPORT_NO_SYMBOLS;
+MODULE_AUTHOR("Steve Hill");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/watchdog/indydog.c b/drivers/char/watchdog/indydog.c
new file mode 100644 (file)
index 0000000..6ecf313
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ *     IndyDog 0.2     A Hardware Watchdog Device for SGI IP22
+ *
+ *     (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, 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.
+ *     
+ *     based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+#include <linux/module.h>
+#include <linux/config.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/smp_lock.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/sgi/sgimc.h>
+
+static unsigned long indydog_alive;
+static struct sgimc_misc_ctrl *mcmisc_regs; 
+
+static void indydog_ping()
+{
+       mcmisc_regs->watchdogt = 0;
+}
+
+
+/*
+ *     Allow only one person to hold it open
+ */
+
+static int indydog_open(struct inode *inode, struct file *file)
+{
+       u32 mc_ctrl0;
+       
+       if( test_and_set_bit(0,&indydog_alive) )
+               return -EBUSY;
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+       MOD_INC_USE_COUNT;
+#endif
+       /*
+        *      Activate timer
+        */
+       mcmisc_regs = (struct sgimc_misc_ctrl *)(KSEG1+0x1fa00000);
+
+       mc_ctrl0 = mcmisc_regs->cpuctrl0 | SGIMC_CCTRL0_WDOG;
+       mcmisc_regs->cpuctrl0 = mc_ctrl0;
+       indydog_ping();
+                       
+       printk("Started watchdog timer.\n");
+       return 0;
+}
+
+static int indydog_release(struct inode *inode, struct file *file)
+{
+       /*
+        *      Shut off the timer.
+        *      Lock it in if it's a module and we defined ...NOWAYOUT
+        */
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+       {
+       u32 mc_ctrl0 = mcmisc_regs->cpuctrl0; 
+       mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
+       mcmisc_regs->cpuctrl0 = mc_ctrl0;
+       printk("Stopped watchdog timer.\n");
+       }
+#endif
+       clear_bit(0,&indydog_alive);
+       return 0;
+}
+
+static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+       /*  Can't seek (pwrite) on this device  */
+       if (ppos != &file->f_pos)
+               return -ESPIPE;
+
+       /*
+        *      Refresh the timer.
+        */
+       if(len) {
+               indydog_ping();
+               return 1;
+       }
+       return 0;
+}
+
+static int indydog_ioctl(struct inode *inode, struct file *file,
+       unsigned int cmd, unsigned long arg)
+{
+       static struct watchdog_info ident = {
+               identity: "Hardware Watchdog for SGI IP22",
+       };
+       switch (cmd) {
+               default:
+                       return -ENOIOCTLCMD;
+               case WDIOC_GETSUPPORT:
+                       if(copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident)))
+                               return -EFAULT;
+                       return 0;
+               case WDIOC_GETSTATUS:
+               case WDIOC_GETBOOTSTATUS:
+                       return put_user(0,(int *)arg);
+               case WDIOC_KEEPALIVE:
+                       indydog_ping();
+                       return 0;
+       }
+}
+
+static struct file_operations indydog_fops = {
+       .owner  = THIS_MODULE,
+       .write  = indydog_write,
+       .ioctl  = indydog_ioctl,
+       .open   = indydog_open,
+       .release= indydog_release,
+};
+
+static struct miscdevice indydog_miscdev = {
+       .minor  = WATCHDOG_MINOR,
+       .name   = "watchdog",
+       .fops   = &indydog_fops,
+};
+
+static const char banner[] __initdata = KERN_INFO "Hardware Watchdog Timer for SGI IP22: 0.2\n";
+
+static int __init watchdog_init(void)
+{
+       int ret;
+
+       ret = misc_register(&indydog_miscdev);
+
+       if (ret)
+               return ret;
+
+       printk(banner);
+
+       return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+       misc_deregister(&indydog_miscdev);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/watchdog/sc1200wdt.c b/drivers/char/watchdog/sc1200wdt.c
new file mode 100644 (file)
index 0000000..0047475
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ *     National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ *     (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>,
+ *                     All Rights Reserved.
+ *     Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively.
+ *
+ *     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.
+ *
+ *     The author(s) of this software shall not be held liable for damages
+ *     of any nature resulting due to the use of this software. This
+ *     software is provided AS-IS with no warranties.
+ *
+ *     Changelog:
+ *     20020220 Zwane Mwaikambo        Code based on datasheet, no hardware.
+ *     20020221 Zwane Mwaikambo        Cleanups as suggested by Jeff Garzik and Alan Cox.
+ *     20020222 Zwane Mwaikambo        Added probing.
+ *     20020225 Zwane Mwaikambo        Added ISAPNP support.
+ *     20020412 Rob Radez              Broke out start/stop functions
+ *              <rob@osinvestor.com>   Return proper status instead of temperature warning
+ *                                     Add WDIOC_GETBOOTSTATUS and WDIOC_SETOPTIONS ioctls
+ *                                     Fix CONFIG_WATCHDOG_NOWAYOUT
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/ioport.h>
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/isapnp.h>
+
+#define SC1200_MODULE_VER      "build 20020303"
+#define SC1200_MODULE_NAME     "sc1200wdt"
+#define PFX                    SC1200_MODULE_NAME ": "
+
+#define        MAX_TIMEOUT     255     /* 255 minutes */
+#define PMIR           (io)    /* Power Management Index Register */
+#define PMDR           (io+1)  /* Power Management Data Register */
+
+/* Data Register indexes */
+#define FER1           0x00    /* Function enable register 1 */
+#define FER2           0x01    /* Function enable register 2 */
+#define PMC1           0x02    /* Power Management Ctrl 1 */
+#define PMC2           0x03    /* Power Management Ctrl 2 */
+#define PMC3           0x04    /* Power Management Ctrl 3 */
+#define WDTO           0x05    /* Watchdog timeout register */
+#define        WDCF            0x06    /* Watchdog config register */
+#define WDST           0x07    /* Watchdog status register */
+
+/* WDCF bitfields - which devices assert WDO */
+#define KBC_IRQ                0x01    /* Keyboard Controller */
+#define MSE_IRQ                0x02    /* Mouse */
+#define UART1_IRQ      0x03    /* Serial0 */
+#define UART2_IRQ      0x04    /* Serial1 */
+/* 5 -7 are reserved */
+
+static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER;
+static int timeout = 1;
+static int io = -1;
+static int io_len = 2;         /* for non plug and play */
+struct semaphore open_sem;
+static char expect_close;
+spinlock_t sc1200wdt_lock;     /* io port access serialisation */
+
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+static int isapnp = 1;
+static struct pci_dev *wdt_dev;
+
+MODULE_PARM(isapnp, "i");
+MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled");
+#endif
+
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "io port");
+MODULE_PARM(timeout, "i");
+MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1");
+
+
+
+/* Read from Data Register */
+static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data)
+{
+       spin_lock(&sc1200wdt_lock);
+       outb_p(index, PMIR);
+       *data = inb(PMDR);
+       spin_unlock(&sc1200wdt_lock);
+}
+
+
+/* Write to Data Register */
+static inline void sc1200wdt_write_data(unsigned char index, unsigned char data)
+{
+       spin_lock(&sc1200wdt_lock);
+       outb_p(index, PMIR);
+       outb(data, PMDR);
+       spin_unlock(&sc1200wdt_lock);
+}
+
+
+static void sc1200wdt_start(void)
+{
+       unsigned char reg;
+
+       sc1200wdt_read_data(WDCF, &reg);
+       /* assert WDO when any of the following interrupts are triggered too */
+       reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ);
+       sc1200wdt_write_data(WDCF, reg);
+       /* set the timeout and get the ball rolling */
+       sc1200wdt_write_data(WDTO, timeout);
+}
+
+
+static void sc1200wdt_stop(void)
+{
+       sc1200wdt_write_data(WDTO, 0);
+}
+
+
+/* This returns the status of the WDO signal, inactive high. */
+static inline int sc1200wdt_status(void)
+{
+       unsigned char ret;
+
+       sc1200wdt_read_data(WDST, &ret);
+       /* If the bit is inactive, the watchdog is enabled, so return
+        * KEEPALIVEPING which is a bit of a kludge because there's nothing
+        * else for enabled/disabled status
+        */
+       return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING;  /* bits 1 - 7 are undefined */
+}
+
+
+static int sc1200wdt_open(struct inode *inode, struct file *file)
+{
+       /* allow one at a time */
+       if (down_trylock(&open_sem))
+               return -EBUSY;
+
+       if (timeout > MAX_TIMEOUT)
+               timeout = MAX_TIMEOUT;
+
+       sc1200wdt_start();
+       printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout);
+
+       return 0;
+}
+
+
+static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+       int new_timeout;
+       static struct watchdog_info ident = {
+               options:                WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+               firmware_version:       0,
+               identity:               "PC87307/PC97307"
+       };
+
+       switch (cmd) {
+               default:
+                       return -ENOTTY; /* Keep Pavel Machek amused ;) */
+
+               case WDIOC_GETSUPPORT:
+                       if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof ident))
+                               return -EFAULT;
+                       return 0;
+
+               case WDIOC_GETSTATUS:
+                       return put_user(sc1200wdt_status(), (int *)arg);
+
+               case WDIOC_GETBOOTSTATUS:
+                       return put_user(0, (int *)arg);
+
+               case WDIOC_KEEPALIVE:
+                       sc1200wdt_write_data(WDTO, timeout);
+                       return 0;
+
+               case WDIOC_SETTIMEOUT:
+                       if (get_user(new_timeout, (int *)arg))
+                               return -EFAULT;
+
+                       /* the API states this is given in secs */
+                       new_timeout /= 60;
+                       if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+                               return -EINVAL;
+
+                       timeout = new_timeout;
+                       sc1200wdt_write_data(WDTO, timeout);
+                       /* fall through and return the new timeout */
+
+               case WDIOC_GETTIMEOUT:
+                       return put_user(timeout * 60, (int *)arg);
+
+               case WDIOC_SETOPTIONS:
+               {
+                       int options, retval = -EINVAL;
+
+                       if (get_user(options, (int *)arg))
+                               return -EFAULT;
+
+                       if (options & WDIOS_DISABLECARD) {
+                               sc1200wdt_stop();
+                               retval = 0;
+                       }
+
+                       if (options & WDIOS_ENABLECARD) {
+                               sc1200wdt_start();
+                               retval = 0;
+                       }
+
+                       return retval;
+               }
+       }
+}
+
+
+static int sc1200wdt_release(struct inode *inode, struct file *file)
+{
+       if (expect_close == 42) {
+               sc1200wdt_stop();
+               printk(KERN_INFO PFX "Watchdog disabled\n");
+       } else {
+               sc1200wdt_write_data(WDTO, timeout);
+               printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout);
+       }
+       up(&open_sem);
+       expect_close = 0;
+
+       return 0;
+}
+
+
+static ssize_t sc1200wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+       if (ppos != &file->f_pos)
+               return -ESPIPE;
+       
+       if (len) {
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+               size_t i;
+
+               expect_close = 0;
+
+               for (i = 0; i != len; i++)
+               {
+                       char c;
+                       if(get_user(c, data+i))
+                               return -EFAULT;
+                       if (c == 'V')
+                               expect_close = 42;
+               }
+#endif
+               sc1200wdt_write_data(WDTO, timeout);
+               return len;
+       }
+
+       return 0;
+}
+
+
+static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+       if (code == SYS_DOWN || code == SYS_HALT)
+               sc1200wdt_stop();
+
+       return NOTIFY_DONE;
+}
+
+
+static struct notifier_block sc1200wdt_notifier =
+{
+       notifier_call:  sc1200wdt_notify_sys
+};
+
+static struct file_operations sc1200wdt_fops =
+{
+       owner:          THIS_MODULE,
+       write:          sc1200wdt_write,
+       ioctl:          sc1200wdt_ioctl,
+       open:           sc1200wdt_open,
+       release:        sc1200wdt_release
+};
+
+static struct miscdevice sc1200wdt_miscdev =
+{
+       minor:          WATCHDOG_MINOR,
+       name:           "watchdog",
+       fops:           &sc1200wdt_fops,
+};
+
+
+static int __init sc1200wdt_probe(void)
+{
+       /* The probe works by reading the PMC3 register's default value of 0x0e
+        * there is one caveat, if the device disables the parallel port or any
+        * of the UARTs we won't be able to detect it.
+        * Nb. This could be done with accuracy by reading the SID registers, but
+        * we don't have access to those io regions.
+        */
+       
+       unsigned char reg;
+
+       sc1200wdt_read_data(PMC3, &reg);
+       reg &= 0x0f;                            /* we don't want the UART busy bits */
+       return (reg == 0x0e) ? 0 : -ENODEV;
+}
+
+
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+
+static int __init sc1200wdt_isapnp_probe(void)
+{
+       int ret;
+
+       /* The WDT is logical device 8 on the main device */
+       wdt_dev = isapnp_find_dev(NULL, ISAPNP_VENDOR('N','S','C'), ISAPNP_FUNCTION(0x08), NULL);
+       if (!wdt_dev)
+               return -ENODEV;
+       
+       if (wdt_dev->prepare(wdt_dev) < 0) {
+               printk(KERN_ERR PFX "ISA PnP found device that could not be autoconfigured\n");
+               return -EAGAIN;
+       }
+
+       if (!(pci_resource_flags(wdt_dev, 0) & IORESOURCE_IO)) {
+               printk(KERN_ERR PFX "ISA PnP could not find io ports\n");
+               return -ENODEV;
+       }
+
+       ret = wdt_dev->activate(wdt_dev);
+       if (ret && (ret != -EBUSY))
+               return -ENOMEM;
+
+       /* io port resource overriding support? */
+       io = pci_resource_start(wdt_dev, 0);
+       io_len = pci_resource_len(wdt_dev, 0);
+
+       printk(KERN_DEBUG PFX "ISA PnP found device at io port %#x/%d\n", io, io_len);
+       return 0;
+}
+
+#endif /* CONFIG_ISAPNP */
+
+
+static int __init sc1200wdt_init(void)
+{
+       int ret;
+
+       printk(banner);
+
+       spin_lock_init(&sc1200wdt_lock);
+       sema_init(&open_sem, 1);
+
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+       if (isapnp) {
+               ret = sc1200wdt_isapnp_probe();
+               if (ret)
+                       goto out_clean;
+       }
+#endif
+
+       if (io == -1) {
+               printk(KERN_ERR PFX "io parameter must be specified\n");
+               ret = -EINVAL;
+               goto out_clean;
+       }
+
+       if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+               printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
+               ret = -EBUSY;
+               goto out_pnp;
+       }
+
+       ret = sc1200wdt_probe();
+       if (ret)
+               goto out_io;
+
+       ret = register_reboot_notifier(&sc1200wdt_notifier);
+       if (ret) {
+               printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
+               goto out_io;
+       }
+
+       ret = misc_register(&sc1200wdt_miscdev);
+       if (ret) {
+               printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
+               goto out_rbt;
+       }
+
+       /* ret = 0 */
+
+out_clean:
+       return ret;
+
+out_rbt:
+       unregister_reboot_notifier(&sc1200wdt_notifier);
+
+out_io:
+       release_region(io, io_len);
+
+out_pnp:
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+       if (isapnp && wdt_dev)
+               wdt_dev->deactivate(wdt_dev);
+#endif
+       goto out_clean;
+}      
+
+
+static void __exit sc1200wdt_exit(void)
+{
+       misc_deregister(&sc1200wdt_miscdev);
+       unregister_reboot_notifier(&sc1200wdt_notifier);
+
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+       if(isapnp && wdt_dev)
+               wdt_dev->deactivate(wdt_dev);
+#endif
+
+       release_region(io, io_len);
+}
+
+
+#ifndef MODULE
+static int __init sc1200wdt_setup(char *str)
+{
+       int ints[4];
+
+       str = get_options (str, ARRAY_SIZE(ints), ints);
+
+       if (ints[0] > 0) {
+               io = ints[1];
+               if (ints[0] > 1)
+                       timeout = ints[2];
+
+#if defined CONFIG_ISAPNP || defined CONFIG_ISAPNP_MODULE
+               if (ints[0] > 2)
+                       isapnp = ints[3];
+#endif
+       }
+
+       return 1;
+}
+
+__setup("sc1200wdt=", sc1200wdt_setup);
+#endif /* MODULE */
+
+
+module_init(sc1200wdt_init);
+module_exit(sc1200wdt_exit);
+
+MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
+MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component");
+MODULE_LICENSE("GPL");
+EXPORT_NO_SYMBOLS;
+
diff --git a/drivers/char/watchdog/sc520_wdt.c b/drivers/char/watchdog/sc520_wdt.c
new file mode 100644 (file)
index 0000000..590e543
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ *     AMD Elan SC520 processor Watchdog Timer driver for Linux 2.4.x
+ *
+ *      Based on acquirewdt.c by Alan Cox,
+ *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@ostenfeld.dk>
+ *     
+ *     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.
+ *     
+ *     The authors do NOT admit liability nor provide warranty for 
+ *     any of this software. This material is provided "AS-IS" in 
+ *      the hope that it may be useful for others.
+ *
+ *     (c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
+ *           9/27 - 2001      [Initial release]
+ *     
+ *     Additional fixes Alan Cox
+ *     -       Fixed formatting
+ *     -       Removed debug printks
+ *     -       Fixed SMP built kernel deadlock
+ *     -       Switched to private locks not lock_kernel
+ *     -       Used ioremap/writew/readw
+ *     -       Added NOWAYOUT support
+ *
+ *  Theory of operation:
+ *  A Watchdog Timer (WDT) is a hardware circuit that can 
+ *  reset the computer system in case of a software fault.
+ *  You probably knew that already.
+ *
+ *  Usually a userspace daemon will notify the kernel WDT driver
+ *  via the /proc/watchdog special device file that userspace is
+ *  still alive, at regular intervals.  When such a notification
+ *  occurs, the driver will usually tell the hardware watchdog
+ *  that everything is in order, and that the watchdog should wait
+ *  for yet another little while to reset the system.
+ *  If userspace fails (RAM error, kernel bug, whatever), the
+ *  notifications cease to occur, and the hardware watchdog will
+ *  reset the system (causing a reboot) after the timeout occurs.
+ *
+ *  This WDT driver is different from most other Linux WDT
+ *  drivers in that the driver will ping the watchdog by itself,
+ *  because this particular WDT has a very short timeout (1.6
+ *  seconds) and it would be insane to count on any userspace
+ *  daemon always getting scheduled within that time frame.
+ *
+ *  This driver uses memory mapped IO, and spinlock.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+/*
+ * The SC520 can timeout anywhere from 492us to 32.21s.
+ * If we reset the watchdog every ~250ms we should be safe.
+ */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WDT_HEARTBEAT (HZ * 30)
+
+/*
+ * AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
+ *
+ *   0: 492us    2: 1.01s    4: 4.03s   6: 16.22s
+ *   1: 503ms    3: 2.01s    5: 8.05s   7: 32.21s
+ */
+
+#define TIMEOUT_EXPONENT ( 1 << 3 )  /* 0x08 = 2.01s */
+
+/* #define MMCR_BASE_DEFAULT 0xfffef000 */
+#define MMCR_BASE_DEFAULT ((__u16 *)0xffffe)
+#define OFFS_WDTMRCTL ((unsigned int)0xcb0)
+#define WDT_ENB 0x8000         /* [15] Watchdog Timer Enable */
+#define WDT_WRST_ENB 0x4000    /* [14] Watchdog Timer Reset Enable */
+
+#define OUR_NAME "sc520_wdt"
+
+#define WRT_DOG(data) *wdtmrctl=data
+
+static __u16 *wdtmrctl;
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static int wdt_expect_close;
+
+static spinlock_t wdt_spinlock;
+/*
+ *     Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+       /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+        * we agree to ping the WDT 
+        */
+       if(time_before(jiffies, next_heartbeat)) 
+       {
+               /* Ping the WDT */
+               spin_lock(&wdt_spinlock);
+               writew(0xAAAA, wdtmrctl);
+               writew(0x5555, wdtmrctl);
+               spin_unlock(&wdt_spinlock);
+
+               /* Re-set the timer interval */
+               timer.expires = jiffies + WDT_INTERVAL;
+               add_timer(&timer);
+       } else {
+               printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n");
+       }
+}
+
+/* 
+ * Utility routines
+ */
+
+static void wdt_config(int writeval)
+{
+       __u16 dummy;
+       unsigned long flags;
+
+       /* buy some time (ping) */
+       spin_lock_irqsave(&wdt_spinlock, flags);
+       dummy=readw(wdtmrctl);  /* ensure write synchronization */
+       writew(0xAAAA, wdtmrctl);
+       writew(0x5555, wdtmrctl);
+       /* make WDT configuration register writable one time */
+       writew(0x3333, wdtmrctl);
+       writew(0xCCCC, wdtmrctl);
+       /* write WDT configuration register */
+       writew(writeval, wdtmrctl);
+       spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static void wdt_startup(void)
+{
+       next_heartbeat = jiffies + WDT_HEARTBEAT;
+
+       /* Start the timer */
+       timer.expires = jiffies + WDT_INTERVAL; 
+       add_timer(&timer);
+
+       wdt_config(WDT_ENB | WDT_WRST_ENB | TIMEOUT_EXPONENT);
+       printk(OUR_NAME ": Watchdog timer is now enabled.\n");  
+}
+
+static void wdt_turnoff(void)
+{
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+       /* Stop the timer */
+       del_timer(&timer);
+       wdt_config(0);
+       printk(OUR_NAME ": Watchdog timer is now disabled...\n");
+#endif
+}
+
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
+{
+       /* We can't seek */
+       if(ppos != &file->f_pos)
+               return -ESPIPE;
+
+       /* See if we got the magic character */
+       if(count) 
+       {
+               size_t ofs;
+
+               /* note: just in case someone wrote the magic character
+                * five months ago... */
+               wdt_expect_close = 0;
+
+               /* now scan */
+               for(ofs = 0; ofs != count; ofs++) {
+                       char c;
+                       if (get_user(c, buf + ofs))
+                               return -EFAULT;
+                       if(c == 'V')
+                               wdt_expect_close = 1;
+               }
+
+               /* Well, anyhow someone wrote to us, we should return that favour */
+               next_heartbeat = jiffies + WDT_HEARTBEAT;
+               return 1;
+       }
+       return 0;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+       switch(minor(inode->i_rdev)) 
+       {
+               case WATCHDOG_MINOR:
+                       /* Just in case we're already talking to someone... */
+                       if(test_and_set_bit(0, &wdt_is_open))
+                               return -EBUSY;
+                       /* Good, fire up the show */
+                       wdt_startup();
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+                       MOD_INC_USE_COUNT;
+#endif 
+                       return 0;
+               default:
+                       return -ENODEV;
+       }
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+       if(minor(inode->i_rdev) == WATCHDOG_MINOR) 
+       {
+               if(wdt_expect_close)
+                       wdt_turnoff();
+               else {
+                       del_timer(&timer);
+                       printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n");
+               }
+       }
+       clear_bit(0, &wdt_is_open);
+       return 0;
+}
+
+static long long fop_llseek(struct file *file, long long offset, int origin)
+{
+       return -ESPIPE;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+       unsigned long arg)
+{
+       static struct watchdog_info ident=
+       {
+               0,
+               1,
+               "SC520"
+       };
+       
+       switch(cmd)
+       {
+               default:
+                       return -ENOIOCTLCMD;
+               case WDIOC_GETSUPPORT:
+                       return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
+               case WDIOC_KEEPALIVE:
+                       next_heartbeat = jiffies + WDT_HEARTBEAT;
+                       return 0;
+       }
+}
+
+static struct file_operations wdt_fops = {
+       owner:          THIS_MODULE,
+       llseek:         fop_llseek,
+       write:          fop_write,
+       open:           fop_open,
+       release:        fop_close,
+       ioctl:          fop_ioctl
+};
+
+static struct miscdevice wdt_miscdev = {
+       WATCHDOG_MINOR,
+       "watchdog",
+       &wdt_fops
+};
+
+/*
+ *     Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+       void *unused)
+{
+       if(code==SYS_DOWN || code==SYS_HALT) 
+               wdt_turnoff();
+       return NOTIFY_DONE;
+}
+/*
+ *     The WDT needs to learn about soft shutdowns in order to
+ *     turn the timebomb registers off. 
+ */
+static struct notifier_block wdt_notifier=
+{
+       wdt_notify_sys,
+       0,
+       0
+};
+
+static void __exit sc520_wdt_unload(void)
+{
+       wdt_turnoff();
+
+       /* Deregister */
+       misc_deregister(&wdt_miscdev);
+       iounmap(wdtmrctl);
+       unregister_reboot_notifier(&wdt_notifier);
+}
+
+static int __init sc520_wdt_init(void)
+{
+       int rc = -EBUSY;
+       unsigned long cbar;
+
+       spin_lock_init(&wdt_spinlock);
+
+       init_timer(&timer);
+       timer.function = wdt_timer_ping;
+       timer.data = 0;
+
+       rc = misc_register(&wdt_miscdev);
+       if (rc)
+               goto err_out_region2;
+
+       rc = register_reboot_notifier(&wdt_notifier);
+       if (rc)
+               goto err_out_miscdev;
+
+       /* get the Base Address Register */
+       cbar = inl_p(0xfffc);
+       printk(OUR_NAME ": CBAR: 0x%08lx\n", cbar);
+       /* check if MMCR aliasing bit is set */
+       if (cbar & 0x80000000) {
+               printk(OUR_NAME ": MMCR Aliasing enabled.\n");
+               wdtmrctl = (__u16 *)(cbar & 0x3fffffff);
+       } else {
+               printk(OUR_NAME "!!! WARNING !!!\n"
+                 "\t MMCR Aliasing found NOT enabled!\n"
+                 "\t Using default value of: %p\n"
+                 "\t This has not been tested!\n"
+                 "\t Please email Scott Jennings <smj@oro.net>\n"
+                 "\t  and Bill Jennings <bj@oro.net> if it works!\n"
+                 , MMCR_BASE_DEFAULT
+                 );
+         wdtmrctl = MMCR_BASE_DEFAULT;
+       }
+
+       wdtmrctl = (__u16 *)((char *)wdtmrctl + OFFS_WDTMRCTL);
+       wdtmrctl = ioremap((unsigned long)wdtmrctl, 2);
+       printk(KERN_INFO OUR_NAME ": WDT driver for SC520 initialised.\n");
+
+       return 0;
+
+err_out_miscdev:
+       misc_deregister(&wdt_miscdev);
+err_out_region2:
+       return rc;
+}
+
+module_init(sc520_wdt_init);
+module_exit(sc520_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
+MODULE_LICENSE("GPL");
+EXPORT_NO_SYMBOLS;
diff --git a/drivers/char/watchdog/wafer5823wdt.c b/drivers/char/watchdog/wafer5823wdt.c
new file mode 100644 (file)
index 0000000..c40e47a
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ *     ICP Wafer 5823 Single Board Computer WDT driver for Linux 2.4.x
+ *      http://www.icpamerica.com/wafer_5823.php
+ *      May also work on other similar models
+ *
+ *     (c) Copyright 2002 Justin Cormack <justin@street-vision.com>
+ *
+ *      Release 0.02
+ *
+ *     Based on advantechwdt.c which is based on wdt.c.
+ *     Original copyright messages:
+ *
+ *     (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *                             http://www.redhat.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 nor CymruNet Ltd. admit liability nor provide 
+ *     warranty for any of this software. This material is provided 
+ *     "AS-IS" and at no charge.       
+ *
+ *     (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+
+static unsigned long wafwdt_is_open;
+static spinlock_t wafwdt_lock;
+
+/*
+ *     You must set these - there is no sane way to probe for this board.
+ *
+ *      To enable, write the timeout value in seconds (1 to 255) to I/O
+ *      port WDT_START, then read the port to start the watchdog. To pat
+ *      the dog, read port WDT_STOP to stop the timer, then read WDT_START
+ *      to restart it again.
+ */
+
+#define WDT_START 0x443
+#define WDT_STOP 0x843
+
+#define WD_TIMO 60             /* 1 minute */
+static int wd_margin = WD_TIMO;
+
+static void wafwdt_ping(void)
+{
+       /* pat watchdog */
+        spin_lock(&wafwdt_lock);
+       inb_p(WDT_STOP);
+       inb_p(WDT_START);
+        spin_unlock(&wafwdt_lock);
+}
+
+static void wafwdt_start(void)
+{
+       /* start up watchdog */
+       outb_p(wd_margin, WDT_START);
+       inb_p(WDT_START);
+}
+
+static void
+wafwdt_stop(void)
+{
+       /* stop watchdog */
+       inb_p(WDT_STOP);
+}
+
+static ssize_t wafwdt_write(struct file *file, const char *buf, size_t count, loff_t * ppos)
+{
+       /*  Can't seek (pwrite) on this device  */
+       if (ppos != &file->f_pos)
+               return -ESPIPE;
+
+       if (count) {
+               wafwdt_ping();
+               return 1;
+       }
+       return 0;
+}
+
+static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+            unsigned long arg)
+{
+       int new_margin;
+       static struct watchdog_info ident = {
+               WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+               1, "Wafer 5823 WDT"
+       };
+       int one=1;
+
+       switch (cmd) {
+       case WDIOC_GETSUPPORT:
+               if (copy_to_user
+                   ((struct watchdog_info *) arg, &ident, sizeof (ident)))
+                       return -EFAULT;
+               break;
+
+       case WDIOC_GETSTATUS:
+               if (copy_to_user((int *) arg, &one, sizeof (int)))
+                       return -EFAULT;
+               break;
+
+       case WDIOC_KEEPALIVE:
+               wafwdt_ping();
+               break;
+
+       case WDIOC_SETTIMEOUT:
+               if (get_user(new_margin, (int *)arg))
+                       return -EFAULT;
+               if ((new_margin < 1) || (new_margin > 255))
+                       return -EINVAL;
+               wd_margin = new_margin;
+               wafwdt_stop();
+               wafwdt_start();
+               /* Fall */
+       case WDIOC_GETTIMEOUT:
+               return put_user(wd_margin, (int *)arg);
+
+       default:
+               return -ENOTTY;
+       }
+       return 0;
+}
+
+static int wafwdt_open(struct inode *inode, struct file *file)
+{
+       if (test_and_set_bit(0, &wafwdt_is_open))
+               return -EBUSY;
+       wafwdt_start();
+       return 0;
+}
+
+static int
+wafwdt_close(struct inode *inode, struct file *file)
+{
+       clear_bit(0, &wafwdt_is_open);
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+        wafwdt_stop();
+#endif
+       return 0;
+}
+
+/*
+ *     Notifier for system down
+ */
+
+static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+       if (code == SYS_DOWN || code == SYS_HALT) {
+               /* Turn the WDT off */
+               wafwdt_stop();
+       }
+       return NOTIFY_DONE;
+}
+
+/*
+ *     Kernel Interfaces
+ */
+
+static struct file_operations wafwdt_fops = {
+       owner:THIS_MODULE,
+       write:wafwdt_write,
+       ioctl:wafwdt_ioctl,
+       open:wafwdt_open,
+       release:wafwdt_close,
+};
+
+static struct miscdevice wafwdt_miscdev = {
+       WATCHDOG_MINOR,
+       "watchdog",
+       &wafwdt_fops
+};
+
+/*
+ *     The WDT needs to learn about soft shutdowns in order to
+ *     turn the timebomb registers off. 
+ */
+
+static struct notifier_block wafwdt_notifier = {
+       wafwdt_notify_sys,
+       NULL,
+       0
+};
+
+static int __init wafwdt_init(void)
+{
+       printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n");
+
+       spin_lock_init(&wafwdt_lock);
+       if(!request_region(WDT_STOP, 1, "Wafer 5823 WDT"))
+               goto error;
+       if(!request_region(WDT_START, 1, "Wafer 5823 WDT"))
+               goto error2;
+       if(misc_register(&wafwdt_miscdev)<0)
+               goto error3;
+       register_reboot_notifier(&wafwdt_notifier);
+       return 0;
+error3:
+       release_region(WDT_START, 1);
+error2:
+       release_region(WDT_STOP, 1);
+error:
+       return -ENODEV;
+}
+
+static void __exit wafwdt_exit(void)
+{
+       misc_deregister(&wafwdt_miscdev);
+       unregister_reboot_notifier(&wafwdt_notifier);
+       release_region(WDT_STOP, 1);
+       release_region(WDT_START, 1);
+}
+
+module_init(wafwdt_init);
+module_exit(wafwdt_exit);
+
+MODULE_AUTHOR("Justin Cormack");
+MODULE_LICENSE("GPL");
+EXPORT_NO_SYMBOLS;
+
+/* end of wafer5823wdt.c */