/*
* Industrial Computer Source WDT500/501 driver
*
* (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
* Release 0.10.
*
* Fixes
* Dave Gregorich : Modularisation and minor bugs
* Alan Cox : Added the watchdog ioctl() stuff
* Alan Cox : Fixed the reboot problem (as noted by
* Matt Crocker).
* Alan Cox : Added wdt= boot option
* Alan Cox : Cleaned up copy/user stuff
* Tim Hockin : Added insmod parameters, comment cleanup
* Parameterized timeout
* Tigran Aivazian : Restructured wdt_init() to handle failures
* Joel Becker : Added WDIOC_GET/SETTIMEOUT
* Matt Domsch : Added nowayout module option
*/
#include <linux/config.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include "wd501p.h"
static unsigned long wdt_is_open;
static char expect_close;
/*
* Module parameters
*/
#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
static int heartbeat = WD_TIMO;
static int wd_heartbeat;
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
/* You must set these - there is no sane way to probe for this board. */
static int io=0x240;
static int irq=11;
module_param(io, int, 0);
MODULE_PARM_DESC(io, "WDT io port (default=0x240)");
module_param(irq, int, 0);
MODULE_PARM_DESC(irq, "WDT irq (default=11)");
#ifdef CONFIG_WDT_501
/* Support for the Fan Tachometer on the WDT501-P */
static int tachometer;
module_param(tachometer, int, 0);
MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)");
#endif /* CONFIG_WDT_501 */
/*
* Programming support
*/
static void wdt_ctr_mode(int ctr, int mode)
{
ctr<<=6;
ctr|=0x30;
ctr|=(mode<<1);
outb_p(ctr, WDT_CR);
}
static void wdt_ctr_load(int ctr, int val)
{
outb_p(val&0xFF, WDT_COUNT0+ctr);
outb_p(val>>8, WDT_COUNT0+ctr);
}
/**
* wdt_start:
*
* Start the watchdog driver.
*/
static int wdt_start(void)
{
inb_p(WDT_DC); /* Disable watchdog */
wdt_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */
wdt_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */
wdt_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */
wdt_ctr_load(0, 8948); /* Count at 100Hz */
wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */
wdt_ctr_load(2,65535); /* Length of reset pulse */
outb_p(0, WDT_DC); /* Enable watchdog */
return 0;
}
/**
* wdt_stop:
*
* Stop the watchdog driver.
*/
static int wdt_stop (void)
{
/* Turn the card off */
inb_p(WDT_DC); /* Disable watchdog */
wdt_ctr_load(2,0); /* 0 length reset pulses now */
return 0;
}
/**
* wdt_ping:
*
* Reload counter one with the watchdog heartbeat. We don't bother reloading
* the cascade counter.
*/
static int wdt_ping(void)
{
/* Write a watchdog value */
inb_p(WDT_DC); /* Disable watchdog */
wdt_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */
wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */
outb_p(0, WDT_DC); /* Enable watchdog */
return 0;
}
/**
* wdt_set_heartbeat:
* @t: the new heartbeat value that needs to be set.
*
* Set a new heartbeat value for the watchdog device. If the heartbeat value is
* incorrect we keep the old value and return -EINVAL. If successfull we
* return 0.
*/
static int wdt_set_heartbeat(int t)
{
if ((t < 1) || (t > 65535))
return -EINVAL;
heartbeat = t;
wd_heartbeat = t * 100;
return 0;
}
/**
* wdt_get_status:
* @status: the new status.
*
* Extract the status information from a WDT watchdog device. There are
* several board variants so we have to know which bits are valid. Some
* bits default to one and some to zero in order to be maximally painful.
*
* we then map the bits onto the status ioctl flags.
*/
static int wdt_get_status(int *status)
{
unsigned char new_status=inb_p(WDT_SR);
*status=0;
if (new_status & WDC_SR_ISOI0)
*status |= WDIOF_EXTERN1;
if (new_status & WDC_SR_ISII1)
*status |= WDIOF_EXTERN2;
#ifdef CONFIG_WDT_501
if (!(new_status & WDC_SR_TGOOD))
*status |= WDIOF_OVERHEAT;
if (!(new_status & WDC_SR_PSUOVER))
*status |= WDIOF_POWEROVER;
if (!(new_status & WDC_SR_PSUUNDR))
*status |= WDIOF_POWERUNDER;
if (tachometer) {
if (!(new_status & WDC_SR_FANGOOD))
*status |= WDIOF_FANFAULT;
}
#endif /* CONFIG_WDT_501 */
return 0;
}
#ifdef CONFIG_WDT_501
/**
* wdt_get_temperature:
*
* Reports the temperature in degrees Fahrenheit. The API is in
* farenheit. It was designed by an imperial measurement luddite.
*/
static int wdt_get_temperature(int *temperature)
{
unsigned short c=inb_p(WDT_RT);
*temperature = (c * 11 / 15) + 7;
return 0;
}
#endif /* CONFIG_WDT_501 */
/**
* wdt_interrupt:
* @irq: Interrupt number
* @dev_id: Unused as we don't allow multiple devices.
* @regs: Unused.
*
* Handle an interrupt from the board. These are raised when the status
* map changes in what the board considers an interesting way. That means
* a failure condition occurring.
*/
static irqreturn_t wdt_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* Read the status register see what is up and
* then printk it.
*/
unsigned char status=inb_p(WDT_SR);
printk(KERN_CRIT "WDT status %d\n", status);
#ifdef CONFIG_WDT_501
if (!(status & WDC_SR_TGOOD))
printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT));
if (!(status & WDC_SR_PSUOVER))
printk(KERN_CRIT "PSU over voltage.\n");
if (!(status & WDC_SR_PSUUNDR))
printk(KERN_CRIT "PSU under voltage.\n");
if (tachometer) {
if (!(status & WDC_SR_FANGOOD))
printk(KERN_CRIT "Possible fan fault.\n");
}
#endif /* CONFIG_WDT_501 */
if (!(status & WDC_SR_WCCR))
#ifdef SOFTWARE_REBOOT
#ifdef ONLY_TESTING
printk(KERN_CRIT "Would Reboot.\n");
#else
printk(KERN_CRIT "Initiating system reboot.\n");
emergency_restart();
#endif
#else
printk(KERN_CRIT "Reset in 5ms.\n");
#endif
return IRQ_HANDLED;
}
/**
* wdt_write:
* @file: file handle to the watchdog
* @buf: buffer to write (unused as data does not matter here
* @count: count of bytes
* @ppos: pointer to the position to write. No seeks allowed
*
* A write to a watchdog device is defined as a keepalive signal. Any
* write of data will do, as we we don't define content meaning.
*/
static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
if(count) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
expect_close = 0;
for (i = 0; i != count; i++) {
char c;
if (get_user(c, buf + i))
return -EFAULT;
if (c == 'V')
expect_close = 42;
}
}
wdt_ping();
}
return count;
}
/**
* wdt_ioctl:
* @inode: inode of the device
* @file: file handle to the device
* @cmd: watchdog command
* @arg: argument pointer
*
* The watchdog API defines a common set of functions for all watchdogs
* according to their available features. We only actually usefully support
* querying capabilities and current status.