11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
4143a2e54bf53216674eada16e8953f48b159e08aWim Van Sebroeck *	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
5127c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
5227c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/module.h>
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/moduleparam.h>
551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/types.h>
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/timer.h>
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/jiffies.h>
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/miscdevice.h>
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/watchdog.h>
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/fs.h>
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/ioport.h>
621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/notifier.h>
631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/reboot.h>
641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/init.h>
651780de41406d783aa57459ba636a09aeda21d180Alan Cox#include <linux/io.h>
661780de41406d783aa57459ba636a09aeda21d180Alan Cox#include <linux/uaccess.h>
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define OUR_NAME "sbc60xxwdt"
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define PFX OUR_NAME ": "
711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * You must set these - The driver cannot probe for the settings
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_stop = 0x45;
771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_stop, int, 0);
781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_start = 0x443;
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(wdt_start, int, 0);
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * The 60xx board can use watchdog timeout values from one second
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * to several minutes.  The default is one second, so if we reset
871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * the watchdog every ~250ms we should be safe.
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WDT_INTERVAL (HZ/4+1)
911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * We must not require too good response from the userspace daemon.
941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Here we require the userspace daemon to send us a heartbeat
951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * char to /dev/watchdog every 30 seconds.
961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * If the daemon pulses us every 25 seconds, we can still afford
971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * a 5 second scheduling delay on the (high priority) daemon. That
981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * should be sufficient for a box under any load.
991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
1021780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic int timeout = WATCHDOG_TIMEOUT;	/* in seconds, multiplied by HZ to
1031780de41406d783aa57459ba636a09aeda21d180Alan Cox					   get seconds to wait for a ping */
1041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_param(timeout, int, 0);
1051780de41406d783aa57459ba636a09aeda21d180Alan CoxMODULE_PARM_DESC(timeout,
1061780de41406d783aa57459ba636a09aeda21d180Alan Cox	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
1071780de41406d783aa57459ba636a09aeda21d180Alan Cox				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
1081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
10986a1e1896c2710402e29a875d8d830244274244dWim Van Sebroeckstatic bool nowayout = WATCHDOG_NOWAYOUT;
11086a1e1896c2710402e29a875d8d830244274244dWim Van Sebroeckmodule_param(nowayout, bool, 0);
1111780de41406d783aa57459ba636a09aeda21d180Alan CoxMODULE_PARM_DESC(nowayout,
1121780de41406d783aa57459ba636a09aeda21d180Alan Cox	"Watchdog cannot be stopped once started (default="
1131780de41406d783aa57459ba636a09aeda21d180Alan Cox				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
1141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long);
11682eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slabystatic DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
1171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long next_heartbeat;
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic unsigned long wdt_is_open;
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic char wdt_expect_close;
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
1221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Whack the dog
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void wdt_timer_ping(unsigned long data)
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * we agree to ping the WDT
1291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
1301780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (time_before(jiffies, next_heartbeat)) {
1311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Ping the WDT by reading from wdt_start */
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		inb_p(wdt_start);
1331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Re-set the timer interval */
13482eb7c5059de64bd43f6b3cf3f128470f2b3fb83Jiri Slaby		mod_timer(&timer, jiffies + WDT_INTERVAL);
1351780de41406d783aa57459ba636a09aeda21d180Alan Cox	} else
13627c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_warn("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);
14927c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches	pr_info("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);
15727c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches	pr_info("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;
1867944d3a5a70ee5c1904ed1e8b1d71ff0af2854d9Wim Van Sebroeck				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);
22027c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
2211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	clear_bit(0, &wdt_is_open);
2231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_expect_close = 0;
2241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
2251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2271780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
2281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	void __user *argp = (void __user *)arg;
2301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int __user *p = argp;
2311780de41406d783aa57459ba636a09aeda21d180Alan Cox	static const struct watchdog_info ident = {
2321780de41406d783aa57459ba636a09aeda21d180Alan Cox		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
2331780de41406d783aa57459ba636a09aeda21d180Alan Cox							WDIOF_MAGICCLOSE,
2341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.firmware_version = 1,
2351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		.identity = "SBC60xx",
2361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	};
2371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2381780de41406d783aa57459ba636a09aeda21d180Alan Cox	switch (cmd) {
2391780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETSUPPORT:
2407944d3a5a70ee5c1904ed1e8b1d71ff0af2854d9Wim Van Sebroeck		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
2411780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETSTATUS:
2421780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETBOOTSTATUS:
2431780de41406d783aa57459ba636a09aeda21d180Alan Cox		return put_user(0, p);
2441780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_SETOPTIONS:
2451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	{
2461780de41406d783aa57459ba636a09aeda21d180Alan Cox		int new_options, retval = -EINVAL;
2471780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (get_user(new_options, p))
2481780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EFAULT;
2491780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_options & WDIOS_DISABLECARD) {
2501780de41406d783aa57459ba636a09aeda21d180Alan Cox			wdt_turnoff();
2511780de41406d783aa57459ba636a09aeda21d180Alan Cox			retval = 0;
2521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2531780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_options & WDIOS_ENABLECARD) {
2541780de41406d783aa57459ba636a09aeda21d180Alan Cox			wdt_startup();
2551780de41406d783aa57459ba636a09aeda21d180Alan Cox			retval = 0;
2561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2571780de41406d783aa57459ba636a09aeda21d180Alan Cox		return retval;
2581780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
2590c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck	case WDIOC_KEEPALIVE:
2600c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		wdt_keepalive();
2610c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		return 0;
2621780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_SETTIMEOUT:
2631780de41406d783aa57459ba636a09aeda21d180Alan Cox	{
2641780de41406d783aa57459ba636a09aeda21d180Alan Cox		int new_timeout;
2651780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (get_user(new_timeout, p))
2661780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EFAULT;
2671780de41406d783aa57459ba636a09aeda21d180Alan Cox		/* arbitrary upper limit */
2681780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (new_timeout < 1 || new_timeout > 3600)
2691780de41406d783aa57459ba636a09aeda21d180Alan Cox			return -EINVAL;
2701780de41406d783aa57459ba636a09aeda21d180Alan Cox
2711780de41406d783aa57459ba636a09aeda21d180Alan Cox		timeout = new_timeout;
2721780de41406d783aa57459ba636a09aeda21d180Alan Cox		wdt_keepalive();
2731780de41406d783aa57459ba636a09aeda21d180Alan Cox		/* Fall through */
2741780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
2751780de41406d783aa57459ba636a09aeda21d180Alan Cox	case WDIOC_GETTIMEOUT:
2761780de41406d783aa57459ba636a09aeda21d180Alan Cox		return put_user(timeout, p);
2770c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck	default:
2780c06090c9472db0525cb6fe229c3bea33bbbbb3cWim Van Sebroeck		return -ENOTTY;
2791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
28262322d2554d2f9680c8ace7bbf1f97d8fa84ad1aArjan van de Venstatic const struct file_operations wdt_fops = {
2831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.owner		= THIS_MODULE,
2841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.llseek		= no_llseek,
2851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.write		= fop_write,
2861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.open		= fop_open,
2871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.release	= fop_close,
2881780de41406d783aa57459ba636a09aeda21d180Alan Cox	.unlocked_ioctl	= fop_ioctl,
2891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
2901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic struct miscdevice wdt_miscdev = {
2921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.minor = WATCHDOG_MINOR,
2931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.name = "watchdog",
2941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.fops = &wdt_fops,
2951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
2961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
2981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	Notifier for system down
2991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
3001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int wdt_notify_sys(struct notifier_block *this, unsigned long code,
3021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	void *unused)
3031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3041780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (code == SYS_DOWN || code == SYS_HALT)
3051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		wdt_turnoff();
3061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return NOTIFY_DONE;
3071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
3101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	The WDT needs to learn about soft shutdowns in order to
3111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *	turn the timebomb registers off.
3121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
3131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3141780de41406d783aa57459ba636a09aeda21d180Alan Coxstatic struct notifier_block wdt_notifier = {
3151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	.notifier_call = wdt_notify_sys,
3161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
3171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void __exit sbc60xxwdt_unload(void)
3191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wdt_turnoff();
3211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* Deregister */
3231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	misc_deregister(&wdt_miscdev);
3241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	unregister_reboot_notifier(&wdt_notifier);
3261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
3271780de41406d783aa57459ba636a09aeda21d180Alan Cox		release_region(wdt_stop, 1);
3281780de41406d783aa57459ba636a09aeda21d180Alan Cox	release_region(wdt_start, 1);
3291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int __init sbc60xxwdt_init(void)
3321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int rc = -EBUSY;
3341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3351780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
3361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		timeout = WATCHDOG_TIMEOUT;
33727c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
33827c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches			timeout);
3391780de41406d783aa57459ba636a09aeda21d180Alan Cox	}
3401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3411780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (!request_region(wdt_start, 1, "SBC 60XX WDT")) {
34227c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("I/O address 0x%04x already in use\n", wdt_start);
3431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		rc = -EIO;
3441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out;
3451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* We cannot reserve 0x45 - the kernel already has! */
3481780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (wdt_stop != 0x45 && wdt_stop != wdt_start) {
3491780de41406d783aa57459ba636a09aeda21d180Alan Cox		if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) {
35027c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches			pr_err("I/O address 0x%04x already in use\n", wdt_stop);
3511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			rc = -EIO;
3521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			goto err_out_region1;
3531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
3541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
356c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	rc = register_reboot_notifier(&wdt_notifier);
3571780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (rc) {
35827c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("cannot register reboot notifier (err=%d)\n", rc);
3591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto err_out_region2;
3601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
362c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	rc = misc_register(&wdt_miscdev);
3631780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (rc) {
36427c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
36527c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		       wdt_miscdev.minor, rc);
366c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck		goto err_out_reboot;
3671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
36827c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches	pr_info("WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
36927c766aaacb265d625dc634bf7903f7f9fd0c697Joe Perches		timeout, nowayout);
3701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
3721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
373c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeckerr_out_reboot:
374c6cb13aead3a3cf5bd3e2cfa945602d5cd7825cdWim Van Sebroeck	unregister_reboot_notifier(&wdt_notifier);
3751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region2:
3761780de41406d783aa57459ba636a09aeda21d180Alan Cox	if (wdt_stop != 0x45 && wdt_stop != wdt_start)
3771780de41406d783aa57459ba636a09aeda21d180Alan Cox		release_region(wdt_stop, 1);
3781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out_region1:
3791780de41406d783aa57459ba636a09aeda21d180Alan Cox	release_region(wdt_start, 1);
3801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldserr_out:
3811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return rc;
3821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_init(sbc60xxwdt_init);
3851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsmodule_exit(sbc60xxwdt_unload);
3861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
3881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
3891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_LICENSE("GPL");
390