Ok. I didn't make 2.4.0 in 2000. Tough. I tried, but we had some
[davej-history.git] / drivers / char / pcwd.c
blob2b4a71546c9f89a23647ce6b1296c00ef8ad8219
1 /*
2 * PC Watchdog Driver
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
10 * WD_TIMEOUT define.
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
26 * routine.
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>
60 #include <linux/fs.h>
61 #include <linux/mm.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>
69 #include <asm/io.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;
107 * PCWD_CHECKCARD
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);
119 return 0;
122 card_dat = 0x00;
123 prev_card_dat = 0x00;
125 prev_card_dat = inb(current_readport);
126 if (prev_card_dat == 0xFF)
127 return 0;
129 while(count < WD_TIMEOUT) {
131 /* Read the raw card data from the port, and strip off the
132 first 4 bits */
134 card_dat = inb_p(current_readport);
135 card_dat &= 0x000F;
137 /* Sleep 1/2 second (or 500000 microseconds :) */
139 mdelay(500);
140 done = 0;
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) &&
147 (!done)) {
148 found = 1;
149 done = 1;
150 break;
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)) {
162 count++;
163 done = 1;
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)) {
170 done = 1;
171 found = 1;
172 break;
175 /* Otherwise something else strange happened. */
177 if (!done)
178 count++;
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);
190 else {
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");
207 } else {
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)
224 int wdrst_stat;
226 wdrst_stat = inb_p(current_readport);
227 wdrst_stat &= 0x0F;
229 wdrst_stat |= WD_WDRST;
231 if (revision == PCWD_REVISION_A)
232 outb_p(wdrst_stat, current_readport + 1);
233 else
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)
240 int i, cdat, rv;
241 static struct watchdog_info ident=
243 WDIOF_OVERHEAT|WDIOF_CARDRESET,
245 "PCWD"
248 switch(cmd) {
249 default:
250 return -ENOIOCTLCMD;
252 case WDIOC_GETSUPPORT:
253 i = copy_to_user((void*)arg, &ident, sizeof(ident));
254 return i ? -EFAULT : 0;
256 case WDIOC_GETSTATUS:
257 spin_lock(&io_lock);
258 if (revision == PCWD_REVISION_A)
259 cdat = inb(current_readport);
260 else
261 cdat = inb(current_readport + 1 );
262 spin_unlock(&io_lock);
263 rv = 0;
265 if (revision == PCWD_REVISION_A)
267 if (cdat & WD_WDRST)
268 rv |= WDIOF_CARDRESET;
270 if (cdat & WD_T110)
272 rv |= WDIOF_OVERHEAT;
274 if (temp_panic)
275 panic("pcwd: Temperature overheat trip!\n");
278 else
280 if (cdat & 0x01)
281 rv |= WDIOF_CARDRESET;
283 if (cdat & 0x04)
285 rv |= WDIOF_OVERHEAT;
287 if (temp_panic)
288 panic("pcwd: Temperature overheat trip!\n");
292 if(put_user(rv, (int *) arg))
293 return -EFAULT;
294 return 0;
296 case WDIOC_GETBOOTSTATUS:
297 rv = 0;
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;
307 else
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))
317 return -EFAULT;
318 return 0;
320 case WDIOC_GETTEMP:
322 rv = 0;
323 if ((supports_temp) && (mode_debug == 0))
325 spin_lock(&io_lock);
326 rv = inb(current_readport);
327 spin_unlock(&io_lock);
328 if(put_user(rv, (int*) arg))
329 return -EFAULT;
330 } else if(put_user(rv, (int*) arg))
331 return -EFAULT;
332 return 0;
334 case WDIOC_SETOPTIONS:
335 if (revision == PCWD_REVISION_C)
337 if(copy_from_user(&rv, (int*) arg, sizeof(int)))
338 return -EFAULT;
340 if (rv & WDIOS_DISABLECARD)
342 spin_lock(&io_lock);
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");
350 return -EIO;
353 return 0;
356 if (rv & WDIOS_ENABLECARD)
358 spin_lock(&io_lock);
359 outb_p(0x00, current_readport + 3);
360 cdat = inb_p(current_readport + 2);
361 spin_unlock(&io_lock);
362 if (cdat & 0x10)
364 printk("pcwd: Could not enable card.\n");
365 return -EIO;
367 return 0;
370 if (rv & WDIOS_TEMPPANIC)
372 temp_panic = 1;
375 return -EINVAL;
377 case WDIOC_KEEPALIVE:
378 pcwd_send_heartbeat();
379 return 0;
382 return 0;
385 static ssize_t pcwd_write(struct file *file, const char *buf, size_t len,
386 loff_t *ppos)
388 /* Can't seek (pwrite) on this device */
389 if (ppos != &file->f_pos)
390 return -ESPIPE;
392 if (len)
394 pcwd_send_heartbeat();
395 return 1;
397 return 0;
400 static int pcwd_open(struct inode *ino, struct file *filep)
402 switch (MINOR(ino->i_rdev))
404 case WATCHDOG_MINOR:
405 if (is_open)
406 return -EBUSY;
407 MOD_INC_USE_COUNT;
408 /* Enable the port */
409 if (revision == PCWD_REVISION_C)
411 spin_lock(&io_lock);
412 outb_p(0x00, current_readport + 3);
413 spin_unlock(&io_lock);
415 is_open = 1;
416 return(0);
417 case TEMP_MINOR:
418 return(0);
419 default:
420 return (-ENODEV);
424 static ssize_t pcwd_read(struct file *file, char *buf, size_t count,
425 loff_t *ppos)
427 unsigned short c;
428 unsigned char cp;
430 /* Can't seek (pread) on this device */
431 if (ppos != &file->f_pos)
432 return -ESPIPE;
433 switch(MINOR(file->f_dentry->d_inode->i_rdev))
435 case TEMP_MINOR:
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))
444 return -EFAULT;
445 return 1;
446 default:
447 return -EINVAL;
451 static int pcwd_close(struct inode *ino, struct file *filep)
453 if (MINOR(ino->i_rdev)==WATCHDOG_MINOR)
455 lock_kernel();
456 is_open = 0;
457 #ifndef CONFIG_WATCHDOG_NOWAYOUT
458 /* Disable the board */
459 if (revision == PCWD_REVISION_C) {
460 spin_lock(&io_lock);
461 outb_p(0xA5, current_readport + 3);
462 outb_p(0xA5, current_readport + 3);
463 spin_unlock(&io_lock);
465 unlock_kernel();
466 #endif
468 return 0;
471 static inline void get_support(void)
473 if (inb(current_readport) != 0xF0)
474 supports_temp = 1;
477 static inline int get_revision(void)
479 int r = PCWD_REVISION_C;
481 spin_lock(&io_lock);
482 if ((inb(current_readport + 2) == 0xFF) ||
483 (inb(current_readport + 3) == 0xFF))
484 r=PCWD_REVISION_A;
485 spin_unlock(&io_lock);
487 return r;
490 static int __init send_command(int cmd)
492 int i;
494 outb_p(cmd, current_readport + 2);
495 mdelay(1);
497 i = inb(current_readport);
498 i = inb(current_readport);
500 return(i);
503 static inline char *get_firmware(void)
505 int i, found = 0, count = 0, one, ten, hund, minor;
506 char *ret;
508 ret = kmalloc(6, GFP_KERNEL);
510 while((count < 3) && (!found)) {
511 outb_p(0x80, current_readport + 2);
512 i = inb(current_readport);
514 if (i == 0x00)
515 found = 1;
516 else if (i == 0xF3)
517 outb_p(0x00, current_readport + 2);
519 udelay(400L);
520 count++;
523 if (found) {
524 mode_debug = 1;
526 one = send_command(0x81);
527 ten = send_command(0x82);
528 hund = send_command(0x83);
529 minor = send_command(0x84);
532 if (found)
533 sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
534 else
535 sprintf(ret, "ERROR");
537 return(ret);
540 static void debug_off(void)
542 outb_p(0x00, current_readport + 2);
543 mode_debug = 0;
546 static struct file_operations pcwd_fops = {
547 owner: THIS_MODULE,
548 read: pcwd_read,
549 write: pcwd_write,
550 ioctl: pcwd_ioctl,
551 open: pcwd_open,
552 release: pcwd_close,
555 static struct miscdevice pcwd_miscdev = {
556 WATCHDOG_MINOR,
557 "watchdog",
558 &pcwd_fops
561 static struct miscdevice temp_miscdev = {
562 TEMP_MINOR,
563 "temperature",
564 &pcwd_fops
567 static int __init pcwatchdog_init(void)
569 int i, found = 0;
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 */
577 is_open = 0;
578 supports_temp = 0;
579 mode_debug = 0;
580 temp_panic = 0;
581 initial_status = 0x0000;
583 #ifndef PCWD_BLIND
584 for (i = 0; pcwd_ioports[i] != 0; i++) {
585 current_readport = pcwd_ioports[i];
587 if (pcwd_checkcard()) {
588 found = 1;
589 break;
593 if (!found) {
594 printk("pcwd: No card detected, or port not available.\n");
595 return(-EIO);
597 #endif
599 #ifdef PCWD_BLIND
600 current_readport = PCWD_BLIND;
601 #endif
603 get_support();
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());
611 else {
612 /* Should NEVER happen, unless get_revision() fails. */
613 printk("pcwd: Unable to get revision.\n");
614 return -1;
617 if (supports_temp)
618 printk("pcwd: Temperature Option Detected.\n");
620 debug_off();
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)");
632 else
633 request_region(current_readport, 4, "PCWD Rev.C (Berkshire)");
635 misc_register(&pcwd_miscdev);
637 if (supports_temp)
638 misc_register(&temp_miscdev);
640 return 0;
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);
651 if (supports_temp)
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);