Linux 2.3.7pre1
[davej-history.git] / drivers / char / pcwd.c
blobded35916ced647ac0ab173979f9520d8f62bffd3
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
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>
54 #include <linux/fs.h>
55 #include <linux/mm.h>
56 #include <linux/watchdog.h>
57 #include <linux/init.h>
59 #include <asm/uaccess.h>
60 #include <asm/io.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;
97 * PCWD_CHECKCARD
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);
109 return 0;
112 card_dat = 0x00;
113 prev_card_dat = 0x00;
115 prev_card_dat = inb(current_readport);
116 if (prev_card_dat == 0xFF)
117 return 0;
119 while(count < WD_TIMEOUT) {
121 /* Read the raw card data from the port, and strip off the
122 first 4 bits */
124 card_dat = inb_p(current_readport);
125 card_dat &= 0x000F;
127 /* Sleep 1/2 second (or 500000 microseconds :) */
129 mdelay(500);
130 done = 0;
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) &&
137 (!done)) {
138 found = 1;
139 done = 1;
140 break;
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)) {
152 count++;
153 done = 1;
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)) {
160 done = 1;
161 found = 1;
162 break;
165 /* Otherwise something else strange happened. */
167 if (!done)
168 count++;
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);
180 else
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");
195 } else {
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)
212 int wdrst_stat;
214 wdrst_stat = inb_p(current_readport);
215 wdrst_stat &= 0x0F;
217 wdrst_stat |= WD_WDRST;
219 if (revision == PCWD_REVISION_A)
220 outb_p(wdrst_stat, current_readport + 1);
221 else
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)
228 int i, cdat, rv;
229 static struct watchdog_info ident=
231 /* FIXME: should set A/C here */
232 WDIOF_OVERHEAT|WDIOF_CARDRESET,
234 "PCWD."
237 switch(cmd) {
238 default:
239 return -ENOIOCTLCMD;
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);
247 rv = 0;
249 if (revision == PCWD_REVISION_A)
251 if (cdat & WD_WDRST)
252 rv |= WDIOF_CARDRESET;
254 if (cdat & WD_T110)
256 rv |= WDIOF_OVERHEAT;
258 if (temp_panic)
259 panic("pcwd: Temperature overheat trip!\n");
262 else
264 if (cdat & 0x01)
265 rv |= WDIOF_CARDRESET;
267 if (cdat & 0x04)
269 rv |= WDIOF_OVERHEAT;
271 if (temp_panic)
272 panic("pcwd: Temperature overheat trip!\n");
276 if(put_user(rv, (int *) arg))
277 return -EFAULT;
278 return 0;
280 case WDIOC_GETBOOTSTATUS:
281 rv = 0;
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;
291 else
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))
301 return -EFAULT;
302 return 0;
304 case WDIOC_GETTEMP:
306 rv = 0;
307 if ((supports_temp) && (mode_debug == 0))
309 rv = inb(current_readport);
310 if(put_user(rv, (int*) arg))
311 return -EFAULT;
312 } else if(put_user(rv, (int*) arg))
313 return -EFAULT;
314 return 0;
316 case WDIOC_SETOPTIONS:
317 if (revision == PCWD_REVISION_C)
319 if(copy_from_user(&rv, (int*) arg, sizeof(int)))
320 return -EFAULT;
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");
330 return -EIO;
333 return 0;
336 if (rv & WDIOS_ENABLECARD)
338 outb_p(0x00, current_readport + 3);
339 cdat = inb_p(current_readport + 2);
340 if (cdat & 0x10)
342 printk("pcwd: Could not enable card.\n");
343 return -EIO;
345 return 0;
348 if (rv & WDIOS_TEMPPANIC)
350 temp_panic = 1;
353 return -EINVAL;
355 case WDIOC_KEEPALIVE:
356 pcwd_send_heartbeat();
357 return 0;
360 return 0;
363 static ssize_t pcwd_write(struct file *file, const char *buf, size_t len,
364 loff_t *ppos)
366 /* Can't seek (pwrite) on this device */
367 if (ppos != &file->f_pos)
368 return -ESPIPE;
370 if (len)
372 pcwd_send_heartbeat();
373 return 1;
375 return 0;
378 static int pcwd_open(struct inode *ino, struct file *filep)
380 switch (MINOR(ino->i_rdev))
382 case WATCHDOG_MINOR:
383 if (is_open)
384 return -EBUSY;
385 MOD_INC_USE_COUNT;
386 /* Enable the port */
387 if (revision == PCWD_REVISION_C)
388 outb_p(0x00, current_readport + 3);
389 is_open = 1;
390 return(0);
391 case TEMP_MINOR:
392 MOD_INC_USE_COUNT;
393 return(0);
394 default:
395 return (-ENODEV);
399 static ssize_t pcwd_read(struct file *file, char *buf, size_t count,
400 loff_t *ppos)
402 unsigned short c = inb(current_readport);
403 unsigned char cp;
405 /* Can't seek (pread) on this device */
406 if (ppos != &file->f_pos)
407 return -ESPIPE;
408 switch(MINOR(file->f_dentry->d_inode->i_rdev))
410 case TEMP_MINOR:
411 /* c is in celsius, we need fahrenheit */
412 cp = (c*9/5)+32;
413 if(copy_to_user(buf, &cp, 1))
414 return -EFAULT;
415 return 1;
416 default:
417 return -EINVAL;
421 static int pcwd_close(struct inode *ino, struct file *filep)
423 MOD_DEC_USE_COUNT;
424 if (MINOR(ino->i_rdev)==WATCHDOG_MINOR)
426 is_open = 0;
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);
433 #endif
435 return 0;
438 static inline void get_support(void)
440 if (inb(current_readport) != 0xF0)
441 supports_temp = 1;
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))
455 int i;
457 outb_p(cmd, current_readport + 2);
458 mdelay(1);
460 i = inb(current_readport);
461 i = inb(current_readport);
463 return(i);
466 static inline char *get_firmware(void)
468 int i, found = 0, count = 0, one, ten, hund, minor;
469 char *ret;
471 ret = kmalloc(6, GFP_KERNEL);
473 while((count < 3) && (!found)) {
474 outb_p(0x80, current_readport + 2);
475 i = inb(current_readport);
477 if (i == 0x00)
478 found = 1;
479 else if (i == 0xF3)
480 outb_p(0x00, current_readport + 2);
482 udelay(400L);
483 count++;
486 if (found) {
487 mode_debug = 1;
489 one = send_command(0x81);
490 ten = send_command(0x82);
491 hund = send_command(0x83);
492 minor = send_command(0x84);
495 if (found)
496 sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
497 else
498 sprintf(ret, "ERROR");
500 return(ret);
503 static void debug_off(void)
505 outb_p(0x00, current_readport + 2);
506 mode_debug = 0;
509 static struct file_operations pcwd_fops = {
510 NULL, /* Seek */
511 pcwd_read, /* Read */
512 pcwd_write, /* Write */
513 NULL, /* Readdir */
514 NULL, /* Poll */
515 pcwd_ioctl, /* IOctl */
516 NULL, /* MMAP */
517 pcwd_open, /* Open */
518 NULL, /* flush */
519 pcwd_close, /* Release */
520 NULL, /* Fsync */
521 NULL, /* Fasync */
522 NULL, /* CheckMediaChange */
523 NULL, /* Revalidate */
524 NULL, /* Lock */
527 static struct miscdevice pcwd_miscdev = {
528 WATCHDOG_MINOR,
529 "watchdog",
530 &pcwd_fops
533 static struct miscdevice temp_miscdev = {
534 TEMP_MINOR,
535 "temperature",
536 &pcwd_fops
539 #ifdef MODULE
540 int init_module(void)
541 #else
542 __initfunc(int pcwatchdog_init(void))
543 #endif
545 int i, found = 0;
547 revision = PCWD_REVISION_A;
549 printk("pcwd: v%s Ken Hollis (khollis@nurk.org)\n", WD_VER);
551 /* Initial variables */
552 is_open = 0;
553 supports_temp = 0;
554 mode_debug = 0;
555 temp_panic = 0;
556 initial_status = 0x0000;
558 #ifndef PCWD_BLIND
559 for (i = 0; pcwd_ioports[i] != 0; i++) {
560 current_readport = pcwd_ioports[i];
562 if (pcwd_checkcard()) {
563 found = 1;
564 break;
568 if (!found) {
569 printk("pcwd: No card detected, or port not available.\n");
570 return(-EIO);
572 #endif
574 #ifdef PCWD_BLIND
575 current_readport = PCWD_BLIND;
576 #endif
578 get_support();
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());
586 else {
587 /* Should NEVER happen, unless get_revision() fails. */
588 printk("pcwd: Unable to get revision.\n");
589 return -1;
592 debug_off();
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)");
604 else
605 request_region(current_readport, 4, "PCWD Rev.C (Berkshire)");
607 misc_register(&pcwd_miscdev);
609 if (supports_temp)
610 misc_register(&temp_miscdev);
612 return 0;
615 #ifdef MODULE
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);
624 if (supports_temp)
625 misc_deregister(&temp_miscdev);
627 release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
629 #endif