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