diff options
Diffstat (limited to 'drivers/char/watchdog')
-rw-r--r-- | drivers/char/watchdog/Kconfig | 102 | ||||
-rw-r--r-- | drivers/char/watchdog/Makefile | 8 | ||||
-rw-r--r-- | drivers/char/watchdog/i6300esb.c | 527 | ||||
-rw-r--r-- | drivers/char/watchdog/ibmasr.c | 405 | ||||
-rw-r--r-- | drivers/char/watchdog/mpcore_wdt.c | 434 | ||||
-rw-r--r-- | drivers/char/watchdog/mv64x60_wdt.c | 252 | ||||
-rw-r--r-- | drivers/char/watchdog/pcwd_pci.c | 44 | ||||
-rw-r--r-- | drivers/char/watchdog/s3c2410_wdt.c | 2 | ||||
-rw-r--r-- | drivers/char/watchdog/sbc8360.c | 414 | ||||
-rw-r--r-- | drivers/char/watchdog/w83977f_wdt.c | 543 |
10 files changed, 2688 insertions, 43 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig index c3898afce3a..344001b45af 100644 --- a/drivers/char/watchdog/Kconfig +++ b/drivers/char/watchdog/Kconfig @@ -84,6 +84,17 @@ config 977_WATCHDOG Not sure? It's safe to say N. +config IXP2000_WATCHDOG + tristate "IXP2000 Watchdog" + depends on WATCHDOG && ARCH_IXP2000 + help + Say Y here if to include support for the watchdog timer + in the Intel IXP2000(2400, 2800, 2850) network processors. + This driver can be built as a module by choosing M. The module + will be called ixp2000_wdt. + + Say N if you are unsure. + config IXP4XX_WATCHDOG tristate "IXP4xx Watchdog" depends on WATCHDOG && ARCH_IXP4XX @@ -100,17 +111,6 @@ config IXP4XX_WATCHDOG Say N if you are unsure. -config IXP2000_WATCHDOG - tristate "IXP2000 Watchdog" - depends on WATCHDOG && ARCH_IXP2000 - help - Say Y here if to include support for the watchdog timer - in the Intel IXP2000(2400, 2800, 2850) network processors. - This driver can be built as a module by choosing M. The module - will be called ixp2000_wdt. - - Say N if you are unsure. - config S3C2410_WATCHDOG tristate "S3C2410 Watchdog" depends on WATCHDOG && ARCH_S3C2410 @@ -139,6 +139,15 @@ config SA1100_WATCHDOG To compile this driver as a module, choose M here: the module will be called sa1100_wdt. +config MPCORE_WATCHDOG + tristate "MPcore watchdog" + depends on WATCHDOG && ARM_MPCORE_PLATFORM && LOCAL_TIMERS + help + Watchdog timer embedded into the MPcore system. + + To compile this driver as a module, choose M here: the + module will be called mpcore_wdt. + # X86 (i386 + ia64 + x86_64) Architecture config ACQUIRE_WDT @@ -224,6 +233,16 @@ config IB700_WDT Most people will say N. +config IBMASR + tristate "IBM Automatic Server Restart" + depends on WATCHDOG && X86 + help + This is the driver for the IBM Automatic Server Restart watchdog + timer builtin into some eServer xSeries machines. + + To compile this driver as a module, choose M here: the + module will be called ibmasr. + config WAFER_WDT tristate "ICP Wafer 5823 Single Board Computer Watchdog" depends on WATCHDOG && X86 @@ -234,6 +253,16 @@ config WAFER_WDT To compile this driver as a module, choose M here: the module will be called wafer5823wdt. +config I6300ESB_WDT + tristate "Intel 6300ESB Timer/Watchdog" + depends on WATCHDOG && X86 && PCI + ---help--- + Hardware driver for the watchdog timer built into the Intel + 6300ESB controller hub. + + To compile this driver as a module, choose M here: the + module will be called i6300esb. + config I8XX_TCO tristate "Intel i8xx TCO Timer/Watchdog" depends on WATCHDOG && (X86 || IA64) && PCI @@ -289,6 +318,19 @@ config 60XX_WDT You can compile this driver directly into the kernel, or use it as a module. The module will be called sbc60xxwdt. +config SBC8360_WDT + tristate "SBC8360 Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + + This is the driver for the hardware watchdog on the SBC8360 Single + Board Computer produced by Axiomtek Co., Ltd. (www.axiomtek.com). + + To compile this driver as a module, choose M here: the + module will be called sbc8360.ko. + + Most people will say N. + config CPU5_WDT tristate "SMA CPU5 Watchdog" depends on WATCHDOG && X86 @@ -327,6 +369,19 @@ config W83877F_WDT Most people will say N. +config W83977F_WDT + tristate "W83977F (PCM-5335) Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + This is the driver for the hardware watchdog on the W83977F I/O chip + as used in AAEON's PCM-5335 SBC (and likely others). This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called w83977f_wdt. + config MACHZ_WDT tristate "ZF MachZ Watchdog" depends on WATCHDOG && X86 @@ -346,6 +401,10 @@ config 8xx_WDT tristate "MPC8xx Watchdog Timer" depends on WATCHDOG && 8xx +config MV64X60_WDT + tristate "MV64X60 (Marvell Discovery) Watchdog Timer" + depends on WATCHDOG && MV64X60 + config BOOKE_WDT tristate "PowerPC Book-E Watchdog Timer" depends on WATCHDOG && (BOOKE || 4xx) @@ -353,6 +412,17 @@ config BOOKE_WDT Please see Documentation/watchdog/watchdog-api.txt for more information. +# PPC64 Architecture + +config WATCHDOG_RTAS + tristate "RTAS watchdog" + depends on WATCHDOG && PPC_RTAS + help + This driver adds watchdog support for the RTAS watchdog. + + To compile this driver as a module, choose M here. The module + will be called wdrtas. + # MIPS Architecture config INDYDOG @@ -421,16 +491,6 @@ config WATCHDOG_RIO machines. The watchdog timeout period is normally one minute but can be changed with a boot-time parameter. -# ppc64 RTAS watchdog -config WATCHDOG_RTAS - tristate "RTAS watchdog" - depends on WATCHDOG && PPC_RTAS - help - This driver adds watchdog support for the RTAS watchdog. - - To compile this driver as a module, choose M here. The module - will be called wdrtas. - # # ISA-based Watchdog Cards # diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile index cfeac6f1013..cfd0a398771 100644 --- a/drivers/char/watchdog/Makefile +++ b/drivers/char/watchdog/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o +obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o # X86 (i386 + ia64 + x86_64) Architecture obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o @@ -38,22 +39,27 @@ obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o obj-$(CONFIG_SC520_WDT) += sc520_wdt.o obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o obj-$(CONFIG_IB700_WDT) += ib700wdt.o +obj-$(CONFIG_IBMASR) += ibmasr.o obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o +obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o +obj-$(CONFIG_SBC8360_WDT) += sbc8360.o obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o +obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o # PowerPC Architecture obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o +obj-$(CONFIG_MV64X60_WDT) += mv64x60_wdt.o +obj-$(CONFIG_BOOKE_WDT) += booke_wdt.o # PPC64 Architecture obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o -obj-$(CONFIG_BOOKE_WDT) += booke_wdt.o # MIPS Architecture obj-$(CONFIG_INDYDOG) += indydog.o diff --git a/drivers/char/watchdog/i6300esb.c b/drivers/char/watchdog/i6300esb.c new file mode 100644 index 00000000000..93785f13242 --- /dev/null +++ b/drivers/char/watchdog/i6300esb.c @@ -0,0 +1,527 @@ +/* + * i6300esb: Watchdog timer driver for Intel 6300ESB chipset + * + * (c) Copyright 2004 Google Inc. + * (c) Copyright 2005 David Härdeman <david@2gen.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. + * + * based on i810-tco.c which is in turn based on softdog.c + * + * The timer is implemented in the following I/O controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * 6300ESB chip : document number 300641-003 + * + * 2004YYZZ Ross Biro + * Initial version 0.01 + * 2004YYZZ Ross Biro + * Version 0.02 + * 20050210 David Härdeman <david@2gen.com> + * Ported driver to kernel 2.6 + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/ioport.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +/* Module and version information */ +#define ESB_VERSION "0.03" +#define ESB_MODULE_NAME "i6300ESB timer" +#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION +#define PFX ESB_MODULE_NAME ": " + +/* PCI configuration registers */ +#define ESB_CONFIG_REG 0x60 /* Config register */ +#define ESB_LOCK_REG 0x68 /* WDT lock register */ + +/* Memory mapped registers */ +#define ESB_TIMER1_REG BASEADDR + 0x00 /* Timer1 value after each reset */ +#define ESB_TIMER2_REG BASEADDR + 0x04 /* Timer2 value after each reset */ +#define ESB_GINTSR_REG BASEADDR + 0x08 /* General Interrupt Status Register */ +#define ESB_RELOAD_REG BASEADDR + 0x0c /* Reload register */ + +/* Lock register bits */ +#define ESB_WDT_FUNC ( 0x01 << 2 ) /* Watchdog functionality */ +#define ESB_WDT_ENABLE ( 0x01 << 1 ) /* Enable WDT */ +#define ESB_WDT_LOCK ( 0x01 << 0 ) /* Lock (nowayout) */ + +/* Config register bits */ +#define ESB_WDT_REBOOT ( 0x01 << 5 ) /* Enable reboot on timeout */ +#define ESB_WDT_FREQ ( 0x01 << 2 ) /* Decrement frequency */ +#define ESB_WDT_INTTYPE ( 0x11 << 0 ) /* Interrupt type on timer1 timeout */ + +/* Reload register bits */ +#define ESB_WDT_RELOAD ( 0x01 << 8 ) /* prevent timeout */ + +/* Magic constants */ +#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ +#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ + +/* internal variables */ +static void __iomem *BASEADDR; +static spinlock_t esb_lock; /* Guards the hardware */ +static unsigned long timer_alive; +static struct pci_dev *esb_pci; +static unsigned short triggered; /* The status of the watchdog upon boot */ +static char esb_expect_close; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (1<heartbeat<2*1023) */ +static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<heartbeat<2046, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Some i6300ESB specific functions + */ + +/* + * Prepare for reloading the timer by unlocking the proper registers. + * This is performed by first writing 0x80 followed by 0x86 to the + * reload register. After this the appropriate registers can be written + * to once before they need to be unlocked again. + */ +static inline void esb_unlock_registers(void) { + writeb(ESB_UNLOCK1, ESB_RELOAD_REG); + writeb(ESB_UNLOCK2, ESB_RELOAD_REG); +} + +static void esb_timer_start(void) +{ + u8 val; + + /* Enable or Enable + Lock? */ + val = 0x02 | (nowayout ? 0x01 : 0x00); + + pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); +} + +static int esb_timer_stop(void) +{ + u8 val; + + spin_lock(&esb_lock); + /* First, reset timers as suggested by the docs */ + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); + /* Then disable the WDT */ + pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x0); + pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val); + spin_unlock(&esb_lock); + + /* Returns 0 if the timer was disabled, non-zero otherwise */ + return (val & 0x01); +} + +static void esb_timer_keepalive(void) +{ + spin_lock(&esb_lock); + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); + /* FIXME: Do we need to flush anything here? */ + spin_unlock(&esb_lock); +} + +static int esb_timer_set_heartbeat(int time) +{ + u32 val; + + if (time < 0x1 || time > (2 * 0x03ff)) + return -EINVAL; + + spin_lock(&esb_lock); + + /* We shift by 9, so if we are passed a value of 1 sec, + * val will be 1 << 9 = 512, then write that to two + * timers => 2 * 512 = 1024 (which is decremented at 1KHz) + */ + val = time << 9; + + /* Write timer 1 */ + esb_unlock_registers(); + writel(val, ESB_TIMER1_REG); + + /* Write timer 2 */ + esb_unlock_registers(); + writel(val, ESB_TIMER2_REG); + + /* Reload */ + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); + + /* FIXME: Do we need to flush everything out? */ + + /* Done */ + heartbeat = time; + spin_unlock(&esb_lock); + return 0; +} + +static int esb_timer_read (void) +{ + u32 count; + + /* This isn't documented, and doesn't take into + * acount which stage is running, but it looks + * like a 20 bit count down, so we might as well report it. + */ + pci_read_config_dword(esb_pci, 0x64, &count); + return (int)count; +} + +/* + * /dev/watchdog handling + */ + +static int esb_open (struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + /* Reload and activate timer */ + esb_timer_keepalive (); + esb_timer_start (); + + return nonseekable_open(inode, file); +} + +static int esb_release (struct inode *inode, struct file *file) +{ + /* Shut off the timer. */ + if (esb_expect_close == 42) { + esb_timer_stop (); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + esb_timer_keepalive (); + } + clear_bit(0, &timer_alive); + esb_expect_close = 0; + return 0; +} + +static ssize_t esb_write (struct file *file, const char __user *data, + size_t len, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + esb_expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + esb_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + esb_timer_keepalive (); + } + return len; +} + +static int esb_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_options, retval = -EINVAL; + int new_heartbeat; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = ESB_MODULE_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof (ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + return put_user (esb_timer_read(), p); + + case WDIOC_GETBOOTSTATUS: + return put_user (triggered, p); + + case WDIOC_KEEPALIVE: + esb_timer_keepalive (); + return 0; + + case WDIOC_SETOPTIONS: + { + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + esb_timer_stop (); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + esb_timer_keepalive (); + esb_timer_start (); + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (esb_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + + esb_timer_keepalive (); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + default: + return -ENOIOCTLCMD; + } +} + +/* + * Notify system + */ + +static int esb_notify_sys (struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + esb_timer_stop (); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations esb_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = esb_write, + .ioctl = esb_ioctl, + .open = esb_open, + .release = esb_release, +}; + +static struct miscdevice esb_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &esb_fops, +}; + +static struct notifier_block esb_notifier = { + .notifier_call = esb_notify_sys, +}; + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static struct pci_device_id esb_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE (pci, esb_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __init esb_getdevice (void) +{ + u8 val1; + unsigned short val2; + + struct pci_dev *dev = NULL; + /* + * Find the PCI device + */ + + for_each_pci_dev(dev) { + if (pci_match_id(esb_pci_tbl, dev)) { + esb_pci = dev; + break; + } + } + + if (esb_pci) { + if (pci_enable_device(esb_pci)) { + printk (KERN_ERR PFX "failed to enable device\n"); + goto err_devput; + } + + if (pci_request_region(esb_pci, 0, ESB_MODULE_NAME)) { + printk (KERN_ERR PFX "failed to request region\n"); + goto err_disable; + } + + BASEADDR = ioremap(pci_resource_start(esb_pci, 0), + pci_resource_len(esb_pci, 0)); + if (BASEADDR == NULL) { + /* Something's wrong here, BASEADDR has to be set */ + printk (KERN_ERR PFX "failed to get BASEADDR\n"); + goto err_release; + } + + /* + * The watchdog has two timers, it can be setup so that the + * expiry of timer1 results in an interrupt and the expiry of + * timer2 results in a reboot. We set it to not generate + * any interrupts as there is not much we can do with it + * right now. + * + * We also enable reboots and set the timer frequency to + * the PCI clock divided by 2^15 (approx 1KHz). + */ + pci_write_config_word(esb_pci, ESB_CONFIG_REG, 0x0003); + + /* Check that the WDT isn't already locked */ + pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1); + if (val1 & ESB_WDT_LOCK) + printk (KERN_WARNING PFX "nowayout already set\n"); + + /* Set the timer to watchdog mode and disable it for now */ + pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00); + + /* Check if the watchdog was previously triggered */ + esb_unlock_registers(); + val2 = readw(ESB_RELOAD_REG); + triggered = (val2 & (0x01 << 9) >> 9); + + /* Reset trigger flag and timers */ + esb_unlock_registers(); + writew((0x11 << 8), ESB_RELOAD_REG); + + /* Done */ + return 1; + +err_release: + pci_release_region(esb_pci, 0); +err_disable: + pci_disable_device(esb_pci); +err_devput: + pci_dev_put(esb_pci); + } + return 0; +} + +static int __init watchdog_init (void) +{ + int ret; + + spin_lock_init(&esb_lock); + + /* Check whether or not the hardware watchdog is there */ + if (!esb_getdevice () || esb_pci == NULL) + return -ENODEV; + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (esb_timer_set_heartbeat (heartbeat)) { + esb_timer_set_heartbeat (WATCHDOG_HEARTBEAT); + printk(KERN_INFO PFX "heartbeat value must be 1<heartbeat<2046, using %d\n", + heartbeat); + } + + ret = register_reboot_notifier(&esb_notifier); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto err_unmap; + } + + ret = misc_register(&esb_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto err_notifier; + } + + esb_timer_stop (); + + printk (KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", + BASEADDR, heartbeat, nowayout); + + return 0; + +err_notifier: + unregister_reboot_notifier(&esb_notifier); +err_unmap: + iounmap(BASEADDR); +/* err_release: */ + pci_release_region(esb_pci, 0); +/* err_disable: */ + pci_disable_device(esb_pci); +/* err_devput: */ + pci_dev_put(esb_pci); + return ret; +} + +static void __exit watchdog_cleanup (void) +{ + /* Stop the timer before we leave */ + if (!nowayout) + esb_timer_stop (); + + /* Deregister */ + misc_deregister(&esb_miscdev); + unregister_reboot_notifier(&esb_notifier); + iounmap(BASEADDR); + pci_release_region(esb_pci, 0); + pci_disable_device(esb_pci); + pci_dev_put(esb_pci); +} + +module_init(watchdog_init); +module_exit(watchdog_cleanup); + +MODULE_AUTHOR("Ross Biro and David Härdeman"); +MODULE_DESCRIPTION("Watchdog driver for Intel 6300ESB chipsets"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/ibmasr.c b/drivers/char/watchdog/ibmasr.c new file mode 100644 index 00000000000..294c474ae48 --- /dev/null +++ b/drivers/char/watchdog/ibmasr.c @@ -0,0 +1,405 @@ +/* + * IBM Automatic Server Restart driver. + * + * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru> + * + * Based on driver written by Pete Reynolds. + * Copyright (c) IBM Corporation, 1998-2004. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + */ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/dmi.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + + +enum { + ASMTYPE_UNKNOWN, + ASMTYPE_TOPAZ, + ASMTYPE_JASPER, + ASMTYPE_PEARL, + ASMTYPE_JUNIPER, + ASMTYPE_SPRUCE, +}; + +#define PFX "ibmasr: " + +#define TOPAZ_ASR_REG_OFFSET 4 +#define TOPAZ_ASR_TOGGLE 0x40 +#define TOPAZ_ASR_DISABLE 0x80 + +/* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */ +#define PEARL_BASE 0xe04 +#define PEARL_WRITE 0xe06 +#define PEARL_READ 0xe07 + +#define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */ +#define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */ + +/* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */ +#define JASPER_ASR_REG_OFFSET 0x38 + +#define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */ +#define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ + +#define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */ +#define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */ +#define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ + +#define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */ +#define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */ +#define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ + + +static int nowayout = WATCHDOG_NOWAYOUT; + +static unsigned long asr_is_open; +static char asr_expect_close; + +static unsigned int asr_type, asr_base, asr_length; +static unsigned int asr_read_addr, asr_write_addr; +static unsigned char asr_toggle_mask, asr_disable_mask; + +static void asr_toggle(void) +{ + unsigned char reg = inb(asr_read_addr); + + outb(reg & ~asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg | asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg & ~asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); +} + +static void asr_enable(void) +{ + unsigned char reg; + + if (asr_type == ASMTYPE_TOPAZ) { + /* asr_write_addr == asr_read_addr */ + reg = inb(asr_read_addr); + outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE), + asr_read_addr); + } else { + /* + * First make sure the hardware timer is reset by toggling + * ASR hardware timer line. + */ + asr_toggle(); + + reg = inb(asr_read_addr); + outb(reg & ~asr_disable_mask, asr_write_addr); + } + reg = inb(asr_read_addr); +} + +static void asr_disable(void) +{ + unsigned char reg = inb(asr_read_addr); + + if (asr_type == ASMTYPE_TOPAZ) + /* asr_write_addr == asr_read_addr */ + outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE, + asr_read_addr); + else { + outb(reg | asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg | asr_disable_mask, asr_write_addr); + } + reg = inb(asr_read_addr); +} + +static int __init asr_get_base_address(void) +{ + unsigned char low, high; + const char *type = ""; + + asr_length = 1; + + switch (asr_type) { + case ASMTYPE_TOPAZ: + /* SELECT SuperIO CHIP FOR QUERYING (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ + outb(0x07, 0x2e); + outb(0x07, 0x2f); + + /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */ + outb(0x60, 0x2e); + high = inb(0x2f); + + /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */ + outb(0x61, 0x2e); + low = inb(0x2f); + + asr_base = (high << 16) | low; + asr_read_addr = asr_write_addr = + asr_base + TOPAZ_ASR_REG_OFFSET; + asr_length = 5; + + break; + + case ASMTYPE_JASPER: + type = "Jaspers "; + + /* FIXME: need to use pci_config_lock here, but it's not exported */ + +/* spin_lock_irqsave(&pci_config_lock, flags);*/ + + /* Select the SuperIO chip in the PCI I/O port register */ + outl(0x8000f858, 0xcf8); + + /* + * Read the base address for the SuperIO chip. + * Only the lower 16 bits are valid, but the address is word + * aligned so the last bit must be masked off. + */ + asr_base = inl(0xcfc) & 0xfffe; + +/* spin_unlock_irqrestore(&pci_config_lock, flags);*/ + + asr_read_addr = asr_write_addr = + asr_base + JASPER_ASR_REG_OFFSET; + asr_toggle_mask = JASPER_ASR_TOGGLE_MASK; + asr_disable_mask = JASPER_ASR_DISABLE_MASK; + asr_length = JASPER_ASR_REG_OFFSET + 1; + + break; + + case ASMTYPE_PEARL: + type = "Pearls "; + asr_base = PEARL_BASE; + asr_read_addr = PEARL_READ; + asr_write_addr = PEARL_WRITE; + asr_toggle_mask = PEARL_ASR_TOGGLE_MASK; + asr_disable_mask = PEARL_ASR_DISABLE_MASK; + asr_length = 4; + break; + + case ASMTYPE_JUNIPER: + type = "Junipers "; + asr_base = JUNIPER_BASE_ADDRESS; + asr_read_addr = asr_write_addr = asr_base; + asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK; + asr_disable_mask = JUNIPER_ASR_DISABLE_MASK; + break; + + case ASMTYPE_SPRUCE: + type = "Spruce's "; + asr_base = SPRUCE_BASE_ADDRESS; + asr_read_addr = asr_write_addr = asr_base; + asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK; + asr_disable_mask = SPRUCE_ASR_DISABLE_MASK; + break; + } + + if (!request_region(asr_base, asr_length, "ibmasr")) { + printk(KERN_ERR PFX "address %#x already in use\n", + asr_base); + return -EBUSY; + } + + printk(KERN_INFO PFX "found %sASR @ addr %#x\n", type, asr_base); + + return 0; +} + + +static ssize_t asr_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 */ + asr_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + asr_expect_close = 42; + } + } + asr_toggle(); + } + return count; +} + +static int asr_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "IBM ASR" + }; + void __user *argp = (void __user *)arg; + int __user *p = argp; + int heartbeat; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? + -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + asr_toggle(); + return 0; + + /* + * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT + * and WDIOC_GETTIMEOUT always returns 256. + */ + case WDIOC_GETTIMEOUT: + heartbeat = 256; + return put_user(heartbeat, p); + + case WDIOC_SETOPTIONS: { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + asr_disable(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + asr_enable(); + asr_toggle(); + retval = 0; + } + + return retval; + } + } + + return -ENOIOCTLCMD; +} + +static int asr_open(struct inode *inode, struct file *file) +{ + if(test_and_set_bit(0, &asr_is_open)) + return -EBUSY; + + asr_toggle(); + asr_enable(); + + return nonseekable_open(inode, file); +} + +static int asr_release(struct inode *inode, struct file *file) +{ + if (asr_expect_close == 42) + asr_disable(); + else { + printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); + asr_toggle(); + } + clear_bit(0, &asr_is_open); + asr_expect_close = 0; + return 0; +} + +static struct file_operations asr_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = asr_write, + .ioctl = asr_ioctl, + .open = asr_open, + .release = asr_release, +}; + +static struct miscdevice asr_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &asr_fops, +}; + + +struct ibmasr_id { + const char *desc; + int type; +}; + +static struct ibmasr_id __initdata ibmasr_id_table[] = { + { "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ }, + { "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL }, + { "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER }, + { "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER }, + { "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE }, + { NULL } +}; + +static int __init ibmasr_init(void) +{ + struct ibmasr_id *id; + int rc; + + for (id = ibmasr_id_table; id->desc; id++) { + if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) { + asr_type = id->type; + break; + } + } + + if (!asr_type) + return -ENODEV; + + rc = misc_register(&asr_miscdev); + if (rc < 0) { + printk(KERN_ERR PFX "failed to register misc device\n"); + return rc; + } + + rc = asr_get_base_address(); + if (rc) { + misc_deregister(&asr_miscdev); + return rc; + } + + return 0; +} + +static void __exit ibmasr_exit(void) +{ + if (!nowayout) + asr_disable(); + + misc_deregister(&asr_miscdev); + + release_region(asr_base, asr_length); +} + +module_init(ibmasr_init); +module_exit(ibmasr_exit); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +MODULE_DESCRIPTION("IBM Automatic Server Restart driver"); +MODULE_AUTHOR("Andrey Panin"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/mpcore_wdt.c b/drivers/char/watchdog/mpcore_wdt.c new file mode 100644 index 00000000000..c694eee1fb2 --- /dev/null +++ b/drivers/char/watchdog/mpcore_wdt.c @@ -0,0 +1,434 @@ +/* + * Watchdog driver for the mpcore watchdog timer + * + * (c) Copyright 2004 ARM Limited + * + * Based on the SoftDog driver: + * (c) Copyright 1996 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> + * + */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <asm/uaccess.h> + +struct mpcore_wdt { + unsigned long timer_alive; + struct device *dev; + void __iomem *base; + int irq; + unsigned int perturb; + char expect_close; +}; + +static struct platform_device *mpcore_wdt_dev; + +extern unsigned int mpcore_timer_rate; + +#define TIMER_MARGIN 60 +static int mpcore_margin = TIMER_MARGIN; +module_param(mpcore_margin, int, 0); +MODULE_PARM_DESC(mpcore_margin, "MPcore timer margin in seconds. (0<mpcore_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define ONLY_TESTING 0 +static int mpcore_noboot = ONLY_TESTING; +module_param(mpcore_noboot, int, 0); +MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); + +/* + * This is the interrupt handler. Note that we only use this + * in testing mode, so don't actually do a reboot here. + */ +static irqreturn_t mpcore_wdt_fire(int irq, void *arg, struct pt_regs *regs) +{ + struct mpcore_wdt *wdt = arg; + + /* Check it really was our interrupt */ + if (readl(wdt->base + TWD_WDOG_INTSTAT)) { + dev_printk(KERN_CRIT, wdt->dev, "Triggered - Reboot ignored.\n"); + + /* Clear the interrupt on the watchdog */ + writel(1, wdt->base + TWD_WDOG_INTSTAT); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/* + * mpcore_wdt_keepalive - reload the timer + * + * Note that the spec says a DIFFERENT value must be written to the reload + * register each time. The "perturb" variable deals with this by adding 1 + * to the count every other time the function is called. + */ +static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) +{ + unsigned int count; + + /* Assume prescale is set to 256 */ + count = (mpcore_timer_rate / 256) * mpcore_margin; + + /* Reload the counter */ + writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); + + wdt->perturb = wdt->perturb ? 0 : 1; +} + +static void mpcore_wdt_stop(struct mpcore_wdt *wdt) +{ + writel(0x12345678, wdt->base + TWD_WDOG_DISABLE); + writel(0x87654321, wdt->base + TWD_WDOG_DISABLE); + writel(0x0, wdt->base + TWD_WDOG_CONTROL); +} + +static void mpcore_wdt_start(struct mpcore_wdt *wdt) +{ + dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n"); + + /* This loads the count register but does NOT start the count yet */ + mpcore_wdt_keepalive(wdt); + + if (mpcore_noboot) { + /* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */ + writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL); + } else { + /* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */ + writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL); + } +} + +static int mpcore_wdt_set_heartbeat(int t) +{ + if (t < 0x0001 || t > 0xFFFF) + return -EINVAL; + + mpcore_margin = t; + return 0; +} + +/* + * /dev/watchdog handling + */ +static int mpcore_wdt_open(struct inode *inode, struct file *file) +{ + struct mpcore_wdt *wdt = dev_get_drvdata(&mpcore_wdt_dev->dev); + + if (test_and_set_bit(0, &wdt->timer_alive)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + file->private_data = wdt; + + /* + * Activate timer + */ + mpcore_wdt_start(wdt); + + return nonseekable_open(inode, file); +} + +static int mpcore_wdt_release(struct inode *inode, struct file *file) +{ + struct mpcore_wdt *wdt = file->private_data; + + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (wdt->expect_close == 42) { + mpcore_wdt_stop(wdt); + } else { + dev_printk(KERN_CRIT, wdt->dev, "unexpected close, not stopping watchdog!\n"); + mpcore_wdt_keepalive(wdt); + } + clear_bit(0, &wdt->timer_alive); + wdt->expect_close = 0; + return 0; +} + +static ssize_t mpcore_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + struct mpcore_wdt *wdt = file->private_data; + + /* Can't seek (pwrite) on this device */ + if (ppos != &file->f_pos) + return -ESPIPE; + + /* + * Refresh the timer. + */ + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + wdt->expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + wdt->expect_close = 42; + } + } + mpcore_wdt_keepalive(wdt); + } + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "MPcore Watchdog", +}; + +static int mpcore_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct mpcore_wdt *wdt = file->private_data; + int ret; + union { + struct watchdog_info ident; + int i; + } uarg; + + if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg)) + return -ENOIOCTLCMD; + + if (_IOC_DIR(cmd) & _IOC_WRITE) { + ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd)); + if (ret) + return -EFAULT; + } + + switch (cmd) { + case WDIOC_GETSUPPORT: + uarg.ident = ident; + ret = 0; + break; + + case WDIOC_SETOPTIONS: + ret = -EINVAL; + if (uarg.i & WDIOS_DISABLECARD) { + mpcore_wdt_stop(wdt); + ret = 0; + } + if (uarg.i & WDIOS_ENABLECARD) { + mpcore_wdt_start(wdt); + ret = 0; + } + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + uarg.i = 0; + ret = 0; + break; + + case WDIOC_KEEPALIVE: + mpcore_wdt_keepalive(wdt); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = mpcore_wdt_set_heartbeat(uarg.i); + if (ret) + break; + + mpcore_wdt_keepalive(wdt); + /* Fall */ + case WDIOC_GETTIMEOUT: + uarg.i = mpcore_margin; + ret = 0; + break; + + default: + return -ENOIOCTLCMD; + } + + if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { + ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd)); + if (ret) + ret = -EFAULT; + } + return ret; +} + +/* + * System shutdown handler. Turn off the watchdog if we're + * restarting or halting the system. + */ +static void mpcore_wdt_shutdown(struct device *_dev) +{ + struct mpcore_wdt *wdt = dev_get_drvdata(_dev); + + if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT) + mpcore_wdt_stop(wdt); +} + +/* + * Kernel Interfaces + */ +static struct file_operations mpcore_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mpcore_wdt_write, + .ioctl = mpcore_wdt_ioctl, + .open = mpcore_wdt_open, + .release = mpcore_wdt_release, +}; + +static struct miscdevice mpcore_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mpcore_wdt_fops, +}; + +static int __devinit mpcore_wdt_probe(struct device *_dev) +{ + struct platform_device *dev = to_platform_device(_dev); + struct mpcore_wdt *wdt; + struct resource *res; + int ret; + + /* We only accept one device, and it must have an id of -1 */ + if (dev->id != -1) + return -ENODEV; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto err_out; + } + + wdt = kmalloc(sizeof(struct mpcore_wdt), GFP_KERNEL); + if (!wdt) { + ret = -ENOMEM; + goto err_out; + } + memset(wdt, 0, sizeof(struct mpcore_wdt)); + + wdt->dev = &dev->dev; + wdt->irq = platform_get_irq(dev, 0); + wdt->base = ioremap(res->start, res->end - res->start + 1); + if (!wdt->base) { + ret = -ENOMEM; + goto err_free; + } + + mpcore_wdt_miscdev.dev = &dev->dev; + ret = misc_register(&mpcore_wdt_miscdev); + if (ret) { + dev_printk(KERN_ERR, _dev, "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto err_misc; + } + + ret = request_irq(wdt->irq, mpcore_wdt_fire, SA_INTERRUPT, "mpcore_wdt", wdt); + if (ret) { + dev_printk(KERN_ERR, _dev, "cannot register IRQ%d for watchdog\n", wdt->irq); + goto err_irq; + } + + mpcore_wdt_stop(wdt); + dev_set_drvdata(&dev->dev, wdt); + mpcore_wdt_dev = dev; + + return 0; + + err_irq: + misc_deregister(&mpcore_wdt_miscdev); + err_misc: + iounmap(wdt->base); + err_free: + kfree(wdt); + err_out: + return ret; +} + +static int __devexit mpcore_wdt_remove(struct device *dev) +{ + struct mpcore_wdt *wdt = dev_get_drvdata(dev); + + dev_set_drvdata(dev, NULL); + + misc_deregister(&mpcore_wdt_miscdev); + + mpcore_wdt_dev = NULL; + + free_irq(wdt->irq, wdt); + iounmap(wdt->base); + kfree(wdt); + return 0; +} + +static struct device_driver mpcore_wdt_driver = { + .name = "mpcore_wdt", + .bus = &platform_bus_type, + .probe = mpcore_wdt_probe, + .remove = __devexit_p(mpcore_wdt_remove), + .shutdown = mpcore_wdt_shutdown, +}; + +static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n"; + +static int __init mpcore_wdt_init(void) +{ + /* + * Check that the margin value is within it's range; + * if not reset to the default + */ + if (mpcore_wdt_set_heartbeat(mpcore_margin)) { + mpcore_wdt_set_heartbeat(TIMER_MARGIN); + printk(KERN_INFO "mpcore_margin value must be 0<mpcore_margin<65536, using %d\n", + TIMER_MARGIN); + } + + printk(banner, mpcore_noboot, mpcore_margin, nowayout); + + return driver_register(&mpcore_wdt_driver); +} + +static void __exit mpcore_wdt_exit(void) +{ + driver_unregister(&mpcore_wdt_driver); +} + +module_init(mpcore_wdt_init); +module_exit(mpcore_wdt_exit); + +MODULE_AUTHOR("ARM Limited"); +MODULE_DESCRIPTION("MPcore Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/mv64x60_wdt.c b/drivers/char/watchdog/mv64x60_wdt.c new file mode 100644 index 00000000000..1436aea3b28 --- /dev/null +++ b/drivers/char/watchdog/mv64x60_wdt.c @@ -0,0 +1,252 @@ +/* + * mv64x60_wdt.c - MV64X60 (Marvell Discovery) watchdog userspace interface + * + * Author: James Chapman <jchapman@katalix.com> + * + * Platform-specific setup code should configure the dog to generate + * interrupt or reset as required. This code only enables/disables + * and services the watchdog. + * + * Derived from mpc8xx_wdt.c, with the following copyright. + * + * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/watchdog.h> +#include <asm/mv64x60.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +/* MV64x60 WDC (config) register access definitions */ +#define MV64x60_WDC_CTL1_MASK (3 << 24) +#define MV64x60_WDC_CTL1(val) ((val & 3) << 24) +#define MV64x60_WDC_CTL2_MASK (3 << 26) +#define MV64x60_WDC_CTL2(val) ((val & 3) << 26) + +/* Flags bits */ +#define MV64x60_WDOG_FLAG_OPENED 0 +#define MV64x60_WDOG_FLAG_ENABLED 1 + +static unsigned long wdt_flags; +static int wdt_status; +static void __iomem *mv64x60_regs; +static int mv64x60_wdt_timeout; + +static void mv64x60_wdt_reg_write(u32 val) +{ + /* Allow write only to CTL1 / CTL2 fields, retaining values in + * other fields. + */ + u32 data = readl(mv64x60_regs + MV64x60_WDT_WDC); + data &= ~(MV64x60_WDC_CTL1_MASK | MV64x60_WDC_CTL2_MASK); + data |= val; + writel(data, mv64x60_regs + MV64x60_WDT_WDC); +} + +static void mv64x60_wdt_service(void) +{ + /* Write 01 followed by 10 to CTL2 */ + mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x01)); + mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x02)); +} + +static void mv64x60_wdt_handler_disable(void) +{ + if (test_and_clear_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) { + /* Write 01 followed by 10 to CTL1 */ + mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01)); + mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02)); + printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n"); + } +} + +static void mv64x60_wdt_handler_enable(void) +{ + if (!test_and_set_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) { + /* Write 01 followed by 10 to CTL1 */ + mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01)); + mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02)); + printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n"); + } +} + +static int mv64x60_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags)) + return -EBUSY; + + mv64x60_wdt_service(); + mv64x60_wdt_handler_enable(); + + return 0; +} + +static int mv64x60_wdt_release(struct inode *inode, struct file *file) +{ + mv64x60_wdt_service(); + +#if !defined(CONFIG_WATCHDOG_NOWAYOUT) + mv64x60_wdt_handler_disable(); +#endif + + clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags); + + return 0; +} + +static ssize_t mv64x60_wdt_write(struct file *file, const char *data, + size_t len, loff_t * ppos) +{ + if (*ppos != file->f_pos) + return -ESPIPE; + + if (len) + mv64x60_wdt_service(); + + return len; +} + +static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int timeout; + static struct watchdog_info info = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 0, + .identity = "MV64x60 watchdog", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(wdt_status, (int *)arg)) + return -EFAULT; + wdt_status &= ~WDIOF_KEEPALIVEPING; + break; + + case WDIOC_GETTEMP: + return -EOPNOTSUPP; + + case WDIOC_SETOPTIONS: + return -EOPNOTSUPP; + + case WDIOC_KEEPALIVE: + mv64x60_wdt_service(); + wdt_status |= WDIOF_KEEPALIVEPING; + break; + + case WDIOC_SETTIMEOUT: + return -EOPNOTSUPP; + + case WDIOC_GETTIMEOUT: + timeout = mv64x60_wdt_timeout * HZ; + if (put_user(timeout, (int *)arg)) + return -EFAULT; + break; + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static struct file_operations mv64x60_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mv64x60_wdt_write, + .ioctl = mv64x60_wdt_ioctl, + .open = mv64x60_wdt_open, + .release = mv64x60_wdt_release, +}; + +static struct miscdevice mv64x60_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mv64x60_wdt_fops, +}; + +static int __devinit mv64x60_wdt_probe(struct device *dev) +{ + struct platform_device *pd = to_platform_device(dev); + struct mv64x60_wdt_pdata *pdata = pd->dev.platform_data; + int bus_clk = 133; + + mv64x60_wdt_timeout = 10; + if (pdata) { + mv64x60_wdt_timeout = pdata->timeout; + bus_clk = pdata->bus_clk; + } + + mv64x60_regs = mv64x60_get_bridge_vbase(); + + writel((mv64x60_wdt_timeout * (bus_clk * 1000000)) >> 8, + mv64x60_regs + MV64x60_WDT_WDC); + + return misc_register(&mv64x60_wdt_miscdev); +} + +static int __devexit mv64x60_wdt_remove(struct device *dev) +{ + misc_deregister(&mv64x60_wdt_miscdev); + + mv64x60_wdt_service(); + mv64x60_wdt_handler_disable(); + + return 0; +} + +static struct device_driver mv64x60_wdt_driver = { + .name = MV64x60_WDT_NAME, + .bus = &platform_bus_type, + .probe = mv64x60_wdt_probe, + .remove = __devexit_p(mv64x60_wdt_remove), +}; + +static struct platform_device *mv64x60_wdt_dev; + +static int __init mv64x60_wdt_init(void) +{ + int ret; + + printk(KERN_INFO "MV64x60 watchdog driver\n"); + + mv64x60_wdt_dev = platform_device_register_simple(MV64x60_WDT_NAME, + -1, NULL, 0); + if (IS_ERR(mv64x60_wdt_dev)) { + ret = PTR_ERR(mv64x60_wdt_dev); + goto out; + } + + ret = driver_register(&mv64x60_wdt_driver); + out: + return ret; +} + +static void __exit mv64x60_wdt_exit(void) +{ + driver_unregister(&mv64x60_wdt_driver); + platform_device_unregister(mv64x60_wdt_dev); +} + +module_init(mv64x60_wdt_init); +module_exit(mv64x60_wdt_exit); + +MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); +MODULE_DESCRIPTION("MV64x60 watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/pcwd_pci.c b/drivers/char/watchdog/pcwd_pci.c index 2b13afb09c5..5a80adbf803 100644 --- a/drivers/char/watchdog/pcwd_pci.c +++ b/drivers/char/watchdog/pcwd_pci.c @@ -29,27 +29,29 @@ * Includes, defines, variables, module parameters, ... */ -#include <linux/config.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/types.h> -#include <linux/delay.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/init.h> -#include <linux/fs.h> -#include <linux/pci.h> -#include <linux/ioport.h> -#include <linux/spinlock.h> - -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/config.h> /* For CONFIG_WATCHDOG_NOWAYOUT/... */ +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/delay.h> /* For mdelay function */ +#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#include <linux/watchdog.h> /* For the watchdog specific items */ +#include <linux/notifier.h> /* For notifier support */ +#include <linux/reboot.h> /* For reboot_notifier stuff */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/fs.h> /* For file operations */ +#include <linux/pci.h> /* For pci functions */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ + +#include <asm/uaccess.h> /* For copy_to_user/put_user/... */ +#include <asm/io.h> /* For inb/outb/... */ /* Module and version information */ #define WATCHDOG_VERSION "1.01" -#define WATCHDOG_DATE "15 Mar 2005" +#define WATCHDOG_DATE "02 Sep 2005" #define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" #define WATCHDOG_NAME "pcwd_pci" #define PFX WATCHDOG_NAME ": " @@ -335,12 +337,14 @@ static int pcipcwd_ioctl(struct inode *inode, struct file *file, return -EFAULT; if (new_options & WDIOS_DISABLECARD) { - pcipcwd_stop(); + if (pcipcwd_stop()) + return -EIO; retval = 0; } if (new_options & WDIOS_ENABLECARD) { - pcipcwd_start(); + if (pcipcwd_start()) + return -EIO; retval = 0; } diff --git a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c index 8b292bf343c..3625b2601b4 100644 --- a/drivers/char/watchdog/s3c2410_wdt.c +++ b/drivers/char/watchdog/s3c2410_wdt.c @@ -464,7 +464,7 @@ static void s3c2410wdt_shutdown(struct device *dev) static unsigned long wtcon_save; static unsigned long wtdat_save; -static int s3c2410wdt_suspend(struct device *dev, u32 state, u32 level) +static int s3c2410wdt_suspend(struct device *dev, pm_message_t state, u32 level) { if (level == SUSPEND_POWER_DOWN) { /* Save watchdog state, and turn it off. */ diff --git a/drivers/char/watchdog/sbc8360.c b/drivers/char/watchdog/sbc8360.c new file mode 100644 index 00000000000..c6cbf808d8c --- /dev/null +++ b/drivers/char/watchdog/sbc8360.c @@ -0,0 +1,414 @@ +/* + * SBC8360 Watchdog driver + * + * (c) Copyright 2005 Webcon, Inc. + * + * Based on ib700wdt.c, which is based on advantechwdt.c which is based + * on acquirewdt.c which is based on wdt.c. + * + * (c) Copyright 2001 Charles Howes <chowes@vsol.net> + * + * Based on advantechwdt.c which is based on acquirewdt.c which + * is based on wdt.c. + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 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@redhat.com> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Added timeout module option to override default + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/notifier.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +static unsigned long sbc8360_is_open; +static spinlock_t sbc8360_lock; +static char expect_close; + +#define PFX "sbc8360: " + +/* + * + * Watchdog Timer Configuration + * + * The function of the watchdog timer is to reset the system automatically + * and is defined at I/O port 0120H and 0121H. To enable the watchdog timer + * and allow the system to reset, write appropriate values from the table + * below to I/O port 0120H and 0121H. To disable the timer, write a zero + * value to I/O port 0121H for the system to stop the watchdog function. + * + * The following describes how the timer should be programmed (according to + * the vendor documentation) + * + * Enabling Watchdog: + * MOV AX,000AH (enable, phase I) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000BH (enable, phase II) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000nH (set multiplier n, from 1-4) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000mH (set base timer m, from 0-F) + * MOV DX,0121H + * OUT DX,AX + * + * Reset timer: + * MOV AX,000mH (same as set base timer, above) + * MOV DX,0121H + * OUT DX,AX + * + * Disabling Watchdog: + * MOV AX,0000H (a zero value) + * MOV DX,0120H + * OUT DX,AX + * + * Watchdog timeout configuration values: + * N + * M | 1 2 3 4 + * --|---------------------------------- + * 0 | 0.5s 5s 50s 100s + * 1 | 1s 10s 100s 200s + * 2 | 1.5s 15s 150s 300s + * 3 | 2s 20s 200s 400s + * 4 | 2.5s 25s 250s 500s + * 5 | 3s 30s 300s 600s + * 6 | 3.5s 35s 350s 700s + * 7 | 4s 40s 400s 800s + * 8 | 4.5s 45s 450s 900s + * 9 | 5s 50s 500s 1000s + * A | 5.5s 55s 550s 1100s + * B | 6s 60s 600s 1200s + * C | 6.5s 65s 650s 1300s + * D | 7s 70s 700s 1400s + * E | 7.5s 75s 750s 1500s + * F | 8s 80s 800s 1600s + * + * Another way to say the same things is: + * For N=1, Timeout = (M+1) * 0.5s + * For N=2, Timeout = (M+1) * 5s + * For N=3, Timeout = (M+1) * 50s + * For N=4, Timeout = (M+1) * 100s + * + */ + +static int wd_times[64][2] = { + {0, 1}, /* 0 = 0.5s */ + {1, 1}, /* 1 = 1s */ + {2, 1}, /* 2 = 1.5s */ + {3, 1}, /* 3 = 2s */ + {4, 1}, /* 4 = 2.5s */ + {5, 1}, /* 5 = 3s */ + {6, 1}, /* 6 = 3.5s */ + {7, 1}, /* 7 = 4s */ + {8, 1}, /* 8 = 4.5s */ + {9, 1}, /* 9 = 5s */ + {0xA, 1}, /* 10 = 5.5s */ + {0xB, 1}, /* 11 = 6s */ + {0xC, 1}, /* 12 = 6.5s */ + {0xD, 1}, /* 13 = 7s */ + {0xE, 1}, /* 14 = 7.5s */ + {0xF, 1}, /* 15 = 8s */ + {0, 2}, /* 16 = 5s */ + {1, 2}, /* 17 = 10s */ + {2, 2}, /* 18 = 15s */ + {3, 2}, /* 19 = 20s */ + {4, 2}, /* 20 = 25s */ + {5, 2}, /* 21 = 30s */ + {6, 2}, /* 22 = 35s */ + {7, 2}, /* 23 = 40s */ + {8, 2}, /* 24 = 45s */ + {9, 2}, /* 25 = 50s */ + {0xA, 2}, /* 26 = 55s */ + {0xB, 2}, /* 27 = 60s */ + {0xC, 2}, /* 28 = 65s */ + {0xD, 2}, /* 29 = 70s */ + {0xE, 2}, /* 30 = 75s */ + {0xF, 2}, /* 31 = 80s */ + {0, 3}, /* 32 = 50s */ + {1, 3}, /* 33 = 100s */ + {2, 3}, /* 34 = 150s */ + {3, 3}, /* 35 = 200s */ + {4, 3}, /* 36 = 250s */ + {5, 3}, /* 37 = 300s */ + {6, 3}, /* 38 = 350s */ + {7, 3}, /* 39 = 400s */ + {8, 3}, /* 40 = 450s */ + {9, 3}, /* 41 = 500s */ + {0xA, 3}, /* 42 = 550s */ + {0xB, 3}, /* 43 = 600s */ + {0xC, 3}, /* 44 = 650s */ + {0xD, 3}, /* 45 = 700s */ + {0xE, 3}, /* 46 = 750s */ + {0xF, 3}, /* 47 = 800s */ + {0, 4}, /* 48 = 100s */ + {1, 4}, /* 49 = 200s */ + {2, 4}, /* 50 = 300s */ + {3, 4}, /* 51 = 400s */ + {4, 4}, /* 52 = 500s */ + {5, 4}, /* 53 = 600s */ + {6, 4}, /* 54 = 700s */ + {7, 4}, /* 55 = 800s */ + {8, 4}, /* 56 = 900s */ + {9, 4}, /* 57 = 1000s */ + {0xA, 4}, /* 58 = 1100s */ + {0xB, 4}, /* 59 = 1200s */ + {0xC, 4}, /* 60 = 1300s */ + {0xD, 4}, /* 61 = 1400s */ + {0xE, 4}, /* 62 = 1500s */ + {0xF, 4} /* 63 = 1600s */ +}; + +#define SBC8360_ENABLE 0x120 +#define SBC8360_BASETIME 0x121 + +static int timeout = 27; +static int wd_margin = 0xB; +static int wd_multiplier = 2; +static int nowayout = WATCHDOG_NOWAYOUT; + +module_param(timeout, int, 27); +MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))"); +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Kernel methods. + */ + +/* Activate and pre-configure watchdog */ +static void sbc8360_activate(void) +{ + /* Enable the watchdog */ + outb(0x0A, SBC8360_ENABLE); + msleep_interruptible(100); + outb(0x0B, SBC8360_ENABLE); + msleep_interruptible(100); + /* Set timeout multiplier */ + outb(wd_multiplier, SBC8360_ENABLE); + msleep_interruptible(100); + /* Nothing happens until first sbc8360_ping() */ +} + +/* Kernel pings watchdog */ +static void sbc8360_ping(void) +{ + /* Write the base timer register */ + outb(wd_margin, SBC8360_BASETIME); +} + +/* Userspace pings kernel driver, or requests clean close */ +static ssize_t sbc8360_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; + } + } + sbc8360_ping(); + } + return count; +} + +static int sbc8360_open(struct inode *inode, struct file *file) +{ + spin_lock(&sbc8360_lock); + if (test_and_set_bit(0, &sbc8360_is_open)) { + spin_unlock(&sbc8360_lock); + return -EBUSY; + } + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate and ping once to start the countdown */ + spin_unlock(&sbc8360_lock); + sbc8360_activate(); + sbc8360_ping(); + return nonseekable_open(inode, file); +} + +static int sbc8360_close(struct inode *inode, struct file *file) +{ + spin_lock(&sbc8360_lock); + if (expect_close == 42) + outb(0, SBC8360_ENABLE); + else + printk(KERN_CRIT PFX + "SBC8360 device closed unexpectedly. SBC8360 will not stop!\n"); + + clear_bit(0, &sbc8360_is_open); + expect_close = 0; + spin_unlock(&sbc8360_lock); + return 0; +} + +/* + * Notifier for system down + */ + +static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Disable the SBC8360 Watchdog */ + outb(0, SBC8360_ENABLE); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations sbc8360_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sbc8360_write, + .open = sbc8360_open, + .release = sbc8360_close, +}; + +static struct miscdevice sbc8360_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sbc8360_fops, +}; + +/* + * The SBC8360 needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block sbc8360_notifier = { + .notifier_call = sbc8360_notify_sys, +}; + +static int __init sbc8360_init(void) +{ + int res; + unsigned long int mseconds = 60000; + + spin_lock_init(&sbc8360_lock); + res = misc_register(&sbc8360_miscdev); + if (res) { + printk(KERN_ERR PFX "failed to register misc device\n"); + goto out_nomisc; + } + + if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) { + printk(KERN_ERR PFX "ENABLE method I/O %X is not available.\n", + SBC8360_ENABLE); + res = -EIO; + goto out_noenablereg; + } + if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) { + printk(KERN_ERR PFX + "BASETIME method I/O %X is not available.\n", + SBC8360_BASETIME); + res = -EIO; + goto out_nobasetimereg; + } + + res = register_reboot_notifier(&sbc8360_notifier); + if (res) { + printk(KERN_ERR PFX "Failed to register reboot notifier.\n"); + goto out_noreboot; + } + + if (timeout < 0 || timeout > 63) { + printk(KERN_ERR PFX "Invalid timeout index (must be 0-63).\n"); + res = -EINVAL; + goto out_noreboot; + } + + wd_margin = wd_times[timeout][0]; + wd_multiplier = wd_times[timeout][1]; + + if (wd_multiplier == 1) + mseconds = (wd_margin + 1) * 500; + else if (wd_multiplier == 2) + mseconds = (wd_margin + 1) * 5000; + else if (wd_multiplier == 3) + mseconds = (wd_margin + 1) * 50000; + else if (wd_multiplier == 4) + mseconds = (wd_margin + 1) * 100000; + + /* My kingdom for the ability to print "0.5 seconds" in the kernel! */ + printk(KERN_INFO PFX "Timeout set at %ld ms.\n", mseconds); + + return 0; + + out_noreboot: + release_region(SBC8360_ENABLE, 1); + release_region(SBC8360_BASETIME, 1); + out_noenablereg: + out_nobasetimereg: + misc_deregister(&sbc8360_miscdev); + out_nomisc: + return res; +} + +static void __exit sbc8360_exit(void) +{ + misc_deregister(&sbc8360_miscdev); + unregister_reboot_notifier(&sbc8360_notifier); + release_region(SBC8360_ENABLE, 1); + release_region(SBC8360_BASETIME, 1); +} + +module_init(sbc8360_init); +module_exit(sbc8360_exit); + +MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>"); +MODULE_DESCRIPTION("SBC8360 watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +/* end of sbc8360.c */ diff --git a/drivers/char/watchdog/w83977f_wdt.c b/drivers/char/watchdog/w83977f_wdt.c new file mode 100644 index 00000000000..a7ff64c8921 --- /dev/null +++ b/drivers/char/watchdog/w83977f_wdt.c @@ -0,0 +1,543 @@ +/* + * W83977F Watchdog Timer Driver for Winbond W83977F I/O Chip + * + * (c) Copyright 2005 Jose Goncalves <jose.goncalves@inov.pt> + * + * Based on w83877f_wdt.c by Scott Jennings, + * and wdt977.c by Woody Suwalski + * + * ----------------------- + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#define WATCHDOG_VERSION "1.00" +#define WATCHDOG_NAME "W83977F WDT" +#define PFX WATCHDOG_NAME ": " +#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" + +#define IO_INDEX_PORT 0x3F0 +#define IO_DATA_PORT (IO_INDEX_PORT+1) + +#define UNLOCK_DATA 0x87 +#define LOCK_DATA 0xAA +#define DEVICE_REGISTER 0x07 + +#define DEFAULT_TIMEOUT 45 /* default timeout in seconds */ + +static int timeout = DEFAULT_TIMEOUT; +static int timeoutW; /* timeout in watchdog counter units */ +static unsigned long timer_alive; +static int testmode; +static char expect_close; +static spinlock_t spinlock; + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (15..7635), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); +module_param(testmode, int, 0); +MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Start the watchdog + */ + +static int wdt_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + + /* + * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. + * F2 has the timeout in watchdog counter units. + * F3 is set to enable watchdog LED blink at timeout. + * F4 is used to just clear the TIMEOUT'ed state (bit 0). + */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + outb_p(0xF2,IO_INDEX_PORT); + outb_p(timeoutW,IO_DATA_PORT); + outb_p(0xF3,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + outb_p(0xF4,IO_INDEX_PORT); + outb_p(0x00,IO_DATA_PORT); + + /* Set device Aux2 active */ + outb_p(0x30,IO_INDEX_PORT); + outb_p(0x01,IO_DATA_PORT); + + /* + * Select device Aux1 (dev=7) to set GP16 as the watchdog output + * (in reg E6) and GP13 as the watchdog LED output (in reg E3). + * Map GP16 at pin 119. + * In test mode watch the bit 0 on F4 to indicate "triggered" or + * check watchdog LED on SBC. + */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x07,IO_DATA_PORT); + if (!testmode) + { + unsigned pin_map; + + outb_p(0xE6,IO_INDEX_PORT); + outb_p(0x0A,IO_DATA_PORT); + outb_p(0x2C,IO_INDEX_PORT); + pin_map = inb_p(IO_DATA_PORT); + pin_map |= 0x10; + pin_map &= ~(0x20); + outb_p(0x2C,IO_INDEX_PORT); + outb_p(pin_map,IO_DATA_PORT); + } + outb_p(0xE3,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + + /* Set device Aux1 active */ + outb_p(0x30,IO_INDEX_PORT); + outb_p(0x01,IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA,IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + printk(KERN_INFO PFX "activated.\n"); + + return 0; +} + +/* + * Stop the watchdog + */ + +static int wdt_stop(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + + /* + * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. + * F2 is reset to its default value (watchdog timer disabled). + * F3 is reset to its default state. + * F4 clears the TIMEOUT'ed state (bit 0) - back to default. + */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + outb_p(0xF2,IO_INDEX_PORT); + outb_p(0xFF,IO_DATA_PORT); + outb_p(0xF3,IO_INDEX_PORT); + outb_p(0x00,IO_DATA_PORT); + outb_p(0xF4,IO_INDEX_PORT); + outb_p(0x00,IO_DATA_PORT); + outb_p(0xF2,IO_INDEX_PORT); + outb_p(0x00,IO_DATA_PORT); + + /* + * Select device Aux1 (dev=7) to set GP16 (in reg E6) and + * Gp13 (in reg E3) as inputs. + */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x07,IO_DATA_PORT); + if (!testmode) + { + outb_p(0xE6,IO_INDEX_PORT); + outb_p(0x01,IO_DATA_PORT); + } + outb_p(0xE3,IO_INDEX_PORT); + outb_p(0x01,IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA,IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + printk(KERN_INFO PFX "shutdown.\n"); + + return 0; +} + +/* + * Send a keepalive ping to the watchdog + * This is done by simply re-writing the timeout to reg. 0xF2 + */ + +static int wdt_keepalive(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + + /* Select device Aux2 (device=8) to kick watchdog reg F2 */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + outb_p(0xF2,IO_INDEX_PORT); + outb_p(timeoutW,IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA,IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + return 0; +} + +/* + * Set the watchdog timeout value + */ + +static int wdt_set_timeout(int t) +{ + int tmrval; + + /* + * Convert seconds to watchdog counter time units, rounding up. + * On PCM-5335 watchdog units are 30 seconds/step with 15 sec startup + * value. This information is supplied in the PCM-5335 manual and was + * checked by me on a real board. This is a bit strange because W83977f + * datasheet says counter unit is in minutes! + */ + if (t < 15) + return -EINVAL; + + tmrval = ((t + 15) + 29) / 30; + + if (tmrval > 255) + return -EINVAL; + + /* + * timeout is the timeout in seconds, + * timeoutW is the timeout in watchdog counter units. + */ + timeoutW = tmrval; + timeout = (timeoutW * 30) - 15; + return 0; +} + +/* + * Get the watchdog status + */ + +static int wdt_get_status(int *status) +{ + int new_status; + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + outb_p(UNLOCK_DATA,IO_INDEX_PORT); + + /* Select device Aux2 (device=8) to read watchdog reg F4 */ + outb_p(DEVICE_REGISTER,IO_INDEX_PORT); + outb_p(0x08,IO_DATA_PORT); + outb_p(0xF4,IO_INDEX_PORT); + new_status = inb_p(IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA,IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + *status = 0; + if (new_status & 1) + *status |= WDIOF_CARDRESET; + + return 0; +} + + +/* + * /dev/watchdog handling + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + /* If the watchdog is alive we don't need to start it again */ + if( test_and_set_bit(0, &timer_alive) ) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + wdt_start(); + return nonseekable_open(inode, file); +} + +static int wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (expect_close == 42) + { + wdt_stop(); + clear_bit(0, &timer_alive); + } else { + wdt_keepalive(); + printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); + } + expect_close = 0; + return 0; +} + +/* + * 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) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) + { + if (!nowayout) + { + size_t ofs; + + /* note: just in case someone wrote the magic character long ago */ + expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for(ofs = 0; ofs != count; ofs++) + { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') { + expect_close = 42; + } + } + } + + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + 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. + */ + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +static int wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int status; + int new_options, retval = -EINVAL; + int new_timeout; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + uarg.i = (int __user *)arg; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + wdt_get_status(&status); + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + + case WDIOC_SETOPTIONS: + if (get_user (new_options, uarg.i)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (wdt_set_timeout(new_timeout)) + return -EINVAL; + + wdt_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, uarg.i); + + } +} + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) + wdt_stop(); + return NOTIFY_DONE; +} + +static struct file_operations wdt_fops= +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_release, +}; + +static struct miscdevice wdt_miscdev= +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static int __init w83977f_wdt_init(void) +{ + int rc; + + printk(KERN_INFO PFX DRIVER_VERSION); + + spin_lock_init(&spinlock); + + /* + * Check that the timeout value is within it's range ; + * if not reset to the default + */ + if (wdt_set_timeout(timeout)) { + wdt_set_timeout(DEFAULT_TIMEOUT); + printk(KERN_INFO PFX "timeout value must be 15<=timeout<=7635, using %d\n", + DEFAULT_TIMEOUT); + } + + if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) + { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + IO_INDEX_PORT); + rc = -EIO; + goto err_out; + } + + rc = misc_register(&wdt_miscdev); + if (rc) + { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_region; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) + { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + rc); + goto err_out_miscdev; + } + + printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d testmode=%d)\n", + timeout, nowayout, testmode); + + return 0; + +err_out_miscdev: + misc_deregister(&wdt_miscdev); +err_out_region: + release_region(IO_INDEX_PORT,2); +err_out: + return rc; +} + +static void __exit w83977f_wdt_exit(void) +{ + wdt_stop(); + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(IO_INDEX_PORT,2); +} + +module_init(w83977f_wdt_init); +module_exit(w83977f_wdt_exit); + +MODULE_AUTHOR("Jose Goncalves <jose.goncalves@inov.pt>"); +MODULE_DESCRIPTION("Driver for watchdog timer in W83977F I/O chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |