1 /*
2 * Advantech Single Board Computer WDT driver for Linux 2.4.x
3 *
4 * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
5 *
6 * Based on acquirewdt.c which is based on wdt.c.
7 * Original copyright messages:
8 *
9 * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
10 * http://www.redhat.com
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version
15 * 2 of the License, or (at your option) any later version.
16 *
17 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
18 * warranty for any of this software. This material is provided
19 * "AS-IS" and at no charge.
20 *
21 * (c) Copyright 1995 Alan Cox <alan@redhat.com>
22 *
23 * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
24 * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
25 */
26
27 #include <linux/config.h>
28 #include <linux/module.h>
29 #include <linux/types.h>
30 #include <linux/errno.h>
31 #include <linux/kernel.h>
32 #include <linux/miscdevice.h>
33 #include <linux/watchdog.h>
34 #include <linux/ioport.h>
35 #include <linux/fcntl.h>
36 #include <asm/io.h>
37 #include <asm/uaccess.h>
38 #include <asm/system.h>
39 #include <linux/notifier.h>
40 #include <linux/reboot.h>
41 #include <linux/init.h>
42
43 static unsigned long advwdt_is_open;
44 static char adv_expect_close;
45
46 /*
47 * You must set these - there is no sane way to probe for this board.
48 *
49 * To enable or restart, write the timeout value in seconds (1 to 63)
50 * to I/O port wdt_start. To disable, read I/O port wdt_stop.
51 * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but
52 * check your manual (at least the PCA-6159 seems to be different -
53 * the manual says wdt_stop is 0x43, not 0x443).
54 * (0x43 is also a write-only control register for the 8254 timer!)
55 */
56
57 static int wdt_stop = 0x443;
58 static int wdt_start = 0x443;
59
60 static int wd_margin = 60; /* 60 sec default timeout */
61
62 #ifdef CONFIG_WATCHDOG_NOWAYOUT
63 static int nowayout = 1;
64 #else
65 static int nowayout = 0;
66 #endif
67
68 MODULE_PARM(nowayout,"i");
69 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
70
71 /*
72 * Kernel methods.
73 */
74
75 #ifndef MODULE
76
adv_setup(char * str)77 static int __init adv_setup(char *str)
78 {
79 int ints[4];
80
81 str = get_options(str, ARRAY_SIZE(ints), ints);
82
83 if(ints[0] > 0){
84 wdt_stop = ints[1];
85 if(ints[0] > 1)
86 wdt_start = ints[2];
87 }
88
89 return 1;
90 }
91
92 __setup("advwdt=", adv_setup);
93
94 #endif /* !MODULE */
95
96 MODULE_PARM(wdt_stop, "i");
97 MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)");
98 MODULE_PARM(wdt_start, "i");
99 MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)");
100
101 static void
advwdt_ping(void)102 advwdt_ping(void)
103 {
104 /* Write a watchdog value */
105 outb_p(wd_margin, wdt_start);
106 }
107
108 static void
advwdt_disable(void)109 advwdt_disable(void)
110 {
111 inb_p(wdt_stop);
112 }
113
114 static ssize_t
advwdt_write(struct file * file,const char * buf,size_t count,loff_t * ppos)115 advwdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
116 {
117 /* Can't seek (pwrite) on this device */
118 if (ppos != &file->f_pos)
119 return -ESPIPE;
120
121 if (count) {
122 if (!nowayout) {
123 size_t i;
124
125 adv_expect_close = 0;
126
127 for (i = 0; i != count; i++) {
128 char c;
129 if(get_user(c, buf+i))
130 return -EFAULT;
131 if (c == 'V')
132 adv_expect_close = 42;
133 }
134 }
135 advwdt_ping();
136 }
137 return count;
138 }
139
140 static int
advwdt_ioctl(struct inode * inode,struct file * file,unsigned int cmd,unsigned long arg)141 advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
142 unsigned long arg)
143 {
144 int new_margin;
145 static struct watchdog_info ident = {
146 options: WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
147 firmware_version: 0,
148 identity: "Advantech WDT"
149 };
150
151 switch (cmd) {
152 case WDIOC_GETSUPPORT:
153 if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident)))
154 return -EFAULT;
155 break;
156
157 case WDIOC_GETSTATUS:
158 case WDIOC_GETBOOTSTATUS:
159 return put_user(0, (int *)arg);
160
161 case WDIOC_KEEPALIVE:
162 advwdt_ping();
163 break;
164
165 case WDIOC_SETTIMEOUT:
166 if (get_user(new_margin, (int *)arg))
167 return -EFAULT;
168 if ((new_margin < 1) || (new_margin > 63))
169 return -EINVAL;
170 wd_margin = new_margin;
171 advwdt_ping();
172 /* Fall */
173
174 case WDIOC_GETTIMEOUT:
175 return put_user(wd_margin, (int *)arg);
176
177 case WDIOC_SETOPTIONS:
178 {
179 int options, retval = -EINVAL;
180
181 if (get_user(options, (int *)arg))
182 return -EFAULT;
183
184 if (options & WDIOS_DISABLECARD) {
185 advwdt_disable();
186 retval = 0;
187 }
188
189 if (options & WDIOS_ENABLECARD) {
190 advwdt_ping();
191 retval = 0;
192 }
193
194 return retval;
195 }
196
197 default:
198 return -ENOTTY;
199 }
200 return 0;
201 }
202
203 static int
advwdt_open(struct inode * inode,struct file * file)204 advwdt_open(struct inode *inode, struct file *file)
205 {
206 if (test_and_set_bit(0, &advwdt_is_open))
207 return -EBUSY;
208 /*
209 * Activate
210 */
211
212 advwdt_ping();
213 return 0;
214 }
215
216 static int
advwdt_close(struct inode * inode,struct file * file)217 advwdt_close(struct inode *inode, struct file *file)
218 {
219 if (adv_expect_close == 42) {
220 advwdt_disable();
221 } else {
222 printk(KERN_CRIT "advancetechwdt: Unexpected close, not stopping watchdog!\n");
223 advwdt_ping();
224 }
225 clear_bit(0, &advwdt_is_open);
226 adv_expect_close = 0;
227 return 0;
228 }
229
230 /*
231 * Notifier for system down
232 */
233
234 static int
advwdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)235 advwdt_notify_sys(struct notifier_block *this, unsigned long code,
236 void *unused)
237 {
238 if (code == SYS_DOWN || code == SYS_HALT) {
239 /* Turn the WDT off */
240 advwdt_disable();
241 }
242 return NOTIFY_DONE;
243 }
244
245 /*
246 * Kernel Interfaces
247 */
248
249 static struct file_operations advwdt_fops = {
250 owner: THIS_MODULE,
251 llseek: no_llseek,
252 write: advwdt_write,
253 ioctl: advwdt_ioctl,
254 open: advwdt_open,
255 release: advwdt_close,
256 };
257
258 static struct miscdevice advwdt_miscdev = {
259 minor: WATCHDOG_MINOR,
260 name: "watchdog",
261 fops: &advwdt_fops,
262 };
263
264 /*
265 * The WDT needs to learn about soft shutdowns in order to
266 * turn the timebomb registers off.
267 */
268
269 static struct notifier_block advwdt_notifier = {
270 advwdt_notify_sys,
271 NULL,
272 0
273 };
274
275 static int __init
advwdt_init(void)276 advwdt_init(void)
277 {
278 printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n");
279
280 misc_register(&advwdt_miscdev);
281 if(wdt_stop != wdt_start)
282 request_region(wdt_stop, 1, "Advantech WDT");
283 request_region(wdt_start, 1, "Advantech WDT");
284 register_reboot_notifier(&advwdt_notifier);
285 return 0;
286 }
287
288 static void __exit
advwdt_exit(void)289 advwdt_exit(void)
290 {
291 misc_deregister(&advwdt_miscdev);
292 unregister_reboot_notifier(&advwdt_notifier);
293 if(wdt_stop != wdt_start)
294 release_region(wdt_stop,1);
295 release_region(wdt_start,1);
296 }
297
298 module_init(advwdt_init);
299 module_exit(advwdt_exit);
300
301 MODULE_LICENSE("GPL");
302 MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>");
303 MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver");
304 EXPORT_NO_SYMBOLS;
305
306 /* end of advantechwdt.c */
307
308