2 * i810-tco 0.05: TCO timer driver for i8xx chipsets
4 * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
5 * http://www.kernelconcepts.de
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.
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.
16 * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
18 * Jentro AG, Haar/Munich (Germany)
20 * TCO timer driver for i8xx chipsets
21 * based on softdog.c by Alan Cox <alan@redhat.com>
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
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.
48 #include <linux/module.h>
49 #include <linux/types.h>
50 #include <linux/kernel.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>
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
69 /* Default expire timeout */
70 #define TIMER_MARGIN 50 /* steps of 0.6sec, 3<n<64. Default is 30 seconds */
72 static unsigned int ACPIBASE;
73 static spinlock_t tco_lock; /* Guards the hardware */
75 static int i810_margin = TIMER_MARGIN; /* steps of 0.6sec */
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)");
80 #ifdef CONFIG_WATCHDOG_NOWAYOUT
81 static int nowayout = 1;
83 static int nowayout = 0;
86 MODULE_PARM(nowayout,"i");
87 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
94 static unsigned long timer_alive;
95 static char tco_expect_close;
98 * Some TCO specific functions
103 * Start the timer countdown
105 static int tco_timer_start (void)
109 spin_lock(&tco_lock);
110 val = inb (TCO1_CNT + 1);
112 outb (val, TCO1_CNT + 1);
113 val = inb (TCO1_CNT + 1);
114 spin_unlock(&tco_lock);
122 * Stop the timer countdown
124 static int tco_timer_stop (void)
128 spin_lock(&tco_lock);
129 val = inb (TCO1_CNT + 1);
131 outb (val, TCO1_CNT + 1);
132 val = inb (TCO1_CNT + 1);
133 spin_unlock(&tco_lock);
135 if ((val & 0x08) == 0)
141 * Set the timer reload value
143 static int tco_timer_settimer (unsigned char tmrval)
147 /* from the specs: */
148 /* "Values of 0h-3h are ignored and should not be attempted" */
149 if (tmrval > 0x3f || tmrval < 0x04)
152 spin_lock(&tco_lock);
153 val = inb (TCO1_TMR);
156 outb (val, TCO1_TMR);
157 val = inb (TCO1_TMR);
158 spin_unlock(&tco_lock);
160 if ((val & 0x3f) != tmrval)
167 * Reload (trigger) the timer. Lock is needed so we dont reload it during
168 * a reprogramming event
171 static void tco_timer_reload (void)
173 spin_lock(&tco_lock);
174 outb (0x01, TCO1_RLD);
175 spin_unlock(&tco_lock);
179 * Allow only one person to hold it open
182 static int i810tco_open (struct inode *inode, struct file *file)
184 if (test_and_set_bit(0, &timer_alive))
188 * Reload and activate timer
195 static int i810tco_release (struct inode *inode, struct file *file)
198 * Shut off the timer.
200 if (tco_expect_close == 42 && !nowayout) {
204 printk(KERN_CRIT TCO_MODULE_NAME ": Unexpected close, not stopping watchdog!\n");
206 clear_bit(0, &timer_alive);
207 tco_expect_close = 0;
211 static ssize_t i810tco_write (struct file *file, const char *data,
212 size_t len, loff_t * ppos)
214 /* Can't seek (pwrite) on this device */
215 if (ppos != &file->f_pos)
218 /* See if we got the magic character 'V' and reload the timer */
222 tco_expect_close = 0;
224 /* scan to see wether or not we got the magic character */
225 for (i = 0; i != len; i++) {
227 if(get_user(c, data+i))
230 tco_expect_close = 42;
233 /* someone wrote to us, we should reload the timer */
240 static int i810tco_ioctl (struct inode *inode, struct file *file,
241 unsigned int cmd, unsigned long arg)
243 int new_margin, u_margin;
244 int options, retval = -EINVAL;
246 static struct watchdog_info ident = {
247 options: WDIOF_SETTIMEOUT |
248 WDIOF_KEEPALIVEPING |
251 identity: "i810 TCO timer",
256 case WDIOC_GETSUPPORT:
258 ((struct watchdog_info *) arg, &ident, sizeof (ident)))
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))
267 if (options & WDIOS_DISABLECARD) {
271 if (options & WDIOS_ENABLECARD) {
277 case WDIOC_KEEPALIVE:
280 case WDIOC_SETTIMEOUT:
281 if (get_user (u_margin, (int *) arg))
283 new_margin = (u_margin * 10 + 5) / 6;
284 if ((new_margin < 4) || (new_margin > 63))
286 if (tco_timer_settimer ((unsigned char) new_margin))
288 i810_margin = new_margin;
291 case WDIOC_GETTIMEOUT:
292 return put_user ((int)(i810_margin * 6 / 10), (int *) arg);
297 * Data for PCI driver interface
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.
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, },
315 MODULE_DEVICE_TABLE (pci, i810tco_pci_tbl);
317 static struct pci_dev *i810tco_pci;
319 static unsigned char __init i810tco_getdevice (void)
325 * Find the PCI device
328 pci_for_each_dev(dev) {
329 if (pci_match_device(i810tco_pci_tbl, dev)) {
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
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;
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");
351 * Check chipset's NO_REBOOT bit
353 pci_read_config_byte (i810tco_pci, 0xd4, &val1);
356 pci_write_config_byte (i810tco_pci, 0xd4, val1);
357 pci_read_config_byte (i810tco_pci, 0xd4, &val1);
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 */
363 /* Set the TCO_EN bit in SMI_EN register */
364 val1 = inb (SMI_EN + 1);
366 outb (val1, SMI_EN + 1);
367 /* Clear out the (probably old) status */
375 static struct file_operations i810tco_fops = {
377 write: i810tco_write,
378 ioctl: i810tco_ioctl,
380 release: i810tco_release,
383 static struct miscdevice i810tco_miscdev = {
384 minor: WATCHDOG_MINOR,
389 static int __init watchdog_init (void)
391 spin_lock_init(&tco_lock);
392 if (!i810tco_getdevice () || i810tco_pci == NULL)
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",
400 if (misc_register (&i810tco_miscdev) != 0) {
401 release_region (TCOBASE, 0x10);
402 printk (KERN_ERR TCO_MODULE_NAME ": cannot register miscdev\n");
405 tco_timer_settimer ((unsigned char) i810_margin);
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);
414 static void __exit watchdog_cleanup (void)
418 /* Reset the timer before we leave */
420 /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
421 pci_read_config_byte (i810tco_pci, 0xd4, &val);
423 pci_write_config_byte (i810tco_pci, 0xd4, val);
424 release_region (TCOBASE, 0x10);
425 misc_deregister (&i810tco_miscdev);
428 module_init(watchdog_init);
429 module_exit(watchdog_cleanup);
431 MODULE_AUTHOR("Nils Faerber");
432 MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
433 MODULE_LICENSE("GPL");