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