diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-23 10:41:17 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-06-23 10:41:17 -0700 |
commit | cf9c1b92ae62ce71a8e861f02476724348defc6a (patch) | |
tree | 433d77497f909eea2c35b7afff24de9f86212221 /drivers | |
parent | 135aae340d66c5e273af297bbd5178a5e5c458ee (diff) | |
parent | 9b901ee0cb007eb4e2ee056e5b1c5c2837d53bdb (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog
* git://git.kernel.org/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog:
[WATCHDOG] wdt_pci.c: remove #ifdef CONFIG_WDT_501_PCI
[WATCHDOG] hpwdt: Add NMI priority option
[WATCHDOG] OMAP fixes: enable clock in probe, trigger timer reload
[WATCHDOG] add bcm47xx watchdog driver
[WATCHDOG] Freescale STMP: watchdog driver
[WATCHDOG] twl4030 watchdog driver
[WATCHDOG] U300 COH 901 327 watchdog driver
[WATCHDOG] Add pnx833x_wdt
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/twl4030-core.c | 12 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 59 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 5 | ||||
-rw-r--r-- | drivers/watchdog/bcm47xx_wdt.c | 286 | ||||
-rw-r--r-- | drivers/watchdog/coh901327_wdt.c | 537 | ||||
-rw-r--r-- | drivers/watchdog/hpwdt.c | 26 | ||||
-rw-r--r-- | drivers/watchdog/omap_wdt.c | 7 | ||||
-rw-r--r-- | drivers/watchdog/pnx833x_wdt.c | 282 | ||||
-rw-r--r-- | drivers/watchdog/stmp3xxx_wdt.c | 296 | ||||
-rw-r--r-- | drivers/watchdog/twl4030_wdt.c | 272 | ||||
-rw-r--r-- | drivers/watchdog/wdt_pci.c | 122 |
11 files changed, 1825 insertions, 79 deletions
diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index cd1008c19cd..ca54996ffd0 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -101,6 +101,12 @@ #define twl_has_usb() false #endif +#if defined(CONFIG_TWL4030_WATCHDOG) || \ + defined(CONFIG_TWL4030_WATCHDOG_MODULE) +#define twl_has_watchdog() true +#else +#define twl_has_watchdog() false +#endif /* Triton Core internal information (BEGIN) */ @@ -526,6 +532,12 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) usb_transceiver = child; } + if (twl_has_watchdog()) { + child = add_child(0, "twl4030_wdt", NULL, 0, false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + if (twl_has_regulator()) { /* child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b166f2852a6..b1ccc04f3c9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -240,6 +240,32 @@ config ORION_WATCHDOG To compile this driver as a module, choose M here: the module will be called orion_wdt. +config COH901327_WATCHDOG + bool "ST-Ericsson COH 901 327 watchdog" + depends on ARCH_U300 + default y if MACH_U300 + help + Say Y here to include Watchdog timer support for the + watchdog embedded into the ST-Ericsson U300 series platforms. + This watchdog is used to reset the system and thus cannot be + compiled as a module. + +config TWL4030_WATCHDOG + tristate "TWL4030 Watchdog" + depends on TWL4030_CORE + help + Support for TI TWL4030 watchdog. Say 'Y' here to enable the + watchdog timer support for TWL4030 chips. + +config STMP3XXX_WATCHDOG + tristate "Freescale STMP3XXX watchdog" + depends on ARCH_STMP3XXX + help + Say Y here if to include support for the watchdog timer + for the Sigmatel STMP37XX/378X SoC. + To compile this driver as a module, choose M here: the + module will be called stmp3xxx_wdt. + # AVR32 Architecture config AT32AP700X_WDT @@ -703,6 +729,12 @@ config SBC_EPX_C3_WATCHDOG # MIPS Architecture +config BCM47XX_WDT + tristate "Broadcom BCM47xx Watchdog Timer" + depends on BCM47XX + help + Hardware driver for the Broadcom BCM47xx Watchog Timer. + config RC32434_WDT tristate "IDT RC32434 SoC Watchdog Timer" depends on MIKROTIK_RB532 @@ -729,6 +761,15 @@ config WDT_MTX1 Hardware driver for the MTX-1 boards. This is a watchdog timer that will reboot the machine after a 100 seconds timer expired. +config PNX833X_WDT + tristate "PNX833x Hardware Watchdog" + depends on SOC_PNX8335 + help + Hardware driver for the PNX833x's watchdog. This is a + watchdog timer that will reboot the machine after a programable + timer has expired and no process has written to /dev/watchdog during + that time. + config WDT_RM9K_GPI tristate "RM9000/GPI hardware watchdog" depends on CPU_RM9000 @@ -966,24 +1007,16 @@ config WDTPCI ---help--- If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. - To compile this driver as a module, choose M here: the - module will be called wdt_pci. - -config WDT_501_PCI - bool "PCI-WDT501 features" - depends on WDTPCI - help - Saying Y here and creating a character special file /dev/temperature - with major number 10 and minor number 131 ("man mknod") will give - you a thermometer inside your computer: reading from - /dev/temperature yields one byte, the temperature in degrees - Fahrenheit. This works only if you have a PCI-WDT501 watchdog board - installed. + If you have a PCI-WDT501 watchdog board then you can enable the + temperature sensor by setting the type parameter to 501. If you want to enable the Fan Tachometer on the PCI-WDT501, then you can do this via the tachometer parameter. Only do this if you have a fan tachometer actually set up. + To compile this driver as a module, choose M here: the + module will be called wdt_pci. + # # USB-based Watchdog Cards # diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c3afa14d5be..3d774294a2b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o +obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o obj-$(CONFIG_21285_WATCHDOG) += wdt285.o obj-$(CONFIG_977_WATCHDOG) += wdt977.o obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o @@ -41,6 +42,8 @@ obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o +obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o +obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o @@ -98,9 +101,11 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o # M68KNOMMU Architecture # MIPS Architecture +obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o obj-$(CONFIG_INDYDOG) += indydog.o obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o +obj-$(CONFIG_PNX833X_WDT) += pnx833x_wdt.o obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o obj-$(CONFIG_AR7_WDT) += ar7_wdt.o diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c new file mode 100644 index 00000000000..5c7011cda6a --- /dev/null +++ b/drivers/watchdog/bcm47xx_wdt.c @@ -0,0 +1,286 @@ +/* + * Watchdog driver for Broadcom BCM47XX + * + * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs> + * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.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. + */ + +#include <linux/bitops.h> +#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/reboot.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/ssb/ssb_embedded.h> +#include <asm/mach-bcm47xx/bcm47xx.h> + +#define DRV_NAME "bcm47xx_wdt" + +#define WDT_DEFAULT_TIME 30 /* seconds */ +#define WDT_MAX_TIME 255 /* seconds */ + +static int wdt_time = WDT_DEFAULT_TIME; +static int nowayout = WATCHDOG_NOWAYOUT; + +module_param(wdt_time, int, 0); +MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" + __MODULE_STRING(WDT_DEFAULT_TIME) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif + +static unsigned long bcm47xx_wdt_busy; +static char expect_release; +static struct timer_list wdt_timer; +static atomic_t ticks; + +static inline void bcm47xx_wdt_hw_start(void) +{ + /* this is 2,5s on 100Mhz clock and 2s on 133 Mhz */ + ssb_watchdog_timer_set(&ssb_bcm47xx, 0xfffffff); +} + +static inline int bcm47xx_wdt_hw_stop(void) +{ + return ssb_watchdog_timer_set(&ssb_bcm47xx, 0); +} + +static void bcm47xx_timer_tick(unsigned long unused) +{ + if (!atomic_dec_and_test(&ticks)) { + bcm47xx_wdt_hw_start(); + mod_timer(&wdt_timer, jiffies + HZ); + } else { + printk(KERN_CRIT DRV_NAME "Watchdog will fire soon!!!\n"); + } +} + +static inline void bcm47xx_wdt_pet(void) +{ + atomic_set(&ticks, wdt_time); +} + +static void bcm47xx_wdt_start(void) +{ + bcm47xx_wdt_pet(); + bcm47xx_timer_tick(0); +} + +static void bcm47xx_wdt_pause(void) +{ + del_timer_sync(&wdt_timer); + bcm47xx_wdt_hw_stop(); +} + +static void bcm47xx_wdt_stop(void) +{ + bcm47xx_wdt_pause(); +} + +static int bcm47xx_wdt_settimeout(int new_time) +{ + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) + return -EINVAL; + + wdt_time = new_time; + return 0; +} + +static int bcm47xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &bcm47xx_wdt_busy)) + return -EBUSY; + + bcm47xx_wdt_start(); + return nonseekable_open(inode, file); +} + +static int bcm47xx_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_release == 42) { + bcm47xx_wdt_stop(); + } else { + printk(KERN_CRIT DRV_NAME + ": Unexpected close, not stopping watchdog!\n"); + bcm47xx_wdt_start(); + } + + clear_bit(0, &bcm47xx_wdt_busy); + expect_release = 0; + return 0; +} + +static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + expect_release = 0; + + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + bcm47xx_wdt_pet(); + } + return len; +} + +static struct watchdog_info bcm47xx_wdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +static long bcm47xx_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value, retval = -EINVAL;; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &bcm47xx_wdt_info, + sizeof(bcm47xx_wdt_info)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + if (get_user(new_value, p)) + return -EFAULT; + + if (new_value & WDIOS_DISABLECARD) { + bcm47xx_wdt_stop(); + retval = 0; + } + + if (new_value & WDIOS_ENABLECARD) { + bcm47xx_wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + bcm47xx_wdt_pet(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + if (bcm47xx_wdt_settimeout(new_value)) + return -EINVAL; + + bcm47xx_wdt_pet(); + + case WDIOC_GETTIMEOUT: + return put_user(wdt_time, p); + + default: + return -ENOTTY; + } +} + +static int bcm47xx_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + bcm47xx_wdt_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations bcm47xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = bcm47xx_wdt_ioctl, + .open = bcm47xx_wdt_open, + .release = bcm47xx_wdt_release, + .write = bcm47xx_wdt_write, +}; + +static struct miscdevice bcm47xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bcm47xx_wdt_fops, +}; + +static struct notifier_block bcm47xx_wdt_notifier = { + .notifier_call = bcm47xx_wdt_notify_sys, +}; + +static int __init bcm47xx_wdt_init(void) +{ + int ret; + + if (bcm47xx_wdt_hw_stop() < 0) + return -ENODEV; + + setup_timer(&wdt_timer, bcm47xx_timer_tick, 0L); + + if (bcm47xx_wdt_settimeout(wdt_time)) { + bcm47xx_wdt_settimeout(WDT_DEFAULT_TIME); + printk(KERN_INFO DRV_NAME ": " + "wdt_time value must be 0 < wdt_time < %d, using %d\n", + (WDT_MAX_TIME + 1), wdt_time); + } + + ret = register_reboot_notifier(&bcm47xx_wdt_notifier); + if (ret) + return ret; + + ret = misc_register(&bcm47xx_wdt_miscdev); + if (ret) { + unregister_reboot_notifier(&bcm47xx_wdt_notifier); + return ret; + } + + printk(KERN_INFO "BCM47xx Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); + return 0; +} + +static void __exit bcm47xx_wdt_exit(void) +{ + if (!nowayout) + bcm47xx_wdt_stop(); + + misc_deregister(&bcm47xx_wdt_miscdev); + + unregister_reboot_notifier(&bcm47xx_wdt_notifier); +} + +module_init(bcm47xx_wdt_init); +module_exit(bcm47xx_wdt_exit); + +MODULE_AUTHOR("Aleksandar Radovanovic"); +MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c new file mode 100644 index 00000000000..fecb307d28e --- /dev/null +++ b/drivers/watchdog/coh901327_wdt.c @@ -0,0 +1,537 @@ +/* + * coh901327_wdt.c + * + * Copyright (C) 2008-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Watchdog driver for the ST-Ericsson AB COH 901 327 IP core + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/uaccess.h> +#include <linux/clk.h> + +#define DRV_NAME "WDOG COH 901 327" + +/* + * COH 901 327 register definitions + */ + +/* WDOG_FEED Register 32bit (-/W) */ +#define U300_WDOG_FR 0x00 +#define U300_WDOG_FR_FEED_RESTART_TIMER 0xFEEDU +/* WDOG_TIMEOUT Register 32bit (R/W) */ +#define U300_WDOG_TR 0x04 +#define U300_WDOG_TR_TIMEOUT_MASK 0x7FFFU +/* WDOG_DISABLE1 Register 32bit (-/W) */ +#define U300_WDOG_D1R 0x08 +#define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER 0x2BADU +/* WDOG_DISABLE2 Register 32bit (R/W) */ +#define U300_WDOG_D2R 0x0C +#define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER 0xCAFEU +#define U300_WDOG_D2R_DISABLE_STATUS_DISABLED 0xDABEU +#define U300_WDOG_D2R_DISABLE_STATUS_ENABLED 0x0000U +/* WDOG_STATUS Register 32bit (R/W) */ +#define U300_WDOG_SR 0x10 +#define U300_WDOG_SR_STATUS_TIMED_OUT 0xCFE8U +#define U300_WDOG_SR_STATUS_NORMAL 0x0000U +#define U300_WDOG_SR_RESET_STATUS_RESET 0xE8B4U +/* WDOG_COUNT Register 32bit (R/-) */ +#define U300_WDOG_CR 0x14 +#define U300_WDOG_CR_VALID_IND 0x8000U +#define U300_WDOG_CR_VALID_STABLE 0x0000U +#define U300_WDOG_CR_COUNT_VALUE_MASK 0x7FFFU +/* WDOG_JTAGOVR Register 32bit (R/W) */ +#define U300_WDOG_JOR 0x18 +#define U300_WDOG_JOR_JTAG_MODE_IND 0x0002U +#define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE 0x0001U +/* WDOG_RESTART Register 32bit (-/W) */ +#define U300_WDOG_RR 0x1C +#define U300_WDOG_RR_RESTART_VALUE_RESUME 0xACEDU +/* WDOG_IRQ_EVENT Register 32bit (R/W) */ +#define U300_WDOG_IER 0x20 +#define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND 0x0001U +#define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE 0x0001U +/* WDOG_IRQ_MASK Register 32bit (R/W) */ +#define U300_WDOG_IMR 0x24 +#define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE 0x0001U +/* WDOG_IRQ_FORCE Register 32bit (R/W) */ +#define U300_WDOG_IFR 0x28 +#define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE 0x0001U + +/* Default timeout in seconds = 1 minute */ +static int margin = 60; +static resource_size_t phybase; +static resource_size_t physize; +static int irq; +static void __iomem *virtbase; +static unsigned long coh901327_users; +static unsigned long boot_status; +static u16 wdogenablestore; +static u16 irqmaskstore; +static struct device *parent; + +/* + * The watchdog block is of course always clocked, the + * clk_enable()/clk_disable() calls are mainly for performing reference + * counting higher up in the clock hierarchy. + */ +static struct clk *clk; + +/* + * Enabling and disabling functions. + */ +static void coh901327_enable(u16 timeout) +{ + u16 val; + + clk_enable(clk); + /* Restart timer if it is disabled */ + val = readw(virtbase + U300_WDOG_D2R); + if (val == U300_WDOG_D2R_DISABLE_STATUS_DISABLED) + writew(U300_WDOG_RR_RESTART_VALUE_RESUME, + virtbase + U300_WDOG_RR); + /* Acknowledge any pending interrupt so it doesn't just fire off */ + writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, + virtbase + U300_WDOG_IER); + /* Enable the watchdog interrupt */ + writew(U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE, virtbase + U300_WDOG_IMR); + /* Activate the watchdog timer */ + writew(timeout, virtbase + U300_WDOG_TR); + /* Start the watchdog timer */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, virtbase + U300_WDOG_FR); + /* + * Extra read so that this change propagate in the watchdog. + */ + (void) readw(virtbase + U300_WDOG_CR); + val = readw(virtbase + U300_WDOG_D2R); + clk_disable(clk); + if (val != U300_WDOG_D2R_DISABLE_STATUS_ENABLED) + dev_err(parent, + "%s(): watchdog not enabled! D2R value %04x\n", + __func__, val); +} + +static void coh901327_disable(void) +{ + u16 val; + + clk_enable(clk); + /* Disable the watchdog interrupt if it is active */ + writew(0x0000U, virtbase + U300_WDOG_IMR); + /* If the watchdog is currently enabled, attempt to disable it */ + val = readw(virtbase + U300_WDOG_D2R); + if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) { + writew(U300_WDOG_D1R_DISABLE1_DISABLE_TIMER, + virtbase + U300_WDOG_D1R); + writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, + virtbase + U300_WDOG_D2R); + /* Write this twice (else problems occur) */ + writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, + virtbase + U300_WDOG_D2R); + } + val = readw(virtbase + U300_WDOG_D2R); + clk_disable(clk); + if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) + dev_err(parent, + "%s(): watchdog not disabled! D2R value %04x\n", + __func__, val); +} + +static void coh901327_start(void) +{ + coh901327_enable(margin * 100); +} + +static void coh901327_keepalive(void) +{ + clk_enable(clk); + /* Feed the watchdog */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + clk_disable(clk); +} + +static int coh901327_settimeout(int time) +{ + /* + * Max margin is 327 since the 10ms + * timeout register is max + * 0x7FFF = 327670ms ~= 327s. + */ + if (time <= 0 || time > 327) + return -EINVAL; + + margin = time; + clk_enable(clk); + /* Set new timeout value */ + writew(margin * 100, virtbase + U300_WDOG_TR); + /* Feed the dog */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + clk_disable(clk); + return 0; +} + +/* + * This interrupt occurs 10 ms before the watchdog WILL bark. + */ +static irqreturn_t coh901327_interrupt(int irq, void *data) +{ + u16 val; + + /* + * Ack IRQ? If this occurs we're FUBAR anyway, so + * just acknowledge, disable the interrupt and await the imminent end. + * If you at some point need a host of callbacks to be called + * when the system is about to watchdog-reset, add them here! + * + * NOTE: on future versions of this IP-block, it will be possible + * to prevent a watchdog reset by feeding the watchdog at this + * point. + */ + clk_enable(clk); + val = readw(virtbase + U300_WDOG_IER); + if (val == U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND) + writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, + virtbase + U300_WDOG_IER); + writew(0x0000U, virtbase + U300_WDOG_IMR); + clk_disable(clk); + dev_crit(parent, "watchdog is barking!\n"); + return IRQ_HANDLED; +} + +/* + * Allow only one user (daemon) to open the watchdog + */ +static int coh901327_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(1, &coh901327_users)) + return -EBUSY; + coh901327_start(); + return nonseekable_open(inode, file); +} + +static int coh901327_release(struct inode *inode, struct file *file) +{ + clear_bit(1, &coh901327_users); + coh901327_disable(); + return 0; +} + +static ssize_t coh901327_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) + coh901327_keepalive(); + return len; +} + +static long coh901327_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + u16 val; + int time; + int new_options; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + static struct watchdog_info ident = { + .options = WDIOF_CARDRESET | + WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "COH 901 327 Watchdog", + .firmware_version = 1, + }; + uarg.i = (int __user *)arg; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, uarg.i); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, uarg.i); + break; + + case WDIOC_SETOPTIONS: + ret = get_user(new_options, uarg.i); + if (ret) + break; + if (new_options & WDIOS_DISABLECARD) + coh901327_disable(); + if (new_options & WDIOS_ENABLECARD) + coh901327_start(); + ret = 0; + break; + + case WDIOC_KEEPALIVE: + coh901327_keepalive(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, uarg.i); + if (ret) + break; + + ret = coh901327_settimeout(time); + if (ret) + break; + /* Then fall through to return set value */ + + case WDIOC_GETTIMEOUT: + ret = put_user(margin, uarg.i); + break; + + case WDIOC_GETTIMELEFT: + clk_enable(clk); + /* Read repeatedly until the value is stable! */ + val = readw(virtbase + U300_WDOG_CR); + while (val & U300_WDOG_CR_VALID_IND) + val = readw(virtbase + U300_WDOG_CR); + val &= U300_WDOG_CR_COUNT_VALUE_MASK; + clk_disable(clk); + if (val != 0) + val /= 100; + ret = put_user(val, uarg.i); + break; + } + return ret; +} + +static const struct file_operations coh901327_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = coh901327_write, + .unlocked_ioctl = coh901327_ioctl, + .open = coh901327_open, + .release = coh901327_release, +}; + +static struct miscdevice coh901327_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &coh901327_fops, +}; + +static int __exit coh901327_remove(struct platform_device *pdev) +{ + misc_deregister(&coh901327_miscdev); + coh901327_disable(); + free_irq(irq, pdev); + clk_put(clk); + iounmap(virtbase); + release_mem_region(phybase, physize); + return 0; +} + + +static int __init coh901327_probe(struct platform_device *pdev) +{ + int ret; + u16 val; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + parent = &pdev->dev; + physize = resource_size(res); + phybase = res->start; + + if (request_mem_region(phybase, physize, DRV_NAME) == NULL) { + ret = -EBUSY; + goto out; + } + + virtbase = ioremap(phybase, physize); + if (!virtbase) { + ret = -ENOMEM; + goto out_no_remap; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&pdev->dev, "could not get clock\n"); + goto out_no_clk; + } + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "could not enable clock\n"); + goto out_no_clk_enable; + } + + val = readw(virtbase + U300_WDOG_SR); + switch (val) { + case U300_WDOG_SR_STATUS_TIMED_OUT: + dev_info(&pdev->dev, + "watchdog timed out since last chip reset!\n"); + boot_status = WDIOF_CARDRESET; + /* Status will be cleared below */ + break; + case U300_WDOG_SR_STATUS_NORMAL: + dev_info(&pdev->dev, + "in normal status, no timeouts have occurred.\n"); + break; + default: + dev_info(&pdev->dev, + "contains an illegal status code (%08x)\n", val); + break; + } + + val = readw(virtbase + U300_WDOG_D2R); + switch (val) { + case U300_WDOG_D2R_DISABLE_STATUS_DISABLED: + dev_info(&pdev->dev, "currently disabled.\n"); + break; + case U300_WDOG_D2R_DISABLE_STATUS_ENABLED: + dev_info(&pdev->dev, + "currently enabled! (disabling it now)\n"); + coh901327_disable(); + break; + default: + dev_err(&pdev->dev, + "contains an illegal enable/disable code (%08x)\n", + val); + break; + } + + /* Reset the watchdog */ + writew(U300_WDOG_SR_RESET_STATUS_RESET, virtbase + U300_WDOG_SR); + + irq = platform_get_irq(pdev, 0); + if (request_irq(irq, coh901327_interrupt, IRQF_DISABLED, + DRV_NAME " Bark", pdev)) { + ret = -EIO; + goto out_no_irq; + } + + clk_disable(clk); + + ret = misc_register(&coh901327_miscdev); + if (ret == 0) + dev_info(&pdev->dev, + "initialized. timer margin=%d sec\n", margin); + else + goto out_no_wdog; + + return 0; + +out_no_wdog: + free_irq(irq, pdev); +out_no_irq: + clk_disable(clk); +out_no_clk_enable: + clk_put(clk); +out_no_clk: + iounmap(virtbase); +out_no_remap: + release_mem_region(phybase, SZ_4K); +out: + return ret; +} + +#ifdef CONFIG_PM +static int coh901327_suspend(struct platform_device *pdev, pm_message_t state) +{ + irqmaskstore = readw(virtbase + U300_WDOG_IMR) & 0x0001U; + wdogenablestore = readw(virtbase + U300_WDOG_D2R); + /* If watchdog is on, disable it here and now */ + if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) + coh901327_disable(); + return 0; +} + +static int coh901327_resume(struct platform_device *pdev) +{ + /* Restore the watchdog interrupt */ + writew(irqmaskstore, virtbase + U300_WDOG_IMR); + if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) { + /* Restart the watchdog timer */ + writew(U300_WDOG_RR_RESTART_VALUE_RESUME, + virtbase + U300_WDOG_RR); + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + } + return 0; +} +#else +#define coh901327_suspend NULL +#define coh901327_resume NULL +#endif + +/* + * Mistreating the watchdog is the only way to perform a software reset of the + * system on EMP platforms. So we implement this and export a symbol for it. + */ +void coh901327_watchdog_reset(void) +{ + /* Enable even if on JTAG too */ + writew(U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE, + virtbase + U300_WDOG_JOR); + /* + * Timeout = 5s, we have to wait for the watchdog reset to + * actually take place: the watchdog will be reloaded with the + * default value immediately, so we HAVE to reboot and get back + * into the kernel in 30s, or the device will reboot again! + * The boot loader will typically deactivate the watchdog, so we + * need time enough for the boot loader to get to the point of + * deactivating the watchdog before it is shut down by it. + * + * NOTE: on future versions of the watchdog, this restriction is + * gone: the watchdog will be reloaded with a defaul value (1 min) + * instead of last value, and you can conveniently set the watchdog + * timeout to 10ms (value = 1) without any problems. + */ + coh901327_enable(500); + /* Return and await doom */ +} + +static struct platform_driver coh901327_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "coh901327_wdog", + }, + .remove = __exit_p(coh901327_remove), + .suspend = coh901327_suspend, + .resume = coh901327_resume, +}; + +static int __init coh901327_init(void) +{ + return platform_driver_probe(&coh901327_driver, coh901327_probe); +} +module_init(coh901327_init); + +static void __exit coh901327_exit(void) +{ + platform_driver_unregister(&coh901327_driver); +} +module_exit(coh901327_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("COH 901 327 Watchdog"); + +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index c0b9169ba5d..a6c5674c78e 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -120,7 +120,8 @@ static int nowayout = WATCHDOG_NOWAYOUT; static char expect_release; static unsigned long hpwdt_is_open; static unsigned int allow_kdump; -static int hpwdt_nmi_sourcing; +static unsigned int hpwdt_nmi_sourcing; +static unsigned int priority; /* hpwdt at end of die_notify list */ static void __iomem *pci_mem_addr; /* the PCI-memory address */ static unsigned long __iomem *hpwdt_timer_reg; @@ -623,7 +624,7 @@ static struct miscdevice hpwdt_miscdev = { static struct notifier_block die_notifier = { .notifier_call = hpwdt_pretimeout, - .priority = 0x7FFFFFFF, + .priority = 0, }; /* @@ -641,7 +642,8 @@ static void __devinit hpwdt_check_nmi_sourcing(struct pci_dev *dev) hpwdt_nmi_sourcing = 1; else dev_warn(&dev->dev, "NMI sourcing is disabled. To enable this " - "functionality you must reboot with nmi_watchdog=0.\n"); + "functionality you must reboot with nmi_watchdog=0 " + "and load the hpwdt driver with priority=1.\n"); } #else static void __devinit hpwdt_check_nmi_sourcing(struct pci_dev *dev) @@ -714,6 +716,14 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, cmn_regs.u1.rah = 0x0D; cmn_regs.u1.ral = 0x02; + /* + * If the priority is set to 1, then we will be put first on the + * die notify list to handle a critical NMI. The default is to + * be last so other users of the NMI signal can function. + */ + if (priority) + die_notifier.priority = 0x7FFFFFFF; + retval = register_die_notifier(&die_notifier); if (retval != 0) { dev_warn(&dev->dev, @@ -733,9 +743,11 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, printk(KERN_INFO "hp Watchdog Timer Driver: %s" ", timer margin: %d seconds (nowayout=%d)" - ", allow kernel dump: %s (default = 0/OFF).\n", + ", allow kernel dump: %s (default = 0/OFF)" + ", priority: %s (default = 0/LAST).\n", HPWDT_VERSION, soft_margin, nowayout, - (allow_kdump == 0) ? "OFF" : "ON"); + (allow_kdump == 0) ? "OFF" : "ON", + (priority == 0) ? "LAST" : "FIRST"); return 0; @@ -798,5 +810,9 @@ module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +module_param(priority, int, 0); +MODULE_PARM_DESC(priority, "The hpwdt driver handles NMIs first or last" + " (default = 0/Last)\n"); + |