xref: /linux/drivers/watchdog/wafer5823wdt.c (revision bd490f82)
1d0173278SGuenter Roeck // SPDX-License-Identifier: GPL-2.0+
2b7e04f8cSWim Van Sebroeck /*
3b7e04f8cSWim Van Sebroeck  *	ICP Wafer 5823 Single Board Computer WDT driver
4b7e04f8cSWim Van Sebroeck  *	http://www.icpamerica.com/wafer_5823.php
5b7e04f8cSWim Van Sebroeck  *	May also work on other similar models
6b7e04f8cSWim Van Sebroeck  *
7b7e04f8cSWim Van Sebroeck  *	(c) Copyright 2002 Justin Cormack <justin@street-vision.com>
8b7e04f8cSWim Van Sebroeck  *
9b7e04f8cSWim Van Sebroeck  *	Release 0.02
10b7e04f8cSWim Van Sebroeck  *
11b7e04f8cSWim Van Sebroeck  *	Based on advantechwdt.c which is based on wdt.c.
12b7e04f8cSWim Van Sebroeck  *	Original copyright messages:
13b7e04f8cSWim Van Sebroeck  *
1429fa0586SAlan Cox  *	(c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
1529fa0586SAlan Cox  *						All Rights Reserved.
16b7e04f8cSWim Van Sebroeck  *
17b7e04f8cSWim Van Sebroeck  *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
18b7e04f8cSWim Van Sebroeck  *	warranty for any of this software. This material is provided
19b7e04f8cSWim Van Sebroeck  *	"AS-IS" and at no charge.
20b7e04f8cSWim Van Sebroeck  *
21b7e04f8cSWim Van Sebroeck  *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
22b7e04f8cSWim Van Sebroeck  *
23b7e04f8cSWim Van Sebroeck  */
24b7e04f8cSWim Van Sebroeck 
2527c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2627c766aaSJoe Perches 
27b7e04f8cSWim Van Sebroeck #include <linux/module.h>
28b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>
29b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>
30b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>
31b7e04f8cSWim Van Sebroeck #include <linux/fs.h>
32b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>
33b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>
34b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>
35b7e04f8cSWim Van Sebroeck #include <linux/init.h>
36b7e04f8cSWim Van Sebroeck #include <linux/spinlock.h>
37694b16b2SAlan Cox #include <linux/io.h>
38694b16b2SAlan Cox #include <linux/uaccess.h>
39b7e04f8cSWim Van Sebroeck 
40b7e04f8cSWim Van Sebroeck #define WATCHDOG_NAME "Wafer 5823 WDT"
41b7e04f8cSWim Van Sebroeck #define PFX WATCHDOG_NAME ": "
42b7e04f8cSWim Van Sebroeck #define WD_TIMO 60			/* 60 sec default timeout */
43b7e04f8cSWim Van Sebroeck 
44b7e04f8cSWim Van Sebroeck static unsigned long wafwdt_is_open;
45b7e04f8cSWim Van Sebroeck static char expect_close;
46c7dfd0ccSAlexey Dobriyan static DEFINE_SPINLOCK(wafwdt_lock);
47b7e04f8cSWim Van Sebroeck 
48b7e04f8cSWim Van Sebroeck /*
49b7e04f8cSWim Van Sebroeck  *	You must set these - there is no sane way to probe for this board.
50b7e04f8cSWim Van Sebroeck  *
51b7e04f8cSWim Van Sebroeck  *	To enable, write the timeout value in seconds (1 to 255) to I/O
52b7e04f8cSWim Van Sebroeck  *	port WDT_START, then read the port to start the watchdog. To pat
53b7e04f8cSWim Van Sebroeck  *	the dog, read port WDT_STOP to stop the timer, then read WDT_START
54b7e04f8cSWim Van Sebroeck  *	to restart it again.
55b7e04f8cSWim Van Sebroeck  */
56b7e04f8cSWim Van Sebroeck 
57b7e04f8cSWim Van Sebroeck static int wdt_stop = 0x843;
58b7e04f8cSWim Van Sebroeck static int wdt_start = 0x443;
59b7e04f8cSWim Van Sebroeck 
60b7e04f8cSWim Van Sebroeck static int timeout = WD_TIMO;  /* in seconds */
61b7e04f8cSWim Van Sebroeck module_param(timeout, int, 0);
62694b16b2SAlan Cox MODULE_PARM_DESC(timeout,
63694b16b2SAlan Cox 		"Watchdog timeout in seconds. 1 <= timeout <= 255, default="
64694b16b2SAlan Cox 				__MODULE_STRING(WD_TIMO) ".");
65b7e04f8cSWim Van Sebroeck 
6686a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
6786a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
68694b16b2SAlan Cox MODULE_PARM_DESC(nowayout,
69694b16b2SAlan Cox 		"Watchdog cannot be stopped once started (default="
70694b16b2SAlan Cox 				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
71b7e04f8cSWim Van Sebroeck 
wafwdt_ping(void)72b7e04f8cSWim Van Sebroeck static void wafwdt_ping(void)
73b7e04f8cSWim Van Sebroeck {
74b7e04f8cSWim Van Sebroeck 	/* pat watchdog */
75b7e04f8cSWim Van Sebroeck 	spin_lock(&wafwdt_lock);
76b7e04f8cSWim Van Sebroeck 	inb_p(wdt_stop);
77b7e04f8cSWim Van Sebroeck 	inb_p(wdt_start);
78b7e04f8cSWim Van Sebroeck 	spin_unlock(&wafwdt_lock);
79b7e04f8cSWim Van Sebroeck }
80b7e04f8cSWim Van Sebroeck 
wafwdt_start(void)81b7e04f8cSWim Van Sebroeck static void wafwdt_start(void)
82b7e04f8cSWim Van Sebroeck {
83b7e04f8cSWim Van Sebroeck 	/* start up watchdog */
84b7e04f8cSWim Van Sebroeck 	outb_p(timeout, wdt_start);
85b7e04f8cSWim Van Sebroeck 	inb_p(wdt_start);
86b7e04f8cSWim Van Sebroeck }
87b7e04f8cSWim Van Sebroeck 
wafwdt_stop(void)887944d3a5SWim Van Sebroeck static void wafwdt_stop(void)
89b7e04f8cSWim Van Sebroeck {
90b7e04f8cSWim Van Sebroeck 	/* stop watchdog */
91b7e04f8cSWim Van Sebroeck 	inb_p(wdt_stop);
92b7e04f8cSWim Van Sebroeck }
93b7e04f8cSWim Van Sebroeck 
wafwdt_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)94694b16b2SAlan Cox static ssize_t wafwdt_write(struct file *file, const char __user *buf,
95694b16b2SAlan Cox 						size_t count, loff_t *ppos)
96b7e04f8cSWim Van Sebroeck {
97b7e04f8cSWim Van Sebroeck 	/* See if we got the magic character 'V' and reload the timer */
98b7e04f8cSWim Van Sebroeck 	if (count) {
99b7e04f8cSWim Van Sebroeck 		if (!nowayout) {
100b7e04f8cSWim Van Sebroeck 			size_t i;
101b7e04f8cSWim Van Sebroeck 
102b7e04f8cSWim Van Sebroeck 			/* In case it was set long ago */
103b7e04f8cSWim Van Sebroeck 			expect_close = 0;
104b7e04f8cSWim Van Sebroeck 
105694b16b2SAlan Cox 			/* scan to see whether or not we got the magic
106694b16b2SAlan Cox 			   character */
107b7e04f8cSWim Van Sebroeck 			for (i = 0; i != count; i++) {
108b7e04f8cSWim Van Sebroeck 				char c;
109b7e04f8cSWim Van Sebroeck 				if (get_user(c, buf + i))
110b7e04f8cSWim Van Sebroeck 					return -EFAULT;
111b7e04f8cSWim Van Sebroeck 				if (c == 'V')
112b7e04f8cSWim Van Sebroeck 					expect_close = 42;
113b7e04f8cSWim Van Sebroeck 			}
114b7e04f8cSWim Van Sebroeck 		}
115694b16b2SAlan Cox 		/* Well, anyhow someone wrote to us, we should
116694b16b2SAlan Cox 		   return that favour */
117b7e04f8cSWim Van Sebroeck 		wafwdt_ping();
118b7e04f8cSWim Van Sebroeck 	}
119b7e04f8cSWim Van Sebroeck 	return count;
120b7e04f8cSWim Van Sebroeck }
121b7e04f8cSWim Van Sebroeck 
wafwdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)122694b16b2SAlan Cox static long wafwdt_ioctl(struct file *file, unsigned int cmd,
123b7e04f8cSWim Van Sebroeck 							unsigned long arg)
124b7e04f8cSWim Van Sebroeck {
125b7e04f8cSWim Van Sebroeck 	int new_timeout;
126b7e04f8cSWim Van Sebroeck 	void __user *argp = (void __user *)arg;
127b7e04f8cSWim Van Sebroeck 	int __user *p = argp;
128694b16b2SAlan Cox 	static const struct watchdog_info ident = {
129694b16b2SAlan Cox 		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
130694b16b2SAlan Cox 							WDIOF_MAGICCLOSE,
131b7e04f8cSWim Van Sebroeck 		.firmware_version = 1,
132b7e04f8cSWim Van Sebroeck 		.identity = "Wafer 5823 WDT",
133b7e04f8cSWim Van Sebroeck 	};
134b7e04f8cSWim Van Sebroeck 
135b7e04f8cSWim Van Sebroeck 	switch (cmd) {
136b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSUPPORT:
137b7e04f8cSWim Van Sebroeck 		if (copy_to_user(argp, &ident, sizeof(ident)))
138b7e04f8cSWim Van Sebroeck 			return -EFAULT;
139b7e04f8cSWim Van Sebroeck 		break;
140b7e04f8cSWim Van Sebroeck 
141b7e04f8cSWim Van Sebroeck 	case WDIOC_GETSTATUS:
142b7e04f8cSWim Van Sebroeck 	case WDIOC_GETBOOTSTATUS:
143b7e04f8cSWim Van Sebroeck 		return put_user(0, p);
144b7e04f8cSWim Van Sebroeck 
145b7e04f8cSWim Van Sebroeck 	case WDIOC_SETOPTIONS:
146b7e04f8cSWim Van Sebroeck 	{
147b7e04f8cSWim Van Sebroeck 		int options, retval = -EINVAL;
148b7e04f8cSWim Van Sebroeck 
149b7e04f8cSWim Van Sebroeck 		if (get_user(options, p))
150b7e04f8cSWim Van Sebroeck 			return -EFAULT;
151b7e04f8cSWim Van Sebroeck 
152b7e04f8cSWim Van Sebroeck 		if (options & WDIOS_DISABLECARD) {
1538a062ac6SAxel Lin 			wafwdt_stop();
154b7e04f8cSWim Van Sebroeck 			retval = 0;
155b7e04f8cSWim Van Sebroeck 		}
156b7e04f8cSWim Van Sebroeck 
157b7e04f8cSWim Van Sebroeck 		if (options & WDIOS_ENABLECARD) {
1588a062ac6SAxel Lin 			wafwdt_start();
159b7e04f8cSWim Van Sebroeck 			retval = 0;
160b7e04f8cSWim Van Sebroeck 		}
161b7e04f8cSWim Van Sebroeck 
162b7e04f8cSWim Van Sebroeck 		return retval;
163b7e04f8cSWim Van Sebroeck 	}
164b7e04f8cSWim Van Sebroeck 
1650c06090cSWim Van Sebroeck 	case WDIOC_KEEPALIVE:
1660c06090cSWim Van Sebroeck 		wafwdt_ping();
1670c06090cSWim Van Sebroeck 		break;
1680c06090cSWim Van Sebroeck 
1690c06090cSWim Van Sebroeck 	case WDIOC_SETTIMEOUT:
1700c06090cSWim Van Sebroeck 		if (get_user(new_timeout, p))
1710c06090cSWim Van Sebroeck 			return -EFAULT;
1720c06090cSWim Van Sebroeck 		if ((new_timeout < 1) || (new_timeout > 255))
1730c06090cSWim Van Sebroeck 			return -EINVAL;
1740c06090cSWim Van Sebroeck 		timeout = new_timeout;
1750c06090cSWim Van Sebroeck 		wafwdt_stop();
1760c06090cSWim Van Sebroeck 		wafwdt_start();
177*bd490f82SGustavo A. R. Silva 		fallthrough;
1780c06090cSWim Van Sebroeck 	case WDIOC_GETTIMEOUT:
1790c06090cSWim Van Sebroeck 		return put_user(timeout, p);
1800c06090cSWim Van Sebroeck 
181b7e04f8cSWim Van Sebroeck 	default:
182b7e04f8cSWim Van Sebroeck 		return -ENOTTY;
183b7e04f8cSWim Van Sebroeck 	}
184b7e04f8cSWim Van Sebroeck 	return 0;
185b7e04f8cSWim Van Sebroeck }
186b7e04f8cSWim Van Sebroeck 
wafwdt_open(struct inode * inode,struct file * file)187b7e04f8cSWim Van Sebroeck static int wafwdt_open(struct inode *inode, struct file *file)
188b7e04f8cSWim Van Sebroeck {
189b7e04f8cSWim Van Sebroeck 	if (test_and_set_bit(0, &wafwdt_is_open))
190b7e04f8cSWim Van Sebroeck 		return -EBUSY;
191b7e04f8cSWim Van Sebroeck 
192b7e04f8cSWim Van Sebroeck 	/*
193b7e04f8cSWim Van Sebroeck 	 *      Activate
194b7e04f8cSWim Van Sebroeck 	 */
195b7e04f8cSWim Van Sebroeck 	wafwdt_start();
196c5bf68feSKirill Smelkov 	return stream_open(inode, file);
197b7e04f8cSWim Van Sebroeck }
198b7e04f8cSWim Van Sebroeck 
wafwdt_close(struct inode * inode,struct file * file)1997944d3a5SWim Van Sebroeck static int wafwdt_close(struct inode *inode, struct file *file)
200b7e04f8cSWim Van Sebroeck {
201694b16b2SAlan Cox 	if (expect_close == 42)
202b7e04f8cSWim Van Sebroeck 		wafwdt_stop();
203694b16b2SAlan Cox 	else {
20427c766aaSJoe Perches 		pr_crit("WDT device closed unexpectedly.  WDT will not stop!\n");
205b7e04f8cSWim Van Sebroeck 		wafwdt_ping();
206b7e04f8cSWim Van Sebroeck 	}
207b7e04f8cSWim Van Sebroeck 	clear_bit(0, &wafwdt_is_open);
208b7e04f8cSWim Van Sebroeck 	expect_close = 0;
209b7e04f8cSWim Van Sebroeck 	return 0;
210b7e04f8cSWim Van Sebroeck }
211b7e04f8cSWim Van Sebroeck 
212b7e04f8cSWim Van Sebroeck /*
213b7e04f8cSWim Van Sebroeck  *	Notifier for system down
214b7e04f8cSWim Van Sebroeck  */
215b7e04f8cSWim Van Sebroeck 
wafwdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)216694b16b2SAlan Cox static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code,
217694b16b2SAlan Cox 								void *unused)
218b7e04f8cSWim Van Sebroeck {
219694b16b2SAlan Cox 	if (code == SYS_DOWN || code == SYS_HALT)
220b7e04f8cSWim Van Sebroeck 		wafwdt_stop();
221b7e04f8cSWim Van Sebroeck 	return NOTIFY_DONE;
222b7e04f8cSWim Van Sebroeck }
223b7e04f8cSWim Van Sebroeck 
224b7e04f8cSWim Van Sebroeck /*
225b7e04f8cSWim Van Sebroeck  *	Kernel Interfaces
226b7e04f8cSWim Van Sebroeck  */
227b7e04f8cSWim Van Sebroeck 
228b7e04f8cSWim Van Sebroeck static const struct file_operations wafwdt_fops = {
229b7e04f8cSWim Van Sebroeck 	.owner		= THIS_MODULE,
230b7e04f8cSWim Van Sebroeck 	.llseek		= no_llseek,
231b7e04f8cSWim Van Sebroeck 	.write		= wafwdt_write,
232694b16b2SAlan Cox 	.unlocked_ioctl	= wafwdt_ioctl,
233b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
234b7e04f8cSWim Van Sebroeck 	.open		= wafwdt_open,
235b7e04f8cSWim Van Sebroeck 	.release	= wafwdt_close,
236b7e04f8cSWim Van Sebroeck };
237b7e04f8cSWim Van Sebroeck 
238b7e04f8cSWim Van Sebroeck static struct miscdevice wafwdt_miscdev = {
239b7e04f8cSWim Van Sebroeck 	.minor	= WATCHDOG_MINOR,
240b7e04f8cSWim Van Sebroeck 	.name	= "watchdog",
241b7e04f8cSWim Van Sebroeck 	.fops	= &wafwdt_fops,
242b7e04f8cSWim Van Sebroeck };
243b7e04f8cSWim Van Sebroeck 
244b7e04f8cSWim Van Sebroeck /*
245b7e04f8cSWim Van Sebroeck  *	The WDT needs to learn about soft shutdowns in order to
246b7e04f8cSWim Van Sebroeck  *	turn the timebomb registers off.
247b7e04f8cSWim Van Sebroeck  */
248b7e04f8cSWim Van Sebroeck 
249b7e04f8cSWim Van Sebroeck static struct notifier_block wafwdt_notifier = {
250b7e04f8cSWim Van Sebroeck 	.notifier_call = wafwdt_notify_sys,
251b7e04f8cSWim Van Sebroeck };
252b7e04f8cSWim Van Sebroeck 
wafwdt_init(void)253b7e04f8cSWim Van Sebroeck static int __init wafwdt_init(void)
254b7e04f8cSWim Van Sebroeck {
255b7e04f8cSWim Van Sebroeck 	int ret;
256b7e04f8cSWim Van Sebroeck 
25727c766aaSJoe Perches 	pr_info("WDT driver for Wafer 5823 single board computer initialising\n");
258b7e04f8cSWim Van Sebroeck 
259b7e04f8cSWim Van Sebroeck 	if (timeout < 1 || timeout > 255) {
260b7e04f8cSWim Van Sebroeck 		timeout = WD_TIMO;
26127c766aaSJoe Perches 		pr_info("timeout value must be 1 <= x <= 255, using %d\n",
262b7e04f8cSWim Van Sebroeck 			timeout);
263b7e04f8cSWim Van Sebroeck 	}
264b7e04f8cSWim Van Sebroeck 
265b7e04f8cSWim Van Sebroeck 	if (wdt_stop != wdt_start) {
266b7e04f8cSWim Van Sebroeck 		if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
26727c766aaSJoe Perches 			pr_err("I/O address 0x%04x already in use\n", wdt_stop);
268b7e04f8cSWim Van Sebroeck 			ret = -EIO;
269b7e04f8cSWim Van Sebroeck 			goto error;
270b7e04f8cSWim Van Sebroeck 		}
271b7e04f8cSWim Van Sebroeck 	}
272b7e04f8cSWim Van Sebroeck 
273b7e04f8cSWim Van Sebroeck 	if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
27427c766aaSJoe Perches 		pr_err("I/O address 0x%04x already in use\n", wdt_start);
275b7e04f8cSWim Van Sebroeck 		ret = -EIO;
276b7e04f8cSWim Van Sebroeck 		goto error2;
277b7e04f8cSWim Van Sebroeck 	}
278b7e04f8cSWim Van Sebroeck 
279b7e04f8cSWim Van Sebroeck 	ret = register_reboot_notifier(&wafwdt_notifier);
280b7e04f8cSWim Van Sebroeck 	if (ret != 0) {
28127c766aaSJoe Perches 		pr_err("cannot register reboot notifier (err=%d)\n", ret);
282b7e04f8cSWim Van Sebroeck 		goto error3;
283b7e04f8cSWim Van Sebroeck 	}
284b7e04f8cSWim Van Sebroeck 
285b7e04f8cSWim Van Sebroeck 	ret = misc_register(&wafwdt_miscdev);
286b7e04f8cSWim Van Sebroeck 	if (ret != 0) {
28727c766aaSJoe Perches 		pr_err("cannot register miscdev on minor=%d (err=%d)\n",
288b7e04f8cSWim Van Sebroeck 		       WATCHDOG_MINOR, ret);
289b7e04f8cSWim Van Sebroeck 		goto error4;
290b7e04f8cSWim Van Sebroeck 	}
291b7e04f8cSWim Van Sebroeck 
29227c766aaSJoe Perches 	pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
293b7e04f8cSWim Van Sebroeck 		timeout, nowayout);
294b7e04f8cSWim Van Sebroeck 
295b7e04f8cSWim Van Sebroeck 	return ret;
296b7e04f8cSWim Van Sebroeck error4:
297b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&wafwdt_notifier);
298b7e04f8cSWim Van Sebroeck error3:
299b7e04f8cSWim Van Sebroeck 	release_region(wdt_start, 1);
300b7e04f8cSWim Van Sebroeck error2:
301b7e04f8cSWim Van Sebroeck 	if (wdt_stop != wdt_start)
302b7e04f8cSWim Van Sebroeck 		release_region(wdt_stop, 1);
303b7e04f8cSWim Van Sebroeck error:
304b7e04f8cSWim Van Sebroeck 	return ret;
305b7e04f8cSWim Van Sebroeck }
306b7e04f8cSWim Van Sebroeck 
wafwdt_exit(void)307b7e04f8cSWim Van Sebroeck static void __exit wafwdt_exit(void)
308b7e04f8cSWim Van Sebroeck {
309b7e04f8cSWim Van Sebroeck 	misc_deregister(&wafwdt_miscdev);
310b7e04f8cSWim Van Sebroeck 	unregister_reboot_notifier(&wafwdt_notifier);
311b7e04f8cSWim Van Sebroeck 	if (wdt_stop != wdt_start)
312b7e04f8cSWim Van Sebroeck 		release_region(wdt_stop, 1);
313b7e04f8cSWim Van Sebroeck 	release_region(wdt_start, 1);
314b7e04f8cSWim Van Sebroeck }
315b7e04f8cSWim Van Sebroeck 
316b7e04f8cSWim Van Sebroeck module_init(wafwdt_init);
317b7e04f8cSWim Van Sebroeck module_exit(wafwdt_exit);
318b7e04f8cSWim Van Sebroeck 
319b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Justin Cormack");
320b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");
321b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
322b7e04f8cSWim Van Sebroeck 
323b7e04f8cSWim Van Sebroeck /* end of wafer5823wdt.c */
324