sbc60xxwdt.c revision 0c06090c9472db0525cb6fe229c3bea33bbbbb3c
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
191780de41406d783aa57459ba636a09aeda21d180Alan Cox *           09/5 - 2001      [smj@oro.net] fixed fop_write to "return 1"
201780de41406d783aa57459ba636a09aeda21d180Alan Cox *					on success
211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           12/4 - 2002      [rob@osinvestor.com] eliminate fop_read
221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            fix possible wdt_is_open race
231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            add CONFIG_WATCHDOG_NOWAYOUT support
241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            remove lock_kernel/unlock_kernel pairs
251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added KERN_* to printk's
261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            got rid of extraneous comments
271780de41406d783aa57459ba636a09aeda21d180Alan Cox *                            changed watchdog_info to correctly reflect what
281780de41406d783aa57459ba636a09aeda21d180Alan Cox *			      the driver offers
291780de41406d783aa57459ba636a09aeda21d180Alan Cox *			      added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
301780de41406d783aa57459ba636a09aeda21d180Alan Cox *			      WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, and
311780de41406d783aa57459ba636a09aeda21d180Alan Cox *			      WDIOC_SETOPTIONS ioctls
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            use module_param
341780de41406d783aa57459ba636a09aeda21d180Alan Cox *                            made timeout (the emulated heartbeat) a
351780de41406d783aa57459ba636a09aeda21d180Alan Cox *			      module_param
361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            made the keepalive ping an internal subroutine
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            made wdt_stop and wdt_start module params
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added extra printk's for startup problems
391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *                            added MODULE_AUTHOR and MODULE_DESCRIPTION info
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  This WDT driver is different from the other Linux WDT
431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  drivers in the following ways:
441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  *)  The driver will ping the watchdog by itself, because this
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      particular WDT has a very short timeout (one second) and it
461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      would be insane to count on any userspace daemon always
471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *      getting scheduled within that time frame.
481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/module.h>
521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/moduleparam.h>
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/types.h>
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/timer.h>
551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/jiffies.h>
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/miscdevice.h>
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/watchdog.h>
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/fs.h>
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/ioport.h>
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/notifier.h>
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/reboot.h>
621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/init.h>
631780de41406d783aa57459ba636a09aeda21d180Alan Cox#include <linux/io.h>
641780de41406d783aa57459ba636a09aeda21d180Alan Cox#include <linux/uaccess.h>
651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <asm/system.h>
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define OUR_NAME "sbc60xxwdt"
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define PFX OUR_NAME ": "
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * You must set these - The driver cannot probe for the settings
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_stop = 0x45;
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_stop, int, 0);
771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_start = 0x443;
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_start, int, 0);
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * The 60xx board can use watchdog timeout values from one second
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * to several minutes.  The default is one second, so if we reset
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * the watchdog every ~250ms we should be safe.
871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WDT_INTERVAL (HZ/4+1)
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * We must not require too good response from the userspace daemon.
931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Here we require the userspace daemon to send us a heartbeat
941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * char to /dev/watchdog every 30 seconds.
951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * If the daemon pulses us every 25 seconds, we can still afford
961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * a 5 second scheduling delay on the (high priority) daemon. That
971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * should be sufficient for a box under any load.
981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
1011780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic int timeout = WATCHDOG_TIMEOUT;	/* in seconds, multiplied by HZ to
1021780de41406d783aa57459ba636a09aeda21d180Alan Cox					   get seconds to wait for a ping */
1031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(timeout, int, 0);
1041780de41406d783aa57459ba636a09aeda21d180Alan CoxMODULE_PARM_DESC(timeout,
1051780de41406d783aa57459ba636a09aeda21d180Alan Cox	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
1061780de41406d783aa57459ba636a09aeda21d180Alan Cox				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
1071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1084bfdf37830111321e2cd1fe0102dd776ce93194dAndrey Paninstatic int nowayout = WATCHDOG_NOWAYOUT;
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(nowayout, int, 0);
1101780de41406d783aa57459ba636a09aeda21d180Alan CoxMODULE_PARM_DESC(nowayout,
1111780de41406d783aa57459ba636a09aeda21d180Alan Cox	"Watchdog cannot be stopped once started (default="
1121780de41406d783aa57459ba636a09aeda21d180Alan Cox				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long);
11582eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slabystatic DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
1161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long next_heartbeat;
1171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long wdt_is_open;
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic char wdt_expect_close;
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Whack the dog
1221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long data)
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * we agree to ping the WDT
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
1291780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (time_before(jiffies, next_heartbeat)) {
1301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Ping the WDT by reading from wdt_start */
1311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		inb_p(wdt_start);
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Re-set the timer interval */
13382eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slaby		mod_timer(&timer, jiffies + WDT_INTERVAL);
1341780de41406d783aa57459ba636a09aeda21d180Alan Cox	} else
1351780de41406d783aa57459ba636a09aeda21d180Alan Cox		printk(KERN_WARNING PFX
1361780de41406d783aa57459ba636a09aeda21d180Alan Cox			"Heartbeat lost! Will not ping the watchdog\n");
1371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Utility routines
1411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_startup(void)
1441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	next_heartbeat = jiffies + (timeout * HZ);
1461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Start the timer */
14882eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slaby	mod_timer(&timer, jiffies + WDT_INTERVAL);
1491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
1501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_turnoff(void)
1531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Stop the timer */
1551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	del_timer(&timer);
1561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	inb_p(wdt_stop);
1571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
1581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_keepalive(void)
1611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* user land ping */
1631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	next_heartbeat = jiffies + (timeout * HZ);
1641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * /dev/watchdog handling
1681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1701780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic ssize_t fop_write(struct file *file, const char __user *buf,
1711780de41406d783aa57459ba636a09aeda21d180Alan Cox						size_t count, loff_t *ppos)
1721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* See if we got the magic character 'V' and reload the timer */
1741780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (count) {
1751780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (!nowayout) {
1761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size_t ofs;
1771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1781780de41406d783aa57459ba636a09aeda21d180Alan Cox			/* note: just in case someone wrote the
1791780de41406d783aa57459ba636a09aeda21d180Alan Cox			   magic character five months ago... */
1801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_expect_close = 0;
1811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1821780de41406d783aa57459ba636a09aeda21d180Alan Cox			/* scan to see whether or not we got the
1831780de41406d783aa57459ba636a09aeda21d180Alan Cox			   magic character */
1841780de41406d783aa57459ba636a09aeda21d180Alan Cox			for (ofs = 0; ofs != count; ofs++) {
1851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				char c;
1861780de41406d783aa57459ba636a09aeda21d180Alan Cox				if (get_user(c, buf+ofs))
1871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					return -EFAULT;
1881780de41406d783aa57459ba636a09aeda21d180Alan Cox				if (c == 'V')
1891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					wdt_expect_close = 42;
1901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
1911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
1921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1931780de41406d783aa57459ba636a09aeda21d180Alan Cox		/* Well, anyhow someone wrote to us, we should
1941780de41406d783aa57459ba636a09aeda21d180Alan Cox		   return that favour */
1951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_keepalive();
1961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
1971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return count;
1981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2001780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic int fop_open(struct inode *inode, struct file *file)
2011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Just in case we're already talking to someone... */
2031780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (test_and_set_bit(0, &wdt_is_open))
2041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EBUSY;
2051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (nowayout)
2071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		__module_get(THIS_MODULE);
2081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Good, fire up the show */
2101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_startup();
2116abe78bf195c633f67f6349e3d09b2bcd5d32a79Wim Van Sebroeck	return nonseekable_open(inode, file);
2121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2141780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic int fop_close(struct inode *inode, struct file *file)
2151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2161780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (wdt_expect_close == 42)
2171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_turnoff();
2181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	else {
2191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		del_timer(&timer);
2201780de41406d783aa57459ba636a09aeda21d180Alan Cox		printk(KERN_CRIT PFX
2211780de41406d783aa57459ba636a09aeda21d180Alan Cox		  "device file closed unexpectedly. Will not stop the WDT!\n");
2221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	clear_bit(0, &wdt_is_open);
2241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_expect_close = 0;
2251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
2261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2281780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
2291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	void __user *argp = (void __user *)arg;
2311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int __user *p = argp;
2321780de41406d783aa57459ba636a09aeda21d180Alan Cox	static const struct watchdog_info ident = {
2331780de41406d783aa57459ba636a09aeda21d180Alan Cox		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
2341780de41406d783aa57459ba636a09aeda21d180Alan Cox							WDIOF_MAGICCLOSE,
2351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.firmware_version = 1,
2361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.identity = "SBC60xx",
2371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	};
2381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2391780de41406d783aa57459ba636a09aeda21d180Alan Cox	switch (cmd) {
2401780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETSUPPORT:
2411780de41406d783aa57459ba636a09aeda21d180Alan Cox		return copy_to_user(argp, &ident, sizeof(ident))? -EFAULT : 0;
2421780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETSTATUS:
2431780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETBOOTSTATUS:
2441780de41406d783aa57459ba636a09aeda21d180Alan Cox		return put_user(0, p);
2451780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_SETOPTIONS:
2461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
2471780de41406d783aa57459ba636a09aeda21d180Alan Cox		int new_options, retval = -EINVAL;
2481780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (get_user(new_options, p))
2491780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EFAULT;
2501780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_options & WDIOS_DISABLECARD) {
2511780de41406d783aa57459ba636a09aeda21d180Alan Cox			wdt_turnoff();
2521780de41406d783aa57459ba636a09aeda21d180Alan Cox			retval = 0;
2531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2541780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_options & WDIOS_ENABLECARD) {
2551780de41406d783aa57459ba636a09aeda21d180Alan Cox			wdt_startup();
2561780de41406d783aa57459ba636a09aeda21d180Alan Cox			retval = 0;
2571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2581780de41406d783aa57459ba636a09aeda21d180Alan Cox		return retval;
2591780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
2600c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck	case WDIOC_KEEPALIVE:
2610c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		wdt_keepalive();
2620c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		return 0;
2631780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_SETTIMEOUT:
2641780de41406d783aa57459ba636a09aeda21d180Alan Cox	{
2651780de41406d783aa57459ba636a09aeda21d180Alan Cox		int new_timeout;
2661780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (get_user(new_timeout, p))
2671780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EFAULT;
2681780de41406d783aa57459ba636a09aeda21d180Alan Cox		/* arbitrary upper limit */
2691780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_timeout < 1 || new_timeout > 3600)
2701780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EINVAL;
2711780de41406d783aa57459ba636a09aeda21d180Alan Cox
2721780de41406d783aa57459ba636a09aeda21d180Alan Cox		timeout = new_timeout;
2731780de41406d783aa57459ba636a09aeda21d180Alan Cox		wdt_keepalive();
2741780de41406d783aa57459ba636a09aeda21d180Alan Cox		/* Fall through */
2751780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
2761780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETTIMEOUT:
2771780de41406d783aa57459ba636a09aeda21d180Alan Cox		return put_user(timeout, p);
2780c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck	default:
2790c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		return -ENOTTY;
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,
2891780de41406d783aa57459ba636a09aeda21d180Alan Cox	.unlocked_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{
3051780de41406d783aa57459ba636a09aeda21d180Alan Cox	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
3151780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic struct notifier_block wdt_notifier = {
3161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.notifier_call = wdt_notify_sys,
3171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
3181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void __exit sbc60xxwdt_unload(void)
3201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_turnoff();
3221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Deregister */
3241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	misc_deregister(&wdt_miscdev);
3251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unregister_reboot_notifier(&wdt_notifier);
3271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
3281780de41406d783aa57459ba636a09aeda21d180Alan Cox		release_region(wdt_stop, 1);
3291780de41406d783aa57459ba636a09aeda21d180Alan Cox	release_region(wdt_start, 1);
3301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __init sbc60xxwdt_init(void)
3331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int rc = -EBUSY;
3351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3361780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
3371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		timeout = WATCHDOG_TIMEOUT;
3381780de41406d783aa57459ba636a09aeda21d180Alan Cox		printk(KERN_INFO PFX
3391780de41406d783aa57459ba636a09aeda21d180Alan Cox			"timeout value must be 1 <= x <= 3600, using %d\n",
3401780de41406d783aa57459ba636a09aeda21d180Alan Cox								timeout);
3411780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
3421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3431780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (!request_region(wdt_start, 1, "SBC 60XX WDT")) {
3441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
3451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			wdt_start);
3461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		rc = -EIO;
3471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out;
3481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* We cannot reserve 0x45 - the kernel already has! */
3511780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (wdt_stop != 0x45 && wdt_stop != wdt_start) {
3521780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) {
3531780de41406d783aa57459ba636a09aeda21d180Alan Cox			printk(KERN_ERR PFX
3541780de41406d783aa57459ba636a09aeda21d180Alan Cox				"I/O address 0x%04x already in use\n",
3551780de41406d783aa57459ba636a09aeda21d180Alan Cox							wdt_stop);
3561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			rc = -EIO;
3571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			goto err_out_region1;
3581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
3591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
361c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	rc = register_reboot_notifier(&wdt_notifier);
3621780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (rc) {
3631780de41406d783aa57459ba636a09aeda21d180Alan Cox		printk(KERN_ERR PFX
3641780de41406d783aa57459ba636a09aeda21d180Alan Cox			"cannot register reboot notifier (err=%d)\n", rc);
3651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out_region2;
3661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
368c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	rc = misc_register(&wdt_miscdev);
3691780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (rc) {
3701780de41406d783aa57459ba636a09aeda21d180Alan Cox		printk(KERN_ERR PFX
3711780de41406d783aa57459ba636a09aeda21d180Alan Cox			"cannot register miscdev on minor=%d (err=%d)\n",
3721780de41406d783aa57459ba636a09aeda21d180Alan Cox						wdt_miscdev.minor, rc);
373c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck		goto err_out_reboot;
3741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
3761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		timeout, nowayout);
3771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
3791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
380c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeckerr_out_reboot:
381c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	unregister_reboot_notifier(&wdt_notifier);
3821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region2:
3831780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (wdt_stop != 0x45 && wdt_stop != wdt_start)
3841780de41406d783aa57459ba636a09aeda21d180Alan Cox		release_region(wdt_stop, 1);
3851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region1:
3861780de41406d783aa57459ba636a09aeda21d180Alan Cox	release_region(wdt_start, 1);
3871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out:
3881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return rc;
3891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_init(sbc60xxwdt_init);
3921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_exit(sbc60xxwdt_unload);
3931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
3951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
3961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_LICENSE("GPL");
3971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
398