commented early_printk patch because of rejects.
[linux-flexiantxendom0-3.2.10.git] / drivers / char / watchdog / pcwd.c
1 /*
2  * PC Watchdog Driver
3  * by Ken Hollis (khollis@bitgate.com)
4  *
5  * Permission granted from Simon Machell (73244.1270@compuserve.com)
6  * Written for the Linux Kernel, and GPLed by Ken Hollis
7  *
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>
43  * 011214       Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
44  *              Added timeout module option to override default
45  */
46
47 #include <linux/module.h>
48 #include <linux/moduleparam.h>
49 #include <linux/types.h>
50 #include <linux/timer.h>
51 #include <linux/config.h>
52 #include <linux/wait.h>
53 #include <linux/slab.h>
54 #include <linux/ioport.h>
55 #include <linux/delay.h>
56 #include <linux/fs.h>
57 #include <linux/miscdevice.h>
58 #include <linux/watchdog.h>
59 #include <linux/init.h>
60 #include <linux/spinlock.h>
61 #include <linux/reboot.h>
62
63 #include <asm/uaccess.h>
64 #include <asm/io.h>
65
66 /*
67  * These are the auto-probe addresses available.
68  *
69  * Revision A only uses ports 0x270 and 0x370.  Revision C introduced 0x350.
70  * Revision A has an address range of 2 addresses, while Revision C has 3.
71  */
72 static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
73
74 #define WD_VER                  "1.12 (12/14/2001)"
75
76 /*
77  * It should be noted that PCWD_REVISION_B was removed because A and B
78  * are essentially the same types of card, with the exception that B
79  * has temperature reporting.  Since I didn't receive a Rev.B card,
80  * the Rev.B card is not supported.  (It's a good thing too, as they
81  * are no longer in production.)
82  */
83 #define PCWD_REVISION_A         1
84 #define PCWD_REVISION_C         2
85
86 #define WD_TIMEOUT              4       /* 2 seconds for a timeout */
87 static int timeout_val = WD_TIMEOUT;
88 static int timeout = 2;
89 static int expect_close = 0;
90
91 module_param(timeout, int, 0);
92 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=2)"); 
93
94 #ifdef CONFIG_WATCHDOG_NOWAYOUT
95 static int nowayout = 1;
96 #else
97 static int nowayout = 0;
98 #endif
99
100 module_param(nowayout, int, 0);
101 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
102
103
104 /*
105  * These are the defines for the PC Watchdog card, revision A.
106  */
107 #define WD_WDRST                0x01    /* Previously reset state */
108 #define WD_T110                 0x02    /* Temperature overheat sense */
109 #define WD_HRTBT                0x04    /* Heartbeat sense */
110 #define WD_RLY2                 0x08    /* External relay triggered */
111 #define WD_SRLY2                0x80    /* Software external relay triggered */
112
113 static int current_readport, revision, temp_panic;
114 static atomic_t open_allowed = ATOMIC_INIT(1);
115 static int initial_status, supports_temp, mode_debug;
116 static spinlock_t io_lock;
117
118 /*
119  * PCWD_CHECKCARD
120  *
121  * This routine checks the "current_readport" to see if the card lies there.
122  * If it does, it returns accordingly.
123  */
124 static int __init pcwd_checkcard(void)
125 {
126         int card_dat, prev_card_dat, found = 0, count = 0, done = 0;
127
128         card_dat = 0x00;
129         prev_card_dat = 0x00;
130
131         prev_card_dat = inb(current_readport);
132         if (prev_card_dat == 0xFF)
133                 return 0;
134
135         while(count < timeout_val) {
136
137         /* Read the raw card data from the port, and strip off the
138            first 4 bits */
139
140                 card_dat = inb_p(current_readport);
141                 card_dat &= 0x000F;
142
143         /* Sleep 1/2 second (or 500000 microseconds :) */
144
145                 mdelay(500);
146                 done = 0;
147
148         /* If there's a heart beat in both instances, then this means we
149            found our card.  This also means that either the card was
150            previously reset, or the computer was power-cycled. */
151
152                 if ((card_dat & WD_HRTBT) && (prev_card_dat & WD_HRTBT) &&
153                         (!done)) {
154                         found = 1;
155                         done = 1;
156                         break;
157                 }
158
159         /* If the card data is exactly the same as the previous card data,
160            it's safe to assume that we should check again.  The manual says
161            that the heart beat will change every second (or the bit will
162            toggle), and this can be used to see if the card is there.  If
163            the card was powered up with a cold boot, then the card will
164            not start blinking until 2.5 minutes after a reboot, so this
165            bit will stay at 1. */
166
167                 if ((card_dat == prev_card_dat) && (!done)) {
168                         count++;
169                         done = 1;
170                 }
171
172         /* If the card data is toggling any bits, this means that the heart
173            beat was detected, or something else about the card is set. */
174
175                 if ((card_dat != prev_card_dat) && (!done)) {
176                         done = 1;
177                         found = 1;
178                         break;
179                 }
180
181         /* Otherwise something else strange happened. */
182
183                 if (!done)
184                         count++;
185         }
186
187         return((found) ? 1 : 0);
188 }
189
190 void pcwd_showprevstate(void)
191 {
192         int card_status = 0x0000;
193
194         if (revision == PCWD_REVISION_A)
195                 initial_status = card_status = inb(current_readport);
196         else {
197                 initial_status = card_status = inb(current_readport + 1);
198                 outb_p(0x00, current_readport + 1); /* clear reset status */
199         }
200
201         if (revision == PCWD_REVISION_A) {
202                 if (card_status & WD_WDRST)
203                         printk(KERN_INFO "pcwd: Previous reboot was caused by the card.\n");
204
205                 if (card_status & WD_T110) {
206                         printk(KERN_EMERG "pcwd: Card senses a CPU Overheat.  Panicking!\n");
207                         printk(KERN_EMERG "pcwd: CPU Overheat.\n");
208                         machine_power_off();
209                 }
210
211                 if ((!(card_status & WD_WDRST)) &&
212                     (!(card_status & WD_T110)))
213                         printk(KERN_INFO "pcwd: Cold boot sense.\n");
214         } else {
215                 if (card_status & 0x01)
216                         printk(KERN_INFO "pcwd: Previous reboot was caused by the card.\n");
217
218                 if (card_status & 0x04) {
219                         printk(KERN_EMERG "pcwd: Card senses a CPU Overheat.  Panicking!\n");
220                         printk(KERN_EMERG "pcwd: CPU Overheat.\n");
221                         machine_power_off();
222                 }
223
224                 if ((!(card_status & 0x01)) &&
225                     (!(card_status & 0x04)))
226                         printk(KERN_INFO "pcwd: Cold boot sense.\n");
227         }
228 }
229
230 static void pcwd_send_heartbeat(void)
231 {
232         int wdrst_stat;
233
234         wdrst_stat = inb_p(current_readport);
235         wdrst_stat &= 0x0F;
236
237         wdrst_stat |= WD_WDRST;
238
239         if (revision == PCWD_REVISION_A)
240                 outb_p(wdrst_stat, current_readport + 1);
241         else
242                 outb_p(wdrst_stat, current_readport);
243 }
244
245 static int pcwd_ioctl(struct inode *inode, struct file *file,
246                       unsigned int cmd, unsigned long arg)
247 {
248         int cdat, rv;
249         static struct watchdog_info ident=
250         {
251                 WDIOF_OVERHEAT|WDIOF_CARDRESET,
252                 1,
253                 "PCWD"
254         };
255
256         switch(cmd) {
257         default:
258                 return -ENOTTY;
259
260         case WDIOC_GETSUPPORT:
261                 if(copy_to_user((void*)arg, &ident, sizeof(ident)))
262                         return -EFAULT;
263                 return 0;
264
265         case WDIOC_GETSTATUS:
266                 spin_lock(&io_lock);
267                 if (revision == PCWD_REVISION_A) 
268                         cdat = inb(current_readport);
269                 else
270                         cdat = inb(current_readport + 1 );
271                 spin_unlock(&io_lock);
272                 rv = WDIOF_MAGICCLOSE;
273
274                 if (revision == PCWD_REVISION_A) 
275                 {
276                         if (cdat & WD_WDRST)
277                                 rv |= WDIOF_CARDRESET;
278
279                         if (cdat & WD_T110) 
280                         {
281                                 rv |= WDIOF_OVERHEAT;
282
283                                 if (temp_panic) {
284                                         printk (KERN_INFO "pcwd: Temperature overheat trip!\n");
285                                         machine_power_off();
286                                 }
287                         }
288                 }
289                 else 
290                 {
291                         if (cdat & 0x01)
292                                 rv |= WDIOF_CARDRESET;
293
294                         if (cdat & 0x04) 
295                         {
296                                 rv |= WDIOF_OVERHEAT;
297
298                                 if (temp_panic) {
299                                         printk (KERN_INFO "pcwd: Temperature overheat trip!\n");
300                                         machine_power_off();
301                                 }
302                         }
303                 }
304
305                 if(put_user(rv, (int *) arg))
306                         return -EFAULT;
307                 return 0;
308
309         case WDIOC_GETBOOTSTATUS:
310                 rv = 0;
311
312                 if (revision == PCWD_REVISION_A) 
313                 {
314                         if (initial_status & WD_WDRST)
315                                 rv |= WDIOF_CARDRESET;
316
317                         if (initial_status & WD_T110)
318                                 rv |= WDIOF_OVERHEAT;
319                 }
320                 else
321                 {
322                         if (initial_status & 0x01)
323                                 rv |= WDIOF_CARDRESET;
324
325                         if (initial_status & 0x04)
326                                 rv |= WDIOF_OVERHEAT;
327                 }
328
329                 if(put_user(rv, (int *) arg))
330                         return -EFAULT;
331                 return 0;
332
333         case WDIOC_GETTEMP:
334
335                 rv = 0;
336                 if ((supports_temp) && (mode_debug == 0)) 
337                 {
338                         spin_lock(&io_lock);
339                         rv = inb(current_readport);
340                         spin_unlock(&io_lock);
341                         if(put_user(rv, (int*) arg))
342                                 return -EFAULT;
343                 } else if(put_user(rv, (int*) arg))
344                                 return -EFAULT;
345                 return 0;
346
347         case WDIOC_SETOPTIONS:
348                 if (revision == PCWD_REVISION_C) 
349                 {
350                         if(copy_from_user(&rv, (int*) arg, sizeof(int)))
351                                 return -EFAULT;
352
353                         if (rv & WDIOS_DISABLECARD) 
354                         {
355                                 spin_lock(&io_lock);
356                                 outb_p(0xA5, current_readport + 3);
357                                 outb_p(0xA5, current_readport + 3);
358                                 cdat = inb_p(current_readport + 2);
359                                 spin_unlock(&io_lock);
360                                 if ((cdat & 0x10) == 0) 
361                                 {
362                                         printk(KERN_INFO "pcwd: Could not disable card.\n");
363                                         return -EIO;
364                                 }
365
366                                 return 0;
367                         }
368
369                         if (rv & WDIOS_ENABLECARD) 
370                         {
371                                 spin_lock(&io_lock);
372                                 outb_p(0x00, current_readport + 3);
373                                 cdat = inb_p(current_readport + 2);
374                                 spin_unlock(&io_lock);
375                                 if (cdat & 0x10) 
376                                 {
377                                         printk(KERN_INFO "pcwd: Could not enable card.\n");
378                                         return -EIO;
379                                 }
380                                 return 0;
381                         }
382
383                         if (rv & WDIOS_TEMPPANIC) 
384                         {
385                                 temp_panic = 1;
386                         }
387                 }
388                 return -EINVAL;
389                 
390         case WDIOC_KEEPALIVE:
391                 pcwd_send_heartbeat();
392                 return 0;
393         }
394
395         return 0;
396 }
397
398 static ssize_t pcwd_write(struct file *file, const char *buf, size_t len,
399                           loff_t *ppos)
400 {
401         /*  Can't seek (pwrite) on this device  */
402         if (ppos != &file->f_pos)
403                 return -ESPIPE;
404
405         if (len) {
406                 if (!nowayout) {
407                         size_t i;
408
409                         /* In case it was set long ago */
410                         expect_close = 0;
411
412                         for (i = 0; i != len; i++) {
413                                 char c;
414
415                                 if (get_user(c, buf + i))
416                                         return -EFAULT;
417                                 if (c == 'V')
418                                         expect_close = 1;
419                         }
420                 }
421                 pcwd_send_heartbeat();
422                 return 1;
423         }
424         return 0;
425 }
426
427 static int pcwd_open(struct inode *ino, struct file *filep)
428 {
429         switch (minor(ino->i_rdev)) {
430         case WATCHDOG_MINOR:
431                 if (!atomic_dec_and_test(&open_allowed) ) {
432                         atomic_inc( &open_allowed );
433                         return -EBUSY;
434                 }
435                 __module_get(THIS_MODULE);
436                 /*  Enable the port  */
437                 if (revision == PCWD_REVISION_C) {
438                         spin_lock(&io_lock);
439                         outb_p(0x00, current_readport + 3);
440                         spin_unlock(&io_lock);
441                 }
442                 return(0);
443
444         case TEMP_MINOR:
445                 return(0);
446         default:
447                 return (-ENODEV);
448         }
449 }
450
451 static ssize_t pcwd_read(struct file *file, char *buf, size_t count,
452                          loff_t *ppos)
453 {
454         unsigned short c;
455         unsigned char cp;
456
457         /*  Can't seek (pread) on this device  */
458         if (ppos != &file->f_pos)
459                 return -ESPIPE;
460         switch(minor(file->f_dentry->d_inode->i_rdev)) 
461         {
462                 case TEMP_MINOR:
463                         /*
464                          * Convert metric to Fahrenheit, since this was
465                          * the decided 'standard' for this return value.
466                          */
467                         
468                         c = inb(current_readport);
469                         cp = (c * 9 / 5) + 32;
470                         if(copy_to_user(buf, &cp, 1))
471                                 return -EFAULT;
472                         return 1;
473                 default:
474                         return -EINVAL;
475         }
476 }
477
478 static int pcwd_close(struct inode *ino, struct file *filep)
479 {
480         if (minor(ino->i_rdev)==WATCHDOG_MINOR) {
481                 if (expect_close) {
482                         /*  Disable the board  */
483                         if (revision == PCWD_REVISION_C) {
484                                 spin_lock(&io_lock);
485                                 outb_p(0xA5, current_readport + 3);
486                                 outb_p(0xA5, current_readport + 3);
487                                 spin_unlock(&io_lock);
488                         }
489                         atomic_inc( &open_allowed );
490                 }
491         }
492         return 0;
493 }
494
495 static inline void get_support(void)
496 {
497         if (inb(current_readport) != 0xF0)
498                 supports_temp = 1;
499 }
500
501 static inline int get_revision(void)
502 {
503         int r = PCWD_REVISION_C;
504         
505         spin_lock(&io_lock);
506         if ((inb(current_readport + 2) == 0xFF) ||
507             (inb(current_readport + 3) == 0xFF))
508                 r=PCWD_REVISION_A;
509         spin_unlock(&io_lock);
510
511         return r;
512 }
513
514 static int __init send_command(int cmd)
515 {
516         int i;
517
518         outb_p(cmd, current_readport + 2);
519         mdelay(1);
520
521         i = inb(current_readport);
522         i = inb(current_readport);
523
524         return(i);
525 }
526
527 static inline char *get_firmware(void)
528 {
529         int i, found = 0, count = 0, one, ten, hund, minor;
530         char *ret;
531
532         ret = kmalloc(6, GFP_KERNEL);
533         if(ret == NULL)
534                 return NULL;
535
536         while((count < 3) && (!found)) {
537                 outb_p(0x80, current_readport + 2);
538                 i = inb(current_readport);
539
540                 if (i == 0x00)
541                         found = 1;
542                 else if (i == 0xF3)
543                         outb_p(0x00, current_readport + 2);
544
545                 udelay(400L);
546                 count++;
547         }
548
549         if (found) {
550                 mode_debug = 1;
551
552                 one = send_command(0x81);
553                 ten = send_command(0x82);
554                 hund = send_command(0x83);
555                 minor = send_command(0x84);
556                 sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
557         }
558         else
559                 sprintf(ret, "ERROR");
560
561         return(ret);
562 }
563
564 static void debug_off(void)
565 {
566         outb_p(0x00, current_readport + 2);
567         mode_debug = 0;
568 }
569
570 static struct file_operations pcwd_fops = {
571         .owner          = THIS_MODULE,
572         .read           = pcwd_read,
573         .write          = pcwd_write,
574         .ioctl          = pcwd_ioctl,
575         .open           = pcwd_open,
576         .release        = pcwd_close,
577 };
578
579 static struct miscdevice pcwd_miscdev = {
580         WATCHDOG_MINOR,
581         "watchdog",
582         &pcwd_fops
583 };
584
585 static struct miscdevice temp_miscdev = {
586         TEMP_MINOR,
587         "temperature",
588         &pcwd_fops
589 };
590  
591 static void __init pcwd_validate_timeout(void)
592 {
593         timeout_val = timeout * 2;
594 }
595  
596 static int __init pcwatchdog_init(void)
597 {
598         char *firmware;
599         int i, found = 0;
600         pcwd_validate_timeout();
601         spin_lock_init(&io_lock);
602         
603         revision = PCWD_REVISION_A;
604
605         printk(KERN_INFO "pcwd: v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
606
607         /* Initial variables */
608         supports_temp = 0;
609         mode_debug = 0;
610         temp_panic = 0;
611         initial_status = 0x0000;
612
613 #ifndef PCWD_BLIND
614         for (i = 0; pcwd_ioports[i] != 0; i++) {
615                 current_readport = pcwd_ioports[i];
616
617                 if (pcwd_checkcard()) {
618                         found = 1;
619                         break;
620                 }
621         }
622
623         if (!found) {
624                 printk(KERN_INFO "pcwd: No card detected, or port not available.\n");
625                 return(-EIO);
626         }
627 #endif
628
629 #ifdef  PCWD_BLIND
630         current_readport = PCWD_BLIND;
631 #endif
632
633         get_support();
634         revision = get_revision();
635
636         if (revision == PCWD_REVISION_A)
637                 printk(KERN_INFO "pcwd: PC Watchdog (REV.A) detected at port 0x%03x\n", current_readport);
638         else if (revision == PCWD_REVISION_C) {
639                 firmware = get_firmware();
640                 printk(KERN_INFO "pcwd: PC Watchdog (REV.C) detected at port 0x%03x (Firmware version: %s)\n",
641                         current_readport, firmware);
642                 kfree(firmware);
643         } else {
644                 /* Should NEVER happen, unless get_revision() fails. */
645                 printk("pcwd: Unable to get revision.\n");
646                 return -1;
647         }
648
649         if (supports_temp)
650                 printk(KERN_INFO "pcwd: Temperature Option Detected.\n");
651
652         debug_off();
653
654         pcwd_showprevstate();
655
656         /*  Disable the board  */
657         if (revision == PCWD_REVISION_C) {
658                 outb_p(0xA5, current_readport + 3);
659                 outb_p(0xA5, current_readport + 3);
660         }
661
662         if (misc_register(&pcwd_miscdev))
663                 return -ENODEV;
664         
665         if (supports_temp)
666                 if (misc_register(&temp_miscdev)) {
667                         misc_deregister(&pcwd_miscdev);
668                         return -ENODEV;         
669                 }
670
671
672         if (revision == PCWD_REVISION_A) {
673                 if (!request_region(current_readport, 2, "PCWD Rev.A (Berkshire)")) {
674                         misc_deregister(&pcwd_miscdev);
675                         if (supports_temp)
676                                 misc_deregister(&pcwd_miscdev);
677                         return -EIO;            
678                 }
679         }
680         else 
681                 if (!request_region(current_readport, 4, "PCWD Rev.C (Berkshire)")) {
682                         misc_deregister(&pcwd_miscdev);
683                         if (supports_temp)
684                                 misc_deregister(&pcwd_miscdev);
685                         return -EIO;
686                 }
687
688         return 0;
689 }
690
691 static void __exit pcwatchdog_exit(void)
692 {
693         misc_deregister(&pcwd_miscdev);
694         /*  Disable the board  */
695         if (revision == PCWD_REVISION_C) {
696                 outb_p(0xA5, current_readport + 3);
697                 outb_p(0xA5, current_readport + 3);
698         }
699         if (supports_temp)
700                 misc_deregister(&temp_miscdev);
701
702         release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
703 }
704
705 module_init(pcwatchdog_init);
706 module_exit(pcwatchdog_exit);
707
708 MODULE_LICENSE("GPL");
709