1 /*
2  *	IB700 Single Board Computer WDT driver for Linux 2.4.x
3  *
4  *	(c) Copyright 2001 Charles Howes <chowes@vsol.net>
5  *
6  *      Based on advantechwdt.c which is based on acquirewdt.c which
7  *       is based on wdt.c.
8  *
9  *	(c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
10  *
11  *	Based on acquirewdt.c which is based on wdt.c.
12  *	Original copyright messages:
13  *
14  *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
15  *				http://www.redhat.com
16  *
17  *	This program is free software; you can redistribute it and/or
18  *	modify it under the terms of the GNU General Public License
19  *	as published by the Free Software Foundation; either version
20  *	2 of the License, or (at your option) any later version.
21  *
22  *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
23  *	warranty for any of this software. This material is provided
24  *	"AS-IS" and at no charge.
25  *
26  *	(c) Copyright 1995    Alan Cox <alan@redhat.com>
27  *
28  */
29 
30 #include <linux/config.h>
31 #include <linux/module.h>
32 #include <linux/version.h>
33 #include <linux/types.h>
34 #include <linux/errno.h>
35 #include <linux/kernel.h>
36 #include <linux/sched.h>
37 #include <linux/miscdevice.h>
38 #include <linux/watchdog.h>
39 #include <linux/slab.h>
40 #include <linux/ioport.h>
41 #include <linux/fcntl.h>
42 #include <asm/io.h>
43 #include <asm/uaccess.h>
44 #include <asm/system.h>
45 #include <linux/notifier.h>
46 #include <linux/reboot.h>
47 #include <linux/init.h>
48 #include <linux/spinlock.h>
49 #include <linux/smp_lock.h>
50 
51 static int ibwdt_is_open;
52 static spinlock_t ibwdt_lock;
53 static int expect_close = 0;
54 
55 /*
56  *
57  * Watchdog Timer Configuration
58  *
59  * The function of the watchdog timer is to reset the system
60  * automatically and is defined at I/O port 0443H.  To enable the
61  * watchdog timer and allow the system to reset, write I/O port 0443H.
62  * To disable the timer, write I/O port 0441H for the system to stop the
63  * watchdog function.  The timer has a tolerance of 20% for its
64  * intervals.
65  *
66  * The following describes how the timer should be programmed.
67  *
68  * Enabling Watchdog:
69  * MOV AX,000FH (Choose the values from 0 to F)
70  * MOV DX,0443H
71  * OUT DX,AX
72  *
73  * Disabling Watchdog:
74  * MOV AX,000FH (Any value is fine.)
75  * MOV DX,0441H
76  * OUT DX,AX
77  *
78  * Watchdog timer control table:
79  * Level   Value  Time/sec | Level Value Time/sec
80  *   1       F       0     |   9     7      16
81  *   2       E       2     |   10    6      18
82  *   3       D       4     |   11    5      20
83  *   4       C       6     |   12    4      22
84  *   5       B       8     |   13    3      24
85  *   6       A       10    |   14    2      26
86  *   7       9       12    |   15    1      28
87  *   8       8       14    |   16    0      30
88  *
89  */
90 
91 static int wd_times[] = {
92 	30,	/* 0x0 */
93 	28,	/* 0x1 */
94 	26,	/* 0x2 */
95 	24,	/* 0x3 */
96 	22,	/* 0x4 */
97 	20,	/* 0x5 */
98 	18,	/* 0x6 */
99 	16,	/* 0x7 */
100 	14,	/* 0x8 */
101 	12,	/* 0x9 */
102 	10,	/* 0xA */
103 	8,	/* 0xB */
104 	6,	/* 0xC */
105 	4,	/* 0xD */
106 	2,	/* 0xE */
107 	0,	/* 0xF */
108 };
109 
110 #define WDT_STOP 0x441
111 #define WDT_START 0x443
112 
113 /* Default timeout */
114 #define WD_TIMO 0		/* 30 seconds +/- 20%, from table */
115 
116 static int wd_margin = WD_TIMO;
117 
118 #ifdef CONFIG_WATCHDOG_NOWAYOUT
119 static int nowayout = 1;
120 #else
121 static int nowayout = 0;
122 #endif
123 
124 MODULE_PARM(nowayout,"i");
125 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
126 
127 
128 /*
129  *	Kernel methods.
130  */
131 
132 static void
ibwdt_ping(void)133 ibwdt_ping(void)
134 {
135 	/* Write a watchdog value */
136 	outb_p(wd_margin, WDT_START);
137 }
138 
139 static ssize_t
ibwdt_write(struct file * file,const char * buf,size_t count,loff_t * ppos)140 ibwdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
141 {
142 	/*  Can't seek (pwrite) on this device  */
143 	if (ppos != &file->f_pos)
144 		return -ESPIPE;
145 
146 	if (count) {
147 		if (!nowayout) {
148 			size_t i;
149 
150 			/* In case it was set long ago */
151 			expect_close = 0;
152 
153 			for (i = 0; i != count; i++) {
154 				char c;
155 				if (get_user(c, buf + i))
156 					return -EFAULT;
157 				if (c == 'V')
158 					expect_close = 1;
159 			}
160 		}
161 		ibwdt_ping();
162 		return 1;
163 	}
164 	return 0;
165 }
166 
167 static ssize_t
ibwdt_read(struct file * file,char * buf,size_t count,loff_t * ppos)168 ibwdt_read(struct file *file, char *buf, size_t count, loff_t *ppos)
169 {
170 	return -EINVAL;
171 }
172 
173 static int
ibwdt_ioctl(struct inode * inode,struct file * file,unsigned int cmd,unsigned long arg)174 ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
175 	  unsigned long arg)
176 {
177 	int i, new_margin;
178 
179 	static struct watchdog_info ident = {
180 		WDIOF_KEEPALIVEPING |
181 		WDIOF_SETTIMEOUT |
182 		WDIOF_MAGICCLOSE,
183 		1, "IB700 WDT"
184 	};
185 
186 	switch (cmd) {
187 	case WDIOC_GETSUPPORT:
188 	  if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident)))
189 	    return -EFAULT;
190 	  break;
191 
192 	case WDIOC_GETSTATUS:
193 	  if (copy_to_user((int *)arg, &ibwdt_is_open,  sizeof(int)))
194 	    return -EFAULT;
195 	  break;
196 
197 	case WDIOC_KEEPALIVE:
198 	  ibwdt_ping();
199 	  break;
200 
201 	case WDIOC_SETTIMEOUT:
202 	  if (get_user(new_margin, (int *)arg))
203 		  return -EFAULT;
204 	  if ((new_margin < 0) || (new_margin > 30))
205 		  return -EINVAL;
206 	  for (i = 0x0F; i > -1; i--)
207 		  if (wd_times[i] > new_margin)
208 			  break;
209 	  wd_margin = i;
210 	  ibwdt_ping();
211 	  /* Fall */
212 
213 	case WDIOC_GETTIMEOUT:
214 	  return put_user(wd_times[wd_margin], (int *)arg);
215 	  break;
216 
217 	default:
218 	  return -ENOTTY;
219 	}
220 	return 0;
221 }
222 
223 static int
ibwdt_open(struct inode * inode,struct file * file)224 ibwdt_open(struct inode *inode, struct file *file)
225 {
226 	switch (MINOR(inode->i_rdev)) {
227 		case WATCHDOG_MINOR:
228 			spin_lock(&ibwdt_lock);
229 			if (ibwdt_is_open) {
230 				spin_unlock(&ibwdt_lock);
231 				return -EBUSY;
232 			}
233 			/*
234 			 *	Activate
235 			 */
236 
237 			ibwdt_is_open = 1;
238 			ibwdt_ping();
239 			spin_unlock(&ibwdt_lock);
240 			return 0;
241 		default:
242 			return -ENODEV;
243 	}
244 }
245 
246 static int
ibwdt_close(struct inode * inode,struct file * file)247 ibwdt_close(struct inode *inode, struct file *file)
248 {
249 	lock_kernel();
250 	if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) {
251 		spin_lock(&ibwdt_lock);
252 		if (expect_close) {
253 			outb_p(wd_times[wd_margin], WDT_STOP);
254 		} else {
255 			printk(KERN_CRIT "WDT device closed unexpectedly.  WDT will not stop!\n");
256 		}
257 		ibwdt_is_open = 0;
258 		spin_unlock(&ibwdt_lock);
259 	}
260 	unlock_kernel();
261 	return 0;
262 }
263 
264 /*
265  *	Notifier for system down
266  */
267 
268 static int
ibwdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)269 ibwdt_notify_sys(struct notifier_block *this, unsigned long code,
270 	void *unused)
271 {
272 	if (code == SYS_DOWN || code == SYS_HALT) {
273 		/* Turn the WDT off */
274 		outb_p(wd_times[wd_margin], WDT_STOP);
275 	}
276 	return NOTIFY_DONE;
277 }
278 
279 /*
280  *	Kernel Interfaces
281  */
282 
283 static struct file_operations ibwdt_fops = {
284 	owner:		THIS_MODULE,
285 	read:		ibwdt_read,
286 	write:		ibwdt_write,
287 	ioctl:		ibwdt_ioctl,
288 	open:		ibwdt_open,
289 	release:	ibwdt_close,
290 };
291 
292 static struct miscdevice ibwdt_miscdev = {
293 	WATCHDOG_MINOR,
294 	"watchdog",
295 	&ibwdt_fops
296 };
297 
298 /*
299  *	The WDT needs to learn about soft shutdowns in order to
300  *	turn the timebomb registers off.
301  */
302 
303 static struct notifier_block ibwdt_notifier = {
304 	ibwdt_notify_sys,
305 	NULL,
306 	0
307 };
308 
309 static int __init
ibwdt_init(void)310 ibwdt_init(void)
311 {
312 	printk("WDT driver for IB700 single board computer initialising.\n");
313 
314 	spin_lock_init(&ibwdt_lock);
315 	misc_register(&ibwdt_miscdev);
316 #if WDT_START != WDT_STOP
317 	request_region(WDT_STOP, 1, "IB700 WDT");
318 #endif
319 	request_region(WDT_START, 1, "IB700 WDT");
320 	register_reboot_notifier(&ibwdt_notifier);
321 	return 0;
322 }
323 
324 static void __exit
ibwdt_exit(void)325 ibwdt_exit(void)
326 {
327 	misc_deregister(&ibwdt_miscdev);
328 	unregister_reboot_notifier(&ibwdt_notifier);
329 #if WDT_START != WDT_STOP
330 	release_region(WDT_STOP,1);
331 #endif
332 	release_region(WDT_START,1);
333 }
334 
335 module_init(ibwdt_init);
336 module_exit(ibwdt_exit);
337 
338 MODULE_AUTHOR("Charles Howes <chowes@vsol.net>");
339 MODULE_DESCRIPTION("IB700 SBC watchdog driver");
340 MODULE_LICENSE("GPL");
341 
342 /* end of ib700wdt.c */
343