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
39 #include <linux/module.h>
41 #include <linux/types.h>
42 #include <linux/errno.h>
43 #include <linux/sched.h>
44 #include <linux/tty.h>
45 #include <linux/timer.h>
46 #include <linux/config.h>
47 #include <linux/kernel.h>
48 #include <linux/wait.h>
49 #include <linux/string.h>
50 #include <linux/malloc.h>
51 #include <linux/ioport.h>
52 #include <linux/delay.h>
53 #include <linux/miscdevice.h>
56 #include <linux/watchdog.h>
57 #include <linux/init.h>
59 #include <asm/uaccess.h>
63 * These are the auto-probe addresses available.
65 * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
66 * Revision A has an address range of 2 addresses, while Revision C has 3.
68 static int pcwd_ioports
[] = { 0x270, 0x350, 0x370, 0x000 };
70 #define WD_VER "1.0 (11/18/96)"
73 * It should be noted that PCWD_REVISION_B was removed because A and B
74 * are essentially the same types of card, with the exception that B
75 * has temperature reporting. Since I didn't receive a Rev.B card,
76 * the Rev.B card is not supported. (It's a good thing too, as they
77 * are no longer in production.)
79 #define PCWD_REVISION_A 1
80 #define PCWD_REVISION_C 2
82 #define WD_TIMEOUT 3 /* 1 1/2 seconds for a timeout */
85 * These are the defines for the PC Watchdog card, revision A.
87 #define WD_WDRST 0x01 /* Previously reset state */
88 #define WD_T110 0x02 /* Temperature overheat sense */
89 #define WD_HRTBT 0x04 /* Heartbeat sense */
90 #define WD_RLY2 0x08 /* External relay triggered */
91 #define WD_SRLY2 0x80 /* Software external relay triggered */
93 static int current_readport
, revision
, temp_panic
;
94 static int is_open
, initial_status
, supports_temp
, mode_debug
;
99 * This routine checks the "current_readport" to see if the card lies there.
100 * If it does, it returns accordingly.
102 __initfunc(static int pcwd_checkcard(void))
104 int card_dat
, prev_card_dat
, found
= 0, count
= 0, done
= 0;
106 /* As suggested by Alan Cox - this is a safety measure. */
107 if (check_region(current_readport
, 4)) {
108 printk("pcwd: Port 0x%x unavailable.\n", current_readport
);
113 prev_card_dat
= 0x00;
115 prev_card_dat
= inb(current_readport
);
116 if (prev_card_dat
== 0xFF)
119 while(count
< WD_TIMEOUT
) {
121 /* Read the raw card data from the port, and strip off the
124 card_dat
= inb_p(current_readport
);
127 /* Sleep 1/2 second (or 500000 microseconds :) */
132 /* If there's a heart beat in both instances, then this means we
133 found our card. This also means that either the card was
134 previously reset, or the computer was power-cycled. */
136 if ((card_dat
& WD_HRTBT
) && (prev_card_dat
& WD_HRTBT
) &&
143 /* If the card data is exactly the same as the previous card data,
144 it's safe to assume that we should check again. The manual says
145 that the heart beat will change every second (or the bit will
146 toggle), and this can be used to see if the card is there. If
147 the card was powered up with a cold boot, then the card will
148 not start blinking until 2.5 minutes after a reboot, so this
149 bit will stay at 1. */
151 if ((card_dat
== prev_card_dat
) && (!done
)) {
156 /* If the card data is toggling any bits, this means that the heart
157 beat was detected, or something else about the card is set. */
159 if ((card_dat
!= prev_card_dat
) && (!done
)) {
165 /* Otherwise something else strange happened. */
171 return((found
) ? 1 : 0);
174 void pcwd_showprevstate(void)
176 int card_status
= 0x0000;
178 if (revision
== PCWD_REVISION_A
)
179 initial_status
= card_status
= inb(current_readport
);
181 initial_status
= card_status
= inb(current_readport
+ 1);
183 if (revision
== PCWD_REVISION_A
) {
184 if (card_status
& WD_WDRST
)
185 printk("pcwd: Previous reboot was caused by the card.\n");
187 if (card_status
& WD_T110
) {
188 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
189 panic("pcwd: CPU Overheat.\n");
192 if ((!(card_status
& WD_WDRST
)) &&
193 (!(card_status
& WD_T110
)))
194 printk("pcwd: Cold boot sense.\n");
196 if (card_status
& 0x01)
197 printk("pcwd: Previous reboot was caused by the card.\n");
199 if (card_status
& 0x04) {
200 printk("pcwd: Card senses a CPU Overheat. Panicking!\n");
201 panic("pcwd: CPU Overheat.\n");
204 if ((!(card_status
& 0x01)) &&
205 (!(card_status
& 0x04)))
206 printk("pcwd: Cold boot sense.\n");
210 static void pcwd_send_heartbeat(void)
214 wdrst_stat
= inb_p(current_readport
);
217 wdrst_stat
|= WD_WDRST
;
219 if (revision
== PCWD_REVISION_A
)
220 outb_p(wdrst_stat
, current_readport
+ 1);
222 outb_p(wdrst_stat
, current_readport
);
225 static int pcwd_ioctl(struct inode
*inode
, struct file
*file
,
226 unsigned int cmd
, unsigned long arg
)
229 static struct watchdog_info ident
=
231 /* FIXME: should set A/C here */
232 WDIOF_OVERHEAT
|WDIOF_CARDRESET
,
241 case WDIOC_GETSUPPORT
:
242 i
= copy_to_user((void*)arg
, &ident
, sizeof(ident
));
243 return i
? -EFAULT
: 0;
245 case WDIOC_GETSTATUS
:
246 cdat
= inb(current_readport
);
249 if (revision
== PCWD_REVISION_A
)
252 rv
|= WDIOF_CARDRESET
;
256 rv
|= WDIOF_OVERHEAT
;
259 panic("pcwd: Temperature overheat trip!\n");
265 rv
|= WDIOF_CARDRESET
;
269 rv
|= WDIOF_OVERHEAT
;
272 panic("pcwd: Temperature overheat trip!\n");
276 if(put_user(rv
, (int *) arg
))
280 case WDIOC_GETBOOTSTATUS
:
283 if (revision
== PCWD_REVISION_A
)
285 if (initial_status
& WD_WDRST
)
286 rv
|= WDIOF_CARDRESET
;
288 if (initial_status
& WD_T110
)
289 rv
|= WDIOF_OVERHEAT
;
293 if (initial_status
& 0x01)
294 rv
|= WDIOF_CARDRESET
;
296 if (initial_status
& 0x04)
297 rv
|= WDIOF_OVERHEAT
;
300 if(put_user(rv
, (int *) arg
))
307 if ((supports_temp
) && (mode_debug
== 0))
309 rv
= inb(current_readport
);
310 if(put_user(rv
, (int*) arg
))
312 } else if(put_user(rv
, (int*) arg
))
316 case WDIOC_SETOPTIONS
:
317 if (revision
== PCWD_REVISION_C
)
319 if(copy_from_user(&rv
, (int*) arg
, sizeof(int)))
322 if (rv
& WDIOS_DISABLECARD
)
324 outb_p(0xA5, current_readport
+ 3);
325 outb_p(0xA5, current_readport
+ 3);
326 cdat
= inb_p(current_readport
+ 2);
327 if ((cdat
& 0x10) == 0)
329 printk("pcwd: Could not disable card.\n");
336 if (rv
& WDIOS_ENABLECARD
)
338 outb_p(0x00, current_readport
+ 3);
339 cdat
= inb_p(current_readport
+ 2);
342 printk("pcwd: Could not enable card.\n");
348 if (rv
& WDIOS_TEMPPANIC
)
355 case WDIOC_KEEPALIVE
:
356 pcwd_send_heartbeat();
363 static ssize_t
pcwd_write(struct file
*file
, const char *buf
, size_t len
,
366 /* Can't seek (pwrite) on this device */
367 if (ppos
!= &file
->f_pos
)
372 pcwd_send_heartbeat();
378 static int pcwd_open(struct inode
*ino
, struct file
*filep
)
380 switch (MINOR(ino
->i_rdev
))
386 /* Enable the port */
387 if (revision
== PCWD_REVISION_C
)
388 outb_p(0x00, current_readport
+ 3);
399 static ssize_t
pcwd_read(struct file
*file
, char *buf
, size_t count
,
402 unsigned short c
= inb(current_readport
);
405 /* Can't seek (pread) on this device */
406 if (ppos
!= &file
->f_pos
)
408 switch(MINOR(file
->f_dentry
->d_inode
->i_rdev
))
411 /* c is in celsius, we need fahrenheit */
413 if(copy_to_user(buf
, &cp
, 1))
421 static int pcwd_close(struct inode
*ino
, struct file
*filep
)
424 if (MINOR(ino
->i_rdev
)==WATCHDOG_MINOR
)
427 #ifndef CONFIG_WATCHDOG_NOWAYOUT
428 /* Disable the board */
429 if (revision
== PCWD_REVISION_C
) {
430 outb_p(0xA5, current_readport
+ 3);
431 outb_p(0xA5, current_readport
+ 3);
438 static inline void get_support(void)
440 if (inb(current_readport
) != 0xF0)
444 static inline int get_revision(void)
446 if ((inb(current_readport
+ 2) == 0xFF) ||
447 (inb(current_readport
+ 3) == 0xFF))
448 return(PCWD_REVISION_A
);
450 return(PCWD_REVISION_C
);
453 __initfunc(static int send_command(int cmd
))
457 outb_p(cmd
, current_readport
+ 2);
460 i
= inb(current_readport
);
461 i
= inb(current_readport
);
466 static inline char *get_firmware(void)
468 int i
, found
= 0, count
= 0, one
, ten
, hund
, minor
;
471 ret
= kmalloc(6, GFP_KERNEL
);
473 while((count
< 3) && (!found
)) {
474 outb_p(0x80, current_readport
+ 2);
475 i
= inb(current_readport
);
480 outb_p(0x00, current_readport
+ 2);
489 one
= send_command(0x81);
490 ten
= send_command(0x82);
491 hund
= send_command(0x83);
492 minor
= send_command(0x84);
496 sprintf(ret
, "%c.%c%c%c", one
, ten
, hund
, minor
);
498 sprintf(ret
, "ERROR");
503 static void debug_off(void)
505 outb_p(0x00, current_readport
+ 2);
509 static struct file_operations pcwd_fops
= {
511 pcwd_read
, /* Read */
512 pcwd_write
, /* Write */
515 pcwd_ioctl
, /* IOctl */
517 pcwd_open
, /* Open */
519 pcwd_close
, /* Release */
522 NULL
, /* CheckMediaChange */
523 NULL
, /* Revalidate */
527 static struct miscdevice pcwd_miscdev
= {
533 static struct miscdevice temp_miscdev
= {
540 int init_module(void)
542 __initfunc(int pcwatchdog_init(void))
547 revision
= PCWD_REVISION_A
;
549 printk("pcwd: v%s Ken Hollis (khollis@nurk.org)\n", WD_VER
);
551 /* Initial variables */
556 initial_status
= 0x0000;
559 for (i
= 0; pcwd_ioports
[i
] != 0; i
++) {
560 current_readport
= pcwd_ioports
[i
];
562 if (pcwd_checkcard()) {
569 printk("pcwd: No card detected, or port not available.\n");
575 current_readport
= PCWD_BLIND
;
579 revision
= get_revision();
581 if (revision
== PCWD_REVISION_A
)
582 printk("pcwd: PC Watchdog (REV.A) detected at port 0x%03x\n", current_readport
);
583 else if (revision
== PCWD_REVISION_C
)
584 printk("pcwd: PC Watchdog (REV.C) detected at port 0x%03x (Firmware version: %s)\n",
585 current_readport
, get_firmware());
587 /* Should NEVER happen, unless get_revision() fails. */
588 printk("pcwd: Unable to get revision.\n");
594 pcwd_showprevstate();
596 /* Disable the board */
597 if (revision
== PCWD_REVISION_C
) {
598 outb_p(0xA5, current_readport
+ 3);
599 outb_p(0xA5, current_readport
+ 3);
602 if (revision
== PCWD_REVISION_A
)
603 request_region(current_readport
, 2, "PCWD Rev.A (Berkshire)");
605 request_region(current_readport
, 4, "PCWD Rev.C (Berkshire)");
607 misc_register(&pcwd_miscdev
);
610 misc_register(&temp_miscdev
);
616 void cleanup_module(void)
618 /* Disable the board */
619 if (revision
== PCWD_REVISION_C
) {
620 outb_p(0xA5, current_readport
+ 3);
621 outb_p(0xA5, current_readport
+ 3);
623 misc_deregister(&pcwd_miscdev
);
625 misc_deregister(&temp_miscdev
);
627 release_region(current_readport
, (revision
== PCWD_REVISION_A
) ? 2 : 4);