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