import of ftp.dlink.com/GPL/DSMG-600_reB/ppclinux.tar.gz
[linux-2.4.21-pre4.git] / drivers / char / i810-tco.c
1 /*
2  *      i810-tco 0.05:  TCO timer driver for i8xx chipsets
3  *
4  *      (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
5  *                              http://www.kernelconcepts.de
6  *
7  *      This program is free software; you can redistribute it and/or
8  *      modify it under the terms of the GNU General Public License
9  *      as published by the Free Software Foundation; either version
10  *      2 of the License, or (at your option) any later version.
11  *      
12  *      Neither kernel concepts nor Nils Faerber admit liability nor provide
13  *      warranty for any of this software. This material is provided
14  *      "AS-IS" and at no charge.
15  *
16  *      (c) Copyright 2000      kernel concepts <nils@kernelconcepts.de>
17  *                              developed for
18  *                              Jentro AG, Haar/Munich (Germany)
19  *
20  *      TCO timer driver for i8xx chipsets
21  *      based on softdog.c by Alan Cox <alan@redhat.com>
22  *
23  *      The TCO timer is implemented in the following I/O controller hubs:
24  *      (See the intel documentation on http://developer.intel.com.)
25  *      82801AA & 82801AB  chip : document number 290655-003, 290677-004,
26  *      82801BA & 82801BAM chip : document number 290687-002, 298242-005,
27  *      82801CA & 82801CAM chip : document number 290716-001, 290718-001,
28  *      82801DB & 82801E   chip : document number 290744-001, 273599-001
29  *
30  *  20000710 Nils Faerber
31  *      Initial Version 0.01
32  *  20000728 Nils Faerber
33  *      0.02 Fix for SMI_EN->TCO_EN bit, some cleanups
34  *  20011214 Matt Domsch <Matt_Domsch@dell.com>
35  *      0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
36  *           Didn't add timeout option as i810_margin already exists.
37  *  20020224 Joel Becker, Wim Van Sebroeck
38  *      0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3,
39  *           add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT.
40  *  20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck
41  *      0.05 Fix possible timer_alive race, add expect close support,
42  *           clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and
43  *           WDIOC_SETOPTIONS), made i810tco_getdevice __init,
44  *           removed boot_status, removed tco_timer_read,
45  *           added support for 82801DB and 82801E chipset, general cleanup.
46  */
47  
48 #include <linux/module.h>
49 #include <linux/types.h>
50 #include <linux/kernel.h>
51 #include <linux/fs.h>
52 #include <linux/mm.h>
53 #include <linux/miscdevice.h>
54 #include <linux/watchdog.h>
55 #include <linux/reboot.h>
56 #include <linux/init.h>
57 #include <linux/pci.h>
58 #include <linux/ioport.h>
59 #include <asm/uaccess.h>
60 #include <asm/io.h>
61 #include "i810-tco.h"
62
63
64 /* Module and version information */
65 #define TCO_VERSION "0.05"
66 #define TCO_MODULE_NAME "i810 TCO timer"
67 #define TCO_DRIVER_NAME   TCO_MODULE_NAME ", v" TCO_VERSION
68
69 /* Default expire timeout */
70 #define TIMER_MARGIN    50      /* steps of 0.6sec, 3<n<64. Default is 30 seconds */
71
72 static unsigned int ACPIBASE;
73 static spinlock_t tco_lock;     /* Guards the hardware */
74
75 static int i810_margin = TIMER_MARGIN;  /* steps of 0.6sec */
76
77 MODULE_PARM(i810_margin, "i");
78 MODULE_PARM_DESC(i810_margin, "i810-tco timeout in steps of 0.6sec, 3<n<64. Default = 50 (30 seconds)");
79
80 #ifdef CONFIG_WATCHDOG_NOWAYOUT
81 static int nowayout = 1;
82 #else
83 static int nowayout = 0;
84 #endif
85
86 MODULE_PARM(nowayout,"i");
87 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
88
89
90 /*
91  *      Timer active flag
92  */
93
94 static unsigned long timer_alive;
95 static char tco_expect_close;
96
97 /*
98  * Some TCO specific functions
99  */
100
101
102 /*
103  * Start the timer countdown
104  */
105 static int tco_timer_start (void)
106 {
107         unsigned char val;
108
109         spin_lock(&tco_lock);
110         val = inb (TCO1_CNT + 1);
111         val &= 0xf7;
112         outb (val, TCO1_CNT + 1);
113         val = inb (TCO1_CNT + 1);
114         spin_unlock(&tco_lock);
115         
116         if (val & 0x08)
117                 return -1;
118         return 0;
119 }
120
121 /*
122  * Stop the timer countdown
123  */
124 static int tco_timer_stop (void)
125 {
126         unsigned char val;
127
128         spin_lock(&tco_lock);
129         val = inb (TCO1_CNT + 1);
130         val |= 0x08;
131         outb (val, TCO1_CNT + 1);
132         val = inb (TCO1_CNT + 1);
133         spin_unlock(&tco_lock);
134         
135         if ((val & 0x08) == 0)
136                 return -1;
137         return 0;
138 }
139
140 /*
141  * Set the timer reload value
142  */
143 static int tco_timer_settimer (unsigned char tmrval)
144 {
145         unsigned char val;
146
147         /* from the specs: */
148         /* "Values of 0h-3h are ignored and should not be attempted" */
149         if (tmrval > 0x3f || tmrval < 0x04)
150                 return -1;
151         
152         spin_lock(&tco_lock);
153         val = inb (TCO1_TMR);
154         val &= 0xc0;
155         val |= tmrval;
156         outb (val, TCO1_TMR);
157         val = inb (TCO1_TMR);
158         spin_unlock(&tco_lock);
159         
160         if ((val & 0x3f) != tmrval)
161                 return -1;
162
163         return 0;
164 }
165
166 /*
167  * Reload (trigger) the timer. Lock is needed so we dont reload it during
168  * a reprogramming event
169  */
170  
171 static void tco_timer_reload (void)
172 {
173         spin_lock(&tco_lock);
174         outb (0x01, TCO1_RLD);
175         spin_unlock(&tco_lock);
176 }
177
178 /*
179  *      Allow only one person to hold it open
180  */
181
182 static int i810tco_open (struct inode *inode, struct file *file)
183 {
184         if (test_and_set_bit(0, &timer_alive))
185                 return -EBUSY;
186
187         /*
188          *      Reload and activate timer
189          */
190         tco_timer_reload ();
191         tco_timer_start ();
192         return 0;
193 }
194
195 static int i810tco_release (struct inode *inode, struct file *file)
196 {
197         /*
198          *      Shut off the timer.
199          */
200         if (tco_expect_close == 42 && !nowayout) {
201                 tco_timer_stop ();
202         } else {
203                 tco_timer_reload ();
204                 printk(KERN_CRIT TCO_MODULE_NAME ": Unexpected close, not stopping watchdog!\n");
205         }
206         clear_bit(0, &timer_alive);
207         tco_expect_close = 0;
208         return 0;
209 }
210
211 static ssize_t i810tco_write (struct file *file, const char *data,
212                               size_t len, loff_t * ppos)
213 {
214         /*  Can't seek (pwrite) on this device  */
215         if (ppos != &file->f_pos)
216                 return -ESPIPE;
217
218         /* See if we got the magic character 'V' and reload the timer */
219         if (len) {
220                 size_t i;
221
222                 tco_expect_close = 0;
223
224                 /* scan to see wether or not we got the magic character */
225                 for (i = 0; i != len; i++) {
226                         u8 c;
227                         if(get_user(c, data+i))
228                                 return -EFAULT;
229                         if (c == 'V')
230                                 tco_expect_close = 42;
231                 }
232
233                 /* someone wrote to us, we should reload the timer */
234                 tco_timer_reload ();
235                 return 1;
236         }
237         return 0;
238 }
239
240 static int i810tco_ioctl (struct inode *inode, struct file *file,
241                           unsigned int cmd, unsigned long arg)
242 {
243         int new_margin, u_margin;
244         int options, retval = -EINVAL;
245
246         static struct watchdog_info ident = {
247                 options:                WDIOF_SETTIMEOUT |
248                                         WDIOF_KEEPALIVEPING |
249                                         WDIOF_MAGICCLOSE,
250                 firmware_version:       0,
251                 identity:               "i810 TCO timer",
252         };
253         switch (cmd) {
254                 default:
255                         return -ENOTTY;
256                 case WDIOC_GETSUPPORT:
257                         if (copy_to_user
258                             ((struct watchdog_info *) arg, &ident, sizeof (ident)))
259                                 return -EFAULT;
260                         return 0;
261                 case WDIOC_GETSTATUS:
262                 case WDIOC_GETBOOTSTATUS:
263                         return put_user (0, (int *) arg);
264                 case WDIOC_SETOPTIONS:
265                         if (get_user (options, (int *) arg))
266                                 return -EFAULT;
267                         if (options & WDIOS_DISABLECARD) {
268                                 tco_timer_stop ();
269                                 retval = 0;
270                         }
271                         if (options & WDIOS_ENABLECARD) {
272                                 tco_timer_reload ();
273                                 tco_timer_start ();
274                                 retval = 0;
275                         }
276                         return retval;
277                 case WDIOC_KEEPALIVE:
278                         tco_timer_reload ();
279                         return 0;
280                 case WDIOC_SETTIMEOUT:
281                         if (get_user (u_margin, (int *) arg))
282                                 return -EFAULT;
283                         new_margin = (u_margin * 10 + 5) / 6;
284                         if ((new_margin < 4) || (new_margin > 63))
285                                 return -EINVAL;
286                         if (tco_timer_settimer ((unsigned char) new_margin))
287                             return -EINVAL;
288                         i810_margin = new_margin;
289                         tco_timer_reload ();
290                         /* Fall */
291                 case WDIOC_GETTIMEOUT:
292                         return put_user ((int)(i810_margin * 6 / 10), (int *) arg);
293         }
294 }
295
296 /*
297  * Data for PCI driver interface
298  *
299  * This data only exists for exporting the supported
300  * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
301  * register a pci_driver, because someone else might one day
302  * want to register another driver on the same PCI id.
303  */
304 static struct pci_device_id i810tco_pci_tbl[] __initdata = {
305         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0,   PCI_ANY_ID, PCI_ANY_ID, },
306         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0,   PCI_ANY_ID, PCI_ANY_ID, },
307         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0,   PCI_ANY_ID, PCI_ANY_ID, },
308         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10,  PCI_ANY_ID, PCI_ANY_ID, },
309         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0,   PCI_ANY_ID, PCI_ANY_ID, },
310         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12,  PCI_ANY_ID, PCI_ANY_ID, },
311         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,   PCI_ANY_ID, PCI_ANY_ID, },
312         { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0,    PCI_ANY_ID, PCI_ANY_ID, },
313         { 0, },
314 };
315 MODULE_DEVICE_TABLE (pci, i810tco_pci_tbl);
316
317 static struct pci_dev *i810tco_pci;
318
319 static unsigned char __init i810tco_getdevice (void)
320 {
321         struct pci_dev *dev;
322         u8 val1, val2;
323         u16 badr;
324         /*
325          *      Find the PCI device
326          */
327
328         pci_for_each_dev(dev) {
329                 if (pci_match_device(i810tco_pci_tbl, dev)) {
330                         i810tco_pci = dev;
331                         break;
332                 }
333         }
334
335         if (i810tco_pci) {
336                 /*
337                  *      Find the ACPI base I/O address which is the base
338                  *      for the TCO registers (TCOBASE=ACPIBASE + 0x60)
339                  *      ACPIBASE is bits [15:7] from 0x40-0x43
340                  */
341                 pci_read_config_byte (i810tco_pci, 0x40, &val1);
342                 pci_read_config_byte (i810tco_pci, 0x41, &val2);
343                 badr = ((val2 << 1) | (val1 >> 7)) << 7;
344                 ACPIBASE = badr;
345                 /* Something's wrong here, ACPIBASE has to be set */
346                 if (badr == 0x0001 || badr == 0x0000) {
347                         printk (KERN_ERR TCO_MODULE_NAME " init: failed to get TCOBASE address\n");
348                         return 0;
349                 }
350                 /*
351                  * Check chipset's NO_REBOOT bit
352                  */
353                 pci_read_config_byte (i810tco_pci, 0xd4, &val1);
354                 if (val1 & 0x02) {
355                         val1 &= 0xfd;
356                         pci_write_config_byte (i810tco_pci, 0xd4, val1);
357                         pci_read_config_byte (i810tco_pci, 0xd4, &val1);
358                         if (val1 & 0x02) {
359                                 printk (KERN_ERR TCO_MODULE_NAME " init: failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
360                                 return 0;       /* Cannot reset NO_REBOOT bit */
361                         }
362                 }
363                 /* Set the TCO_EN bit in SMI_EN register */
364                 val1 = inb (SMI_EN + 1);
365                 val1 &= 0xdf;
366                 outb (val1, SMI_EN + 1);
367                 /* Clear out the (probably old) status */
368                 outb (0, TCO1_STS);
369                 outb (3, TCO2_STS);
370                 return 1;
371         }
372         return 0;
373 }
374
375 static struct file_operations i810tco_fops = {
376         owner:          THIS_MODULE,
377         write:          i810tco_write,
378         ioctl:          i810tco_ioctl,
379         open:           i810tco_open,
380         release:        i810tco_release,
381 };
382
383 static struct miscdevice i810tco_miscdev = {
384         minor:          WATCHDOG_MINOR,
385         name:           "watchdog",
386         fops:           &i810tco_fops,
387 };
388
389 static int __init watchdog_init (void)
390 {
391         spin_lock_init(&tco_lock);
392         if (!i810tco_getdevice () || i810tco_pci == NULL)
393                 return -ENODEV;
394         if (!request_region (TCOBASE, 0x10, "i810 TCO")) {
395                 printk (KERN_ERR TCO_MODULE_NAME
396                         ": I/O address 0x%04x already in use\n",
397                         TCOBASE);
398                 return -EIO;
399         }
400         if (misc_register (&i810tco_miscdev) != 0) {
401                 release_region (TCOBASE, 0x10);
402                 printk (KERN_ERR TCO_MODULE_NAME ": cannot register miscdev\n");
403                 return -EIO;
404         }
405         tco_timer_settimer ((unsigned char) i810_margin);
406         tco_timer_reload ();
407
408         printk (KERN_INFO TCO_DRIVER_NAME
409                 ": timer margin: %d sec (0x%04x) (nowayout=%d)\n",
410                 (int) (i810_margin * 6 / 10), TCOBASE, nowayout);
411         return 0;
412 }
413
414 static void __exit watchdog_cleanup (void)
415 {
416         u8 val;
417
418         /* Reset the timer before we leave */
419         tco_timer_reload ();
420         /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
421         pci_read_config_byte (i810tco_pci, 0xd4, &val);
422         val |= 0x02;
423         pci_write_config_byte (i810tco_pci, 0xd4, val);
424         release_region (TCOBASE, 0x10);
425         misc_deregister (&i810tco_miscdev);
426 }
427
428 module_init(watchdog_init);
429 module_exit(watchdog_cleanup);
430
431 MODULE_AUTHOR("Nils Faerber");
432 MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
433 MODULE_LICENSE("GPL");
434 EXPORT_NO_SYMBOLS;