blob: b68a86755256c76235f9956cefa6657ea1e4f4ec [file] [log] [blame]
/*
* PC Watchdog Driver
* by Ken Hollis (khollis@bitgate.com)
*
* Permission granted from Simon Machell (73244.1270@compuserve.com)
* Written for the Linux Kernel, and GPLed by Ken Hollis
*
* 960107 Added request_region routines, modulized the whole thing.
* 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
* WD_TIMEOUT define.
* 960216 Added eof marker on the file, and changed verbose messages.
* 960716 Made functional and cosmetic changes to the source for
* inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
* 960717 Removed read/seek routines, replaced with ioctl. Also, added
* check_region command due to Alan's suggestion.
* 960821 Made changes to compile in newer 2.0.x kernels. Added
* "cold reboot sense" entry.
* 960825 Made a few changes to code, deleted some defines and made
* typedefs to replace them. Made heartbeat reset only available
* via ioctl, and removed the write routine.
* 960828 Added new items for PC Watchdog Rev.C card.
* 960829 Changed around all of the IOCTLs, added new features,
* added watchdog disable/re-enable routines. Added firmware
* version reporting. Added read routine for temperature.
* Removed some extra defines, added an autodetect Revision
* routine.
* 961006 Revised some documentation, fixed some cosmetic bugs. Made
* drivers to panic the system if it's overheating at bootup.
* 961118 Changed some verbiage on some of the output, tidied up
* code bits, and added compatibility to 2.1.x.
* 970912 Enabled board on open and disable on close.
* 971107 Took account of recent VFS changes (broke read).
* 971210 Disable board on initialisation in case board already ticking.
* 971222 Changed open/close for temperature handling
* Michael Meskes <meskes@debian.org>.
* 980112 Used minor numbers from include/linux/miscdevice.h
* 990403 Clear reset status after reading control status register in
* pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
* 990605 Made changes to code to support Firmware 1.22a, added
* fairly useless proc entry.
* 990610 removed said useless proc code for the merge <alan>
* 000403 Removed last traces of proc code. <davej>
* 011214 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
* 020210 Backported 2.5 open_allowed changes, and got rid of a useless
* variable <rob@osinvestor.com>
* Added timeout module option to override default
* 020306 Support the PCI version [Lindsay Harris <lindsay@bluegum.com>]
*/
/*
* A bells and whistles driver is available from http://www.pcwd.de/
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/pci.h>
#define WD_VER "1.13 (03/06/2002)"
/* Stuff for the PCI version */
#ifndef PCI_VENDOR_ID_QUICKLOGIC
#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3
#endif
#ifndef PCI_DEVICE_ID_BERKSHIRE
#define PCI_DEVICE_ID_BERKSHIRE 0x5030
#endif
/*
* It should be noted that PCWD_REV_B was removed because A and B
* are essentially the same types of card, with the exception that B
* has temperature reporting. Since I didn't receive a Rev.B card,
* the Rev.B card is not supported. (It's a good thing too, as they
* are no longer in production.)
*/
#define PCWD_REV_A 0
#define PCWD_REV_C 1
#define PCWD_REV_PCI 2
static int timeout_val;
static int timeout = 2;
static int expect_close = 0;
MODULE_PARM (timeout, "i");
MODULE_PARM_DESC (timeout, "Watchdog timeout in seconds (default=2)");
#ifdef CONFIG_WATCHDOG_NOWAYOUT
static int nowayout = 1;
#else
static int nowayout = 0;
#endif
MODULE_PARM (nowayout, "i");
MODULE_PARM_DESC (nowayout,
"Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
/*
* These are the defines for the PC Watchdog card, revision A.
*/
#define WD_WDRST 0x01 /* Previously reset state */
#define WD_T110 0x02 /* Temperature overheat sense */
#define WD_HRTBT 0x04 /* Heartbeat sense */
#define WD_RLY2 0x08 /* External relay triggered */
#define WD_SRLY2 0x80 /* Software external relay triggered */
/*
* Differences between cards regarding how they perform some operations
* are handled by an array of structs, with per card functions for the
* incompatible operations. It's all defined here.
*/
/* ENABLE/DISABLE the card */
typedef int (*fn_enable) (int); /* Enable/disable card */
static int pcwd_enable_card (int enable); /* Actually works */
static int pcwd_enable_nop (int enable); /* NOP - REV A cannot */
/* Obtain firmware version, if possible */
#define PCWD_FIRMWARE_BSZ 16 /* Version buffer size */
typedef void (*fn_firmware) (char *bp);
static void pcwd_firmware_ver_none (char *bp); /* REV A can't do it */
static void pcwd_firmware_ver_revc (char *bp); /* REV C boards can */
static void pcwd_firmware_ver_pci (char *bp); /* PCI boards can too */
/* Tickle the watchdog timer */
typedef void (*fn_tickle) (void);
static void pcwd_tickle_reva (void); /* Rev A only */
static void pcwd_tickle (void); /* Rev C, PCI */
/* Determine reboot and temperature status */
typedef int (*fn_status) (int reset_boot);
static int pcwd_get_stat_reva (int reset_boot);
static int pcwd_get_stat (int reset_boot);
/* Per card type specifications */
typedef struct {
fn_tickle wd_tickle; /* Reset the watchdog */
fn_enable enable_card; /* Enable/disable card, if possible */
fn_firmware firmware_ver; /* Get firmware version, if possible */
fn_status wd_status; /* Card reset and/or over temp */
int io_size; /* I/O space used */
const char *name; /* Nice name to display */
} PCWD_CARD_INFO;
/* Per card information, indexed by card version ID */
static PCWD_CARD_INFO pcwd_card_info[] = {
{
pcwd_tickle_reva,
pcwd_enable_nop,
pcwd_firmware_ver_none,
pcwd_get_stat_reva,
2,
"Berkshire Products PC Watchdog (REV A)",
},
{
pcwd_tickle,
pcwd_enable_card,
pcwd_firmware_ver_revc,
pcwd_get_stat,
4,
"Berkshire Products PC Watchdog (REV C)",
},
{
pcwd_tickle,
pcwd_enable_card,
pcwd_firmware_ver_pci,
pcwd_get_stat,
8,
"Berkshire Products PC Watchdog (PCI)",
},
};
/* Overall driver information, including per card pointer */
static struct {
PCWD_CARD_INFO *card_info; /* Points to one of the above */
atomic_t open_allowed; /* Watchdog is single open */
int flags; /* Defined below */
int boot_status; /* Card status at boot time */
int io_addr; /* Card's base address */
} pcwd_info = {
NULL, ATOMIC_INIT (1), 0, 0, 0};
/* Bits allocated in flags above. */
#define PCWD_HAS_TEMP 0x0001 /* Set when thermometer available */
#define PCWD_PCI_REG 0x0002 /* Set if PCI register code worked */
#define PCWD_TEMP_PANIC 0x0004 /* Panic when over temperature */
static spinlock_t io_lock;
/* D E T E R M I N E C A R D S T A T U S F U N C T I O N S */
/* Rev A cards return status information from the base register,
* which is used for the temperature in other cards. */
static int
pcwd_get_stat_reva (int reset_boot)
{
int retval;
int status;
spin_lock (&io_lock);
status = inb_p (pcwd_info.io_addr);
spin_unlock (&io_lock);
/* Transform the card register to the ioctl bits we use internally */
retval = WDIOF_MAGICCLOSE;
if (status & WD_WDRST)
retval |= WDIOF_CARDRESET;
if (status & WD_T110)
retval |= WDIOF_OVERHEAT;
return retval;
}
/*
* Rev C and PCI cards return card status in the base address + 1 register.
* And use different bits to indicate a card initiated reset, and
* an over-temperature condition. And the reboot status can be reset.
*/
static int
pcwd_get_stat (int reset_boot)
{
int retval;
int status;
spin_lock (&io_lock);
status = inb_p (pcwd_info.io_addr + 1);
if (reset_boot) {
/* NOTE: the REV C card clears the "card caused reboot"
* flag when writing ANY value to this port. However,
* the PCI card requires writing a 1 to bit 0. */
outb_p (0x01, pcwd_info.io_addr + 1);
}
spin_unlock (&io_lock);
retval = 0;
if (status & 0x01)
retval |= WDIOF_CARDRESET;
if (status & 0x04)
retval |= WDIOF_OVERHEAT;
return retval;
}
/* W A T C H D O G T I M E R R E S E T F U N C T I O N S */
/* Rev A cards are reset by setting a specific bit in register 1. */
static void
pcwd_tickle_reva (void)
{
int wdrst_stat;
spin_lock (&io_lock);
wdrst_stat = inb_p (pcwd_info.io_addr);
wdrst_stat = (wdrst_stat & 0x0F) | WD_WDRST;
outb_p (wdrst_stat, pcwd_info.io_addr + 1);
spin_unlock (&io_lock);
return;
}
/* Other cards are reset by writing anything to the base register. */
static void
pcwd_tickle (void)
{
spin_lock (&io_lock);
outb_p (0x42, pcwd_info.io_addr);
spin_unlock (&io_lock);
return;
}
static int
pcwd_ioctl (struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int rv;
int retval;
static struct watchdog_info ident = {
WDIOF_OVERHEAT | WDIOF_CARDRESET,
1,
"PCWD"
};
switch (cmd) {
case WDIOC_GETSUPPORT:
rv = copy_to_user ((void *) arg, &ident, sizeof (ident));
return rv ? -EFAULT : 0;
case WDIOC_GETSTATUS:
rv = pcwd_info.card_info->wd_status (0);
if (rv & WDIOF_OVERHEAT) {
if (pcwd_info.flags & PCWD_TEMP_PANIC)
panic ("pcwd: Temperature overheat trip!\n");
}
if (put_user (rv, (int *) arg))
return -EFAULT;
return 0;
case WDIOC_GETBOOTSTATUS:
rv = pcwd_info.boot_status;
if (put_user (rv, (int *) arg))
return -EFAULT;
return 0;
case WDIOC_GETTEMP:
rv = 0;
if (pcwd_info.flags & PCWD_HAS_TEMP) {
spin_lock (&io_lock);
rv = inb_p (pcwd_info.io_addr);
spin_unlock (&io_lock);
}
if (put_user (rv, (int *) arg))
return -EFAULT;
return 0;
case WDIOC_SETOPTIONS:
if (copy_from_user (&rv, (int *) arg, sizeof (int)))
return -EFAULT;
retval = -EINVAL;
if (rv & WDIOS_DISABLECARD) {
if (!pcwd_info.card_info->enable_card (0)) {
printk (KERN_EMERG
"pcwd: Could not disable card\n");
return -EIO;
}
retval = 0;
}
if (rv & WDIOS_ENABLECARD) {
if (!pcwd_info.card_info->enable_card (1)) {
printk (KERN_EMERG
"pcwd: Could not enable card\n");
return -EIO;
}
retval = 0;
}
if (rv & WDIOS_TEMPPANIC) {
pcwd_info.flags |= PCWD_TEMP_PANIC;
retval = 0;
}
return retval;
case WDIOC_KEEPALIVE:
pcwd_info.card_info->wd_tickle ();
return 0;
default:
return -ENOTTY;
}
return 0;
}
/* Write: only for the watchdog device (thermometer is read-only). */
static ssize_t
pcwd_write (struct file *file, const char *buf, size_t len, loff_t * ppos)
{
/* Can't seek (pwrite) on this device */
if (ppos != &file->f_pos)
return -ESPIPE;
if (len) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
expect_close = 0;
for (i = 0; i != len; i++) {
char c;
if (get_user(c, buf + i))
return -EFAULT;
if (c == 'V')
expect_close = 1;
}
}
pcwd_info.card_info->wd_tickle ();
return 1;
}
return 0;
}
static int
pcwd_open (struct inode *ino, struct file *filep)
{
switch (MINOR (ino->i_rdev)) {
case WATCHDOG_MINOR:
if (!atomic_dec_and_test (&pcwd_info.open_allowed)) {
atomic_inc (&pcwd_info.open_allowed);
return -EBUSY;
}
/* Enable the card */
pcwd_info.card_info->enable_card (1);
pcwd_info.card_info->wd_tickle ();
return 0;
case TEMP_MINOR:
if (pcwd_info.flags & PCWD_HAS_TEMP) {
return 0;
}
return -ENODEV;
default:
return -ENODEV;
}
}
/* Read: applies only to the thermometer (watchdog is write only). */
static ssize_t
pcwd_read (struct file *file, char *buf, size_t count, loff_t * ppos)
{
unsigned short c;
unsigned char cp;
/* Can't seek (pread) on this device */
if (ppos != &file->f_pos)
return -ESPIPE;
/*
* Convert celsius to fahrenheit, since this was
* the decided 'standard' for this return value.
*/
spin_lock (&io_lock);
c = inb_p (pcwd_info.io_addr);
spin_unlock (&io_lock);
cp = (c * 9 / 5) + 32;
if (copy_to_user (buf, &cp, 1))
return -EFAULT;
return 1;
}
static int
pcwd_close (struct inode *ino, struct file *filep)
{
switch (MINOR (ino->i_rdev)) {
case WATCHDOG_MINOR:
if (expect_close)
pcwd_info.card_info->enable_card (0);
atomic_inc (&pcwd_info.open_allowed);
break;
case TEMP_MINOR:
break;
}
return 0;
}
/*
* System is shutting down, so disable the card. Otherwise the timeout
* may expire during shutdown. Of course, this means a hang during
* shutdown will not be reset, but somebody is probably nearby and will
* notice. The alternative is to have the shutdown aborted when the
* watchdog expires and hits reset.
*/
static int
pcwd_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT) {
/*
* If initialisation is still in progress, the device pointer
* may not be valid, so check, just to make sure.
*/
if (pcwd_info.card_info)
pcwd_info.card_info->enable_card (0);
}
return NOTIFY_DONE;
}
/* C A R D E N A B L E / D I S A B L E F U N C T I O N S */
/* Enable/disable the card, not REV A. The two writes are required by card */
static int
pcwd_enable_card (int enable)
{
int stat_reg;
spin_lock (&io_lock);
if (enable) {
outb_p (0x00, pcwd_info.io_addr + 3);
} else {
outb_p (0xA5, pcwd_info.io_addr + 3);
outb_p (0xA5, pcwd_info.io_addr + 3);
}
stat_reg = inb_p (pcwd_info.io_addr + 2);
spin_unlock (&io_lock);
stat_reg &= 0x10; /* "disabled when set" bit */
if (enable) {
stat_reg ^= 0x10;
}
return stat_reg;
}
static int
pcwd_enable_nop (int enable)
{
return 0;
}
static void __init
set_card_type (int is_pci)
{
if (is_pci) {
pcwd_info.card_info = &pcwd_card_info[PCWD_REV_PCI];
pcwd_info.flags |= PCWD_PCI_REG;
} else {
pcwd_info.card_info = &pcwd_card_info[PCWD_REV_C];
/* REV A cards use only 2 io ports; test
* presumes a floating bus reads as 0xff. */
if ((inb (pcwd_info.io_addr + 2) == 0xFF) ||
(inb (pcwd_info.io_addr + 3) == 0xFF)) {
pcwd_info.card_info = &pcwd_card_info[PCWD_REV_A];
}
}
return;
}
/* G E T F I R M W A R E V E R S I O N F U N C T I O N S */
/* REV A can't do it */
static void __init
pcwd_firmware_ver_none (char *bp)
{
strncpy (bp, "<unavailable>", PCWD_FIRMWARE_BSZ);
return;
}
/* PCI boards can too */
static void __init
pcwd_firmware_ver_pci (char *bp)
{
int count;
/* Write the 'Get Firmware Version' command to port 6 and wait */
outb (0x08, pcwd_info.io_addr + 6);
/* Card sets bit 0x40 (WRSP) bit in port 2. Can take 10ms! */
for (count = 0; count < 15; ++count) {
mdelay (1); /* Board responds slowly */
if (inb (pcwd_info.io_addr + 2) & 0x40) {
/* Board says data now valid */
snprintf (bp, PCWD_FIRMWARE_BSZ, "%u.%u",
inb (pcwd_info.io_addr + 5),
inb (pcwd_info.io_addr + 4));
return;
}
}
strncpy (bp, "<card no answer>", PCWD_FIRMWARE_BSZ);
return;
}
/*
* REV C boards read diagnostic (including firmware version) data
* from the register 0. To do this, the card is put into diagnostic
* mode, then the command is submitted and data read from register 0.
* NOTE: the onboard processor writes 4 bits at a time to the register,
* so it's necessary to wait for the data to stabilise before
* accepting it.
*/
static int __init
send_command (int cmd)
{
int ii;
int reg0, last_reg0; /* Double read for stabilising */
outb (cmd, pcwd_info.io_addr + 2);
/*
* The following delay need only be 200 microseconds according
* to the spec I have. But my card seems slower, as waiting
* 250 microseconds returns valid data, but NOT from this
* command. The 1000 value may be excessive, but is reliable.
*/
mdelay (1);
reg0 = inb (pcwd_info.io_addr);
for (ii = 0; ii < 25; ++ii) {
last_reg0 = reg0;
reg0 = inb (pcwd_info.io_addr);
if (reg0 == last_reg0)
break; /* Data is stable */
udelay (250);
}
return reg0;
}
/* REV C board function to retrieve firmware version */
static void __init
pcwd_firmware_ver_revc (char *bp)
{
int i, found = 0, count = 0;
/* Set the card into debug mode to find firmware version */
outb_p (0x00, pcwd_info.io_addr + 2); /* Spec says to do this */
udelay (500);
while ((count < 3) && (!found)) {
i = send_command (0x80);
if (i == 0x00) {
found = 1;
break;
} else if (i == 0xF3) {
/* Card does not like what we've done to it */
outb_p (0x00, pcwd_info.io_addr + 2);
udelay (1200); /* Spec says wait 1ms */
outb_p (0x00, pcwd_info.io_addr + 2);
udelay (500);
}
count++;
}
if (found) {
*bp++ = send_command (0x81);
*bp++ = '.';
*bp++ = send_command (0x82);
*bp++ = send_command (0x83);
*bp++ = send_command (0x84);
*bp++ = '\0';
/* Out of debug mode */
outb (0x00, pcwd_info.io_addr + 2);
} else
strncpy (bp, "<err - no go>", PCWD_FIRMWARE_BSZ);
return;
}
/* Initialisation function called ONLY from the PCI layer. */
static int __init
pcwd_init_one (struct pci_dev *dev, const struct pci_device_id *ent)
{
static int devices = 0;
++devices;
if (devices > 1) {
printk (KERN_ERR "pcwd: Driver supports only ONE device\n");
return -ENODEV;
}
pcwd_info.io_addr = pci_resource_start (dev, 0);
if (pcwd_info.io_addr == 0 || pci_enable_device (dev))
return -ENODEV;
return 0;
}
static struct pci_device_id pcwd_pci_tbl[] __initdata = {
{PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_BERKSHIRE,
PCI_ANY_ID, PCI_ANY_ID,},
{0}, /* End of list */
};
MODULE_DEVICE_TABLE (pci, pcwd_pci_tbl);
static struct pci_driver pcwd_driver = {
name:"pcwd",
id_table:pcwd_pci_tbl,
probe:pcwd_init_one,
};
static struct file_operations pcwd_fops = {
owner:THIS_MODULE,
write:pcwd_write,
ioctl:pcwd_ioctl,
open:pcwd_open,
release:pcwd_close,
};
static struct miscdevice pcwd_miscdev = {
WATCHDOG_MINOR,
"watchdog",
&pcwd_fops
};
static struct file_operations pcwd_temp_fops = {
owner:THIS_MODULE,
read:pcwd_read,
open:pcwd_open,
release:pcwd_close,
};
static struct miscdevice temp_miscdev = {
TEMP_MINOR,
"temperature",
&pcwd_temp_fops
};
/* Need to know about shutdown to kill the timer - may reset during shutdown! */
static struct notifier_block pcwd_notifier =
{
pcwd_notify_sys,
NULL,
0,
};
/*
* The ISA cards have a heartbeat bit in one of the registers, which
* register is card dependent. The heartbeat bit is monitored, and if
* found, is considered proof that a Berkshire card has been found.
* The initial rate is once per second at board start up, then twice
* per second for normal operation.
*/
static int __init
check_isa_card (int base_addr)
{
int reg0, last_reg0; /* Reg 0, in case it's REV A */
int reg1, last_reg1; /* Register 1 for REV C cards */
int ii;
int retval;
/* As suggested by Alan Cox - this is a safety measure. */
if (!request_region (base_addr, 4, "pcwd-isa")) {
printk (KERN_INFO "pcwd: Port 0x%x unavailable\n", base_addr);
return 0;
}
retval = 0;
reg0 = inb_p (base_addr); /* For REV A boards */
reg1 = inb (base_addr + 1); /* For REV C boards */
if (reg0 != 0xff || reg1 != 0xff) {
/* Not an 'ff' from a floating bus, so must be a card! */
for (ii = 0; ii < timeout_val; ++ii) {
set_current_state (TASK_INTERRUPTIBLE);
schedule_timeout (HZ / 2);
last_reg0 = reg0;
last_reg1 = reg1;
reg0 = inb_p (base_addr);
reg1 = inb (base_addr + 1);
/* Has either hearbeat bit changed? */
if ((reg0 ^ last_reg0) & WD_HRTBT ||
(reg1 ^ last_reg1) & 0x02) {
retval = 1;
break;
}
}
}
release_region (base_addr, 4);
return retval;
}
static int __init
pcwd_card_init (void)
{
int retval;
char fvbuf[PCWD_FIRMWARE_BSZ];
pcwd_info.card_info->firmware_ver (fvbuf);
printk (KERN_INFO "pcwd: %s at port 0x%03x (Firmware: %s)\n",
pcwd_info.card_info->name, pcwd_info.io_addr, fvbuf);
/* Returns 0xf0 in temperature register if no thermometer */
if (inb (pcwd_info.io_addr) != 0xF0) {
pcwd_info.flags |= PCWD_HAS_TEMP;
printk (KERN_INFO "pcwd: Temperature option detected\n");
}
if (nowayout)
printk (KERN_INFO
"pcwd: Watchdog cannot be stopped once started\n");
/* Record the power up status of "card did reset" and/or temp trip */
pcwd_info.boot_status = pcwd_info.card_info->wd_status (1);
if (pcwd_info.boot_status & WDIOF_CARDRESET)
printk (KERN_INFO
"pcwd: Previous reboot was caused by the card\n");
if (pcwd_info.boot_status & WDIOF_OVERHEAT) {
printk (KERN_EMERG
"pcwd: Card senses a CPU Overheat. Panicking!\n");
panic ("pcwd: CPU Overheat\n");
}
if (pcwd_info.boot_status == 0)
printk (KERN_INFO "pcwd: Cold boot sense\n");
pcwd_info.card_info->enable_card (0);
retval = 0;
if (!request_region (pcwd_info.io_addr,
pcwd_info.card_info->io_size,
pcwd_info.card_info->name)) {
printk (KERN_ERR "pcwd: I/0 %d is not free\n",
pcwd_info.io_addr);
return retval;
}
retval = misc_register (&pcwd_miscdev);
if (retval) {
release_region (pcwd_info.io_addr,
pcwd_info.card_info->io_size);
printk (KERN_ERR "pcwd: can't misc_register on minor %d\n",
WATCHDOG_MINOR);
return retval;
}
if (pcwd_info.flags & PCWD_HAS_TEMP) {
if (misc_register (&temp_miscdev)) {
printk (KERN_ERR
"pwcd: can't misc_register thermometer - disabling it\n");
pcwd_info.flags &= ~PCWD_HAS_TEMP;
}
}
retval = register_reboot_notifier (&pcwd_notifier);
if (retval) {
if (pcwd_info.flags & PCWD_HAS_TEMP)
misc_deregister (&temp_miscdev);
misc_deregister (&pcwd_miscdev);
release_region (pcwd_info.io_addr,
pcwd_info.card_info->io_size);
}
return retval;
}
static int __init
pcwatchdog_init (void)
{
int i, found = 0;
/*
* ISA card auto-probe addresses available. Last one is only
* available on REV C cards.
*/
static int pcwd_ioports[] = { 0x270, 0x350, 0x370 };
#define PCWD_NUM_ADDR (sizeof(pcwd_ioports)/sizeof(pcwd_ioports[0]))
timeout_val = timeout * 2;
spin_lock_init (&io_lock);
printk (KERN_INFO "pcwd: v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
if (pci_register_driver (&pcwd_driver) > 0) {
found = 1;
set_card_type (1); /* Set to PCI card model */
} else {
/* No PCI entry, try the ISA addresses. */
for (i = 0; i < PCWD_NUM_ADDR; i++) {
if (check_isa_card (pcwd_ioports[i])) {
found = 1;
pcwd_info.io_addr = pcwd_ioports[i];
set_card_type (0);
break;
}
}
}
if (!found) {
printk (KERN_INFO
"pcwd: No card detected, or port not available\n");
return -EIO;
}
return pcwd_card_init ();
}
static void __exit
pcwatchdog_exit (void)
{
unregister_reboot_notifier (&pcwd_notifier);
misc_deregister (&pcwd_miscdev);
if (!nowayout)
pcwd_info.card_info->enable_card (0);
if (pcwd_info.flags & PCWD_HAS_TEMP)
misc_deregister (&temp_miscdev);
release_region (pcwd_info.io_addr, pcwd_info.card_info->io_size);
pci_unregister_driver (&pcwd_driver);
return;
}
module_init (pcwatchdog_init);
module_exit (pcwatchdog_exit);
MODULE_LICENSE ("GPL");
EXPORT_NO_SYMBOLS;