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