xref: /linux/drivers/watchdog/w83877f_wdt.c (revision 86a1e189)
1b7e04f8cSWim Van Sebroeck /*
2b7e04f8cSWim Van Sebroeck  *	W83877F Computer Watchdog Timer driver
3b7e04f8cSWim Van Sebroeck  *
4b7e04f8cSWim Van Sebroeck  *      Based on acquirewdt.c by Alan Cox,
5b7e04f8cSWim Van Sebroeck  *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
6b7e04f8cSWim Van Sebroeck  *
7b7e04f8cSWim Van Sebroeck  *	This program is free software; you can redistribute it and/or
8b7e04f8cSWim Van Sebroeck  *	modify it under the terms of the GNU General Public License
9b7e04f8cSWim Van Sebroeck  *	as published by the Free Software Foundation; either version
10b7e04f8cSWim Van Sebroeck  *	2 of the License, or (at your option) any later version.
11b7e04f8cSWim Van Sebroeck  *
12b7e04f8cSWim Van Sebroeck  *	The authors do NOT admit liability nor provide warranty for
13b7e04f8cSWim Van Sebroeck  *	any of this software. This material is provided "AS-IS" in
14b7e04f8cSWim Van Sebroeck  *      the hope that it may be useful for others.
15b7e04f8cSWim Van Sebroeck  *
16b7e04f8cSWim Van Sebroeck  *	(c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
17b7e04f8cSWim Van Sebroeck  *
18b7e04f8cSWim Van Sebroeck  *           4/19 - 2001      [Initial revision]
19b7e04f8cSWim Van Sebroeck  *           9/27 - 2001      Added spinlocking
20b7e04f8cSWim Van Sebroeck  *           4/12 - 2002      [rob@osinvestor.com] Eliminate extra comments
21b7e04f8cSWim Van Sebroeck  *                            Eliminate fop_read
22b7e04f8cSWim Van Sebroeck  *                            Eliminate extra spin_unlock
23b7e04f8cSWim Van Sebroeck  *                            Added KERN_* tags to printks
24b7e04f8cSWim Van Sebroeck  *                            add CONFIG_WATCHDOG_NOWAYOUT support
25b7e04f8cSWim Van Sebroeck  *                            fix possible wdt_is_open race
26c1cfd1a2SAlan Cox  *                            changed watchdog_info to correctly reflect what
27c1cfd1a2SAlan Cox  *			      the driver offers
28c1cfd1a2SAlan Cox  *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
29c1cfd1a2SAlan Cox  *			      WDIOC_SETTIMEOUT,
30b7e04f8cSWim Van Sebroeck  *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
31b7e04f8cSWim Van Sebroeck  *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
32b7e04f8cSWim Van Sebroeck  *                            added extra printk's for startup problems
33b7e04f8cSWim Van Sebroeck  *                            use module_param
34c1cfd1a2SAlan Cox  *                            made timeout (the emulated heartbeat) a
35c1cfd1a2SAlan Cox  *			      module_param
36b7e04f8cSWim Van Sebroeck  *                            made the keepalive ping an internal subroutine
37b7e04f8cSWim Van Sebroeck  *
38b7e04f8cSWim Van Sebroeck  *  This WDT driver is different from most other Linux WDT
39b7e04f8cSWim Van Sebroeck  *  drivers in that the driver will ping the watchdog by itself,
40b7e04f8cSWim Van Sebroeck  *  because this particular WDT has a very short timeout (1.6
41b7e04f8cSWim Van Sebroeck  *  seconds) and it would be insane to count on any userspace
42b7e04f8cSWim Van Sebroeck  *  daemon always getting scheduled within that time frame.
43b7e04f8cSWim Van Sebroeck  */
44b7e04f8cSWim Van Sebroeck 
4527c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
4627c766aaSJoe Perches 
47b7e04f8cSWim Van Sebroeck #include <linux/module.h>
48b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>
49b7e04f8cSWim Van Sebroeck #include <linux/types.h>
50b7e04f8cSWim Van Sebroeck #include <linux/timer.h>
51b7e04f8cSWim Van Sebroeck #include <linux/jiffies.h>
52b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>
53b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>
54b7e04f8cSWim Van Sebroeck #include <linux/fs.h>
55b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>
56b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>
57b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>
58b7e04f8cSWim Van Sebroeck #include <linux/init.h>
59c1cfd1a2SAlan Cox #include <linux/io.h>
60c1cfd1a2SAlan Cox #include <linux/uaccess.h>
61b7e04f8cSWim Van Sebroeck #include <asm/system.h>
62b7e04f8cSWim Van Sebroeck 
63b7e04f8cSWim Van Sebroeck #define OUR_NAME "w83877f_wdt"
64b7e04f8cSWim Van Sebroeck 
65b7e04f8cSWim Van Sebroeck #define ENABLE_W83877F_PORT 0x3F0
66b7e04f8cSWim Van Sebroeck #define ENABLE_W83877F 0x87
67b7e04f8cSWim Van Sebroeck #define DISABLE_W83877F 0xAA
68b7e04f8cSWim Van Sebroeck #define WDT_PING 0x443
69b7e04f8cSWim Van Sebroeck #define WDT_REGISTER 0x14
70b7e04f8cSWim Van Sebroeck #define WDT_ENABLE 0x9C
71b7e04f8cSWim Van Sebroeck #define WDT_DISABLE 0x8C
72b7e04f8cSWim Van Sebroeck 
73b7e04f8cSWim Van Sebroeck /*
74b7e04f8cSWim Van Sebroeck  * The W83877F seems to be fixed at 1.6s timeout (at least on the
75b7e04f8cSWim Van Sebroeck  * EMACS PC-104 board I'm using). If we reset the watchdog every
76b7e04f8cSWim Van Sebroeck  * ~250ms we should be safe.  */
77b7e04f8cSWim Van Sebroeck 
78b7e04f8cSWim Van Sebroeck #define WDT_INTERVAL (HZ/4+1)
79b7e04f8cSWim Van Sebroeck 
80b7e04f8cSWim Van Sebroeck /*
81b7e04f8cSWim Van Sebroeck  * We must not require too good response from the userspace daemon.
82b7e04f8cSWim Van Sebroeck  * Here we require the userspace daemon to send us a heartbeat
83b7e04f8cSWim Van Sebroeck  * char to /dev/watchdog every 30 seconds.
84b7e04f8cSWim Van Sebroeck  */
85b7e04f8cSWim Van Sebroeck 
86b7e04f8cSWim Van Sebroeck #define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
87c1cfd1a2SAlan Cox /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
88c1cfd1a2SAlan Cox static int timeout = WATCHDOG_TIMEOUT;
89b7e04f8cSWim Van Sebroeck module_param(timeout, int, 0);
90c1cfd1a2SAlan Cox MODULE_PARM_DESC(timeout,
91c1cfd1a2SAlan Cox 	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
92c1cfd1a2SAlan Cox 				__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
93b7e04f8cSWim Van Sebroeck 
94b7e04f8cSWim Van Sebroeck 
95*86a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
96*86a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
97c1cfd1a2SAlan Cox MODULE_PARM_DESC(nowayout,
98c1cfd1a2SAlan Cox 		"Watchdog cannot be stopped once started (default="
99c1cfd1a2SAlan Cox 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
100b7e04f8cSWim Van Sebroeck 
101b7e04f8cSWim Van Sebroeck static void wdt_timer_ping(unsigned long);
102b7e04f8cSWim Van Sebroeck static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0);
103b7e04f8cSWim Van Sebroeck static unsigned long next_heartbeat;
104b7e04f8cSWim Van Sebroeck static unsigned long wdt_is_open;
105b7e04f8cSWim Van Sebroeck static char wdt_expect_close;
106c7dfd0ccSAlexey Dobriyan static DEFINE_SPINLOCK(wdt_spinlock);
107b7e04f8cSWim Van Sebroeck 
108b7e04f8cSWim Van Sebroeck /*
109b7e04f8cSWim Van Sebroeck  *	Whack the dog
110b7e04f8cSWim Van Sebroeck  */
111b7e04f8cSWim Van Sebroeck 
112b7e04f8cSWim Van Sebroeck static void wdt_timer_ping(unsigned long data)
113b7e04f8cSWim Van Sebroeck {
114b7e04f8cSWim Van Sebroeck 	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
115b7e04f8cSWim Van Sebroeck 	 * we agree to ping the WDT
116b7e04f8cSWim Van Sebroeck 	 */
117c1cfd1a2SAlan Cox 	if (time_before(jiffies, next_heartbeat)) {
118b7e04f8cSWim Van Sebroeck 		/* Ping the WDT */
119b7e04f8cSWim Van Sebroeck 		spin_lock(&wdt_spinlock);
120b7e04f8cSWim Van Sebroeck 
121b7e04f8cSWim Van Sebroeck 		/* Ping the WDT by reading from WDT_PING */
122b7e04f8cSWim Van Sebroeck 		inb_p(WDT_PING);
123b7e04f8cSWim Van Sebroeck 
124b7e04f8cSWim Van Sebroeck 		/* Re-set the timer interval */
125b7e04f8cSWim Van Sebroeck 		mod_timer(&timer, jiffies + WDT_INTERVAL);
126b7e04f8cSWim Van Sebroeck 
127b7e04f8cSWim Van Sebroeck 		spin_unlock(&wdt_spinlock);
128b7e04f8cSWim Van Sebroeck 
129c1cfd1a2SAlan Cox 	} else
13027c766aaSJoe Perches 		pr_warn("Heartbeat lost! Will not ping the watchdog\n");
131b7e04f8cSWim Van Sebroeck }
132b7e04f8cSWim Van Sebroeck 
133b7e04f8cSWim Van Sebroeck /*
134b7e04f8cSWim Van Sebroeck  * Utility routines
135b7e04f8cSWim Van Sebroeck  */
136b7e04f8cSWim Van Sebroeck 
137b7e04f8cSWim Van Sebroeck static void wdt_change(int writeval)
138b7e04f8cSWim Van Sebroeck {
139b7e04f8cSWim Van Sebroeck 	unsigned long flags;
140b7e04f8cSWim Van Sebroeck 	spin_lock_irqsave(&wdt_spinlock, flags);
141b7e04f8cSWim Van Sebroeck 
142b7e04f8cSWim Van Sebroeck 	/* buy some time */
143b7e04f8cSWim Van Sebroeck 	inb_p(WDT_PING);
144b7e04f8cSWim Van Sebroeck 
145b7e04f8cSWim Van Sebroeck 	/* make W83877F available */
146b7e04f8cSWim Van Sebroeck 	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
147b7e04f8cSWim Van Sebroeck 	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
148b7e04f8cSWim Van Sebroeck 
149b7e04f8cSWim Van Sebroeck 	/* enable watchdog */
150b7e04f8cSWim Van Sebroeck 	outb_p(WDT_REGISTER,    ENABLE_W83877F_PORT);
151b7e04f8cSWim Van Sebroeck 	outb_p(writeval,        ENABLE_W83877F_PORT+1);
152b7e04f8cSWim Van Sebroeck 
153b7e04f8cSWim Van Sebroeck 	/* lock the W8387FF away */
154b7e04f8cSWim Van Sebroeck 	outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
155b7e04f8cSWim Van Sebroeck 
156b7e04f8cSWim Van Sebroeck 	spin_unlock_irqrestore(&wdt_spinlock, flags);
157b7e04f8cSWim Van Sebroeck }
158b7e04f8cSWim Van Sebroeck 
159b7e04f8cSWim Van Sebroeck static void wdt_startup(void)
160b7e04f8cSWim Van Sebroeck {
161b7e04f8cSWim Van Sebroeck 	next_heartbeat = jiffies + (timeout * HZ);
162b7e04f8cSWim Van Sebroeck 
163b7e04f8cSWim Van Sebroeck 	/* Start the timer */
164b7e04f8cSWim Van Sebroeck 	mod_timer(&timer, jiffies + WDT_INTERVAL);
165b7e04f8cSWim Van Sebroeck 
166b7e04f8cSWim Van Sebroeck 	wdt_change(WDT_ENABLE);
167b7e04f8cSWim Van Sebroeck 
16827c766aaSJoe Perches 	pr_info("Watchdog timer is now enabled\n");
169b7e04f8cSWim Van Sebroeck }
170b7e04f8cSWim Van Sebroeck 
171b7e04f8cSWim Van Sebroeck static void wdt_turnoff(void)
172b7e04f8cSWim Van Sebroeck {
173b7e04f8cSWim Van Sebroeck 	/* Stop the timer */
174b7e04f8cSWim Van Sebroeck 	del_timer(&timer);
175b7e04f8cSWim Van Sebroeck 
176b7e04f8cSWim Van Sebroeck 	wdt_change(WDT_DISABLE);
177b7e04f8cSWim Van Sebroeck 
17827c766aaSJoe Perches 	pr_info("Watchdog timer is now disabled...\n");
179b7e04f8cSWim Van Sebroeck }
180b7e04f8cSWim Van Sebroeck 
181b7e04f8cSWim Van Sebroeck static void wdt_keepalive(void)
182b7e04f8cSWim Van Sebroeck {
183b7e04f8cSWim Van Sebroeck 	/* user land ping */
184b7e04f8cSWim Van Sebroeck 	next_heartbeat = jiffies + (timeout * HZ);
185b7e04f8cSWim Van Sebroeck }
186b7e04f8cSWim Van Sebroeck 
187b7e04f8cSWim Van Sebroeck /*
188b7e04f8cSWim Van Sebroeck  * /dev/watchdog handling
189b7e04f8cSWim Van Sebroeck  */
190b7e04f8cSWim Van Sebroeck 
191c1cfd1a2SAlan Cox static ssize_t fop_write(struct file *file, const char __user *buf,
192c1cfd1a2SAlan Cox 						size_t count, loff_t *ppos)
193b7e04f8cSWim Van Sebroeck {
194b7e04f8cSWim Van Sebroeck 	/* See if we got the magic character 'V' and reload the timer */
195c1cfd1a2SAlan Cox 	if (count) {
196c1cfd1a2SAlan Cox 		if (!nowayout) {
197b7e04f8cSWim Van Sebroeck 			size_t ofs;
198b7e04f8cSWim Van Sebroeck 
199c1cfd1a2SAlan Cox 			/* note: just in case someone wrote the magic
200c1cfd1a2SAlan Cox 			   character five months ago... */
201b7e04f8cSWim Van Sebroeck 			wdt_expect_close = 0;
202b7e04f8cSWim Van Sebroeck 
203c1cfd1a2SAlan Cox 			/* scan to see whether or not we got the
204c1cfd1a2SAlan Cox 			   magic character */
205c1cfd1a2SAlan Cox 			for (ofs = 0; ofs != count; ofs++) {
206b7e04f8cSWim Van Sebroeck 				char c;
207b7e04f8cSWim Van Sebroeck 				if (get_user(c, buf + ofs))
208b7e04f8cSWim Van Sebroeck 					return -EFAULT;
209b7e04f8cSWim Van Sebroeck 				if (c == 'V')
210b7e04f8cSWim Van Sebroeck 					wdt_expect_close = 42;
211b7e04f8cSWim Van Sebroeck 			}
212b7e04f8cSWim Van Sebroeck 		}
213b7e04f8cSWim Van Sebroeck 
214b7e04f8cSWim Van Sebroeck 		/* someone wrote to us, we should restart timer */
215b7e04f8cSWim Van Sebroeck 		wdt_keepalive();
216b7e04f8cSWim Van Sebroeck 	}
217b7e04f8cSWim Van Sebroeck 	return count;
218b7e04f8cSWim Van Sebroeck }
219b7e04f8cSWim Van Sebroeck 
220b7e04f8cSWim Van Sebroeck static int fop_open(struct inode *inode, struct file *file)
221b7e04f8cSWim Van Sebroeck {
222b7e04f8cSWim Van Sebroeck 	/* Just in case we're already talking to someone... */
223b7e04f8cSWim Van Sebroeck 	if (test_and_set_bit(0, &wdt_is_open))
224b7e04f8cSWim Van Sebroeck 		return -EBUSY;
225b7e04f8cSWim Van Sebroeck 
226b7e04f8cSWim Van Sebroeck 	/* Good, fire up the show */
227b7e04f8cSWim Van Sebroeck 	wdt_startup();
228b7e04f8cSWim Van Sebroeck 	return nonseekable_open(inode, file);
229b7e04f8cSWim Van Sebroeck }
230b7e04f8cSWim Van Sebroeck 
231b7e04f8cSWim Van Sebroeck static int fop_close(struct inode *inode, struct file *file)
232b7e04f8cSWim Van Sebroeck {
233b7e04f8cSWim Van Sebroeck 	if (wdt_expect_close == 42)
234b7e04f8cSWim Van Sebroeck 		wdt_turnoff();
235b7e04f8cSWim Van Sebroeck 	else {
236b7e04f8cSWim Van Sebroeck 		del_timer(&timer);
23727c766aaSJoe Perches 		pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
238b7e04f8cSWim Van Sebroeck 	}
239b7e04f8cSWim Van Sebroeck 	clear_bit(0, &wdt_is_open);
240b7e04f8cSWim Van Sebroeck 	wdt_expect_close = 0;
241b7e04f8cSWim Van Sebroeck 	return 0;
242b7e04f8cSWim Van Sebroeck }
243b7e04f8cSWim Van Sebroeck 
244c1cfd1a2SAlan Cox static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
245b7e04f8cSWim Van Sebroeck {
246b7e04f8cSWim Van Sebroeck 	void __user *argp = (void __user *)arg;
247b7e04f8cSWim Van Sebroeck 	int __user *p = argp;
248c1cfd1a2SAlan Cox 	static const struct watchdog_info ident = {
249c1cfd1a2SAlan Cox 		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
250c1cfd1a2SAlan Cox 							| WDIOF_MAGICCLOSE,
251b7e04f8cSWim Van Sebroeck 		.firmware_version = 1,
252b7e04f8cSWim Van Sebroeck 		.identity = "W83877F",
253b7e04f8cSWim Van Sebroeck 	};
254b7e04f8cSWim Van Sebroeck 
255c1cfd1a2SAlan Cox 	switch (cmd) {
256b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSUPPORT:
257b7e04f8cSWim Van Sebroeck 		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
258b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSTATUS:
259b7e04f8cSWim Van Sebroeck 	case WDIOC_GETBOOTSTATUS:
260b7e04f8cSWim Van Sebroeck 		return put_user(0, p);
261b7e04f8cSWim Van Sebroeck 	case WDIOC_SETOPTIONS:
262b7e04f8cSWim Van Sebroeck 	{
263b7e04f8cSWim Van Sebroeck 		int new_options, retval = -EINVAL;
264b7e04f8cSWim Van Sebroeck 
265b7e04f8cSWim Van Sebroeck 		if (get_user(new_options, p))
266b7e04f8cSWim Van Sebroeck 			return -EFAULT;
267b7e04f8cSWim Van Sebroeck 
268b7e04f8cSWim Van Sebroeck 		if (new_options & WDIOS_DISABLECARD) {
269b7e04f8cSWim Van Sebroeck 			wdt_turnoff();
270b7e04f8cSWim Van Sebroeck 			retval = 0;
271b7e04f8cSWim Van Sebroeck 		}
272b7e04f8cSWim Van Sebroeck 
273b7e04f8cSWim Van Sebroeck 		if (new_options & WDIOS_ENABLECARD) {
274b7e04f8cSWim Van Sebroeck 			wdt_startup();
275b7e04f8cSWim Van Sebroeck 			retval = 0;
276b7e04f8cSWim Van Sebroeck 		}
277b7e04f8cSWim Van Sebroeck 
278b7e04f8cSWim Van Sebroeck 		return retval;
279b7e04f8cSWim Van Sebroeck 	}
2800c06090cSWim Van Sebroeck 	case WDIOC_KEEPALIVE:
2810c06090cSWim Van Sebroeck 		wdt_keepalive();
2820c06090cSWim Van Sebroeck 		return 0;
283b7e04f8cSWim Van Sebroeck 	case WDIOC_SETTIMEOUT:
284b7e04f8cSWim Van Sebroeck 	{
285b7e04f8cSWim Van Sebroeck 		int new_timeout;
286b7e04f8cSWim Van Sebroeck 
287b7e04f8cSWim Van Sebroeck 		if (get_user(new_timeout, p))
288b7e04f8cSWim Van Sebroeck 			return -EFAULT;
289b7e04f8cSWim Van Sebroeck 
290c1cfd1a2SAlan Cox 		/* arbitrary upper limit */
291c1cfd1a2SAlan Cox 		if (new_timeout < 1 || new_timeout > 3600)
292b7e04f8cSWim Van Sebroeck 			return -EINVAL;
293b7e04f8cSWim Van Sebroeck 
294b7e04f8cSWim Van Sebroeck 		timeout = new_timeout;
295b7e04f8cSWim Van Sebroeck 		wdt_keepalive();
296b7e04f8cSWim Van Sebroeck 		/* Fall through */
297b7e04f8cSWim Van Sebroeck 	}
298b7e04f8cSWim Van Sebroeck 	case WDIOC_GETTIMEOUT:
299b7e04f8cSWim Van Sebroeck 		return put_user(timeout, p);
3000c06090cSWim Van Sebroeck 	default:
3010c06090cSWim Van Sebroeck 		return -ENOTTY;
302b7e04f8cSWim Van Sebroeck 	}
303b7e04f8cSWim Van Sebroeck }
304b7e04f8cSWim Van Sebroeck 
305b7e04f8cSWim Van Sebroeck static const struct file_operations wdt_fops = {
306b7e04f8cSWim Van Sebroeck 	.owner		= THIS_MODULE,
307b7e04f8cSWim Van Sebroeck 	.llseek		= no_llseek,
308b7e04f8cSWim Van Sebroeck 	.write		= fop_write,
309b7e04f8cSWim Van Sebroeck 	.open		= fop_open,
310b7e04f8cSWim Van Sebroeck 	.release	= fop_close,
311c1cfd1a2SAlan Cox 	.unlocked_ioctl	= fop_ioctl,
312b7e04f8cSWim Van Sebroeck };
313b7e04f8cSWim Van Sebroeck 
314b7e04f8cSWim Van Sebroeck static struct miscdevice wdt_miscdev = {
315b7e04f8cSWim Van Sebroeck 	.minor	= WATCHDOG_MINOR,
316b7e04f8cSWim Van Sebroeck 	.name	= "watchdog",
317b7e04f8cSWim Van Sebroeck 	.fops	= &wdt_fops,
318b7e04f8cSWim Van Sebroeck };
319b7e04f8cSWim Van Sebroeck 
320b7e04f8cSWim Van Sebroeck /*
321b7e04f8cSWim Van Sebroeck  *	Notifier for system down
322b7e04f8cSWim Van Sebroeck  */
323b7e04f8cSWim Van Sebroeck 
324b7e04f8cSWim Van Sebroeck static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
325b7e04f8cSWim Van Sebroeck 	void *unused)
326b7e04f8cSWim Van Sebroeck {
327b7e04f8cSWim Van Sebroeck 	if (code == SYS_DOWN || code == SYS_HALT)
328b7e04f8cSWim Van Sebroeck 		wdt_turnoff();
329b7e04f8cSWim Van Sebroeck 	return NOTIFY_DONE;
330b7e04f8cSWim Van Sebroeck }
331b7e04f8cSWim Van Sebroeck 
332b7e04f8cSWim Van Sebroeck /*
333b7e04f8cSWim Van Sebroeck  *	The WDT needs to learn about soft shutdowns in order to
334b7e04f8cSWim Van Sebroeck  *	turn the timebomb registers off.
335b7e04f8cSWim Van Sebroeck  */
336b7e04f8cSWim Van Sebroeck 
337c1cfd1a2SAlan Cox static struct notifier_block wdt_notifier = {
338b7e04f8cSWim Van Sebroeck 	.notifier_call = wdt_notify_sys,
339b7e04f8cSWim Van Sebroeck };
340b7e04f8cSWim Van Sebroeck 
341b7e04f8cSWim Van Sebroeck static void __exit w83877f_wdt_unload(void)
342b7e04f8cSWim Van Sebroeck {
343b7e04f8cSWim Van Sebroeck 	wdt_turnoff();
344b7e04f8cSWim Van Sebroeck 
345b7e04f8cSWim Van Sebroeck 	/* Deregister */
346b7e04f8cSWim Van Sebroeck 	misc_deregister(&wdt_miscdev);
347b7e04f8cSWim Van Sebroeck 
348b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&wdt_notifier);
349b7e04f8cSWim Van Sebroeck 	release_region(WDT_PING, 1);
350b7e04f8cSWim Van Sebroeck 	release_region(ENABLE_W83877F_PORT, 2);
351b7e04f8cSWim Van Sebroeck }
352b7e04f8cSWim Van Sebroeck 
353b7e04f8cSWim Van Sebroeck static int __init w83877f_wdt_init(void)
354b7e04f8cSWim Van Sebroeck {
355b7e04f8cSWim Van Sebroeck 	int rc = -EBUSY;
356b7e04f8cSWim Van Sebroeck 
357c1cfd1a2SAlan Cox 	if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
358b7e04f8cSWim Van Sebroeck 		timeout = WATCHDOG_TIMEOUT;
35927c766aaSJoe Perches 		pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
360b7e04f8cSWim Van Sebroeck 			timeout);
361b7e04f8cSWim Van Sebroeck 	}
362b7e04f8cSWim Van Sebroeck 
363c1cfd1a2SAlan Cox 	if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) {
36427c766aaSJoe Perches 		pr_err("I/O address 0x%04x already in use\n",
365b7e04f8cSWim Van Sebroeck 		       ENABLE_W83877F_PORT);
366b7e04f8cSWim Van Sebroeck 		rc = -EIO;
367b7e04f8cSWim Van Sebroeck 		goto err_out;
368b7e04f8cSWim Van Sebroeck 	}
369b7e04f8cSWim Van Sebroeck 
370c1cfd1a2SAlan Cox 	if (!request_region(WDT_PING, 1, "W8387FF WDT")) {
37127c766aaSJoe Perches 		pr_err("I/O address 0x%04x already in use\n", WDT_PING);
372b7e04f8cSWim Van Sebroeck 		rc = -EIO;
373b7e04f8cSWim Van Sebroeck 		goto err_out_region1;
374b7e04f8cSWim Van Sebroeck 	}
375b7e04f8cSWim Van Sebroeck 
376b7e04f8cSWim Van Sebroeck 	rc = register_reboot_notifier(&wdt_notifier);
377c1cfd1a2SAlan Cox 	if (rc) {
37827c766aaSJoe Perches 		pr_err("cannot register reboot notifier (err=%d)\n", rc);
379c6cb13aeSWim Van Sebroeck 		goto err_out_region2;
380c6cb13aeSWim Van Sebroeck 	}
381c6cb13aeSWim Van Sebroeck 
382c6cb13aeSWim Van Sebroeck 	rc = misc_register(&wdt_miscdev);
383c1cfd1a2SAlan Cox 	if (rc) {
38427c766aaSJoe Perches 		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
385c6cb13aeSWim Van Sebroeck 		       wdt_miscdev.minor, rc);
386c6cb13aeSWim Van Sebroeck 		goto err_out_reboot;
387b7e04f8cSWim Van Sebroeck 	}
388b7e04f8cSWim Van Sebroeck 
38927c766aaSJoe Perches 	pr_info("WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
390b7e04f8cSWim Van Sebroeck 		timeout, nowayout);
391b7e04f8cSWim Van Sebroeck 
392b7e04f8cSWim Van Sebroeck 	return 0;
393b7e04f8cSWim Van Sebroeck 
394c6cb13aeSWim Van Sebroeck err_out_reboot:
395c6cb13aeSWim Van Sebroeck 	unregister_reboot_notifier(&wdt_notifier);
396b7e04f8cSWim Van Sebroeck err_out_region2:
397b7e04f8cSWim Van Sebroeck 	release_region(WDT_PING, 1);
398b7e04f8cSWim Van Sebroeck err_out_region1:
399b7e04f8cSWim Van Sebroeck 	release_region(ENABLE_W83877F_PORT, 2);
400b7e04f8cSWim Van Sebroeck err_out:
401b7e04f8cSWim Van Sebroeck 	return rc;
402b7e04f8cSWim Van Sebroeck }
403b7e04f8cSWim Van Sebroeck 
404b7e04f8cSWim Van Sebroeck module_init(w83877f_wdt_init);
405b7e04f8cSWim Van Sebroeck module_exit(w83877f_wdt_unload);
406b7e04f8cSWim Van Sebroeck 
407b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Scott and Bill Jennings");
408b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
409b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
410b7e04f8cSWim Van Sebroeck MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
411