sbc60xxwdt.c revision 82eb7c5059de64bd43f6b3cf3f128470f2b3fb83
11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
41da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      Based on acquirewdt.c by Alan Cox.
51da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
61da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	This program is free software; you can redistribute it and/or
71da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	modify it under the terms of the GNU General Public License
81da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	as published by the Free Software Foundation; either version
91da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	2 of the License, or (at your option) any later version.
101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	The author does NOT admit liability nor provide warranty for
121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	any of this software. This material is provided "AS-IS" in
131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	the hope that it may be useful for others.
141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	(c) Copyright 2000    Jakob Oestergaard <jakob@unthought.net>
161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           12/4 - 2000      [Initial revision]
181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           25/4 - 2000      Added /dev/watchdog support
191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           09/5 - 2001      [smj@oro.net] fixed fop_write to "return 1" on success
201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           12/4 - 2002      [rob@osinvestor.com] eliminate fop_read
211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            fix possible wdt_is_open race
221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            add CONFIG_WATCHDOG_NOWAYOUT support
231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            remove lock_kernel/unlock_kernel pairs
241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added KERN_* to printk's
251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            got rid of extraneous comments
261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            changed watchdog_info to correctly reflect what the driver offers
271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT,
281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            use module_param
311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            made timeout (the emulated heartbeat) a module_param
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            made the keepalive ping an internal subroutine
331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            made wdt_stop and wdt_start module params
341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added extra printk's for startup problems
351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added MODULE_AUTHOR and MODULE_DESCRIPTION info
361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  This WDT driver is different from the other Linux WDT
391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  drivers in the following ways:
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  *)  The driver will ping the watchdog by itself, because this
411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      particular WDT has a very short timeout (one second) and it
421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      would be insane to count on any userspace daemon always
431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      getting scheduled within that time frame.
441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/module.h>
481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/moduleparam.h>
491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/types.h>
501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/timer.h>
511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/jiffies.h>
521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/miscdevice.h>
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/watchdog.h>
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/fs.h>
551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/ioport.h>
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/notifier.h>
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/reboot.h>
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/init.h>
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/io.h>
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/uaccess.h>
621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/system.h>
631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define OUR_NAME "sbc60xxwdt"
651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define PFX OUR_NAME ": "
661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * You must set these - The driver cannot probe for the settings
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_stop = 0x45;
721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_stop, int, 0);
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_start = 0x443;
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_start, int, 0);
771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * The 60xx board can use watchdog timeout values from one second
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * to several minutes.  The default is one second, so if we reset
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * the watchdog every ~250ms we should be safe.
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WDT_INTERVAL (HZ/4+1)
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * We must not require too good response from the userspace daemon.
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Here we require the userspace daemon to send us a heartbeat
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * char to /dev/watchdog every 30 seconds.
911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * If the daemon pulses us every 25 seconds, we can still afford
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * a 5 second scheduling delay on the (high priority) daemon. That
931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * should be sufficient for a box under any load.
941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int timeout = WATCHDOG_TIMEOUT;	/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(timeout, int, 0);
991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
1001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1014bfdf37830111321e2cd1fe0102dd776ce93194dAndrey Paninstatic int nowayout = WATCHDOG_NOWAYOUT;
1021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(nowayout, int, 0);
103bffda5c87cf60d27a27f2e862c82c474f8e89767Wim Van SebroeckMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long);
10682eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slabystatic DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
1071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long next_heartbeat;
1081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long wdt_is_open;
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic char wdt_expect_close;
1101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Whack the dog
1131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long data)
1161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * we agree to ping the WDT
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(time_before(jiffies, next_heartbeat))
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
1221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Ping the WDT by reading from wdt_start */
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		inb_p(wdt_start);
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Re-set the timer interval */
12582eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slaby		mod_timer(&timer, jiffies + WDT_INTERVAL);
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	} else {
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
1291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Utility routines
1331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_startup(void)
1361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	next_heartbeat = jiffies + (timeout * HZ);
1381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Start the timer */
14082eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slaby	mod_timer(&timer, jiffies + WDT_INTERVAL);
1411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
1421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_turnoff(void)
1451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Stop the timer */
1471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	del_timer(&timer);
1481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	inb_p(wdt_stop);
1491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
1501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_keepalive(void)
1531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* user land ping */
1551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	next_heartbeat = jiffies + (timeout * HZ);
1561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * /dev/watchdog handling
1601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
1631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* See if we got the magic character 'V' and reload the timer */
1651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(count)
1661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
1671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (!nowayout)
1681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
1691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size_t ofs;
1701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* note: just in case someone wrote the magic character
1721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 * five months ago... */
1731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_expect_close = 0;
1741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* scan to see whether or not we got the magic character */
1761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			for(ofs = 0; ofs != count; ofs++)
1771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			{
1781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				char c;
1791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if(get_user(c, buf+ofs))
1801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					return -EFAULT;
1811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if(c == 'V')
1821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					wdt_expect_close = 42;
1831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
1841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
1851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Well, anyhow someone wrote to us, we should return that favour */
1871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_keepalive();
1881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
1891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return count;
1901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int fop_open(struct inode * inode, struct file * file)
1931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	nonseekable_open(inode, file);
1951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Just in case we're already talking to someone... */
1971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(test_and_set_bit(0, &wdt_is_open))
1981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EBUSY;
1991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (nowayout)
2011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		__module_get(THIS_MODULE);
2021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Good, fire up the show */
2041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_startup();
2051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
2061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int fop_close(struct inode * inode, struct file * file)
2091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(wdt_expect_close == 42)
2111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_turnoff();
2121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	else {
2131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		del_timer(&timer);
2141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
2151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	clear_bit(0, &wdt_is_open);
2171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_expect_close = 0;
2181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
2191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
2221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unsigned long arg)
2231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	void __user *argp = (void __user *)arg;
2251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int __user *p = argp;
2261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	static struct watchdog_info ident=
2271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
2281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
2291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.firmware_version = 1,
2301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.identity = "SBC60xx",
2311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	};
2321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	switch(cmd)
2341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
2351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		default:
236795b89d207d8ff5397f9ff1f4d44662aa7c821fcSamuel Tardieu			return -ENOTTY;
2371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_GETSUPPORT:
2381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
2391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_GETSTATUS:
2401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_GETBOOTSTATUS:
2411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return put_user(0, p);
2421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_KEEPALIVE:
2431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_keepalive();
2441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return 0;
2451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_SETOPTIONS:
2461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
2471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			int new_options, retval = -EINVAL;
2481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if(get_user(new_options, p))
2501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EFAULT;
2511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if(new_options & WDIOS_DISABLECARD) {
2531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				wdt_turnoff();
2541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				retval = 0;
2551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
2561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if(new_options & WDIOS_ENABLECARD) {
2581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				wdt_startup();
2591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				retval = 0;
2601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
2611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return retval;
2631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_SETTIMEOUT:
2651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
2661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			int new_timeout;
2671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if(get_user(new_timeout, p))
2691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EFAULT;
2701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
2721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return -EINVAL;
2731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			timeout = new_timeout;
2751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_keepalive();
2761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* Fall through */
2771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		case WDIOC_GETTIMEOUT:
2791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return put_user(timeout, p);
2801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
28362322d2554d2f9680c8ace7bbf1f97d8fa84ad1aArjan van de Venstatic const struct file_operations wdt_fops = {
2841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.owner		= THIS_MODULE,
2851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.llseek		= no_llseek,
2861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.write		= fop_write,
2871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.open		= fop_open,
2881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.release	= fop_close,
2891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.ioctl		= fop_ioctl,
2901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
2911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic struct miscdevice wdt_miscdev = {
2931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.minor = WATCHDOG_MINOR,
2941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.name = "watchdog",
2951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.fops = &wdt_fops,
2961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
2971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
2991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Notifier for system down
3001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
3011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_notify_sys(struct notifier_block *this, unsigned long code,
3031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	void *unused)
3041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(code==SYS_DOWN || code==SYS_HALT)
3061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_turnoff();
3071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return NOTIFY_DONE;
3081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
3111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	The WDT needs to learn about soft shutdowns in order to
3121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	turn the timebomb registers off.
3131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
3141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic struct notifier_block wdt_notifier=
3161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.notifier_call = wdt_notify_sys,
3181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
3191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void __exit sbc60xxwdt_unload(void)
3211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_turnoff();
3231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Deregister */
3251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	misc_deregister(&wdt_miscdev);
3261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unregister_reboot_notifier(&wdt_notifier);
3281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
3291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		release_region(wdt_stop,1);
3301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	release_region(wdt_start,1);
3311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __init sbc60xxwdt_init(void)
3341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int rc = -EBUSY;
3361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
3381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
3391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		timeout = WATCHDOG_TIMEOUT;
3401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
3411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			timeout);
3421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds 	}
3431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!request_region(wdt_start, 1, "SBC 60XX WDT"))
3451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
3461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
3471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_start);
3481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		rc = -EIO;
3491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out;
3501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* We cannot reserve 0x45 - the kernel already has! */
3531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
3541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
3551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (!request_region(wdt_stop, 1, "SBC 60XX WDT"))
3561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		{
3571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
3581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				wdt_stop);
3591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			rc = -EIO;
3601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			goto err_out_region1;
3611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
3621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	rc = misc_register(&wdt_miscdev);
3651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (rc)
3661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
3671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
3681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_miscdev.minor, rc);
3691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out_region2;
3701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	rc = register_reboot_notifier(&wdt_notifier);
3731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (rc)
3741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
3751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
3761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			rc);
3771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out_miscdev;
3781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
3811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		timeout, nowayout);
3821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
3841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_miscdev:
3861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	misc_deregister(&wdt_miscdev);
3871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region2:
3881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
3891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		release_region(wdt_stop,1);
3901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region1:
3911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	release_region(wdt_start,1);
3921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out:
3931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return rc;
3941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_init(sbc60xxwdt_init);
3971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_exit(sbc60xxwdt_unload);
3981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
4001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
4011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_LICENSE("GPL");
4021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
403