3 * by Ken Hollis (khollis@bitgate.com)
5 * Permission granted from Simon Machell (73244.1270@compuserve.com)
6 * Written for the Linux Kernel, and GPLed by Ken Hollis
8 * 960107 Added request_region routines, modulized the whole thing.
9 * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
11 * 960216 Added eof marker on the file, and changed verbose messages.
12 * 960716 Made functional and cosmetic changes to the source for
13 * inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
14 * 960717 Removed read/seek routines, replaced with ioctl. Also, added
15 * check_region command due to Alan's suggestion.
16 * 960821 Made changes to compile in newer 2.0.x kernels. Added
17 * "cold reboot sense" entry.
18 * 960825 Made a few changes to code, deleted some defines and made
19 * typedefs to replace them. Made heartbeat reset only available
20 * via ioctl, and removed the write routine.
21 * 960828 Added new items for PC Watchdog Rev.C card.
22 * 960829 Changed around all of the IOCTLs, added new features,
23 * added watchdog disable/re-enable routines. Added firmware
24 * version reporting. Added read routine for temperature.
25 * Removed some extra defines, added an autodetect Revision
27 * 961006 Revised some documentation, fixed some cosmetic bugs. Made
28 * drivers to panic the system if it's overheating at bootup.
29 * 961118 Changed some verbiage on some of the output, tidied up
30 * code bits, and added compatibility to 2.1.x.
31 * 970912 Enabled board on open and disable on close.
32 * 971107 Took account of recent VFS changes (broke read).
33 * 971210 Disable board on initialisation in case board already ticking.
34 * 971222 Changed open/close for temperature handling
35 * Michael Meskes <meskes@debian.org>.
36 * 980112 Used minor numbers from include/linux/miscdevice.h
37 * 990403 Clear reset status after reading control status register in
38 * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
39 * 990605 Made changes to code to support Firmware 1.22a, added
40 * fairly useless proc entry.
41 * 990610 removed said useless proc code for the merge <alan>
42 * 000403 Removed last traces of proc code. <davej>
45 #include <linux/module.h>
47 #include <linux/types.h>
48 #include <linux/errno.h>
49 #include <linux/sched.h>
50 #include <linux/tty.h>
51 #include <linux/timer.h>
52 #include <linux/config.h>
53 #include <linux/kernel.h>
54 #include <linux/wait.h>
55 #include <linux/string.h>
56 #include <linux/malloc.h>
57 #include <linux/ioport.h>
58 #include <linux/delay.h>
59 #include <linux/miscdevice.h>
62 #include <linux/watchdog.h>
63 #include <linux/init.h>
64 #include <linux/proc_fs.h>
65 #include <linux/spinlock.h>
66 #include <linux/smp_lock.h>
68 #include <asm/uaccess.h>
72 * These are the auto-probe addresses available.
74 * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
75 * Revision A has an address range of 2 addresses, while Revision C has 3.
77 static int pcwd_ioports
[] = { 0x270, 0x350, 0x370, 0x000 };
79 #define WD_VER "1.10 (06/05/99)"
82 * It should be noted that PCWD_REVISION_B was removed because A and B
83 * are essentially the same types of card, with the exception that B
84 * has temperature reporting. Since I didn't receive a Rev.B card,
85 * the Rev.B card is not supported. (It's a good thing too, as they
86 * are no longer in production.)
88 #define PCWD_REVISION_A 1
89 #define PCWD_REVISION_C 2
91 #define WD_TIMEOUT 3 /* 1 1/2 seconds for a timeout */
94 * These are the defines for the PC Watchdog card, revision A.
96 #define WD_WDRST 0x01 /* Previously reset state */
97 #define WD_T110 0x02 /* Temperature overheat sense */
98 #define WD_HRTBT 0x04 /* Heartbeat sense */
99 #define WD_RLY2 0x08 /* External relay triggered */
100 #define WD_SRLY2 0x80 /* Software external relay triggered */
102 static int current_readport
, revision
, temp_panic
;
103 static int is_open
, initial_status
, supports_temp
, mode_debug
;
104 static spinlock_t io_lock
;
109 * This routine checks the "current_readport" to see if the card lies there.
110 * If it does, it returns accordingly.
112 static int __init
pcwd_checkcard(void)
114 int card_dat
, prev_card_dat
, found
= 0, count
= 0, done
= 0;
116 /* As suggested by Alan Cox - this is a safety measure. */
117 if (check_region(current_readport
, 4)) {
118 printk("pcwd: Port 0x%x unavailable.\n", current_readport
);
123 prev_card_dat
= 0x00;
125 prev_card_dat
= inb(current_readport
);
126 if (prev_card_dat
== 0xFF)
129 while(count
< WD_TIMEOUT
) {
131 /* Read the raw card data from the port, and strip off the
134 card_dat
= inb_p(current_readport
);
137 /* Sleep 1/2 second (or 500000 microseconds :) */
142 /* If there's a heart beat in both instances, then this means we
143 found our card. This also means that either the card was
144 previously reset, or the computer was power-cycled. */
146 if ((card_dat
& WD_HRTBT
) && (prev_card_dat
& WD_HRTBT
) &&
153 /* If the card data is exactly the same as the previous card data,
154 it's safe to assume that we should check again. The manual says
155 that the heart beat will change every second (or the bit will
156 toggle), and this can be used to see if the card is there. If
157 the card was powered up with a cold boot, then the card will
158 not start blinking until 2.5 minutes after a reboot, so this
159 bit will stay at 1. */
161 if ((card_dat
== prev_card_dat
) && (!done
)) {
166 /* If the card data is toggling any bits, this means that the heart
167 beat was detected, or something else about the card is set. */
169 if ((card_dat
!= prev_card_dat
) && (!done
)) {
175 /* Otherwise something else strange happened. */
181 return((found
) ? 1 : 0);
184 void pcwd_showprevstate(void)
186 int card_status
= 0x0000;
188 if (revision
== PCWD_REVISION_A
)
189 initial_status
= card_status
= inb(current_readport
);
191 initial_status
= card_status
= inb(current_readport
+ 1);
192 outb_p(0x00, current_readport
+ 1); /* clear reset status */
195 if (revision
== PCWD_REVISION_A
) {
196 if (card_status
& WD_WDRST
)
197 printk("pcwd: Previous reboot was caused by the card.\n");
199 if (card_status
& WD_T110
) {
200 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
201 panic("pcwd: CPU Overheat.\n");
204 if ((!(card_status
& WD_WDRST
)) &&
205 (!(card_status
& WD_T110
)))
206 printk("pcwd: Cold boot sense.\n");
208 if (card_status
& 0x01)
209 printk("pcwd: Previous reboot was caused by the card.\n");
211 if (card_status
& 0x04) {
212 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
213 panic("pcwd: CPU Overheat.\n");
216 if ((!(card_status
& 0x01)) &&
217 (!(card_status
& 0x04)))
218 printk("pcwd: Cold boot sense.\n");
222 static void pcwd_send_heartbeat(void)
226 wdrst_stat
= inb_p(current_readport
);
229 wdrst_stat
|= WD_WDRST
;
231 if (revision
== PCWD_REVISION_A
)
232 outb_p(wdrst_stat
, current_readport
+ 1);
234 outb_p(wdrst_stat
, current_readport
);
237 static int pcwd_ioctl(struct inode
*inode
, struct file
*file
,
238 unsigned int cmd
, unsigned long arg
)
241 static struct watchdog_info ident
=
243 WDIOF_OVERHEAT
|WDIOF_CARDRESET
,
252 case WDIOC_GETSUPPORT
:
253 i
= copy_to_user((void*)arg
, &ident
, sizeof(ident
));
254 return i
? -EFAULT
: 0;
256 case WDIOC_GETSTATUS
:
258 if (revision
== PCWD_REVISION_A
)
259 cdat
= inb(current_readport
);
261 cdat
= inb(current_readport
+ 1 );
262 spin_unlock(&io_lock
);
265 if (revision
== PCWD_REVISION_A
)
268 rv
|= WDIOF_CARDRESET
;
272 rv
|= WDIOF_OVERHEAT
;
275 panic("pcwd: Temperature overheat trip!\n");
281 rv
|= WDIOF_CARDRESET
;
285 rv
|= WDIOF_OVERHEAT
;
288 panic("pcwd: Temperature overheat trip!\n");
292 if(put_user(rv
, (int *) arg
))
296 case WDIOC_GETBOOTSTATUS
:
299 if (revision
== PCWD_REVISION_A
)
301 if (initial_status
& WD_WDRST
)
302 rv
|= WDIOF_CARDRESET
;
304 if (initial_status
& WD_T110
)
305 rv
|= WDIOF_OVERHEAT
;
309 if (initial_status
& 0x01)
310 rv
|= WDIOF_CARDRESET
;
312 if (initial_status
& 0x04)
313 rv
|= WDIOF_OVERHEAT
;
316 if(put_user(rv
, (int *) arg
))
323 if ((supports_temp
) && (mode_debug
== 0))
326 rv
= inb(current_readport
);
327 spin_unlock(&io_lock
);
328 if(put_user(rv
, (int*) arg
))
330 } else if(put_user(rv
, (int*) arg
))
334 case WDIOC_SETOPTIONS
:
335 if (revision
== PCWD_REVISION_C
)
337 if(copy_from_user(&rv
, (int*) arg
, sizeof(int)))
340 if (rv
& WDIOS_DISABLECARD
)
343 outb_p(0xA5, current_readport
+ 3);
344 outb_p(0xA5, current_readport
+ 3);
345 cdat
= inb_p(current_readport
+ 2);
346 spin_unlock(&io_lock
);
347 if ((cdat
& 0x10) == 0)
349 printk("pcwd: Could not disable card.\n");
356 if (rv
& WDIOS_ENABLECARD
)
359 outb_p(0x00, current_readport
+ 3);
360 cdat
= inb_p(current_readport
+ 2);
361 spin_unlock(&io_lock
);
364 printk("pcwd: Could not enable card.\n");
370 if (rv
& WDIOS_TEMPPANIC
)
377 case WDIOC_KEEPALIVE
:
378 pcwd_send_heartbeat();
385 static ssize_t
pcwd_write(struct file
*file
, const char *buf
, size_t len
,
388 /* Can't seek (pwrite) on this device */
389 if (ppos
!= &file
->f_pos
)
394 pcwd_send_heartbeat();
400 static int pcwd_open(struct inode
*ino
, struct file
*filep
)
402 switch (MINOR(ino
->i_rdev
))
408 /* Enable the port */
409 if (revision
== PCWD_REVISION_C
)
412 outb_p(0x00, current_readport
+ 3);
413 spin_unlock(&io_lock
);
424 static ssize_t
pcwd_read(struct file
*file
, char *buf
, size_t count
,
430 /* Can't seek (pread) on this device */
431 if (ppos
!= &file
->f_pos
)
433 switch(MINOR(file
->f_dentry
->d_inode
->i_rdev
))
437 * Convert metric to Fahrenheit, since this was
438 * the decided 'standard' for this return value.
441 c
= inb(current_readport
);
442 cp
= (c
* 9 / 5) + 32;
443 if(copy_to_user(buf
, &cp
, 1))
451 static int pcwd_close(struct inode
*ino
, struct file
*filep
)
453 if (MINOR(ino
->i_rdev
)==WATCHDOG_MINOR
)
457 #ifndef CONFIG_WATCHDOG_NOWAYOUT
458 /* Disable the board */
459 if (revision
== PCWD_REVISION_C
) {
461 outb_p(0xA5, current_readport
+ 3);
462 outb_p(0xA5, current_readport
+ 3);
463 spin_unlock(&io_lock
);
471 static inline void get_support(void)
473 if (inb(current_readport
) != 0xF0)
477 static inline int get_revision(void)
479 int r
= PCWD_REVISION_C
;
482 if ((inb(current_readport
+ 2) == 0xFF) ||
483 (inb(current_readport
+ 3) == 0xFF))
485 spin_unlock(&io_lock
);
490 static int __init
send_command(int cmd
)
494 outb_p(cmd
, current_readport
+ 2);
497 i
= inb(current_readport
);
498 i
= inb(current_readport
);
503 static inline char *get_firmware(void)
505 int i
, found
= 0, count
= 0, one
, ten
, hund
, minor
;
508 ret
= kmalloc(6, GFP_KERNEL
);
510 while((count
< 3) && (!found
)) {
511 outb_p(0x80, current_readport
+ 2);
512 i
= inb(current_readport
);
517 outb_p(0x00, current_readport
+ 2);
526 one
= send_command(0x81);
527 ten
= send_command(0x82);
528 hund
= send_command(0x83);
529 minor
= send_command(0x84);
533 sprintf(ret
, "%c.%c%c%c", one
, ten
, hund
, minor
);
535 sprintf(ret
, "ERROR");
540 static void debug_off(void)
542 outb_p(0x00, current_readport
+ 2);
546 static struct file_operations pcwd_fops
= {
555 static struct miscdevice pcwd_miscdev
= {
561 static struct miscdevice temp_miscdev
= {
567 static int __init
pcwatchdog_init(void)
570 spin_lock_init(&io_lock
);
572 revision
= PCWD_REVISION_A
;
574 printk("pcwd: v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER
);
576 /* Initial variables */
581 initial_status
= 0x0000;
584 for (i
= 0; pcwd_ioports
[i
] != 0; i
++) {
585 current_readport
= pcwd_ioports
[i
];
587 if (pcwd_checkcard()) {
594 printk("pcwd: No card detected, or port not available.\n");
600 current_readport
= PCWD_BLIND
;
604 revision
= get_revision();
606 if (revision
== PCWD_REVISION_A
)
607 printk("pcwd: PC Watchdog (REV.A) detected at port 0x%03x\n", current_readport
);
608 else if (revision
== PCWD_REVISION_C
)
609 printk("pcwd: PC Watchdog (REV.C) detected at port 0x%03x (Firmware version: %s)\n",
610 current_readport
, get_firmware());
612 /* Should NEVER happen, unless get_revision() fails. */
613 printk("pcwd: Unable to get revision.\n");
618 printk("pcwd: Temperature Option Detected.\n");
622 pcwd_showprevstate();
624 /* Disable the board */
625 if (revision
== PCWD_REVISION_C
) {
626 outb_p(0xA5, current_readport
+ 3);
627 outb_p(0xA5, current_readport
+ 3);
630 if (revision
== PCWD_REVISION_A
)
631 request_region(current_readport
, 2, "PCWD Rev.A (Berkshire)");
633 request_region(current_readport
, 4, "PCWD Rev.C (Berkshire)");
635 misc_register(&pcwd_miscdev
);
638 misc_register(&temp_miscdev
);
643 static void __exit
pcwatchdog_exit(void)
645 /* Disable the board */
646 if (revision
== PCWD_REVISION_C
) {
647 outb_p(0xA5, current_readport
+ 3);
648 outb_p(0xA5, current_readport
+ 3);
650 misc_deregister(&pcwd_miscdev
);
652 misc_deregister(&temp_miscdev
);
654 release_region(current_readport
, (revision
== PCWD_REVISION_A
) ? 2 : 4);
657 module_init(pcwatchdog_init
);
658 module_exit(pcwatchdog_exit
);