diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 43 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 7 | ||||
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 328 | ||||
-rw-r--r-- | drivers/watchdog/cpwd.c | 695 | ||||
-rw-r--r-- | drivers/watchdog/geodewdt.c | 6 | ||||
-rw-r--r-- | drivers/watchdog/ibmasr.c | 1 | ||||
-rw-r--r-- | drivers/watchdog/it87_wdt.c | 725 | ||||
-rw-r--r-- | drivers/watchdog/ixp4xx_wdt.c | 4 | ||||
-rw-r--r-- | drivers/watchdog/omap_wdt.c | 337 | ||||
-rw-r--r-- | drivers/watchdog/omap_wdt.h | 28 | ||||
-rw-r--r-- | drivers/watchdog/orion5x_wdt.c | 245 | ||||
-rw-r--r-- | drivers/watchdog/pnx4008_wdt.c | 4 | ||||
-rw-r--r-- | drivers/watchdog/rc32434_wdt.c | 6 | ||||
-rw-r--r-- | drivers/watchdog/rdc321x_wdt.c | 6 | ||||
-rw-r--r-- | drivers/watchdog/riowd.c | 259 | ||||
-rw-r--r-- | drivers/watchdog/w83697ug_wdt.c | 392 | ||||
-rw-r--r-- | drivers/watchdog/wdt285.c | 15 |
17 files changed, 2947 insertions, 154 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c51036716700..1a22fe782a27 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -66,6 +66,13 @@ config AT91RM9200_WATCHDOG Watchdog timer embedded into AT91RM9200 chips. This will reboot your system when the timeout is reached. +config AT91SAM9X_WATCHDOG + tristate "AT91SAM9X watchdog" + depends on WATCHDOG && (ARCH_AT91SAM9260 || ARCH_AT91SAM9261) + help + Watchdog timer embedded into AT91SAM9X chips. This will reboot your + system when the timeout is reached. + config 21285_WATCHDOG tristate "DC21285 watchdog" depends on FOOTBRIDGE @@ -217,6 +224,15 @@ config DAVINCI_WATCHDOG NOTE: once enabled, this timer cannot be disabled. Say N if you are unsure. +config ORION5X_WATCHDOG + tristate "Orion5x watchdog" + depends on ARCH_ORION5X + help + Say Y here if to include support for the watchdog timer + in the Orion5x ARM SoCs. + To compile this driver as a module, choose M here: the + module will be called orion5x_wdt. + # ARM26 Architecture # AVR32 Architecture @@ -416,6 +432,18 @@ config IT8712F_WDT To compile this driver as a module, choose M here: the module will be called it8712f_wdt. +config IT87_WDT + tristate "IT87 Watchdog Timer" + depends on X86 && EXPERIMENTAL + ---help--- + This is the driver for the hardware watchdog on the ITE IT8716, + IT8718, IT8726, IT8712(Version J,K) Super I/O chips. 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 it87_wdt. + config HP_WATCHDOG tristate "HP Proliant iLO 2 Hardware Watchdog Timer" depends on X86 @@ -573,6 +601,21 @@ config W83697HF_WDT Most people will say N. +config W83697UG_WDT + tristate "W83697UG/W83697UF Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the W83697UG/UF + chipset as used in MSI Fuzzy CX700 VIA motherboards (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 w83697ug_wdt. + + Most people will say N. + config W83877F_WDT tristate "W83877F (EMACS) Watchdog Timer" depends on X86 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index e0ef123fbdea..e352bbb7630b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o +obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o obj-$(CONFIG_21285_WATCHDOG) += wdt285.o obj-$(CONFIG_977_WATCHDOG) += wdt977.o @@ -39,6 +40,7 @@ obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o +obj-$(CONFIG_ORION5X_WATCHDOG) += orion5x_wdt.o # ARM26 Architecture @@ -71,6 +73,7 @@ ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o endif obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o +obj-$(CONFIG_IT87_WDT) += it87_wdt.o obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o @@ -83,6 +86,7 @@ obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o obj-$(CONFIG_W83697HF_WDT) += w83697hf_wdt.o +obj-$(CONFIG_W83697UG_WDT) += w83697ug_wdt.o obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o @@ -123,6 +127,9 @@ obj-$(CONFIG_SH_WDT) += shwdt.o # SPARC64 Architecture +obj-$(CONFIG_WATCHDOG_RIO) += riowd.o +obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o + # XTENSA Architecture # Architecture Independant diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c new file mode 100644 index 000000000000..b4babfc31586 --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c @@ -0,0 +1,328 @@ +/* + * Watchdog driver for Atmel AT91SAM9x processors. + * + * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr + * + * 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. + */ + +/* + * The Watchdog Timer Mode Register can be only written to once. If the + * timeout need to be set from Linux, be sure that the bootstrap or the + * bootloader doesn't write to this register. + */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/jiffies.h> +#include <linux/timer.h> +#include <linux/bitops.h> +#include <linux/uaccess.h> + +#include <asm/arch/at91_wdt.h> + +#define DRV_NAME "AT91SAM9 Watchdog" + +/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, + * use this to convert a watchdog + * value from/to milliseconds. + */ +#define ms_to_ticks(t) (((t << 8) / 1000) - 1) +#define ticks_to_ms(t) (((t + 1) * 1000) >> 8) + +/* Hardware timeout in seconds */ +#define WDT_HW_TIMEOUT 2 + +/* Timer heartbeat (500ms) */ +#define WDT_TIMEOUT (HZ/2) + +/* User land timeout */ +#define WDT_HEARTBEAT 15 +static int heartbeat = WDT_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " + "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); + +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) ")"); + +static void at91_ping(unsigned long data); + +static struct { + unsigned long next_heartbeat; /* the next_heartbeat for the timer */ + unsigned long open; + char expect_close; + struct timer_list timer; /* The timer that pings the watchdog */ +} at91wdt_private; + +/* ......................................................................... */ + + +/* + * Reload the watchdog timer. (ie, pat the watchdog) + */ +static inline void at91_wdt_reset(void) +{ + at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); +} + +/* + * Timer tick + */ +static void at91_ping(unsigned long data) +{ + if (time_before(jiffies, at91wdt_private.next_heartbeat) || + (!nowayout && !at91wdt_private.open)) { + at91_wdt_reset(); + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + } else + printk(KERN_CRIT DRV_NAME": I will reset your machine !\n"); +} + +/* + * Watchdog device is opened, and watchdog starts running. + */ +static int at91_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &at91wdt_private.open)) + return -EBUSY; + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + + return nonseekable_open(inode, file); +} + +/* + * Close the watchdog device. + */ +static int at91_wdt_close(struct inode *inode, struct file *file) +{ + clear_bit(0, &at91wdt_private.open); + + /* stop internal ping */ + if (!at91wdt_private.expect_close) + del_timer(&at91wdt_private.timer); + + at91wdt_private.expect_close = 0; + return 0; +} + +/* + * Set the watchdog time interval in 1/256Hz (write-once) + * Counter is 12 bit. + */ +static int at91_wdt_settimeout(unsigned int timeout) +{ + unsigned int reg; + unsigned int mr; + + /* Check if disabled */ + mr = at91_sys_read(AT91_WDT_MR); + if (mr & AT91_WDT_WDDIS) { + printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n"); + return -EIO; + } + + /* + * All counting occurs at SLOW_CLOCK / 128 = 256 Hz + * + * Since WDV is a 12-bit counter, the maximum period is + * 4096 / 256 = 16 seconds. + */ + reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ + /* | AT91_WDT_WDRPROC causes processor reset only */ + | AT91_WDT_WDDBGHLT /* disabled in debug mode */ + | AT91_WDT_WDD /* restart at any time */ + | (timeout & AT91_WDT_WDV); /* timer value */ + at91_sys_write(AT91_WDT_MR, reg); + + return 0; +} + +static const struct watchdog_info at91_wdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, +}; + +/* + * Handle commands from user-space. + */ +static long at91_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &at91_wdt_info, + sizeof(at91_wdt_info)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + heartbeat = new_value; + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + + return put_user(new_value, p); /* return current value */ + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + } + return -ENOTTY; +} + +/* + * Pat the watchdog whenever device is written to. + */ +static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, + loff_t *ppos) +{ + if (!len) + return 0; + + /* Scan for magic character */ + if (!nowayout) { + size_t i; + + at91wdt_private.expect_close = 0; + + for (i = 0; i < len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') { + at91wdt_private.expect_close = 42; + break; + } + } + } + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + + return len; +} + +/* ......................................................................... */ + +static const struct file_operations at91wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = at91_wdt_ioctl, + .open = at91_wdt_open, + .release = at91_wdt_close, + .write = at91_wdt_write, +}; + +static struct miscdevice at91wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &at91wdt_fops, +}; + +static int __init at91wdt_probe(struct platform_device *pdev) +{ + int res; + + if (at91wdt_miscdev.parent) + return -EBUSY; + at91wdt_miscdev.parent = &pdev->dev; + + /* Set watchdog */ + res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); + if (res) + return res; + + res = misc_register(&at91wdt_miscdev); + if (res) + return res; + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + setup_timer(&at91wdt_private.timer, at91_ping, 0); + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + + printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n", + heartbeat, nowayout); + + return 0; +} + +static int __exit at91wdt_remove(struct platform_device *pdev) +{ + int res; + + res = misc_deregister(&at91wdt_miscdev); + if (!res) + at91wdt_miscdev.parent = NULL; + + return res; +} + +#ifdef CONFIG_PM + +static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) +{ + return 0; +} + +static int at91wdt_resume(struct platform_device *pdev) +{ + return 0; +} + +#else +#define at91wdt_suspend NULL +#define at91wdt_resume NULL +#endif + +static struct platform_driver at91wdt_driver = { + .remove = __exit_p(at91wdt_remove), + .suspend = at91wdt_suspend, + .resume = at91wdt_resume, + .driver = { + .name = "at91_wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init at91sam_wdt_init(void) +{ + return platform_driver_probe(&at91wdt_driver, at91wdt_probe); +} + +static void __exit at91sam_wdt_exit(void) +{ + platform_driver_unregister(&at91wdt_driver); +} + +module_init(at91sam_wdt_init); +module_exit(at91sam_wdt_exit); + +MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); +MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/cpwd.c b/drivers/watchdog/cpwd.c new file mode 100644 index 000000000000..084dfe9cecfb --- /dev/null +++ b/drivers/watchdog/cpwd.c @@ -0,0 +1,695 @@ +/* cpwd.c - driver implementation for hardware watchdog + * timers found on Sun Microsystems CP1400 and CP1500 boards. + * + * This device supports both the generic Linux watchdog + * interface and Solaris-compatible ioctls as best it is + * able. + * + * NOTE: CP1400 systems appear to have a defective intr_mask + * register on the PLD, preventing the disabling of + * timer interrupts. We use a timer to periodically + * reset 'stopped' watchdogs on affected platforms. + * + * Copyright (c) 2000 Eric Brower (ebrower@usa.net) + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/smp_lock.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <asm/irq.h> +#include <asm/uaccess.h> + +#include <asm/watchdog.h> + +#define DRIVER_NAME "cpwd" +#define PFX DRIVER_NAME ": " + +#define WD_OBPNAME "watchdog" +#define WD_BADMODEL "SUNW,501-5336" +#define WD_BTIMEOUT (jiffies + (HZ * 1000)) +#define WD_BLIMIT 0xFFFF + +#define WD0_MINOR 212 +#define WD1_MINOR 213 +#define WD2_MINOR 214 + +/* Internal driver definitions. */ +#define WD0_ID 0 +#define WD1_ID 1 +#define WD2_ID 2 +#define WD_NUMDEVS 3 + +#define WD_INTR_OFF 0 +#define WD_INTR_ON 1 + +#define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */ +#define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */ +#define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */ + +/* Register value definitions + */ +#define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */ +#define WD1_INTR_MASK 0x02 +#define WD2_INTR_MASK 0x04 + +#define WD_S_RUNNING 0x01 /* Watchdog device status running */ +#define WD_S_EXPIRED 0x02 /* Watchdog device status expired */ + +struct cpwd { + void __iomem *regs; + spinlock_t lock; + + unsigned int irq; + + unsigned long timeout; + bool enabled; + bool reboot; + bool broken; + bool initialized; + + struct { + struct miscdevice misc; + void __iomem *regs; + u8 intr_mask; + u8 runstatus; + u16 timeout; + } devs[WD_NUMDEVS]; +}; + +static struct cpwd *cpwd_device; + +/* Sun uses Altera PLD EPF8820ATC144-4 + * providing three hardware watchdogs: + * + * 1) RIC - sends an interrupt when triggered + * 2) XIR - asserts XIR_B_RESET when triggered, resets CPU + * 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board + * + *** Timer register block definition (struct wd_timer_regblk) + * + * dcntr and limit registers (halfword access): + * ------------------- + * | 15 | ...| 1 | 0 | + * ------------------- + * |- counter val -| + * ------------------- + * dcntr - Current 16-bit downcounter value. + * When downcounter reaches '0' watchdog expires. + * Reading this register resets downcounter with 'limit' value. + * limit - 16-bit countdown value in 1/10th second increments. + * Writing this register begins countdown with input value. + * Reading from this register does not affect counter. + * NOTES: After watchdog reset, dcntr and limit contain '1' + * + * status register (byte access): + * --------------------------- + * | 7 | ... | 2 | 1 | 0 | + * --------------+------------ + * |- UNUSED -| EXP | RUN | + * --------------------------- + * status- Bit 0 - Watchdog is running + * Bit 1 - Watchdog has expired + * + *** PLD register block definition (struct wd_pld_regblk) + * + * intr_mask register (byte access): + * --------------------------------- + * | 7 | ... | 3 | 2 | 1 | 0 | + * +-------------+------------------ + * |- UNUSED -| WD3 | WD2 | WD1 | + * --------------------------------- + * WD3 - 1 == Interrupt disabled for watchdog 3 + * WD2 - 1 == Interrupt disabled for watchdog 2 + * WD1 - 1 == Interrupt disabled for watchdog 1 + * + * pld_status register (byte access): + * UNKNOWN, MAGICAL MYSTERY REGISTER + * + */ +#define WD_TIMER_REGSZ 16 +#define WD0_OFF 0 +#define WD1_OFF (WD_TIMER_REGSZ * 1) +#define WD2_OFF (WD_TIMER_REGSZ * 2) +#define PLD_OFF (WD_TIMER_REGSZ * 3) + +#define WD_DCNTR 0x00 +#define WD_LIMIT 0x04 +#define WD_STATUS 0x08 + +#define PLD_IMASK (PLD_OFF + 0x00) +#define PLD_STATUS (PLD_OFF + 0x04) + +static struct timer_list cpwd_timer; + +static int wd0_timeout = 0; +static int wd1_timeout = 0; +static int wd2_timeout = 0; + +module_param (wd0_timeout, int, 0); +MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs"); +module_param (wd1_timeout, int, 0); +MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs"); +module_param (wd2_timeout, int, 0); +MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs"); + +MODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); +MODULE_DESCRIPTION("Hardware watchdog driver for Sun Microsystems CP1400/1500"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("watchdog"); + +static void cpwd_writew(u16 val, void __iomem *addr) +{ + writew(cpu_to_le16(val), addr); +} +static u16 cpwd_readw(void __iomem *addr) +{ + u16 val = readw(addr); + + return le16_to_cpu(val); +} + +static void cpwd_writeb(u8 val, void __iomem *addr) +{ + writeb(val, addr); +} + +static u8 cpwd_readb(void __iomem *addr) +{ + return readb(addr); +} + +/* Enable or disable watchdog interrupts + * Because of the CP1400 defect this should only be + * called during initialzation or by wd_[start|stop]timer() + * + * index - sub-device index, or -1 for 'all' + * enable - non-zero to enable interrupts, zero to disable + */ +static void cpwd_toggleintr(struct cpwd *p, int index, int enable) +{ + unsigned char curregs = cpwd_readb(p->regs + PLD_IMASK); + unsigned char setregs = + (index == -1) ? + (WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) : + (p->devs[index].intr_mask); + + if (enable == WD_INTR_ON) + curregs &= ~setregs; + else + curregs |= setregs; + + cpwd_writeb(curregs, p->regs + PLD_IMASK); +} + +/* Restarts timer with maximum limit value and + * does not unset 'brokenstop' value. + */ +static void cpwd_resetbrokentimer(struct cpwd *p, int index) +{ + cpwd_toggleintr(p, index, WD_INTR_ON); + cpwd_writew(WD_BLIMIT, p->devs[index].regs + WD_LIMIT); +} + +/* Timer method called to reset stopped watchdogs-- + * because of the PLD bug on CP1400, we cannot mask + * interrupts within the PLD so me must continually + * reset the timers ad infinitum. + */ +static void cpwd_brokentimer(unsigned long data) +{ + struct cpwd *p = (struct cpwd *) data; + int id, tripped = 0; + + /* kill a running timer instance, in case we + * were called directly instead of by kernel timer + */ + if (timer_pending(&cpwd_timer)) + del_timer(&cpwd_timer); + + for (id = 0; id < WD_NUMDEVS; id++) { + if (p->devs[id].runstatus & WD_STAT_BSTOP) { + ++tripped; + cpwd_resetbrokentimer(p, id); + } + } + + if (tripped) { + /* there is at least one timer brokenstopped-- reschedule */ + cpwd_timer.expires = WD_BTIMEOUT; + add_timer(&cpwd_timer); + } +} + +/* Reset countdown timer with 'limit' value and continue countdown. + * This will not start a stopped timer. + */ +static void cpwd_pingtimer(struct cpwd *p, int index) +{ + if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING) + cpwd_readw(p->devs[index].regs + WD_DCNTR); +} + +/* Stop a running watchdog timer-- the timer actually keeps + * running, but the interrupt is masked so that no action is + * taken upon expiration. + */ +static void cpwd_stoptimer(struct cpwd *p, int index) +{ + if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING) { + cpwd_toggleintr(p, index, WD_INTR_OFF); + + if (p->broken) { + p->devs[index].runstatus |= WD_STAT_BSTOP; + cpwd_brokentimer((unsigned long) p); + } + } +} + +/* Start a watchdog timer with the specified limit value + * If the watchdog is running, it will be restarted with + * the provided limit value. + * + * This function will enable interrupts on the specified + * watchdog. + */ +static void cpwd_starttimer(struct cpwd *p, int index) +{ + if (p->broken) + p->devs[index].runstatus &= ~WD_STAT_BSTOP; + + p->devs[index].runstatus &= ~WD_STAT_SVCD; + + cpwd_writew(p->devs[index].timeout, p->devs[index].regs + WD_LIMIT); + cpwd_toggleintr(p, index, WD_INTR_ON); +} + +static int cpwd_getstatus(struct cpwd *p, int index) +{ + unsigned char stat = cpwd_readb(p->devs[index].regs + WD_STATUS); + unsigned char intr = cpwd_readb(p->devs[index].regs + PLD_IMASK); + unsigned char ret = WD_STOPPED; + + /* determine STOPPED */ + if (!stat) + return ret; + + /* determine EXPIRED vs FREERUN vs RUNNING */ + else if (WD_S_EXPIRED & stat) { + ret = WD_EXPIRED; + } else if(WD_S_RUNNING & stat) { + if (intr & p->devs[index].intr_mask) { + ret = WD_FREERUN; + } else { + /* Fudge WD_EXPIRED status for defective CP1400-- + * IF timer is running + * AND brokenstop is set + * AND an interrupt has been serviced + * we are WD_EXPIRED. + * + * IF timer is running + * AND brokenstop is set + * AND no interrupt has been serviced + * we are WD_FREERUN. + */ + if (p->broken && + (p->devs[index].runstatus & WD_STAT_BSTOP)) { + if (p->devs[index].runstatus & WD_STAT_SVCD) { + ret = WD_EXPIRED; + } else { + /* we could as well pretend we are expired */ + ret = WD_FREERUN; + } + } else { + ret = WD_RUNNING; + } + } + } + + /* determine SERVICED */ + if (p->devs[index].runstatus & WD_STAT_SVCD) + ret |= WD_SERVICED; + + return(ret); +} + +static irqreturn_t cpwd_interrupt(int irq, void *dev_id) +{ + struct cpwd *p = dev_id; + + /* Only WD0 will interrupt-- others are NMI and we won't + * see them here.... + */ + spin_lock_irq(&p->lock); + + cpwd_stoptimer(p, WD0_ID); + p->devs[WD0_ID].runstatus |= WD_STAT_SVCD; + + spin_unlock_irq(&p->lock); + + return IRQ_HANDLED; +} + +static int cpwd_open(struct inode *inode, struct file *f) +{ + struct cpwd *p = cpwd_device; + + lock_kernel(); + switch(iminor(inode)) { + case WD0_MINOR: + case WD1_MINOR: + case WD2_MINOR: + break; + + default: + unlock_kernel(); + return -ENODEV; + } + + /* Register IRQ on first open of device */ + if (!p->initialized) { + if (request_irq(p->irq, &cpwd_interrupt, + IRQF_SHARED, DRIVER_NAME, p)) { + printk(KERN_ERR PFX "Cannot register IRQ %d\n", + p->irq); + unlock_kernel(); + return -EBUSY; + } + p->initialized = true; + } + + unlock_kernel(); + + return nonseekable_open(inode, f); +} + +static int cpwd_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int cpwd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + static struct watchdog_info info = { + .options = WDIOF_SETTIMEOUT, + .firmware_version = 1, + .identity = DRIVER_NAME, + }; + void __user *argp = (void __user *)arg; + int index = iminor(inode) - WD0_MINOR; + struct cpwd *p = cpwd_device; + int setopt = 0; + + switch (cmd) { + /* Generic Linux IOCTLs */ + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &info, sizeof(struct watchdog_info))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(0, (int __user *)argp)) + return -EFAULT; + break; + + case WDIOC_KEEPALIVE: + cpwd_pingtimer(p, index); + break; + + case WDIOC_SETOPTIONS: + if (copy_from_user(&setopt, argp, sizeof(unsigned int))) + return -EFAULT; + + if (setopt & WDIOS_DISABLECARD) { + if (p->enabled) + return -EINVAL; + cpwd_stoptimer(p, index); + } else if (setopt & WDIOS_ENABLECARD) { + cpwd_starttimer(p, index); + } else { + return -EINVAL; + } + break; + + /* Solaris-compatible IOCTLs */ + case WIOCGSTAT: + setopt = cpwd_getstatus(p, index); + if (copy_to_user(argp, &setopt, sizeof(unsigned int))) + return -EFAULT; + break; + + case WIOCSTART: + cpwd_starttimer(p, index); + break; + + case WIOCSTOP: + if (p->enabled) + return(-EINVAL); + + cpwd_stoptimer(p, index); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static long cpwd_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rval = -ENOIOCTLCMD; + + switch (cmd) { + /* solaris ioctls are specific to this driver */ + case WIOCSTART: + case WIOCSTOP: + case WIOCGSTAT: + lock_kernel(); + rval = cpwd_ioctl(file->f_path.dentry->d_inode, file, cmd, arg); + unlock_kernel(); + break; + + /* everything else is handled by the generic compat layer */ + default: + break; + } + + return rval; +} + +static ssize_t cpwd_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode *inode = file->f_path.dentry->d_inode; + struct cpwd *p = cpwd_device; + int index = iminor(inode); + + if (count) { + cpwd_pingtimer(p, index); + return 1; + } + + return 0; +} + +static ssize_t cpwd_read(struct file * file, char __user *buffer, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static const struct file_operations cpwd_fops = { + .owner = THIS_MODULE, + .ioctl = cpwd_ioctl, + .compat_ioctl = cpwd_compat_ioctl, + .open = cpwd_open, + .write = cpwd_write, + .read = cpwd_read, + .release = cpwd_release, +}; + +static int __devinit cpwd_probe(struct of_device *op, + const struct of_device_id *match) +{ + struct device_node *options; + const char *str_prop; + const void *prop_val; + int i, err = -EINVAL; + struct cpwd *p; + + if (cpwd_device) + return -EINVAL; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + err = -ENOMEM; + if (!p) { + printk(KERN_ERR PFX "Unable to allocate struct cpwd.\n"); + goto out; + } + + p->irq = op->irqs[0]; + + spin_lock_init(&p->lock); + + p->regs = of_ioremap(&op->resource[0], 0, + 4 * WD_TIMER_REGSZ, DRIVER_NAME); + if (!p->regs) { + printk(KERN_ERR PFX "Unable to map registers.\n"); + goto out_free; + } + + options = of_find_node_by_path("/options"); + err = -ENODEV; + if (!options) { + printk(KERN_ERR PFX "Unable to find /options node.\n"); + goto out_iounmap; + } + + prop_val = of_get_property(options, "watchdog-enable?", NULL); + p->enabled = (prop_val ? true : false); + + prop_val = of_get_property(options, "watchdog-reboot?", NULL); + p->reboot = (prop_val ? true : false); + + str_prop = of_get_property(options, "watchdog-timeout", NULL); + if (str_prop) + p->timeout = simple_strtoul(str_prop, NULL, 10); + + /* CP1400s seem to have broken PLD implementations-- the + * interrupt_mask register cannot be written, so no timer + * interrupts can be masked within the PLD. + */ + str_prop = of_get_property(op->node, "model", NULL); + p->broken = (str_prop && !strcmp(str_prop, WD_BADMODEL)); + + if (!p->enabled) + cpwd_toggleintr(p, -1, WD_INTR_OFF); + + for (i = 0; i < WD_NUMDEVS; i++) { + static const char *cpwd_names[] = { "RIC", "XIR", "POR" }; + static int *parms[] = { &wd0_timeout, + &wd1_timeout, + &wd2_timeout }; + struct miscdevice *mp = &p->devs[i].misc; + + mp->minor = WD0_MINOR + i; + mp->name = cpwd_names[i]; + mp->fops = &cpwd_fops; + + p->devs[i].regs = p->regs + (i * WD_TIMER_REGSZ); + p->devs[i].intr_mask = (WD0_INTR_MASK << i); + p->devs[i].runstatus &= ~WD_STAT_BSTOP; + p->devs[i].runstatus |= WD_STAT_INIT; + p->devs[i].timeout = p->timeout; + if (*parms[i]) + p->devs[i].timeout = *parms[i]; + + err = misc_register(&p->devs[i].misc); + if (err) { + printk(KERN_ERR "Could not register misc device for " + "dev %d\n", i); + goto out_unregister; + } + } + + if (p->broken) { + init_timer(&cpwd_timer); + cpwd_timer.function = cpwd_brokentimer; + cpwd_timer.data = (unsigned long) p; + cpwd_timer.expires = WD_BTIMEOUT; + + printk(KERN_INFO PFX "PLD defect workaround enabled for " + "model " WD_BADMODEL ".\n"); + } + + dev_set_drvdata(&op->dev, p); + cpwd_device = p; + err = 0; + +out: + return err; + +out_unregister: + for (i--; i >= 0; i--) + misc_deregister(&p->devs[i].misc); + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ); + +out_free: + kfree(p); + goto out; +} + +static int __devexit cpwd_remove(struct of_device *op) +{ + struct cpwd *p = dev_get_drvdata(&op->dev); + int i; + + for (i = 0; i < 4; i++) { + misc_deregister(&p->devs[i].misc); + + if (!p->enabled) { + cpwd_stoptimer(p, i); + if (p->devs[i].runstatus & WD_STAT_BSTOP) + cpwd_resetbrokentimer(p, i); + } + } + + if (p->broken) + del_timer_sync(&cpwd_timer); + + if (p->initialized) + free_irq(p->irq, p); + + of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ); + kfree(p); + + cpwd_device = NULL; + + return 0; +} + +static const struct of_device_id cpwd_match[] = { + { + .name = "watchdog", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpwd_match); + +static struct of_platform_driver cpwd_driver = { + .name = DRIVER_NAME, + .match_table = cpwd_match, + .probe = cpwd_probe, + .remove = __devexit_p(cpwd_remove), +}; + +static int __init cpwd_init(void) +{ + return of_register_driver(&cpwd_driver, &of_bus_type); +} + +static void __exit cpwd_exit(void) +{ + of_unregister_driver(&cpwd_driver); +} + +module_init(cpwd_init); +module_exit(cpwd_exit); diff --git a/drivers/watchdog/geodewdt.c b/drivers/watchdog/geodewdt.c index 614a5c7017b6..6799a6de66fe 100644 --- a/drivers/watchdog/geodewdt.c +++ b/drivers/watchdog/geodewdt.c @@ -130,8 +130,8 @@ static ssize_t geodewdt_write(struct file *file, const char __user *data, return len; } -static int geodewdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long geodewdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; @@ -198,7 +198,7 @@ static const struct file_operations geodewdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = geodewdt_write, - .ioctl = geodewdt_ioctl, + .unlocked_ioctl = geodewdt_ioctl, .open = geodewdt_open, .release = geodewdt_release, }; diff --git a/drivers/watchdog/ibmasr.c b/drivers/watchdog/ibmasr.c index b82405cfb4cd..89fcefcc8510 100644 --- a/drivers/watchdog/ibmasr.c +++ b/drivers/watchdog/ibmasr.c @@ -85,7 +85,6 @@ static void __asr_toggle(void) outb(reg & ~asr_toggle_mask, asr_write_addr); reg = inb(asr_read_addr); - spin_unlock(&asr_lock); } static void asr_toggle(void) diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c new file mode 100644 index 000000000000..afb8af397a9f --- /dev/null +++ b/drivers/watchdog/it87_wdt.c @@ -0,0 +1,725 @@ +/* + * Watchdog Timer Driver + * for ITE IT87xx Environment Control - Low Pin Count Input / Output + * + * (c) Copyright 2007 Oliver Schuster <olivers137@aol.com> + * + * Based on softdog.c by Alan Cox, + * 83977f_wdt.c by Jose Goncalves, + * it87.c by Chris Gauthron, Jean Delvare + * + * Data-sheets: Publicly available at the ITE website + * http://www.ite.com.tw/ + * + * Support of the watchdog timers, which are available on + * IT8716, IT8718, IT8726 and IT8712 (J,K version). + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/moduleparam.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 <linux/uaccess.h> +#include <linux/io.h> + +#include <asm/system.h> + +#define WATCHDOG_VERSION "1.12" +#define WATCHDOG_NAME "IT87 WDT" +#define PFX WATCHDOG_NAME ": " +#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" +#define WD_MAGIC 'V' + +/* Defaults for Module Parameter */ +#define DEFAULT_NOGAMEPORT 0 +#define DEFAULT_EXCLUSIVE 1 +#define DEFAULT_TIMEOUT 60 +#define DEFAULT_TESTMODE 0 +#define DEFAULT_NOWAYOUT WATCHDOG_NOWAYOUT + +/* IO Ports */ +#define REG 0x2e +#define VAL 0x2f + +/* Logical device Numbers LDN */ +#define GPIO 0x07 +#define GAMEPORT 0x09 +#define CIR 0x0a + +/* Configuration Registers and Functions */ +#define LDNREG 0x07 +#define CHIPID 0x20 +#define CHIPREV 0x22 +#define ACTREG 0x30 +#define BASEREG 0x60 + +/* Chip Id numbers */ +#define NO_DEV_ID 0xffff +#define IT8705_ID 0x8705 +#define IT8712_ID 0x8712 +#define IT8716_ID 0x8716 +#define IT8718_ID 0x8718 +#define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */ + +/* GPIO Configuration Registers LDN=0x07 */ +#define WDTCTRL 0x71 +#define WDTCFG 0x72 +#define WDTVALLSB 0x73 +#define WDTVALMSB 0x74 + +/* GPIO Bits WDTCTRL */ +#define WDT_CIRINT 0x80 +#define WDT_MOUSEINT 0x40 +#define WDT_KYBINT 0x20 +#define WDT_GAMEPORT 0x10 /* not it8718 */ +#define WDT_FORCE 0x02 +#define WDT_ZERO 0x01 + +/* GPIO Bits WDTCFG */ +#define WDT_TOV1 0x80 +#define WDT_KRST 0x40 +#define WDT_TOVE 0x20 +#define WDT_PWROK 0x10 +#define WDT_INT_MASK 0x0f + +/* CIR Configuration Register LDN=0x0a */ +#define CIR_ILS 0x70 + +/* The default Base address is not always available, we use this */ +#define CIR_BASE 0x0208 + +/* CIR Controller */ +#define CIR_DR(b) (b) +#define CIR_IER(b) (b + 1) +#define CIR_RCR(b) (b + 2) +#define CIR_TCR1(b) (b + 3) +#define CIR_TCR2(b) (b + 4) +#define CIR_TSR(b) (b + 5) +#define CIR_RSR(b) (b + 6) +#define CIR_BDLR(b) (b + 5) +#define CIR_BDHR(b) (b + 6) +#define CIR_IIR(b) (b + 7) + +/* Default Base address of Game port */ +#define GP_BASE_DEFAULT 0x0201 + +/* wdt_status */ +#define WDTS_TIMER_RUN 0 +#define WDTS_DEV_OPEN 1 +#define WDTS_KEEPALIVE 2 +#define WDTS_LOCKED 3 +#define WDTS_USE_GP 4 +#define WDTS_EXPECTED 5 + +static unsigned int base, gpact, ciract; +static unsigned long wdt_status; +static DEFINE_SPINLOCK(spinlock); + +static int nogameport = DEFAULT_NOGAMEPORT; +static int exclusive = DEFAULT_EXCLUSIVE; +static int timeout = DEFAULT_TIMEOUT; +static int testmode = DEFAULT_TESTMODE; +static int nowayout = DEFAULT_NOWAYOUT; + +module_param(nogameport, int, 0); +MODULE_PARM_DESC(nogameport, "Forbid the activation of game port, default=" + __MODULE_STRING(DEFAULT_NOGAMEPORT)); +module_param(exclusive, int, 0); +MODULE_PARM_DESC(exclusive, "Watchdog exclusive device open, default=" + __MODULE_STRING(DEFAULT_EXCLUSIVE)); +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default=" + __MODULE_STRING(DEFAULT_TIMEOUT)); +module_param(testmode, int, 0); +MODULE_PARM_DESC(testmode, "Watchdog test mode (1 = no reboot), default=" + __MODULE_STRING(DEFAULT_TESTMODE)); +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started, default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT)); + +/* Superio Chip */ + +static inline void superio_enter(void) +{ + outb(0x87, REG); + outb(0x01, REG); + outb(0x55, REG); + outb(0x55, REG); +} + +static inline void superio_exit(void) +{ + outb(0x02, REG); + outb(0x02, VAL); +} + +static inline void superio_select(int ldn) +{ + outb(LDNREG, REG); + outb(ldn, VAL); +} + +static inline int superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static inline void superio_outb(int val, int reg) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int superio_inw(int reg) +{ + int val; + outb(reg++, REG); + val = inb(VAL) << 8; + outb(reg, REG); + val |= inb(VAL); + return val; +} + +static inline void superio_outw(int val, int reg) +{ + outb(reg++, REG); + outb(val >> 8, VAL); + outb(reg, REG); + outb(val, VAL); +} + +/* watchdog timer handling */ + +static void wdt_keepalive(void) +{ + if (test_bit(WDTS_USE_GP, &wdt_status)) + inb(base); + else + /* The timer reloads with around 5 msec delay */ + outb(0x55, CIR_DR(base)); + set_bit(WDTS_KEEPALIVE, &wdt_status); +} + +static void wdt_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + + superio_select(GPIO); + if (test_bit(WDTS_USE_GP, &wdt_status)) + superio_outb(WDT_GAMEPORT, WDTCTRL); + else + superio_outb(WDT_CIRINT, WDTCTRL); + if (!testmode) + superio_outb(WDT_TOV1 | WDT_KRST | WDT_PWROK, WDTCFG); + else + superio_outb(WDT_TOV1, WDTCFG); + superio_outb(timeout>>8, WDTVALMSB); + superio_outb(timeout, WDTVALLSB); + + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); +} + +static void wdt_stop(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + + superio_select(GPIO); + superio_outb(0x00, WDTCTRL); + superio_outb(WDT_TOV1, WDTCFG); + superio_outb(0x00, WDTVALMSB); + superio_outb(0x00, WDTVALLSB); + + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); +} + +/** + * wdt_set_timeout - set a new timeout value with watchdog ioctl + * @t: timeout value in seconds + * + * The hardware device has a 16 bit watchdog timer, thus the + * timeout time ranges between 1 and 65535 seconds. + * + * Used within WDIOC_SETTIMEOUT watchdog device ioctl. + */ + +static int wdt_set_timeout(int t) +{ + unsigned long flags; + + if (t < 1 || t > 65535) + return -EINVAL; + + timeout = t; + + spin_lock_irqsave(&spinlock, flags); + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + superio_enter(); + + superio_select(GPIO); + superio_outb(t>>8, WDTVALMSB); + superio_outb(t, WDTVALLSB); + + superio_exit(); + } + spin_unlock_irqrestore(&spinlock, flags); + return 0; +} + +/** + * wdt_get_status - determines the status supported by watchdog ioctl + * @status: status returned to user space + * + * The status bit of the device does not allow to distinguish + * between a regular system reset and a watchdog forced reset. + * But, in test mode it is useful, so it is supported through + * WDIOC_GETSTATUS watchdog ioctl. Additionally the driver + * reports the keepalive signal and the acception of the magic. + * + * Used within WDIOC_GETSTATUS watchdog device ioctl. + */ + +static int wdt_get_status(int *status) +{ + unsigned long flags; + + *status = 0; + if (testmode) { + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + superio_select(GPIO); + if (superio_inb(WDTCTRL) & WDT_ZERO) { + superio_outb(0x00, WDTCTRL); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + *status |= WDIOF_CARDRESET; + } + + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + } + if (test_and_clear_bit(WDTS_KEEPALIVE, &wdt_status)) + *status |= WDIOF_KEEPALIVEPING; + if (test_bit(WDTS_EXPECTED, &wdt_status)) + *status |= WDIOF_MAGICCLOSE; + return 0; +} + +/* /dev/watchdog handling */ + +/** + * wdt_open - watchdog file_operations .open + * @inode: inode of the device + * @file: file handle to the device + * + * The watchdog timer starts by opening the device. + * + * Used within the file operation of the watchdog device. + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + if (exclusive && test_and_set_bit(WDTS_DEV_OPEN, &wdt_status)) + return -EBUSY; + if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { + if (nowayout && !test_and_set_bit(WDTS_LOCKED, &wdt_status)) + __module_get(THIS_MODULE); + wdt_start(); + } + return nonseekable_open(inode, file); +} + +/** + * wdt_release - watchdog file_operations .release + * @inode: inode of the device + * @file: file handle to the device + * + * Closing the watchdog device either stops the watchdog timer + * or in the case, that nowayout is set or the magic character + * wasn't written, a critical warning about an running watchdog + * timer is given. + * + * Used within the file operation of the watchdog device. + */ + +static int wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + if (test_and_clear_bit(WDTS_EXPECTED, &wdt_status)) { + wdt_stop(); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + } else { + wdt_keepalive(); + printk(KERN_CRIT PFX + "unexpected close, not stopping watchdog!\n"); + } + } + clear_bit(WDTS_DEV_OPEN, &wdt_status); + return 0; +} + +/** + * wdt_write - watchdog file_operations .write + * @file: file handle to the watchdog + * @buf: buffer to write + * @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 don't define content meaning. + * + * Used within the file operation of the watchdog device. + */ + +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + clear_bit(WDTS_EXPECTED, &wdt_status); + wdt_keepalive(); + } + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character long ago */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == WD_MAGIC) + set_bit(WDTS_EXPECTED, &wdt_status); + } + } + return count; +} + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +/** + * wdt_ioctl - watchdog file_operations .unlocked_ioctl + * @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. + * + * Used within the file operation of the watchdog device. + */ + +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0, status, new_options, new_timeout; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + uarg.i = (int __user *)arg; + + switch (cmd) { + 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; + + switch (new_options) { + case WDIOS_DISABLECARD: + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) + wdt_stop(); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + return 0; + + case WDIOS_ENABLECARD: + if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) + wdt_start(); + return 0; + + default: + return -EFAULT; + } + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + rc = wdt_set_timeout(new_timeout); + case WDIOC_GETTIMEOUT: + if (put_user(timeout, uarg.i)) + return -EFAULT; + return rc; + + default: + return -ENOTTY; + } +} + +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 const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_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 it87_wdt_init(void) +{ + int rc = 0; + u16 chip_type; + u8 chip_rev; + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + chip_type = superio_inw(CHIPID); + chip_rev = superio_inb(CHIPREV) & 0x0f; + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + + switch (chip_type) { + case IT8716_ID: + case IT8718_ID: + case IT8726_ID: + break; + case IT8712_ID: + if (chip_rev > 7) + break; + case IT8705_ID: + printk(KERN_ERR PFX + "Unsupported Chip found, Chip %04x Revision %02x\n", + chip_type, chip_rev); + return -ENODEV; + case NO_DEV_ID: + printk(KERN_ERR PFX "no device\n"); + return -ENODEV; + default: + printk(KERN_ERR PFX + "Unknown Chip found, Chip %04x Revision %04x\n", + chip_type, chip_rev); + return -ENODEV; + } + + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + + superio_select(GPIO); + superio_outb(WDT_TOV1, WDTCFG); + superio_outb(0x00, WDTCTRL); + + /* First try to get Gameport support */ + if (chip_type != IT8718_ID && !nogameport) { + superio_select(GAMEPORT); + base = superio_inw(BASEREG); + if (!base) { + base = GP_BASE_DEFAULT; + superio_outw(base, BASEREG); + } + gpact = superio_inb(ACTREG); + superio_outb(0x01, ACTREG); + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + if (request_region(base, 1, WATCHDOG_NAME)) + set_bit(WDTS_USE_GP, &wdt_status); + else + rc = -EIO; + } else { + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + } + + /* If we haven't Gameport support, try to get CIR support */ + if (!test_bit(WDTS_USE_GP, &wdt_status)) { + if (!request_region(CIR_BASE, 8, WATCHDOG_NAME)) { + if (rc == -EIO) + printk(KERN_ERR PFX + "I/O Address 0x%04x and 0x%04x" + " already in use\n", base, CIR_BASE); + else + printk(KERN_ERR PFX + "I/O Address 0x%04x already in use\n", + CIR_BASE); + rc = -EIO; + goto err_out; + } + base = CIR_BASE; + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + + superio_select(CIR); + superio_outw(base, BASEREG); + superio_outb(0x00, CIR_ILS); + ciract = superio_inb(ACTREG); + superio_outb(0x01, ACTREG); + if (rc == -EIO) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } + + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + } + + if (timeout < 1 || timeout > 65535) { + timeout = DEFAULT_TIMEOUT; + printk(KERN_WARNING PFX + "Timeout value out of range, use default %d sec\n", + DEFAULT_TIMEOUT); + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + printk(KERN_ERR PFX + "Cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region; + } + + 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_reboot; + } + + /* Initialize CIR to use it as keepalive source */ + if (!test_bit(WDTS_USE_GP, &wdt_status)) { + outb(0x00, CIR_RCR(base)); + outb(0xc0, CIR_TCR1(base)); + outb(0x5c, CIR_TCR2(base)); + outb(0x10, CIR_IER(base)); + outb(0x00, CIR_BDHR(base)); + outb(0x01, CIR_BDLR(base)); + outb(0x09, CIR_IER(base)); + } + + printk(KERN_INFO PFX "Chip it%04x revision %d initialized. " + "timeout=%d sec (nowayout=%d testmode=%d exclusive=%d " + "nogameport=%d)\n", chip_type, chip_rev, timeout, + nowayout, testmode, exclusive, nogameport); + + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region: + release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); + if (!test_bit(WDTS_USE_GP, &wdt_status)) { + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + superio_select(CIR); + superio_outb(ciract, ACTREG); + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + } +err_out: + if (chip_type != IT8718_ID && !nogameport) { + spin_lock_irqsave(&spinlock, flags); + superio_enter(); + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + superio_exit(); + spin_unlock_irqrestore(&spinlock, flags); + } + + return rc; +} + +static void __exit it87_wdt_exit(void) +{ + unsigned long flags; + int nolock; + + nolock = !spin_trylock_irqsave(&spinlock, flags); + superio_enter(); + superio_select(GPIO); + superio_outb(0x00, WDTCTRL); + superio_outb(0x00, WDTCFG); + superio_outb(0x00, WDTVALMSB); + superio_outb(0x00, WDTVALLSB); + if (test_bit(WDTS_USE_GP, &wdt_status)) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } else { + superio_select(CIR); + superio_outb(ciract, ACTREG); + } + superio_exit(); + if (!nolock) + spin_unlock_irqrestore(&spinlock, flags); + + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); +} + +module_init(it87_wdt_init); +module_exit(it87_wdt_exit); + +MODULE_AUTHOR("Oliver Schuster"); +MODULE_DESCRIPTION("Hardware Watchdog Device Driver for IT87xx EC-LPC I/O"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c index 8302ef005be7..147b4d5c63b3 100644 --- a/drivers/watchdog/ixp4xx_wdt.c +++ b/drivers/watchdog/ixp4xx_wdt.c @@ -174,10 +174,8 @@ static struct miscdevice ixp4xx_wdt_miscdev = { static int __init ixp4xx_wdt_init(void) { int ret; - unsigned long processor_id; - asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :); - if (!(processor_id & 0xf) && !cpu_is_ixp46x()) { + if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { printk(KERN_ERR "IXP4XXX Watchdog: Rev. A0 IXP42x CPU detected" " - watchdog disabled\n"); diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index 3a11dadfd8e7..7bcbb7f4745f 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@ -1,7 +1,7 @@ /* - * linux/drivers/char/watchdog/omap_wdt.c + * omap_wdt.c * - * Watchdog driver for the TI OMAP 16xx & 24xx 32KHz (non-secure) watchdog + * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog * * Author: MontaVista Software, Inc. * <gdavis@mvista.com> or <source@mvista.com> @@ -47,50 +47,68 @@ #include "omap_wdt.h" +static struct platform_device *omap_wdt_dev; + static unsigned timer_margin; module_param(timer_margin, uint, 0); MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); -static int omap_wdt_users; -static struct clk *armwdt_ck; -static struct clk *mpu_wdt_ick; -static struct clk *mpu_wdt_fck; - static unsigned int wdt_trgr_pattern = 0x1234; static spinlock_t wdt_lock; -static void omap_wdt_ping(void) +struct omap_wdt_dev { + void __iomem *base; /* physical */ + struct device *dev; + int omap_wdt_users; + struct clk *armwdt_ck; + struct clk *mpu_wdt_ick; + struct clk *mpu_wdt_fck; + struct resource *mem; + struct miscdevice omap_wdt_miscdev; +}; + +static void omap_wdt_ping(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* wait for posted write to complete */ - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) cpu_relax(); + wdt_trgr_pattern = ~wdt_trgr_pattern; - omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR)); + __raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); + /* wait for posted write to complete */ - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) cpu_relax(); /* reloaded WCRR from WLDR */ } -static void omap_wdt_enable(void) +static void omap_wdt_enable(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* Sequence to enable the watchdog */ - omap_writel(0xBBBB, OMAP_WATCHDOG_SPR); - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) + __raw_writel(0xBBBB, base + OMAP_WATCHDOG_SPR); + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) cpu_relax(); - omap_writel(0x4444, OMAP_WATCHDOG_SPR); - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) + + __raw_writel(0x4444, base + OMAP_WATCHDOG_SPR); + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) cpu_relax(); } -static void omap_wdt_disable(void) +static void omap_wdt_disable(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* sequence required to disable watchdog */ - omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) + __raw_writel(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) cpu_relax(); - omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) + + __raw_writel(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) cpu_relax(); } @@ -103,83 +121,90 @@ static void omap_wdt_adjust_timeout(unsigned new_timeout) timer_margin = new_timeout; } -static void omap_wdt_set_timeout(void) +static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) { u32 pre_margin = GET_WLDR_VAL(timer_margin); + void __iomem *base = wdev->base; /* just count up at 32 KHz */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) cpu_relax(); - omap_writel(pre_margin, OMAP_WATCHDOG_LDR); - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) + + __raw_writel(pre_margin, base + OMAP_WATCHDOG_LDR); + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) cpu_relax(); } /* * Allow only one task to hold it open */ - static int omap_wdt_open(struct inode *inode, struct file *file) { - if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users)) + struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); + void __iomem *base = wdev->base; + + if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) return -EBUSY; if (cpu_is_omap16xx()) - clk_enable(armwdt_ck); /* Enable the clock */ + clk_enable(wdev->armwdt_ck); /* Enable the clock */ - if (cpu_is_omap24xx()) { - clk_enable(mpu_wdt_ick); /* Enable the interface clock */ - clk_enable(mpu_wdt_fck); /* Enable the functional clock */ + if (cpu_is_omap24xx() || cpu_is_omap34xx()) { + clk_enable(wdev->mpu_wdt_ick); /* Enable the interface clock */ + clk_enable(wdev->mpu_wdt_fck); /* Enable the functional clock */ } /* initialize prescaler */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) cpu_relax(); - omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL); - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) + + __raw_writel((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) cpu_relax(); - omap_wdt_set_timeout(); - omap_wdt_enable(); + file->private_data = (void *) wdev; + + omap_wdt_set_timeout(wdev); + omap_wdt_enable(wdev); + return nonseekable_open(inode, file); } static int omap_wdt_release(struct inode *inode, struct file *file) { + struct omap_wdt_dev *wdev = file->private_data; + /* * Shut off the timer unless NOWAYOUT is defined. */ #ifndef CONFIG_WATCHDOG_NOWAYOUT - omap_wdt_disable(); - if (cpu_is_omap16xx()) { - clk_disable(armwdt_ck); /* Disable the clock */ - clk_put(armwdt_ck); - armwdt_ck = NULL; - } + omap_wdt_disable(wdev); - if (cpu_is_omap24xx()) { - clk_disable(mpu_wdt_ick); /* Disable the clock */ - clk_disable(mpu_wdt_fck); /* Disable the clock */ - clk_put(mpu_wdt_ick); - clk_put(mpu_wdt_fck); - mpu_wdt_ick = NULL; - mpu_wdt_fck = NULL; + if (cpu_is_omap16xx()) + clk_disable(wdev->armwdt_ck); /* Disable the clock */ + + if (cpu_is_omap24xx() || cpu_is_omap34xx()) { + clk_disable(wdev->mpu_wdt_ick); /* Disable the clock */ + clk_disable(wdev->mpu_wdt_fck); /* Disable the clock */ } #else printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n"); #endif - omap_wdt_users = 0; + wdev->omap_wdt_users = 0; + return 0; } static ssize_t omap_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { + struct omap_wdt_dev *wdev = file->private_data; + /* Refresh LOAD_TIME. */ if (len) { spin_lock(&wdt_lock); - omap_wdt_ping(); + omap_wdt_ping(wdev); spin_unlock(&wdt_lock); } return len; @@ -188,6 +213,7 @@ static ssize_t omap_wdt_write(struct file *file, const char __user *data, static long omap_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct omap_wdt_dev *wdev; int new_margin; static const struct watchdog_info ident = { .identity = "OMAP Watchdog", @@ -195,6 +221,8 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, .firmware_version = 0, }; + wdev = file->private_data; + switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user((struct watchdog_info __user *)arg, &ident, @@ -203,14 +231,14 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, return put_user(0, (int __user *)arg); case WDIOC_GETBOOTSTATUS: if (cpu_is_omap16xx()) - return put_user(omap_readw(ARM_SYSST), + return put_user(__raw_readw(ARM_SYSST), (int __user *)arg); if (cpu_is_omap24xx()) return put_user(omap_prcm_get_reset_sources(), (int __user *)arg); case WDIOC_KEEPALIVE: spin_lock(&wdt_lock); - omap_wdt_ping(); + omap_wdt_ping(wdev); spin_unlock(&wdt_lock); return 0; case WDIOC_SETTIMEOUT: @@ -219,11 +247,11 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, omap_wdt_adjust_timeout(new_margin); spin_lock(&wdt_lock); - omap_wdt_disable(); - omap_wdt_set_timeout(); - omap_wdt_enable(); + omap_wdt_disable(wdev); + omap_wdt_set_timeout(wdev); + omap_wdt_enable(wdev); - omap_wdt_ping(); + omap_wdt_ping(wdev); spin_unlock(&wdt_lock); /* Fall */ case WDIOC_GETTIMEOUT: @@ -241,96 +269,173 @@ static const struct file_operations omap_wdt_fops = { .release = omap_wdt_release, }; -static struct miscdevice omap_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &omap_wdt_fops, -}; - static int __init omap_wdt_probe(struct platform_device *pdev) { struct resource *res, *mem; + struct omap_wdt_dev *wdev; int ret; /* reserve static register mappings */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENOENT; + if (!res) { + ret = -ENOENT; + goto err_get_resource; + } + + if (omap_wdt_dev) { + ret = -EBUSY; + goto err_busy; + } mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name); - if (mem == NULL) - return -EBUSY; + if (!mem) { + ret = -EBUSY; + goto err_busy; + } - platform_set_drvdata(pdev, mem); + wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); + if (!wdev) { + ret = -ENOMEM; + goto err_kzalloc; + } - omap_wdt_users = 0; + wdev->omap_wdt_users = 0; + wdev->mem = mem; if (cpu_is_omap16xx()) { - armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); - if (IS_ERR(armwdt_ck)) { - ret = PTR_ERR(armwdt_ck); - armwdt_ck = NULL; - goto fail; + wdev->armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); + if (IS_ERR(wdev->armwdt_ck)) { + ret = PTR_ERR(wdev->armwdt_ck); + wdev->armwdt_ck = NULL; + goto err_clk; } } if (cpu_is_omap24xx()) { - mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); - if (IS_ERR(mpu_wdt_ick)) { - ret = PTR_ERR(mpu_wdt_ick); - mpu_wdt_ick = NULL; - goto fail; + wdev->mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); + if (IS_ERR(wdev->mpu_wdt_ick)) { + ret = PTR_ERR(wdev->mpu_wdt_ick); + wdev->mpu_wdt_ick = NULL; + goto err_clk; } - mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); - if (IS_ERR(mpu_wdt_fck)) { - ret = PTR_ERR(mpu_wdt_fck); - mpu_wdt_fck = NULL; - goto fail; + wdev->mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); + if (IS_ERR(wdev->mpu_wdt_fck)) { + ret = PTR_ERR(wdev->mpu_wdt_fck); + wdev->mpu_wdt_fck = NULL; + goto err_clk; } } - omap_wdt_disable(); + if (cpu_is_omap34xx()) { + wdev->mpu_wdt_ick = clk_get(&pdev->dev, "wdt2_ick"); + if (IS_ERR(wdev->mpu_wdt_ick)) { + ret = PTR_ERR(wdev->mpu_wdt_ick); + wdev->mpu_wdt_ick = NULL; + goto err_clk; + } + wdev->mpu_wdt_fck = clk_get(&pdev->dev, "wdt2_fck"); + if (IS_ERR(wdev->mpu_wdt_fck)) { + ret = PTR_ERR(wdev->mpu_wdt_fck); + wdev->mpu_wdt_fck = NULL; + goto err_clk; + } + } + wdev->base = ioremap(res->start, res->end - res->start + 1); + if (!wdev->base) { + ret = -ENOMEM; + goto err_ioremap; + } + + platform_set_drvdata(pdev, wdev); + + omap_wdt_disable(wdev); omap_wdt_adjust_timeout(timer_margin); - omap_wdt_miscdev.parent = &pdev->dev; - ret = misc_register(&omap_wdt_miscdev); + wdev->omap_wdt_miscdev.parent = &pdev->dev; + wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR; + wdev->omap_wdt_miscdev.name = "watchdog"; + wdev->omap_wdt_miscdev.fops = &omap_wdt_fops; + + ret = misc_register(&(wdev->omap_wdt_miscdev)); if (ret) - goto fail; + goto err_misc; - pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin); + pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", + __raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, + timer_margin); /* autogate OCP interface clock */ - omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG); + __raw_writel(0x01, wdev->base + OMAP_WATCHDOG_SYS_CONFIG); + + omap_wdt_dev = pdev; + return 0; -fail: - if (armwdt_ck) - clk_put(armwdt_ck); - if (mpu_wdt_ick) - clk_put(mpu_wdt_ick); - if (mpu_wdt_fck) - clk_put(mpu_wdt_fck); - release_resource(mem); +err_misc: + platform_set_drvdata(pdev, NULL); + iounmap(wdev->base); + +err_ioremap: + wdev->base = NULL; + +err_clk: + if (wdev->armwdt_ck) + clk_put(wdev->armwdt_ck); + if (wdev->mpu_wdt_ick) + clk_put(wdev->mpu_wdt_ick); + if (wdev->mpu_wdt_fck) + clk_put(wdev->mpu_wdt_fck); + kfree(wdev); + +err_kzalloc: + release_mem_region(res->start, res->end - res->start + 1); + +err_busy: +err_get_resource: + return ret; } static void omap_wdt_shutdown(struct platform_device *pdev) { - omap_wdt_disable(); + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) + omap_wdt_disable(wdev); } static int omap_wdt_remove(struct platform_device *pdev) { - struct resource *mem = platform_get_drvdata(pdev); - misc_deregister(&omap_wdt_miscdev); - release_resource(mem); - if (armwdt_ck) - clk_put(armwdt_ck); - if (mpu_wdt_ick) - clk_put(mpu_wdt_ick); - if (mpu_wdt_fck) - clk_put(mpu_wdt_fck); + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) + return -ENOENT; + + misc_deregister(&(wdev->omap_wdt_miscdev)); + release_mem_region(res->start, res->end - res->start + 1); + platform_set_drvdata(pdev, NULL); + + if (wdev->armwdt_ck) { + clk_put(wdev->armwdt_ck); + wdev->armwdt_ck = NULL; + } + + if (wdev->mpu_wdt_ick) { + clk_put(wdev->mpu_wdt_ick); + wdev->mpu_wdt_ick = NULL; + } + + if (wdev->mpu_wdt_fck) { + clk_put(wdev->mpu_wdt_fck); + wdev->mpu_wdt_fck = NULL; + } + iounmap(wdev->base); + + kfree(wdev); + omap_wdt_dev = NULL; + return 0; } @@ -344,17 +449,23 @@ static int omap_wdt_remove(struct platform_device *pdev) static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) { - if (omap_wdt_users) - omap_wdt_disable(); + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) + omap_wdt_disable(wdev); + return 0; } static int omap_wdt_resume(struct platform_device *pdev) { - if (omap_wdt_users) { - omap_wdt_enable(); - omap_wdt_ping(); + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) { + omap_wdt_enable(wdev); + omap_wdt_ping(wdev); } + return 0; } diff --git a/drivers/watchdog/omap_wdt.h b/drivers/watchdog/omap_wdt.h index 52a532a5114a..fc02ec6a0386 100644 --- a/drivers/watchdog/omap_wdt.h +++ b/drivers/watchdog/omap_wdt.h @@ -30,25 +30,15 @@ #ifndef _OMAP_WATCHDOG_H #define _OMAP_WATCHDOG_H -#define OMAP1610_WATCHDOG_BASE 0xfffeb000 -#define OMAP2420_WATCHDOG_BASE 0x48022000 /*WDT Timer 2 */ - -#ifdef CONFIG_ARCH_OMAP24XX -#define OMAP_WATCHDOG_BASE OMAP2420_WATCHDOG_BASE -#else -#define OMAP_WATCHDOG_BASE OMAP1610_WATCHDOG_BASE -#define RM_RSTST_WKUP 0 -#endif - -#define OMAP_WATCHDOG_REV (OMAP_WATCHDOG_BASE + 0x00) -#define OMAP_WATCHDOG_SYS_CONFIG (OMAP_WATCHDOG_BASE + 0x10) -#define OMAP_WATCHDOG_STATUS (OMAP_WATCHDOG_BASE + 0x14) -#define OMAP_WATCHDOG_CNTRL (OMAP_WATCHDOG_BASE + 0x24) -#define OMAP_WATCHDOG_CRR (OMAP_WATCHDOG_BASE + 0x28) -#define OMAP_WATCHDOG_LDR (OMAP_WATCHDOG_BASE + 0x2c) -#define OMAP_WATCHDOG_TGR (OMAP_WATCHDOG_BASE + 0x30) -#define OMAP_WATCHDOG_WPS (OMAP_WATCHDOG_BASE + 0x34) -#define OMAP_WATCHDOG_SPR (OMAP_WATCHDOG_BASE + 0x48) +#define OMAP_WATCHDOG_REV (0x00) +#define OMAP_WATCHDOG_SYS_CONFIG (0x10) +#define OMAP_WATCHDOG_STATUS (0x14) +#define OMAP_WATCHDOG_CNTRL (0x24) +#define OMAP_WATCHDOG_CRR (0x28) +#define OMAP_WATCHDOG_LDR (0x2c) +#define OMAP_WATCHDOG_TGR (0x30) +#define OMAP_WATCHDOG_WPS (0x34) +#define OMAP_WATCHDOG_SPR (0x48) /* Using the prescaler, the OMAP watchdog could go for many * months before firing. These limits work without scaling, diff --git a/drivers/watchdog/orion5x_wdt.c b/drivers/watchdog/orion5x_wdt.c new file mode 100644 index 000000000000..14a339f58b6a --- /dev/null +++ b/drivers/watchdog/orion5x_wdt.c @@ -0,0 +1,245 @@ +/* + * drivers/watchdog/orion5x_wdt.c + * + * Watchdog driver for Orion5x processors + * + * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/spinlock.h> + +/* + * Watchdog timer block registers. + */ +#define TIMER_CTRL (TIMER_VIRT_BASE + 0x0000) +#define WDT_EN 0x0010 +#define WDT_VAL (TIMER_VIRT_BASE + 0x0024) + +#define WDT_MAX_DURATION (0xffffffff / ORION5X_TCLK) +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static int nowayout = WATCHDOG_NOWAYOUT; +static int heartbeat = WDT_MAX_DURATION; /* (seconds) */ +static unsigned long wdt_status; +static spinlock_t wdt_lock; + +static void wdt_enable(void) +{ + u32 reg; + + spin_lock(&wdt_lock); + + /* Set watchdog duration */ + writel(ORION5X_TCLK * heartbeat, WDT_VAL); + + /* Clear watchdog timer interrupt */ + reg = readl(BRIDGE_CAUSE); + reg &= ~WDT_INT_REQ; + writel(reg, BRIDGE_CAUSE); + + /* Enable watchdog timer */ + reg = readl(TIMER_CTRL); + reg |= WDT_EN; + writel(reg, TIMER_CTRL); + + /* Enable reset on watchdog */ + reg = readl(CPU_RESET_MASK); + reg |= WDT_RESET; + writel(reg, CPU_RESET_MASK); + + spin_unlock(&wdt_lock); +} + +static void wdt_disable(void) +{ + u32 reg; + + spin_lock(&wdt_lock); + + /* Disable reset on watchdog */ + reg = readl(CPU_RESET_MASK); + reg &= ~WDT_RESET; + writel(reg, CPU_RESET_MASK); + + /* Disable watchdog timer */ + reg = readl(TIMER_CTRL); + reg &= ~WDT_EN; + writel(reg, TIMER_CTRL); + + spin_unlock(&wdt_lock); +} + +static int orion5x_wdt_get_timeleft(int *time_left) +{ + spin_lock(&wdt_lock); + *time_left = readl(WDT_VAL) / ORION5X_TCLK; + spin_unlock(&wdt_lock); + return 0; +} + +static int orion5x_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) + return -EBUSY; + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + wdt_enable(); + return nonseekable_open(inode, file); +} + +static ssize_t orion5x_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + set_bit(WDT_OK_TO_CLOSE, &wdt_status); + } + } + wdt_enable(); + } + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "Orion5x Watchdog", +}; + + +static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + int time; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time <= 0 || time > WDT_MAX_DURATION) { + ret = -EINVAL; + break; + } + heartbeat = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + + case WDIOC_GETTIMELEFT: + if (orion5x_wdt_get_timeleft(&time)) { + ret = -EINVAL; + break; + } + ret = put_user(time, (int *)arg); + break; + } + return ret; +} + +static int orion5x_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) + wdt_disable(); + else + printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " + "timer will not stop\n"); + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static const struct file_operations orion5x_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = orion5x_wdt_write, + .unlocked_ioctl = orion5x_wdt_ioctl, + .open = orion5x_wdt_open, + .release = orion5x_wdt_release, +}; + +static struct miscdevice orion5x_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &orion5x_wdt_fops, +}; + +static int __init orion5x_wdt_init(void) +{ + int ret; + + spin_lock_init(&wdt_lock); + + ret = misc_register(&orion5x_wdt_miscdev); + if (ret == 0) + printk("Orion5x Watchdog Timer: heartbeat %d sec\n", + heartbeat); + + return ret; +} + +static void __exit orion5x_wdt_exit(void) +{ + misc_deregister(&orion5x_wdt_miscdev); +} + +module_init(orion5x_wdt_init); +module_exit(orion5x_wdt_exit); + +MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); +MODULE_DESCRIPTION("Orion5x Processor Watchdog"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default is " + __MODULE_STRING(WDT_MAX_DURATION) ")"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index 0ed84162437b..6d9f3d4a9987 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -173,8 +173,8 @@ static const struct watchdog_info ident = { .identity = "PNX4008 Watchdog", }; -static long pnx4008_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long pnx4008_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int ret = -ENOTTY; int time; diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c index 6756bcb009ed..c9c73b69c5e5 100644 --- a/drivers/watchdog/rc32434_wdt.c +++ b/drivers/watchdog/rc32434_wdt.c @@ -182,8 +182,8 @@ static ssize_t rc32434_wdt_write(struct file *file, const char *data, return 0; } -static int rc32434_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { void __user *argp = (void __user *)arg; int new_timeout; @@ -242,7 +242,7 @@ static struct file_operations rc32434_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = rc32434_wdt_write, - .ioctl = rc32434_wdt_ioctl, + .unlocked_ioctl = rc32434_wdt_ioctl, .open = rc32434_wdt_open, .release = rc32434_wdt_release, }; diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c index 9108efa73e7d..bf92802f2bbe 100644 --- a/drivers/watchdog/rdc321x_wdt.c +++ b/drivers/watchdog/rdc321x_wdt.c @@ -144,8 +144,8 @@ static int rdc321x_wdt_release(struct inode *inode, struct file *file) return 0; } -static int rdc321x_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { void __user *argp = (void __user *)arg; unsigned int value; @@ -204,7 +204,7 @@ static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf, static const struct file_operations rdc321x_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = rdc321x_wdt_ioctl, + .unlocked_ioctl = rdc321x_wdt_ioctl, .open = rdc321x_wdt_open, .write = rdc321x_wdt_write, .release = rdc321x_wdt_release, diff --git a/drivers/watchdog/riowd.c b/drivers/watchdog/riowd.c new file mode 100644 index 000000000000..09cb1833ea27 --- /dev/null +++ b/drivers/watchdog/riowd.c @@ -0,0 +1,259 @@ +/* riowd.c - driver for hw watchdog inside Super I/O of RIO + * + * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/smp_lock.h> +#include <linux/watchdog.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + + +/* RIO uses the NatSemi Super I/O power management logical device + * as its' watchdog. + * + * When the watchdog triggers, it asserts a line to the BBC (Boot Bus + * Controller) of the machine. The BBC can only be configured to + * trigger a power-on reset when the signal is asserted. The BBC + * can be configured to ignore the signal entirely as well. + * + * The only Super I/O device register we care about is at index + * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255). + * If set to zero, this disables the watchdog. When set, the system + * must periodically (before watchdog expires) clear (set to zero) and + * re-set the watchdog else it will trigger. + * + * There are two other indexed watchdog registers inside this Super I/O + * logical device, but they are unused. The first, at index 0x06 is + * the watchdog control and can be used to make the watchdog timer re-set + * when the PS/2 mouse or serial lines show activity. The second, at + * index 0x07 is merely a sampling of the line from the watchdog to the + * BBC. + * + * The watchdog device generates no interrupts. + */ + +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO"); +MODULE_SUPPORTED_DEVICE("watchdog"); +MODULE_LICENSE("GPL"); + +#define DRIVER_NAME "riowd" +#define PFX DRIVER_NAME ": " + +struct riowd { + void __iomem *regs; + spinlock_t lock; +}; + +static struct riowd *riowd_device; + +#define WDTO_INDEX 0x05 + +static int riowd_timeout = 1; /* in minutes */ +module_param(riowd_timeout, int, 0); +MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes"); + +static void riowd_writereg(struct riowd *p, u8 val, int index) +{ + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + writeb(index, p->regs + 0); + writeb(val, p->regs + 1); + spin_unlock_irqrestore(&p->lock, flags); +} + +static int riowd_open(struct inode *inode, struct file *filp) +{ + cycle_kernel_lock(); + nonseekable_open(inode, filp); + return 0; +} + +static int riowd_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +static int riowd_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + static struct watchdog_info info = { + .options = WDIOF_SETTIMEOUT, + .firmware_version = 1, + .identity = DRIVER_NAME, + }; + void __user *argp = (void __user *)arg; + struct riowd *p = riowd_device; + unsigned int options; + int new_margin; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(0, (int __user *)argp)) + return -EFAULT; + break; + + case WDIOC_KEEPALIVE: + riowd_writereg(p, riowd_timeout, WDTO_INDEX); + break; + + case WDIOC_SETOPTIONS: + if (copy_from_user(&options, argp, sizeof(options))) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) + riowd_writereg(p, 0, WDTO_INDEX); + else if (options & WDIOS_ENABLECARD) + riowd_writereg(p, riowd_timeout, WDTO_INDEX); + else + return -EINVAL; + + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int __user *)argp)) + return -EFAULT; + if ((new_margin < 60) || (new_margin > (255 * 60))) + return -EINVAL; + riowd_timeout = (new_margin + 59) / 60; + riowd_writereg(p, riowd_timeout, WDTO_INDEX); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(riowd_timeout * 60, (int __user *)argp); + + default: + return -EINVAL; + }; + + return 0; +} + +static ssize_t riowd_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + struct riowd *p = riowd_device; + + if (count) { + riowd_writereg(p, riowd_timeout, WDTO_INDEX); + return 1; + } + + return 0; +} + +static const struct file_operations riowd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = riowd_ioctl, + .open = riowd_open, + .write = riowd_write, + .release = riowd_release, +}; + +static struct miscdevice riowd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &riowd_fops +}; + +static int __devinit riowd_probe(struct of_device *op, + const struct of_device_id *match) +{ + struct riowd *p; + int err = -EINVAL; + + if (riowd_device) + goto out; + + err = -ENOMEM; + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + goto out; + + spin_lock_init(&p->lock); + + p->regs = of_ioremap(&op->resource[0], 0, 2, DRIVER_NAME); + if (!p->regs) { + printk(KERN_ERR PFX "Cannot map registers.\n"); + goto out_free; + } + + err = misc_register(&riowd_miscdev); + if (err) { + printk(KERN_ERR PFX "Cannot register watchdog misc device.\n"); + goto out_iounmap; + } + + printk(KERN_INFO PFX "Hardware watchdog [%i minutes], " + "regs at %p\n", riowd_timeout, p->regs); + + dev_set_drvdata(&op->dev, p); + riowd_device = p; + err = 0; + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, 2); + +out_free: + kfree(p); + +out: + return err; +} + +static int __devexit riowd_remove(struct of_device *op) +{ + struct riowd *p = dev_get_drvdata(&op->dev); + + misc_deregister(&riowd_miscdev); + of_iounmap(&op->resource[0], p->regs, 2); + kfree(p); + + return 0; +} + +static const struct of_device_id riowd_match[] = { + { + .name = "pmc", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, riowd_match); + +static struct of_platform_driver riowd_driver = { + .name = DRIVER_NAME, + .match_table = riowd_match, + .probe = riowd_probe, + .remove = __devexit_p(riowd_remove), +}; + +static int __init riowd_init(void) +{ + return of_register_driver(&riowd_driver, &of_bus_type); +} + +static void __exit riowd_exit(void) +{ + of_unregister_driver(&riowd_driver); +} + +module_init(riowd_init); +module_exit(riowd_exit); diff --git a/drivers/watchdog/w83697ug_wdt.c b/drivers/watchdog/w83697ug_wdt.c new file mode 100644 index 000000000000..c73b5e2919c6 --- /dev/null +++ b/drivers/watchdog/w83697ug_wdt.c @@ -0,0 +1,392 @@ +/* + * w83697ug/uf WDT driver + * + * (c) Copyright 2008 Flemming Fransen <ff@nrvissing.net> + * reused original code to supoprt w83697ug/uf. + * + * Based on w83627hf_wdt.c which is based on advantechwdt.c + * which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> + * added support for W83627THF. + * + * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * (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> + */ + +#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 <linux/spinlock.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#include <asm/system.h> + +#define WATCHDOG_NAME "w83697ug/uf WDT" +#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +static unsigned long wdt_is_open; +static char expect_close; +static DEFINE_SPINLOCK(io_lock); + +static int wdt_io = 0x2e; +module_param(wdt_io, int, 0); +MODULE_PARM_DESC(wdt_io, "w83697ug/uf WDT io port (default 0x2e)"); + +static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1<= timeout <=255 (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +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) ")"); + +/* + * Kernel methods. + */ + +#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ +#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register + (same as EFER) */ +#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ + +static void w83697ug_select_wd_register(void) +{ + unsigned char c; + unsigned char version; + + outb_p(0x87, WDT_EFER); /* Enter extended function mode */ + outb_p(0x87, WDT_EFER); /* Again according to manual */ + + outb(0x20, WDT_EFER); /* check chip version */ + version = inb(WDT_EFDR); + + if (version == 0x68) { /* W83697UG */ + printk(KERN_INFO PFX "Watchdog chip version 0x%02x = " + "W83697UG/UF found at 0x%04x\n", version, wdt_io); + + outb_p(0x2b, WDT_EFER); + c = inb_p(WDT_EFDR); /* select WDT0 */ + c &= ~0x04; + outb_p(0x2b, WDT_EFER); + outb_p(c, WDT_EFDR); /* set pin118 to WDT0 */ + + } else { + printk(KERN_ERR PFX "No W83697UG/UF could be found\n"); + return -EIO; + } + + outb_p(0x07, WDT_EFER); /* point to logical device number reg */ + outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ + outb_p(0x30, WDT_EFER); /* select CR30 */ + c = inb_p(WDT_EFDR); + outb_p(c || 0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ +} + +static void w83697ug_unselect_wd_register(void) +{ + outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ +} + +static void w83697ug_init(void) +{ + unsigned char t; + + w83697ug_select_wd_register(); + + outb_p(0xF6, WDT_EFER); /* Select CRF6 */ + t = inb_p(WDT_EFDR); /* read CRF6 */ + if (t != 0) { + printk(KERN_INFO PFX "Watchdog already running." + " Resetting timeout to %d sec\n", timeout); + outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ + } + outb_p(0xF5, WDT_EFER); /* Select CRF5 */ + t = inb_p(WDT_EFDR); /* read CRF5 */ + t &= ~0x0C; /* set second mode & + disable keyboard turning off watchdog */ + outb_p(t, WDT_EFDR); /* Write back to CRF5 */ + + w83697ug_unselect_wd_register(); +} + +static void wdt_ctrl(int timeout) +{ + spin_lock(&io_lock); + + w83697ug_select_wd_register(); + + outb_p(0xF4, WDT_EFER); /* Select CRF4 */ + outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF4 */ + + w83697ug_unselect_wd_register(); + + spin_unlock(&io_lock); +} + +static int wdt_ping(void) +{ + wdt_ctrl(timeout); + return 0; +} + +static int wdt_disable(void) +{ + wdt_ctrl(0); + return 0; +} + +static int wdt_set_heartbeat(int t) +{ + if (t < 1 || t > 255) + return -EINVAL; + + timeout = t; + return 0; +} + +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; + + 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; +} + +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_timeout; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "W83697UG WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wdt_disable(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wdt_ping(); + retval = 0; + } + + return retval; + } + + case WDIOC_KEEPALIVE: + wdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if (wdt_set_heartbeat(new_timeout)) + return -EINVAL; + wdt_ping(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + return 0; +} + +static int wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + + wdt_ping(); + return nonseekable_open(inode, file); +} + +static int wdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + wdt_disable(); + else { + printk(KERN_CRIT PFX + "Unexpected close, not stopping watchdog!\n"); + wdt_ping(); + } + expect_close = 0; + clear_bit(0, &wdt_is_open); + return 0; +} + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_disable(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_close, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static int __init wdt_init(void) +{ + int ret; + + printk(KERN_INFO "WDT driver for the Winbond(TM) W83697UG/UF Super I/O chip initialising.\n"); + + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + printk(KERN_INFO PFX + "timeout value must be 1<=timeout<=255, using %d\n", + WATCHDOG_TIMEOUT); + } + + if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_io); + ret = -EIO; + goto out; + } + + w83697ug_init(); + + ret = register_reboot_notifier(&wdt_notifier); + if (ret != 0) { + printk(KERN_ERR PFX + "cannot register reboot notifier (err=%d)\n", ret); + goto unreg_regions; + } + + ret = misc_register(&wdt_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX + "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&wdt_notifier); +unreg_regions: + release_region(wdt_io, 1); + goto out; +} + +static void __exit wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(wdt_io, 1); +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Flemming Frandsen <ff@nrvissing.net>"); +MODULE_DESCRIPTION("w83697ug/uf WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c index db362c34958b..191ea6302107 100644 --- a/drivers/watchdog/wdt285.c +++ b/drivers/watchdog/wdt285.c @@ -115,8 +115,8 @@ static int watchdog_release(struct inode *inode, struct file *file) return 0; } -static ssize_t watchdog_write(struct file *file, const char *data, - size_t len, loff_t *ppos) +static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) { /* * Refresh the timer. @@ -133,21 +133,22 @@ static const struct watchdog_info ident = { }; static long watchdog_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) + unsigned long arg) { unsigned int new_margin; + int __user *int_arg = (int __user *)arg; int ret = -ENOTTY; switch (cmd) { case WDIOC_GETSUPPORT: ret = 0; - if (copy_to_user((void *)arg, &ident, sizeof(ident))) + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) ret = -EFAULT; break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: - ret = put_user(0, (int *)arg); + ret = put_user(0, int_arg); break; case WDIOC_KEEPALIVE: @@ -156,7 +157,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, break; case WDIOC_SETTIMEOUT: - ret = get_user(new_margin, (int *)arg); + ret = get_user(new_margin, int_arg); if (ret) break; @@ -171,7 +172,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, watchdog_ping(); /* Fall */ case WDIOC_GETTIMEOUT: - ret = put_user(soft_margin, (int *)arg); + ret = put_user(soft_margin, int_arg); break; } return ret; |