From 8740f71d7f4f0400cd8c42e6584155024119d77e Mon Sep 17 00:00:00 2001 From: Banajit Goswami Date: Thu, 20 May 2010 11:58:24 +0100 Subject: watchdog: s3c2410_wdt - Add extra option to include watchdog for Samsung SoCs This patch adds HAVE_S3C2410_WATCHDOG to control inclusion of watchdog driver for Samsung SoCs. This option will help to include the driver only for the necessary machines and not for all for any given arch. Signed-off-by: Banajit Goswami Signed-off-by: Kukjin Kim Signed-off-by: Ben Dooks Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b87ba23442d2..c57ecffe0085 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -145,13 +145,19 @@ config KS8695_WATCHDOG Watchdog timer embedded into KS8695 processor. This will reboot your system when the timeout is reached. +config HAVE_S3C2410_WATCHDOG + bool + help + This will include watchdog timer support for Samsung SoCs. If + you want to include watchdog support for any machine, kindly + select this in the respective mach-XXXX/Kconfig file. + config S3C2410_WATCHDOG tristate "S3C2410 Watchdog" - depends on ARCH_S3C2410 + depends on ARCH_S3C2410 || HAVE_S3C2410_WATCHDOG help - Watchdog timer block in the Samsung S3C2410 chips. This will - reboot the system when the timer expires with the watchdog - enabled. + Watchdog timer block in the Samsung SoCs. This will reboot + the system when the timer expires with the watchdog enabled. The driver is limited by the speed of the system's PCLK signal, so with reasonably fast systems (PCLK around 50-66MHz) -- cgit v1.2.3 From bb2fd8a844d3a9209599b5fb694b30ac46a56ef0 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 29 Apr 2010 10:03:17 +0200 Subject: watchdog: Driver for the watchdog timer on Freescale IMX2 (and later) processors. This is the driver for the hardware watchdog on the Freescale IMX2 and later processors. Signed-off-by: Wolfram Sang Cc: Vladimir Zapolskiy Cc: Sascha Hauer Tested-by: Juergen Beisert Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 12 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/imx2_wdt.c | 358 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 drivers/watchdog/imx2_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c57ecffe0085..afcfacc9bbe2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -312,6 +312,18 @@ config MAX63XX_WATCHDOG help Support for memory mapped max63{69,70,71,72,73,74} watchdog timer. +config IMX2_WDT + tristate "IMX2+ Watchdog" + depends on ARCH_MX2 || ARCH_MX25 || ARCH_MX3 || ARCH_MX5 + help + This is the driver for the hardware watchdog + on the Freescale IMX2 and later processors. + If you have one of these processors and wish to have + watchdog support enabled, say Y, otherwise say N. + + To compile this driver as a module, choose M here: the + module will be called imx2_wdt. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5e3cb95bb0e9..72f3e2073f8e 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o +obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c new file mode 100644 index 000000000000..ea25885781bb --- /dev/null +++ b/drivers/watchdog/imx2_wdt.c @@ -0,0 +1,358 @@ +/* + * Watchdog driver for IMX2 and later processors + * + * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. + * + * some parts adapted by similar drivers from Darius Augulis and Vladimir + * Zapolskiy, additional improvements by Wim Van Sebroeck. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * NOTE: MX1 has a slightly different Watchdog than MX2 and later: + * + * MX1: MX2+: + * ---- ----- + * Registers: 32-bit 16-bit + * Stopable timer: Yes No + * Need to enable clk: No Yes + * Halt on suspend: Manual Can be automatic + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "imx2-wdt" + +#define IMX2_WDT_WCR 0x00 /* Control Register */ +#define IMX2_WDT_WCR_WT (0xFF << 8) /* -> Watchdog Timeout Field */ +#define IMX2_WDT_WCR_WRE (1 << 3) /* -> WDOG Reset Enable */ +#define IMX2_WDT_WCR_WDE (1 << 2) /* -> Watchdog Enable */ + +#define IMX2_WDT_WSR 0x02 /* Service Register */ +#define IMX2_WDT_SEQ1 0x5555 /* -> service sequence 1 */ +#define IMX2_WDT_SEQ2 0xAAAA /* -> service sequence 2 */ + +#define IMX2_WDT_MAX_TIME 128 +#define IMX2_WDT_DEFAULT_TIME 60 /* in seconds */ + +#define WDOG_SEC_TO_COUNT(s) ((s * 2 - 1) << 8) + +#define IMX2_WDT_STATUS_OPEN 0 +#define IMX2_WDT_STATUS_STARTED 1 +#define IMX2_WDT_EXPECT_CLOSE 2 + +static struct { + struct clk *clk; + void __iomem *base; + unsigned timeout; + unsigned long status; + struct timer_list timer; /* Pings the watchdog when closed */ +} imx2_wdt; + +static struct miscdevice imx2_wdt_miscdev; + +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 unsigned timeout = IMX2_WDT_DEFAULT_TIME; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(IMX2_WDT_DEFAULT_TIME) ")"); + +static const struct watchdog_info imx2_wdt_info = { + .identity = "imx2+ watchdog", + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, +}; + +static inline void imx2_wdt_setup(void) +{ + u16 val = __raw_readw(imx2_wdt.base + IMX2_WDT_WCR); + + /* Strip the old watchdog Time-Out value */ + val &= ~IMX2_WDT_WCR_WT; + /* Generate reset if WDOG times out */ + val &= ~IMX2_WDT_WCR_WRE; + /* Keep Watchdog Disabled */ + val &= ~IMX2_WDT_WCR_WDE; + /* Set the watchdog's Time-Out value */ + val |= WDOG_SEC_TO_COUNT(imx2_wdt.timeout); + + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); + + /* enable the watchdog */ + val |= IMX2_WDT_WCR_WDE; + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); +} + +static inline void imx2_wdt_ping(void) +{ + __raw_writew(IMX2_WDT_SEQ1, imx2_wdt.base + IMX2_WDT_WSR); + __raw_writew(IMX2_WDT_SEQ2, imx2_wdt.base + IMX2_WDT_WSR); +} + +static void imx2_wdt_timer_ping(unsigned long arg) +{ + /* ping it every imx2_wdt.timeout / 2 seconds to prevent reboot */ + imx2_wdt_ping(); + mod_timer(&imx2_wdt.timer, jiffies + imx2_wdt.timeout * HZ / 2); +} + +static void imx2_wdt_start(void) +{ + if (!test_and_set_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + /* at our first start we enable clock and do initialisations */ + clk_enable(imx2_wdt.clk); + + imx2_wdt_setup(); + } else /* delete the timer that pings the watchdog after close */ + del_timer_sync(&imx2_wdt.timer); + + /* Watchdog is enabled - time to reload the timeout value */ + imx2_wdt_ping(); +} + +static void imx2_wdt_stop(void) +{ + /* we don't need a clk_disable, it cannot be disabled once started. + * We use a timer to ping the watchdog while /dev/watchdog is closed */ + imx2_wdt_timer_ping(0); +} + +static void imx2_wdt_set_timeout(int new_timeout) +{ + u16 val = __raw_readw(imx2_wdt.base + IMX2_WDT_WCR); + + /* set the new timeout value in the WSR */ + val &= ~IMX2_WDT_WCR_WT; + val |= WDOG_SEC_TO_COUNT(new_timeout); + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); +} + +static int imx2_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(IMX2_WDT_STATUS_OPEN, &imx2_wdt.status)) + return -EBUSY; + + imx2_wdt_start(); + return nonseekable_open(inode, file); +} + +static int imx2_wdt_close(struct inode *inode, struct file *file) +{ + if (test_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status) && !nowayout) + imx2_wdt_stop(); + else { + dev_crit(imx2_wdt_miscdev.parent, + "Unexpected close: Expect reboot!\n"); + imx2_wdt_ping(); + } + + clear_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status); + clear_bit(IMX2_WDT_STATUS_OPEN, &imx2_wdt.status); + return 0; +} + +static long imx2_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, &imx2_wdt_info, + sizeof(struct watchdog_info)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + imx2_wdt_ping(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + if ((new_value < 1) || (new_value > IMX2_WDT_MAX_TIME)) + return -EINVAL; + imx2_wdt_set_timeout(new_value); + imx2_wdt.timeout = new_value; + imx2_wdt_ping(); + + /* Fallthrough to return current value */ + case WDIOC_GETTIMEOUT: + return put_user(imx2_wdt.timeout, p); + + default: + return -ENOTTY; + } +} + +static ssize_t imx2_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + size_t i; + char c; + + if (len == 0) /* Can we see this even ? */ + return 0; + + clear_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status); + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + set_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status); + } + + imx2_wdt_ping(); + return len; +} + +static const struct file_operations imx2_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = imx2_wdt_ioctl, + .open = imx2_wdt_open, + .release = imx2_wdt_close, + .write = imx2_wdt_write, +}; + +static struct miscdevice imx2_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &imx2_wdt_fops, +}; + +static int __init imx2_wdt_probe(struct platform_device *pdev) +{ + int ret; + int res_size; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "can't get device resources\n"); + return -ENODEV; + } + + res_size = resource_size(res); + if (!devm_request_mem_region(&pdev->dev, res->start, res_size, + res->name)) { + dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n", + res_size, res->start); + return -ENOMEM; + } + + imx2_wdt.base = devm_ioremap_nocache(&pdev->dev, res->start, res_size); + if (!imx2_wdt.base) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + imx2_wdt.clk = clk_get_sys("imx-wdt.0", NULL); + if (IS_ERR(imx2_wdt.clk)) { + dev_err(&pdev->dev, "can't get Watchdog clock\n"); + return PTR_ERR(imx2_wdt.clk); + } + + imx2_wdt.timeout = clamp_t(unsigned, timeout, 1, IMX2_WDT_MAX_TIME); + if (imx2_wdt.timeout != timeout) + dev_warn(&pdev->dev, "Initial timeout out of range! " + "Clamped from %u to %u\n", timeout, imx2_wdt.timeout); + + setup_timer(&imx2_wdt.timer, imx2_wdt_timer_ping, 0); + + imx2_wdt_miscdev.parent = &pdev->dev; + ret = misc_register(&imx2_wdt_miscdev); + if (ret) + goto fail; + + dev_info(&pdev->dev, + "IMX2+ Watchdog Timer enabled. timeout=%ds (nowayout=%d)\n", + imx2_wdt.timeout, nowayout); + return 0; + +fail: + imx2_wdt_miscdev.parent = NULL; + clk_put(imx2_wdt.clk); + return ret; +} + +static int __exit imx2_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&imx2_wdt_miscdev); + + if (test_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + del_timer_sync(&imx2_wdt.timer); + + dev_crit(imx2_wdt_miscdev.parent, + "Device removed: Expect reboot!\n"); + } else + clk_put(imx2_wdt.clk); + + imx2_wdt_miscdev.parent = NULL; + return 0; +} + +static void imx2_wdt_shutdown(struct platform_device *pdev) +{ + if (test_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + /* we are running, we need to delete the timer but will give + * max timeout before reboot will take place */ + del_timer_sync(&imx2_wdt.timer); + imx2_wdt_set_timeout(IMX2_WDT_MAX_TIME); + imx2_wdt_ping(); + + dev_crit(imx2_wdt_miscdev.parent, + "Device shutdown: Expect reboot!\n"); + } +} + +static struct platform_driver imx2_wdt_driver = { + .probe = imx2_wdt_probe, + .remove = __exit_p(imx2_wdt_remove), + .shutdown = imx2_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init imx2_wdt_init(void) +{ + return platform_driver_probe(&imx2_wdt_driver, imx2_wdt_probe); +} +module_init(imx2_wdt_init); + +static void __exit imx2_wdt_exit(void) +{ + platform_driver_unregister(&imx2_wdt_driver); +} +module_exit(imx2_wdt_exit); + +MODULE_AUTHOR("Wolfram Sang"); +MODULE_DESCRIPTION("Watchdog driver for IMX2 and later"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" DRIVER_NAME); -- cgit v1.2.3 From 4c076fb41ac93bc0cbd55f2a731cc31337804acb Mon Sep 17 00:00:00 2001 From: David Daney Date: Sat, 24 Jul 2010 10:16:05 -0700 Subject: WATCHDOG: Add watchdog driver for OCTEON SOCs The OCTEON is a MIPS64 based SOC family with an on chip watchdog unit. The driver is split into two source files one for the C code and one for assembly. Assembly is needed to handle the NMI and then print the machine state before the reboot is triggered. Signed-off-by: David Daney Cc: Wim Van Sebroeck Cc: Andrew Morton Cc: Russell King Cc: Tony Lindgren Cc: Marc Zyngier Cc: Thierry Reding Cc: Sam Ravnborg To: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org, Patchwork: https://patchwork.linux-mips.org/patch/1503/ Signed-off-by: Wim Van Sebroeck Signed-off-by: Ralf Baechle create mode 100644 drivers/watchdog/octeon-wdt-main.c create mode 100644 drivers/watchdog/octeon-wdt-nmi.S --- drivers/watchdog/Kconfig | 18 + drivers/watchdog/Makefile | 2 + drivers/watchdog/octeon-wdt-main.c | 745 +++++++++++++++++++++++++++++++++++++ drivers/watchdog/octeon-wdt-nmi.S | 64 ++++ 4 files changed, 829 insertions(+) create mode 100644 drivers/watchdog/octeon-wdt-main.c create mode 100644 drivers/watchdog/octeon-wdt-nmi.S (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index afcfacc9bbe2..b04b18468932 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -875,6 +875,24 @@ config TXX9_WDT help Hardware driver for the built-in watchdog timer on TXx9 MIPS SoCs. +config OCTEON_WDT + tristate "Cavium OCTEON SOC family Watchdog Timer" + depends on CPU_CAVIUM_OCTEON + default y + select EXPORT_UASM if OCTEON_WDT = m + help + Hardware driver for OCTEON's on chip watchdog timer. + Enables the watchdog for all cores running Linux. It + installs a NMI handler and pokes the watchdog based on an + interrupt. On first expiration of the watchdog, the + interrupt handler pokes it. The second expiration causes an + NMI that prints a message. The third expiration causes a + global soft reset. + + When userspace has /dev/watchdog open, no poking is done + from the first interrupt, it is then only poked when the + device is written. + # PARISC Architecture # POWERPC Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 72f3e2073f8e..e30289a5e367 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -114,6 +114,8 @@ obj-$(CONFIG_PNX833X_WDT) += pnx833x_wdt.o obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o obj-$(CONFIG_AR7_WDT) += ar7_wdt.o obj-$(CONFIG_TXX9_WDT) += txx9wdt.o +obj-$(CONFIG_OCTEON_WDT) += octeon-wdt.o +octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o # PARISC Architecture diff --git a/drivers/watchdog/octeon-wdt-main.c b/drivers/watchdog/octeon-wdt-main.c new file mode 100644 index 000000000000..2a410170eca6 --- /dev/null +++ b/drivers/watchdog/octeon-wdt-main.c @@ -0,0 +1,745 @@ +/* + * Octeon Watchdog driver + * + * Copyright (C) 2007, 2008, 2009, 2010 Cavium Networks + * + * Some parts derived from wdt.c + * + * (c) Copyright 1996-1997 Alan Cox , + * All Rights Reserved. + * + * 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 + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * + * The OCTEON watchdog has a maximum timeout of 2^32 * io_clock. + * For most systems this is less than 10 seconds, so to allow for + * software to request longer watchdog heartbeats, we maintain software + * counters to count multiples of the base rate. If the system locks + * up in such a manner that we can not run the software counters, the + * only result is a watchdog reset sooner than was requested. But + * that is OK, because in this case userspace would likely not be able + * to do anything anyhow. + * + * The hardware watchdog interval we call the period. The OCTEON + * watchdog goes through several stages, after the first period an + * irq is asserted, then if it is not reset, after the next period NMI + * is asserted, then after an additional period a chip wide soft reset. + * So for the software counters, we reset watchdog after each period + * and decrement the counter. But for the last two periods we need to + * let the watchdog progress to the NMI stage so we disable the irq + * and let it proceed. Once in the NMI, we print the register state + * to the serial port and then wait for the reset. + * + * A watchdog is maintained for each CPU in the system, that way if + * one CPU suffers a lockup, we also get a register dump and reset. + * The userspace ping resets the watchdog on all CPUs. + * + * Before userspace opens the watchdog device, we still run the + * watchdogs to catch any lockups that may be kernel related. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* The count needed to achieve timeout_sec. */ +static unsigned int timeout_cnt; + +/* The maximum period supported. */ +static unsigned int max_timeout_sec; + +/* The current period. */ +static unsigned int timeout_sec; + +/* Set to non-zero when userspace countdown mode active */ +static int do_coundown; +static unsigned int countdown_reset; +static unsigned int per_cpu_countdown[NR_CPUS]; + +static cpumask_t irq_enabled_cpus; + +#define WD_TIMO 60 /* Default heartbeat = 60 seconds */ + +static int heartbeat = WD_TIMO; +module_param(heartbeat, int, S_IRUGO); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0 < heartbeat, default=" + __MODULE_STRING(WD_TIMO) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, S_IRUGO); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static unsigned long octeon_wdt_is_open; +static char expect_close; + +static u32 __initdata nmi_stage1_insns[64]; +/* We need one branch and therefore one relocation per target label. */ +static struct uasm_label __initdata labels[5]; +static struct uasm_reloc __initdata relocs[5]; + +enum lable_id { + label_enter_bootloader = 1 +}; + +/* Some CP0 registers */ +#define K0 26 +#define C0_CVMMEMCTL 11, 7 +#define C0_STATUS 12, 0 +#define C0_EBASE 15, 1 +#define C0_DESAVE 31, 0 + +void octeon_wdt_nmi_stage2(void); + +static void __init octeon_wdt_build_stage1(void) +{ + int i; + int len; + u32 *p = nmi_stage1_insns; +#ifdef CONFIG_HOTPLUG_CPU + struct uasm_label *l = labels; + struct uasm_reloc *r = relocs; +#endif + + /* + * For the next few instructions running the debugger may + * cause corruption of k0 in the saved registers. Since we're + * about to crash, nobody probably cares. + * + * Save K0 into the debug scratch register + */ + uasm_i_dmtc0(&p, K0, C0_DESAVE); + + uasm_i_mfc0(&p, K0, C0_STATUS); +#ifdef CONFIG_HOTPLUG_CPU + uasm_il_bbit0(&p, &r, K0, ilog2(ST0_NMI), label_enter_bootloader); +#endif + /* Force 64-bit addressing enabled */ + uasm_i_ori(&p, K0, K0, ST0_UX | ST0_SX | ST0_KX); + uasm_i_mtc0(&p, K0, C0_STATUS); + +#ifdef CONFIG_HOTPLUG_CPU + uasm_i_mfc0(&p, K0, C0_EBASE); + /* Coreid number in K0 */ + uasm_i_andi(&p, K0, K0, 0xf); + /* 8 * coreid in bits 16-31 */ + uasm_i_dsll_safe(&p, K0, K0, 3 + 16); + uasm_i_ori(&p, K0, K0, 0x8001); + uasm_i_dsll_safe(&p, K0, K0, 16); + uasm_i_ori(&p, K0, K0, 0x0700); + uasm_i_drotr_safe(&p, K0, K0, 32); + /* + * Should result in: 0x8001,0700,0000,8*coreid which is + * CVMX_CIU_WDOGX(coreid) - 0x0500 + * + * Now ld K0, CVMX_CIU_WDOGX(coreid) + */ + uasm_i_ld(&p, K0, 0x500, K0); + /* + * If bit one set handle the NMI as a watchdog event. + * otherwise transfer control to bootloader. + */ + uasm_il_bbit0(&p, &r, K0, 1, label_enter_bootloader); + uasm_i_nop(&p); +#endif + + /* Clear Dcache so cvmseg works right. */ + uasm_i_cache(&p, 1, 0, 0); + + /* Use K0 to do a read/modify/write of CVMMEMCTL */ + uasm_i_dmfc0(&p, K0, C0_CVMMEMCTL); + /* Clear out the size of CVMSEG */ + uasm_i_dins(&p, K0, 0, 0, 6); + /* Set CVMSEG to its largest value */ + uasm_i_ori(&p, K0, K0, 0x1c0 | 54); + /* Store the CVMMEMCTL value */ + uasm_i_dmtc0(&p, K0, C0_CVMMEMCTL); + + /* Load the address of the second stage handler */ + UASM_i_LA(&p, K0, (long)octeon_wdt_nmi_stage2); + uasm_i_jr(&p, K0); + uasm_i_dmfc0(&p, K0, C0_DESAVE); + +#ifdef CONFIG_HOTPLUG_CPU + uasm_build_label(&l, p, label_enter_bootloader); + /* Jump to the bootloader and restore K0 */ + UASM_i_LA(&p, K0, (long)octeon_bootloader_entry_addr); + uasm_i_jr(&p, K0); + uasm_i_dmfc0(&p, K0, C0_DESAVE); +#endif + uasm_resolve_relocs(relocs, labels); + + len = (int)(p - nmi_stage1_insns); + pr_debug("Synthesized NMI stage 1 handler (%d instructions).\n", len); + + pr_debug("\t.set push\n"); + pr_debug("\t.set noreorder\n"); + for (i = 0; i < len; i++) + pr_debug("\t.word 0x%08x\n", nmi_stage1_insns[i]); + pr_debug("\t.set pop\n"); + + if (len > 32) + panic("NMI stage 1 handler exceeds 32 instructions, was %d\n", len); +} + +static int cpu2core(int cpu) +{ +#ifdef CONFIG_SMP + return cpu_logical_map(cpu); +#else + return cvmx_get_core_num(); +#endif +} + +static int core2cpu(int coreid) +{ +#ifdef CONFIG_SMP + return cpu_number_map(coreid); +#else + return 0; +#endif +} + +/** + * Poke the watchdog when an interrupt is received + * + * @cpl: + * @dev_id: + * + * Returns + */ +static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id) +{ + unsigned int core = cvmx_get_core_num(); + int cpu = core2cpu(core); + + if (do_coundown) { + if (per_cpu_countdown[cpu] > 0) { + /* We're alive, poke the watchdog */ + cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + per_cpu_countdown[cpu]--; + } else { + /* Bad news, you are about to reboot. */ + disable_irq_nosync(cpl); + cpumask_clear_cpu(cpu, &irq_enabled_cpus); + } + } else { + /* Not open, just ping away... */ + cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + } + return IRQ_HANDLED; +} + +/* From setup.c */ +extern int prom_putchar(char c); + +/** + * Write a string to the uart + * + * @str: String to write + */ +static void octeon_wdt_write_string(const char *str) +{ + /* Just loop writing one byte at a time */ + while (*str) + prom_putchar(*str++); +} + +/** + * Write a hex number out of the uart + * + * @value: Number to display + * @digits: Number of digits to print (1 to 16) + */ +static void octeon_wdt_write_hex(u64 value, int digits) +{ + int d; + int v; + for (d = 0; d < digits; d++) { + v = (value >> ((digits - d - 1) * 4)) & 0xf; + if (v >= 10) + prom_putchar('a' + v - 10); + else + prom_putchar('0' + v); + } +} + +const char *reg_name[] = { + "$0", "at", "v0", "v1", "a0", "a1", "a2", "a3", + "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + "t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra" +}; + +/** + * NMI stage 3 handler. NMIs are handled in the following manner: + * 1) The first NMI handler enables CVMSEG and transfers from + * the bootbus region into normal memory. It is careful to not + * destroy any registers. + * 2) The second stage handler uses CVMSEG to save the registers + * and create a stack for C code. It then calls the third level + * handler with one argument, a pointer to the register values. + * 3) The third, and final, level handler is the following C + * function that prints out some useful infomration. + * + * @reg: Pointer to register state before the NMI + */ +void octeon_wdt_nmi_stage3(u64 reg[32]) +{ + u64 i; + + unsigned int coreid = cvmx_get_core_num(); + /* + * Save status and cause early to get them before any changes + * might happen. + */ + u64 cp0_cause = read_c0_cause(); + u64 cp0_status = read_c0_status(); + u64 cp0_error_epc = read_c0_errorepc(); + u64 cp0_epc = read_c0_epc(); + + /* Delay so output from all cores output is not jumbled together. */ + __delay(100000000ull * coreid); + + octeon_wdt_write_string("\r\n*** NMI Watchdog interrupt on Core 0x"); + octeon_wdt_write_hex(coreid, 1); + octeon_wdt_write_string(" ***\r\n"); + for (i = 0; i < 32; i++) { + octeon_wdt_write_string("\t"); + octeon_wdt_write_string(reg_name[i]); + octeon_wdt_write_string("\t0x"); + octeon_wdt_write_hex(reg[i], 16); + if (i & 1) + octeon_wdt_write_string("\r\n"); + } + octeon_wdt_write_string("\terr_epc\t0x"); + octeon_wdt_write_hex(cp0_error_epc, 16); + + octeon_wdt_write_string("\tepc\t0x"); + octeon_wdt_write_hex(cp0_epc, 16); + octeon_wdt_write_string("\r\n"); + + octeon_wdt_write_string("\tstatus\t0x"); + octeon_wdt_write_hex(cp0_status, 16); + octeon_wdt_write_string("\tcause\t0x"); + octeon_wdt_write_hex(cp0_cause, 16); + octeon_wdt_write_string("\r\n"); + + octeon_wdt_write_string("\tsum0\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_SUM0(coreid * 2)), 16); + octeon_wdt_write_string("\ten0\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_EN0(coreid * 2)), 16); + octeon_wdt_write_string("\r\n"); + + octeon_wdt_write_string("*** Chip soft reset soon ***\r\n"); +} + +static void octeon_wdt_disable_interrupt(int cpu) +{ + unsigned int core; + unsigned int irq; + union cvmx_ciu_wdogx ciu_wdog; + + core = cpu2core(cpu); + + irq = OCTEON_IRQ_WDOG0 + core; + + /* Poke the watchdog to clear out its state */ + cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + + /* Disable the hardware. */ + ciu_wdog.u64 = 0; + cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + + free_irq(irq, octeon_wdt_poke_irq); +} + +static void octeon_wdt_setup_interrupt(int cpu) +{ + unsigned int core; + unsigned int irq; + union cvmx_ciu_wdogx ciu_wdog; + + core = cpu2core(cpu); + + /* Disable it before doing anything with the interrupts. */ + ciu_wdog.u64 = 0; + cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + + per_cpu_countdown[cpu] = countdown_reset; + + irq = OCTEON_IRQ_WDOG0 + core; + + if (request_irq(irq, octeon_wdt_poke_irq, + IRQF_DISABLED, "octeon_wdt", octeon_wdt_poke_irq)) + panic("octeon_wdt: Couldn't obtain irq %d", irq); + + cpumask_set_cpu(cpu, &irq_enabled_cpus); + + /* Poke the watchdog to clear out its state */ + cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + + /* Finally enable the watchdog now that all handlers are installed */ + ciu_wdog.u64 = 0; + ciu_wdog.s.len = timeout_cnt; + ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */ + cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); +} + +static int octeon_wdt_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_DOWN_PREPARE: + octeon_wdt_disable_interrupt(cpu); + break; + case CPU_ONLINE: + case CPU_DOWN_FAILED: + octeon_wdt_setup_interrupt(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} + +static void octeon_wdt_ping(void) +{ + int cpu; + int coreid; + + for_each_online_cpu(cpu) { + coreid = cpu2core(cpu); + cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + per_cpu_countdown[cpu] = countdown_reset; + if ((countdown_reset || !do_coundown) && + !cpumask_test_cpu(cpu, &irq_enabled_cpus)) { + /* We have to enable the irq */ + int irq = OCTEON_IRQ_WDOG0 + coreid; + enable_irq(irq); + cpumask_set_cpu(cpu, &irq_enabled_cpus); + } + } +} + +static void octeon_wdt_calc_parameters(int t) +{ + unsigned int periods; + + timeout_sec = max_timeout_sec; + + + /* + * Find the largest interrupt period, that can evenly divide + * the requested heartbeat time. + */ + while ((t % timeout_sec) != 0) + timeout_sec--; + + periods = t / timeout_sec; + + /* + * The last two periods are after the irq is disabled, and + * then to the nmi, so we subtract them off. + */ + + countdown_reset = periods > 2 ? periods - 2 : 0; + heartbeat = t; + timeout_cnt = ((octeon_get_clock_rate() >> 8) * timeout_sec) >> 8; +} + +static int octeon_wdt_set_heartbeat(int t) +{ + int cpu; + int coreid; + union cvmx_ciu_wdogx ciu_wdog; + + if (t <= 0) + return -1; + + octeon_wdt_calc_parameters(t); + + for_each_online_cpu(cpu) { + coreid = cpu2core(cpu); + cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + ciu_wdog.u64 = 0; + ciu_wdog.s.len = timeout_cnt; + ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */ + cvmx_write_csr(CVMX_CIU_WDOGX(coreid), ciu_wdog.u64); + cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + } + octeon_wdt_ping(); /* Get the irqs back on. */ + return 0; +} + +/** + * octeon_wdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t octeon_wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 1; + } + } + octeon_wdt_ping(); + } + return count; +} + +/** + * octeon_wdt_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. We only + * actually usefully support querying capabilities and setting + * the timeout. + */ + +static long octeon_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "OCTEON", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + octeon_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (octeon_wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + /* Fall through. */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/** + * octeon_wdt_open: + * @inode: inode of device + * @file: file handle to device + * + * The watchdog device has been opened. The watchdog device is single + * open and on opening we do a ping to reset the counters. + */ + +static int octeon_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &octeon_wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + octeon_wdt_ping(); + do_coundown = 1; + return nonseekable_open(inode, file); +} + +/** + * octeon_wdt_release: + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int octeon_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close) { + do_coundown = 0; + octeon_wdt_ping(); + } else { + pr_crit("octeon_wdt: WDT device closed unexpectedly. WDT will not stop!\n"); + } + clear_bit(0, &octeon_wdt_is_open); + expect_close = 0; + return 0; +} + +static const struct file_operations octeon_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = octeon_wdt_write, + .unlocked_ioctl = octeon_wdt_ioctl, + .open = octeon_wdt_open, + .release = octeon_wdt_release, +}; + +static struct miscdevice octeon_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &octeon_wdt_fops, +}; + +static struct notifier_block octeon_wdt_cpu_notifier = { + .notifier_call = octeon_wdt_cpu_callback, +}; + + +/** + * Module/ driver initialization. + * + * Returns Zero on success + */ +static int __init octeon_wdt_init(void) +{ + int i; + int ret; + int cpu; + u64 *ptr; + + /* + * Watchdog time expiration length = The 16 bits of LEN + * represent the most significant bits of a 24 bit decrementer + * that decrements every 256 cycles. + * + * Try for a timeout of 5 sec, if that fails a smaller number + * of even seconds, + */ + max_timeout_sec = 6; + do { + max_timeout_sec--; + timeout_cnt = ((octeon_get_clock_rate() >> 8) * max_timeout_sec) >> 8; + } while (timeout_cnt > 65535); + + BUG_ON(timeout_cnt == 0); + + octeon_wdt_calc_parameters(heartbeat); + + pr_info("octeon_wdt: Initial granularity %d Sec.\n", timeout_sec); + + ret = misc_register(&octeon_wdt_miscdev); + if (ret) { + pr_err("octeon_wdt: cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out; + } + + /* Build the NMI handler ... */ + octeon_wdt_build_stage1(); + + /* ... and install it. */ + ptr = (u64 *) nmi_stage1_insns; + for (i = 0; i < 16; i++) { + cvmx_write_csr(CVMX_MIO_BOOT_LOC_ADR, i * 8); + cvmx_write_csr(CVMX_MIO_BOOT_LOC_DAT, ptr[i]); + } + cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0x81fc0000); + + cpumask_clear(&irq_enabled_cpus); + + for_each_online_cpu(cpu) + octeon_wdt_setup_interrupt(cpu); + + register_hotcpu_notifier(&octeon_wdt_cpu_notifier); +out: + return ret; +} + +/** + * Module / driver shutdown + */ +static void __exit octeon_wdt_cleanup(void) +{ + int cpu; + + misc_deregister(&octeon_wdt_miscdev); + + unregister_hotcpu_notifier(&octeon_wdt_cpu_notifier); + + for_each_online_cpu(cpu) { + int core = cpu2core(cpu); + /* Disable the watchdog */ + cvmx_write_csr(CVMX_CIU_WDOGX(core), 0); + /* Free the interrupt handler */ + free_irq(OCTEON_IRQ_WDOG0 + core, octeon_wdt_poke_irq); + } + /* + * Disable the boot-bus memory, the code it points to is soon + * to go missing. + */ + cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cavium Networks "); +MODULE_DESCRIPTION("Cavium Networks Octeon Watchdog driver."); +module_init(octeon_wdt_init); +module_exit(octeon_wdt_cleanup); diff --git a/drivers/watchdog/octeon-wdt-nmi.S b/drivers/watchdog/octeon-wdt-nmi.S new file mode 100644 index 000000000000..8a900a5e3233 --- /dev/null +++ b/drivers/watchdog/octeon-wdt-nmi.S @@ -0,0 +1,64 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2007 Cavium Networks + */ +#include +#include + +#define SAVE_REG(r) sd $r, -32768+6912-(32-r)*8($0) + + NESTED(octeon_wdt_nmi_stage2, 0, sp) + .set push + .set noreorder + .set noat + /* Save all registers to the top CVMSEG. This shouldn't + * corrupt any state used by the kernel. Also all registers + * should have the value right before the NMI. */ + SAVE_REG(0) + SAVE_REG(1) + SAVE_REG(2) + SAVE_REG(3) + SAVE_REG(4) + SAVE_REG(5) + SAVE_REG(6) + SAVE_REG(7) + SAVE_REG(8) + SAVE_REG(9) + SAVE_REG(10) + SAVE_REG(11) + SAVE_REG(12) + SAVE_REG(13) + SAVE_REG(14) + SAVE_REG(15) + SAVE_REG(16) + SAVE_REG(17) + SAVE_REG(18) + SAVE_REG(19) + SAVE_REG(20) + SAVE_REG(21) + SAVE_REG(22) + SAVE_REG(23) + SAVE_REG(24) + SAVE_REG(25) + SAVE_REG(26) + SAVE_REG(27) + SAVE_REG(28) + SAVE_REG(29) + SAVE_REG(30) + SAVE_REG(31) + /* Set the stack to begin right below the registers */ + li sp, -32768+6912-32*8 + /* Load the address of the third stage handler */ + dla a0, octeon_wdt_nmi_stage3 + /* Call the third stage handler */ + jal a0 + /* a0 is the address of the saved registers */ + move a0, sp + /* Loop forvever if we get here. */ +1: b 1b + nop + .set pop + END(octeon_wdt_nmi_stage2) -- cgit v1.2.3 From 96cb4eb019ce3185ec0d946a74b5a2202f5067c9 Mon Sep 17 00:00:00 2001 From: Giel van Schijndel Date: Sun, 1 Aug 2010 15:30:55 +0200 Subject: watchdog: f71808e_wdt: new watchdog driver for Fintek F71808E and F71882FG Add a new watchdog driver for the Fintek F71808E and F71882FG Super I/O controllers. Signed-off-by: Giel van Schijndel Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 11 + drivers/watchdog/Makefile | 1 + drivers/watchdog/f71808e_wdt.c | 768 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 780 insertions(+) create mode 100644 drivers/watchdog/f71808e_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b04b18468932..910e09f49b36 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -401,6 +401,17 @@ config ALIM7101_WDT Most people will say N. +config F71808E_WDT + tristate "Fintek F71808E and F71882FG Watchdog" + depends on X86 && EXPERIMENTAL + help + This is the driver for the hardware watchdog on the Fintek + F71808E and F71882FG Super I/O controllers. + + You can compile this driver directly into the kernel, or use + it as a module. The module will be called f71808e_wdt. + + config GEODE_WDT tristate "AMD Geode CS5535/CS5536 Watchdog" depends on CS5535_MFGPT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index e30289a5e367..0010ae55556a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o +obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o obj-$(CONFIG_SC520_WDT) += sc520_wdt.o obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c new file mode 100644 index 000000000000..7e5c266cda48 --- /dev/null +++ b/drivers/watchdog/f71808e_wdt.c @@ -0,0 +1,768 @@ +/*************************************************************************** + * Copyright (C) 2006 by Hans Edgington * + * Copyright (C) 2007-2009 Hans de Goede * + * Copyright (C) 2010 Giel van Schijndel * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "f71808e_wdt" + +#define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */ +#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_DEVREV 0x22 /* Device revision */ +#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ +#define SIO_F71808_ID 0x0901 /* Chipset ID */ +#define SIO_F71858_ID 0x0507 /* Chipset ID */ +#define SIO_F71862_ID 0x0601 /* Chipset ID */ +#define SIO_F71882_ID 0x0541 /* Chipset ID */ +#define SIO_F71889_ID 0x0723 /* Chipset ID */ + +#define F71882FG_REG_START 0x01 + +#define F71808FG_REG_WDO_CONF 0xf0 +#define F71808FG_REG_WDT_CONF 0xf5 +#define F71808FG_REG_WD_TIME 0xf6 + +#define F71808FG_FLAG_WDOUT_EN 7 + +#define F71808FG_FLAG_WDTMOUT_STS 5 +#define F71808FG_FLAG_WD_EN 5 +#define F71808FG_FLAG_WD_PULSE 4 +#define F71808FG_FLAG_WD_UNIT 3 + +/* Default values */ +#define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */ +#define WATCHDOG_MAX_TIMEOUT (60 * 255) +#define WATCHDOG_PULSE_WIDTH 125 /* 125 ms, default pulse width for + watchdog signal */ + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static const int max_timeout = WATCHDOG_MAX_TIMEOUT; +static int timeout = 60; /* default timeout in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1<= timeout <=" + __MODULE_STRING(WATCHDOG_MAX_TIMEOUT) " (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static unsigned int pulse_width = WATCHDOG_PULSE_WIDTH; +module_param(pulse_width, uint, 0); +MODULE_PARM_DESC(pulse_width, + "Watchdog signal pulse width. 0(=level), 1 ms, 25 ms, 125 ms or 5000 ms" + " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0444); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +static unsigned int start_withtimeout; +module_param(start_withtimeout, uint, 0); +MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with" + " given initial timeout. Zero (default) disables this feature."); + +enum chips { f71808fg, f71858fg, f71862fg, f71882fg, f71889fg }; + +static const char *f71808e_names[] = { + "f71808fg", + "f71858fg", + "f71862fg", + "f71882fg", + "f71889fg", +}; + +/* Super-I/O Function prototypes */ +static inline int superio_inb(int base, int reg); +static inline int superio_inw(int base, int reg); +static inline void superio_outb(int base, int reg, u8 val); +static inline void superio_set_bit(int base, int reg, int bit); +static inline void superio_clear_bit(int base, int reg, int bit); +static inline int superio_enter(int base); +static inline void superio_select(int base, int ld); +static inline void superio_exit(int base); + +struct watchdog_data { + unsigned short sioaddr; + enum chips type; + unsigned long opened; + struct mutex lock; + char expect_close; + struct watchdog_info ident; + + unsigned short timeout; + u8 timer_val; /* content for the wd_time register */ + char minutes_mode; + u8 pulse_val; /* pulse width flag */ + char pulse_mode; /* enable pulse output mode? */ + char caused_reboot; /* last reboot was by the watchdog */ +}; + +static struct watchdog_data watchdog = { + .lock = __MUTEX_INITIALIZER(watchdog.lock), +}; + +/* Super I/O functions */ +static inline int superio_inb(int base, int reg) +{ + outb(reg, base); + return inb(base + 1); +} + +static int superio_inw(int base, int reg) +{ + int val; + val = superio_inb(base, reg) << 8; + val |= superio_inb(base, reg + 1); + return val; +} + +static inline void superio_outb(int base, int reg, u8 val) +{ + outb(reg, base); + outb(val, base + 1); +} + +static inline void superio_set_bit(int base, int reg, int bit) +{ + unsigned long val = superio_inb(base, reg); + __set_bit(bit, &val); + superio_outb(base, reg, val); +} + +static inline void superio_clear_bit(int base, int reg, int bit) +{ + unsigned long val = superio_inb(base, reg); + __clear_bit(bit, &val); + superio_outb(base, reg, val); +} + +static inline int superio_enter(int base) +{ + /* Don't step on other drivers' I/O space by accident */ + if (!request_muxed_region(base, 2, DRVNAME)) { + printk(KERN_ERR DRVNAME ": I/O address 0x%04x already in use\n", + (int)base); + return -EBUSY; + } + + /* according to the datasheet the key must be send twice! */ + outb(SIO_UNLOCK_KEY, base); + outb(SIO_UNLOCK_KEY, base); + + return 0; +} + +static inline void superio_select(int base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(int base) +{ + outb(SIO_LOCK_KEY, base); + release_region(base, 2); +} + +static int watchdog_set_timeout(int timeout) +{ + if (timeout <= 0 + || timeout > max_timeout) { + printk(KERN_ERR DRVNAME ": watchdog timeout out of range\n"); + return -EINVAL; + } + + mutex_lock(&watchdog.lock); + + watchdog.timeout = timeout; + if (timeout > 0xff) { + watchdog.timer_val = DIV_ROUND_UP(timeout, 60); + watchdog.minutes_mode = true; + } else { + watchdog.timer_val = timeout; + watchdog.minutes_mode = false; + } + + mutex_unlock(&watchdog.lock); + + return 0; +} + +static int watchdog_set_pulse_width(unsigned int pw) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + + if (pw <= 1) { + watchdog.pulse_val = 0; + } else if (pw <= 25) { + watchdog.pulse_val = 1; + } else if (pw <= 125) { + watchdog.pulse_val = 2; + } else if (pw <= 5000) { + watchdog.pulse_val = 3; + } else { + printk(KERN_ERR DRVNAME ": pulse width out of range\n"); + err = -EINVAL; + goto exit_unlock; + } + + watchdog.pulse_mode = pw; + +exit_unlock: + mutex_unlock(&watchdog.lock); + return err; +} + +static int watchdog_keepalive(void) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + if (watchdog.minutes_mode) + /* select minutes for timer units */ + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + else + /* select seconds for timer units */ + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + + /* Set timer value */ + superio_outb(watchdog.sioaddr, F71808FG_REG_WD_TIME, + watchdog.timer_val); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + return err; +} + +static int watchdog_start(void) +{ + /* Make sure we don't die as soon as the watchdog is enabled below */ + int err = watchdog_keepalive(); + if (err) + return err; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + /* Watchdog pin configuration */ + switch (watchdog.type) { + case f71808fg: + /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ + superio_clear_bit(watchdog.sioaddr, 0x2a, 3); + superio_clear_bit(watchdog.sioaddr, 0x2b, 3); + break; + + case f71882fg: + /* Set pin 56 to WDTRST# */ + superio_set_bit(watchdog.sioaddr, 0x29, 1); + break; + + default: + /* + * 'default' label to shut up the compiler and catch + * programmer errors + */ + err = -ENODEV; + goto exit_superio; + } + + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + superio_set_bit(watchdog.sioaddr, SIO_REG_ENABLE, 0); + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDO_CONF, + F71808FG_FLAG_WDOUT_EN); + + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + if (watchdog.pulse_mode) { + /* Select "pulse" output mode with given duration */ + u8 wdt_conf = superio_inb(watchdog.sioaddr, + F71808FG_REG_WDT_CONF); + + /* Set WD_PSWIDTH bits (1:0) */ + wdt_conf = (wdt_conf & 0xfc) | (watchdog.pulse_val & 0x03); + /* Set WD_PULSE to "pulse" mode */ + wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE); + + superio_outb(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + wdt_conf); + } else { + /* Select "level" output mode */ + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_PULSE); + } + +exit_superio: + superio_exit(watchdog.sioaddr); +exit_unlock: + mutex_unlock(&watchdog.lock); + + return err; +} + +static int watchdog_stop(void) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + + return err; +} + +static int watchdog_get_status(void) +{ + int status = 0; + + mutex_lock(&watchdog.lock); + status = (watchdog.caused_reboot) ? WDIOF_CARDRESET : 0; + mutex_unlock(&watchdog.lock); + + return status; +} + +static bool watchdog_is_running(void) +{ + /* + * if we fail to determine the watchdog's status assume it to be + * running to be on the safe side + */ + bool is_running = true; + + mutex_lock(&watchdog.lock); + if (superio_enter(watchdog.sioaddr)) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + is_running = (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0)) + && (superio_inb(watchdog.sioaddr, F71808FG_REG_WDT_CONF) + & F71808FG_FLAG_WD_EN); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + return is_running; +} + +/* /dev/watchdog api */ + +static int watchdog_open(struct inode *inode, struct file *file) +{ + int err; + + /* If the watchdog is alive we don't need to start it again */ + if (test_and_set_bit(0, &watchdog.opened)) + return -EBUSY; + + err = watchdog_start(); + if (err) { + clear_bit(0, &watchdog.opened); + return err; + } + + if (nowayout) + __module_get(THIS_MODULE); + + watchdog.expect_close = 0; + return nonseekable_open(inode, file); +} + +static int watchdog_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &watchdog.opened); + + if (!watchdog.expect_close) { + watchdog_keepalive(); + printk(KERN_CRIT DRVNAME + ": Unexpected close, not stopping watchdog!\n"); + } else if (!nowayout) { + watchdog_stop(); + } + return 0; +} + +/* + * watchdog_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 we don't define content meaning. + */ + +static ssize_t watchdog_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + bool expect_close = false; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + expect_close = (c == 'V'); + } + + /* Properly order writes across fork()ed processes */ + mutex_lock(&watchdog.lock); + watchdog.expect_close = expect_close; + mutex_unlock(&watchdog.lock); + } + + /* someone wrote to us, we should restart timer */ + watchdog_keepalive(); + } + return count; +} + +/* + * watchdog_ioctl: + * @inode: inode of the device + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. + */ +static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int status; + int new_options; + int 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, &watchdog.ident, + sizeof(watchdog.ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + status = watchdog_get_status(); + if (status < 0) + return status; + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_SETOPTIONS: + if (get_user(new_options, uarg.i)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) + watchdog_stop(); + + if (new_options & WDIOS_ENABLECARD) + return watchdog_start(); + + + case WDIOC_KEEPALIVE: + watchdog_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (watchdog_set_timeout(new_timeout)) + return -EINVAL; + + watchdog_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(watchdog.timeout, uarg.i); + + default: + return -ENOTTY; + + } +} + +static int watchdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + watchdog_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, +}; + +static struct miscdevice watchdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &watchdog_fops, +}; + +static struct notifier_block watchdog_notifier = { + .notifier_call = watchdog_notify_sys, +}; + +static int __init watchdog_init(int sioaddr) +{ + int wdt_conf, err = 0; + + /* No need to lock watchdog.lock here because no entry points + * into the module have been registered yet. + */ + watchdog.sioaddr = sioaddr; + watchdog.ident.options = WDIOC_SETTIMEOUT + | WDIOF_MAGICCLOSE + | WDIOF_KEEPALIVEPING; + + snprintf(watchdog.ident.identity, + sizeof(watchdog.ident.identity), "%s watchdog", + f71808e_names[watchdog.type]); + + err = superio_enter(sioaddr); + if (err) + return err; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF); + watchdog.caused_reboot = wdt_conf & F71808FG_FLAG_WDTMOUT_STS; + + superio_exit(sioaddr); + + err = watchdog_set_timeout(timeout); + if (err) + return err; + err = watchdog_set_pulse_width(pulse_width); + if (err) + return err; + + err = register_reboot_notifier(&watchdog_notifier); + if (err) + return err; + + err = misc_register(&watchdog_miscdev); + if (err) { + printk(KERN_ERR DRVNAME + ": cannot register miscdev on minor=%d\n", + watchdog_miscdev.minor); + goto exit_reboot; + } + + if (start_withtimeout) { + if (start_withtimeout <= 0 + || start_withtimeout > max_timeout) { + printk(KERN_ERR DRVNAME + ": starting timeout out of range\n"); + err = -EINVAL; + goto exit_miscdev; + } + + err = watchdog_start(); + if (err) { + printk(KERN_ERR DRVNAME + ": cannot start watchdog timer\n"); + goto exit_miscdev; + } + + mutex_lock(&watchdog.lock); + err = superio_enter(sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + if (start_withtimeout > 0xff) { + /* select minutes for timer units */ + superio_set_bit(sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + superio_outb(sioaddr, F71808FG_REG_WD_TIME, + DIV_ROUND_UP(start_withtimeout, 60)); + } else { + /* select seconds for timer units */ + superio_clear_bit(sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + superio_outb(sioaddr, F71808FG_REG_WD_TIME, + start_withtimeout); + } + + superio_exit(sioaddr); + mutex_unlock(&watchdog.lock); + + if (nowayout) + __module_get(THIS_MODULE); + + printk(KERN_INFO DRVNAME + ": watchdog started with initial timeout of %u sec\n", + start_withtimeout); + } + + return 0; + +exit_unlock: + mutex_unlock(&watchdog.lock); +exit_miscdev: + misc_deregister(&watchdog_miscdev); +exit_reboot: + unregister_reboot_notifier(&watchdog_notifier); + + return err; +} + +static int __init f71808e_find(int sioaddr) +{ + u16 devid; + int err = superio_enter(sioaddr); + if (err) + return err; + + devid = superio_inw(sioaddr, SIO_REG_MANID); + if (devid != SIO_FINTEK_ID) { + pr_debug(DRVNAME ": Not a Fintek device\n"); + err = -ENODEV; + goto exit; + } + + devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); + switch (devid) { + case SIO_F71808_ID: + watchdog.type = f71808fg; + break; + case SIO_F71882_ID: + watchdog.type = f71882fg; + break; + case SIO_F71862_ID: + case SIO_F71889_ID: + /* These have a watchdog, though it isn't implemented (yet). */ + err = -ENOSYS; + goto exit; + case SIO_F71858_ID: + /* Confirmed (by datasheet) not to have a watchdog. */ + err = -ENODEV; + goto exit; + default: + printk(KERN_INFO DRVNAME ": Unrecognized Fintek device: %04x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + printk(KERN_INFO DRVNAME ": Found %s watchdog chip, revision %d\n", + f71808e_names[watchdog.type], + (int)superio_inb(sioaddr, SIO_REG_DEVREV)); +exit: + superio_exit(sioaddr); + return err; +} + +static int __init f71808e_init(void) +{ + static const unsigned short addrs[] = { 0x2e, 0x4e }; + int err = -ENODEV; + int i; + + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + err = f71808e_find(addrs[i]); + if (err == 0) + break; + } + if (i == ARRAY_SIZE(addrs)) + return err; + + return watchdog_init(addrs[i]); +} + +static void __exit f71808e_exit(void) +{ + if (watchdog_is_running()) { + printk(KERN_WARNING DRVNAME + ": Watchdog timer still running, stopping it\n"); + watchdog_stop(); + } + misc_deregister(&watchdog_miscdev); + unregister_reboot_notifier(&watchdog_notifier); +} + +MODULE_DESCRIPTION("F71808E Watchdog Driver"); +MODULE_AUTHOR("Giel van Schijndel "); +MODULE_LICENSE("GPL"); + +module_init(f71808e_init); +module_exit(f71808e_exit); -- cgit v1.2.3 From 4a370278e1041d4c62719bcd773e9c620e775901 Mon Sep 17 00:00:00 2001 From: Viresh KUMAR Date: Wed, 4 Aug 2010 11:44:14 +0530 Subject: watchdog: Adding support for ARM Primecell SP805 Watchdog Technical Reference Manual can be found at: http://infocenter.arm.com/help/topic/com.arm.doc.ddi0270b/DDI0270.pdf Signed-off-by: Viresh Kumar Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 7 + drivers/watchdog/Makefile | 1 + drivers/watchdog/sp805_wdt.c | 387 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+) create mode 100644 drivers/watchdog/sp805_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 910e09f49b36..4d2992aadfb7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -73,6 +73,13 @@ config WM8350_WATCHDOG # ARM Architecture +config ARM_SP805_WATCHDOG + tristate "ARM SP805 Watchdog" + depends on ARM_AMBA + help + ARM Primecell SP805 Watchdog timer. This will reboot your system when + the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on ARCH_AT91RM9200 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0010ae55556a..8374503fcc6a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ALPHA Architecture # ARM Architecture +obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c new file mode 100644 index 000000000000..9127eda2145b --- /dev/null +++ b/drivers/watchdog/sp805_wdt.c @@ -0,0 +1,387 @@ +/* + * drivers/char/watchdog/sp805-wdt.c + * + * Watchdog driver for ARM SP805 watchdog module + * + * Copyright (C) 2010 ST Microelectronics + * Viresh Kumar + * + * This file is licensed under the terms of the GNU General Public + * License version 2 or later. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* default timeout in seconds */ +#define DEFAULT_TIMEOUT 60 + +#define MODULE_NAME "sp805-wdt" + +/* watchdog register offsets and masks */ +#define WDTLOAD 0x000 + #define LOAD_MIN 0x00000001 + #define LOAD_MAX 0xFFFFFFFF +#define WDTVALUE 0x004 +#define WDTCONTROL 0x008 + /* control register masks */ + #define INT_ENABLE (1 << 0) + #define RESET_ENABLE (1 << 1) +#define WDTINTCLR 0x00C +#define WDTRIS 0x010 +#define WDTMIS 0x014 + #define INT_MASK (1 << 0) +#define WDTLOCK 0xC00 + #define UNLOCK 0x1ACCE551 + #define LOCK 0x00000001 + +/** + * struct sp805_wdt: sp805 wdt device structure + * + * lock: spin lock protecting dev structure and io access + * base: base address of wdt + * clk: clock structure of wdt + * dev: amba device structure of wdt + * status: current status of wdt + * load_val: load value to be set for current timeout + * timeout: current programmed timeout + */ +struct sp805_wdt { + spinlock_t lock; + void __iomem *base; + struct clk *clk; + struct amba_device *adev; + unsigned long status; + #define WDT_BUSY 0 + #define WDT_CAN_BE_CLOSED 1 + unsigned int load_val; + unsigned int timeout; +}; + +/* local variables */ +static struct sp805_wdt *wdt; +static int nowayout = WATCHDOG_NOWAYOUT; + +/* This routine finds load value that will reset system in required timout */ +static void wdt_setload(unsigned int timeout) +{ + u64 load, rate; + + rate = clk_get_rate(wdt->clk); + + /* + * sp805 runs counter with given value twice, after the end of first + * counter it gives an interrupt and then starts counter again. If + * interrupt already occured then it resets the system. This is why + * load is half of what should be required. + */ + load = div_u64(rate, 2) * timeout - 1; + + load = (load > LOAD_MAX) ? LOAD_MAX : load; + load = (load < LOAD_MIN) ? LOAD_MIN : load; + + spin_lock(&wdt->lock); + wdt->load_val = load; + /* roundup timeout to closest positive integer value */ + wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); + spin_unlock(&wdt->lock); +} + +/* returns number of seconds left for reset to occur */ +static u32 wdt_timeleft(void) +{ + u64 load, rate; + + rate = clk_get_rate(wdt->clk); + + spin_lock(&wdt->lock); + load = readl(wdt->base + WDTVALUE); + + /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */ + if (!(readl(wdt->base + WDTRIS) & INT_MASK)) + load += wdt->load_val + 1; + spin_unlock(&wdt->lock); + + return div_u64(load, rate); +} + +/* enables watchdog timers reset */ +static void wdt_enable(void) +{ + spin_lock(&wdt->lock); + + writel(UNLOCK, wdt->base + WDTLOCK); + writel(wdt->load_val, wdt->base + WDTLOAD); + writel(INT_MASK, wdt->base + WDTINTCLR); + writel(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); + writel(LOCK, wdt->base + WDTLOCK); + + spin_unlock(&wdt->lock); +} + +/* disables watchdog timers reset */ +static void wdt_disable(void) +{ + spin_lock(&wdt->lock); + + writel(UNLOCK, wdt->base + WDTLOCK); + writel(0, wdt->base + WDTCONTROL); + writel(0, wdt->base + WDTLOAD); + writel(LOCK, wdt->base + WDTLOCK); + + spin_unlock(&wdt->lock); +} + +static ssize_t sp805_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + /* Check for Magic Close character */ + if (c == 'V') { + set_bit(WDT_CAN_BE_CLOSED, + &wdt->status); + break; + } + } + } + wdt_enable(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, +}; + +static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + unsigned int timeout; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(timeout, (unsigned int *)arg); + if (ret) + break; + + wdt_setload(timeout); + + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(wdt->timeout, (unsigned int *)arg); + break; + case WDIOC_GETTIMELEFT: + ret = put_user(wdt_timeleft(), (unsigned int *)arg); + break; + } + return ret; +} + +static int sp805_wdt_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + if (test_and_set_bit(WDT_BUSY, &wdt->status)) + return -EBUSY; + + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock enable fail"); + goto err; + } + + wdt_enable(); + + /* can not be closed, once enabled */ + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + return nonseekable_open(inode, file); + +err: + clear_bit(WDT_BUSY, &wdt->status); + return ret; +} + +static int sp805_wdt_release(struct inode *inode, struct file *file) +{ + if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { + clear_bit(WDT_BUSY, &wdt->status); + dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n"); + return 0; + } + + wdt_disable(); + clk_disable(wdt->clk); + clear_bit(WDT_BUSY, &wdt->status); + + return 0; +} + +static const struct file_operations sp805_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sp805_wdt_write, + .unlocked_ioctl = sp805_wdt_ioctl, + .open = sp805_wdt_open, + .release = sp805_wdt_release, +}; + +static struct miscdevice sp805_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sp805_wdt_fops, +}; + +static int __devinit +sp805_wdt_probe(struct amba_device *adev, struct amba_id *id) +{ + int ret = 0; + + if (!request_mem_region(adev->res.start, resource_size(&adev->res), + "sp805_wdt")) { + dev_warn(&adev->dev, "Failed to get memory region resource\n"); + ret = -ENOENT; + goto err; + } + + wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); + if (!wdt) { + dev_warn(&adev->dev, "Kzalloc failed\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + + wdt->clk = clk_get(&adev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_warn(&adev->dev, "Clock not found\n"); + ret = PTR_ERR(wdt->clk); + goto err_clk_get; + } + + wdt->base = ioremap(adev->res.start, resource_size(&adev->res)); + if (!wdt->base) { + ret = -ENOMEM; + dev_warn(&adev->dev, "ioremap fail\n"); + goto err_ioremap; + } + + wdt->adev = adev; + spin_lock_init(&wdt->lock); + wdt_setload(DEFAULT_TIMEOUT); + + ret = misc_register(&sp805_wdt_miscdev); + if (ret < 0) { + dev_warn(&adev->dev, "cannot register misc device\n"); + goto err_misc_register; + } + + dev_info(&adev->dev, "registration successful\n"); + return 0; + +err_misc_register: + iounmap(wdt->base); +err_ioremap: + clk_put(wdt->clk); +err_clk_get: + kfree(wdt); + wdt = NULL; +err_kzalloc: + release_mem_region(adev->res.start, resource_size(&adev->res)); +err: + dev_err(&adev->dev, "Probe Failed!!!\n"); + return ret; +} + +static int __devexit sp805_wdt_remove(struct amba_device *adev) +{ + misc_deregister(&sp805_wdt_miscdev); + iounmap(wdt->base); + clk_put(wdt->clk); + kfree(wdt); + release_mem_region(adev->res.start, resource_size(&adev->res)); + + return 0; +} + +static struct amba_id sp805_wdt_ids[] __initdata = { + { + .id = 0x00141805, + .mask = 0x00ffffff, + }, + { 0, 0 }, +}; + +static struct amba_driver sp805_wdt_driver = { + .drv = { + .name = MODULE_NAME, + }, + .id_table = sp805_wdt_ids, + .probe = sp805_wdt_probe, + .remove = __devexit_p(sp805_wdt_remove), +}; + +static int __init sp805_wdt_init(void) +{ + return amba_driver_register(&sp805_wdt_driver); +} +module_init(sp805_wdt_init); + +static void __exit sp805_wdt_exit(void) +{ + amba_driver_unregister(&sp805_wdt_driver); +} +module_exit(sp805_wdt_exit); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); + +MODULE_AUTHOR("Viresh Kumar "); +MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- cgit v1.2.3 From 36e3ff44cebd7e46756dec88f30c982bebefdab7 Mon Sep 17 00:00:00 2001 From: dann frazier Date: Tue, 27 Jul 2010 17:50:57 -0600 Subject: watchdog: hpwdt (4/12): Despecificate driver from iLO2 This driver supports both iLO2 and iLO3, but our user-visible strings currently only reference iLO2. Let's just call it "iLO2+" to avoid having to update strings for each iLO generation. This driver doesn't support iLO ASICs prior to iLO2, but that is sufficiently explained in Kconfig. Signed-off-by: dann frazier Acked-by: Thomas Mingarelli Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 4 ++-- drivers/watchdog/hpwdt.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4d2992aadfb7..cee25e401440 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -574,11 +574,11 @@ config IT87_WDT be called it87_wdt. config HP_WATCHDOG - tristate "HP Proliant iLO 2 Hardware Watchdog Timer" + tristate "HP Proliant iLO2+ Hardware Watchdog Timer" depends on X86 help A software monitoring watchdog and NMI sourcing driver. This driver - will detect lockups and provide stack trace. Also, when an NMI + will detect lockups and provide a stack trace. Also, when an NMI occurs this driver will make the necessary BIOS calls to log the cause of the NMI. This is a driver that will only load on a HP ProLiant system with a minimum of iLO2 support. diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index f0ecb14990df..e18f6b9f7947 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -48,8 +48,8 @@ static unsigned long __iomem *hpwdt_timer_reg; static unsigned long __iomem *hpwdt_timer_con; static struct pci_device_id hpwdt_devices[] = { - { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, - { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, /* iLO2 */ + { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, /* iLO3 */ {0}, /* terminate list */ }; MODULE_DEVICE_TABLE(pci, hpwdt_devices); @@ -548,7 +548,7 @@ static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .identity = "HP iLO2 HW Watchdog Timer", + .identity = "HP iLO2+ HW Watchdog Timer", }; static long hpwdt_ioctl(struct file *file, unsigned int cmd, @@ -654,13 +654,13 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, hpwdt_check_nmi_sourcing(dev); /* - * First let's find out if we are on an iLO2 server. We will + * First let's find out if we are on an iLO2+ server. We will * not run on a legacy ASM box. * So we only support the G5 ProLiant servers and higher. */ if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) { dev_warn(&dev->dev, - "This server does not have an iLO2 ASIC.\n"); + "This server does not have an iLO2+ ASIC.\n"); return -ENODEV; } @@ -674,7 +674,7 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, pci_mem_addr = pci_iomap(dev, 1, 0x80); if (!pci_mem_addr) { dev_warn(&dev->dev, - "Unable to detect the iLO2 server memory.\n"); + "Unable to detect the iLO2+ server memory.\n"); retval = -ENOMEM; goto error_pci_iomap; } -- cgit v1.2.3 From 86ded1f35df32ad795cfc8cc1bdaeffbcaec0d5f Mon Sep 17 00:00:00 2001 From: dann frazier Date: Tue, 27 Jul 2010 17:51:02 -0600 Subject: watchdog: hpwdt (12/12): Make NMI decoding a compile-time option hpwdt is quite functional without the NMI decoding feature. This change lets users disable the NMI portion at compile-time via the new HPWDT_NMI_DECODING config option. Signed-off-by: dann frazier Acked-by: Thomas Mingarelli Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 17 +++++++++++------ drivers/watchdog/hpwdt.c | 27 ++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 7 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index cee25e401440..b036677df8c4 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -578,12 +578,17 @@ config HP_WATCHDOG depends on X86 help A software monitoring watchdog and NMI sourcing driver. This driver - will detect lockups and provide a stack trace. Also, when an NMI - occurs this driver will make the necessary BIOS calls to log - the cause of the NMI. This is a driver that will only load on a - HP ProLiant system with a minimum of iLO2 support. - To compile this driver as a module, choose M here: the - module will be called hpwdt. + will detect lockups and provide a stack trace. This is a driver that + will only load on a HP ProLiant system with a minimum of iLO2 support. + To compile this driver as a module, choose M here: the module will be + called hpwdt. + +config HPWDT_NMI_DECODING + bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer" + depends on HP_WATCHDOG + help + When an NMI occurs this feature will make the necessary BIOS calls to + log the cause of the NMI. config SC1200_WDT tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 850f17877e9c..3d77116e4634 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -27,14 +27,16 @@ #include #include #include +#ifdef CONFIG_HPWDT_NMI_DECODING #include #include #include #include #include #include +#endif /* CONFIG_HPWDT_NMI_DECODING */ -#define HPWDT_VERSION "1.1.1" +#define HPWDT_VERSION "1.2.0" #define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) #define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) #define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) @@ -57,6 +59,7 @@ static struct pci_device_id hpwdt_devices[] = { }; MODULE_DEVICE_TABLE(pci, hpwdt_devices); +#ifdef CONFIG_HPWDT_NMI_DECODING #define PCI_BIOS32_SD_VALUE 0x5F32335F /* "_32_" */ #define CRU_BIOS_SIGNATURE_VALUE 0x55524324 #define PCI_BIOS32_PARAGRAPH_LEN 16 @@ -407,6 +410,7 @@ static int __devinit detect_cru_service(void) } /* ------------------------------------------------------------------------- */ #endif /* CONFIG_X86_64 */ +#endif /* CONFIG_HPWDT_NMI_DECODING */ /* * Watchdog operations @@ -455,6 +459,7 @@ static int hpwdt_time_left(void) return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); } +#ifdef CONFIG_HPWDT_NMI_DECODING /* * NMI Handler */ @@ -487,6 +492,7 @@ static int hpwdt_pretimeout(struct notifier_block *nb, unsigned long ulReason, out: return NOTIFY_OK; } +#endif /* CONFIG_HPWDT_NMI_DECODING */ /* * /dev/watchdog handling @@ -624,15 +630,18 @@ static struct miscdevice hpwdt_miscdev = { .fops = &hpwdt_fops, }; +#ifdef CONFIG_HPWDT_NMI_DECODING static struct notifier_block die_notifier = { .notifier_call = hpwdt_pretimeout, .priority = 0, }; +#endif /* CONFIG_HPWDT_NMI_DECODING */ /* * Init & Exit */ +#ifdef CONFIG_HPWDT_NMI_DECODING #ifdef ARCH_HAS_NMI_WATCHDOG static void __devinit hpwdt_check_nmi_decoding(struct pci_dev *dev) { @@ -712,6 +721,20 @@ static void __devexit hpwdt_exit_nmi_decoding(void) if (cru_rom_addr) iounmap(cru_rom_addr); } +#else /* !CONFIG_HPWDT_NMI_DECODING */ +static void __devinit hpwdt_check_nmi_decoding(struct pci_dev *dev) +{ +} + +static int __devinit hpwdt_init_nmi_decoding(struct pci_dev *dev) +{ + return 0; +} + +static void __devexit hpwdt_exit_nmi_decoding(void) +{ +} +#endif /* CONFIG_HPWDT_NMI_DECODING */ static int __devinit hpwdt_init_one(struct pci_dev *dev, const struct pci_device_id *ent) @@ -823,12 +846,14 @@ module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#ifdef CONFIG_HPWDT_NMI_DECODING module_param(allow_kdump, int, 0); MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); module_param(priority, int, 0); MODULE_PARM_DESC(priority, "The hpwdt driver handles NMIs first or last" " (default = 0/Last)\n"); +#endif /* !CONFIG_HPWDT_NMI_DECODING */ module_init(hpwdt_init); module_exit(hpwdt_cleanup); -- cgit v1.2.3 From 0a18e15598274b79ce14342ce0bfb76a87dadb45 Mon Sep 17 00:00:00 2001 From: Kevin Wells Date: Tue, 17 Aug 2010 17:45:28 -0700 Subject: watchdog: Enable NXP LPC32XX support in Kconfig (resend) The NXP LPC32XX processor use the same watchdog as the Philips PNX4008 processor. Signed-off-by: Kevin Wells Tested-by: Wolfram Sang Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b036677df8c4..24efd8ea41bb 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -213,11 +213,11 @@ config OMAP_WATCHDOG here to enable the OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog timer. config PNX4008_WATCHDOG - tristate "PNX4008 Watchdog" - depends on ARCH_PNX4008 + tristate "PNX4008 and LPC32XX Watchdog" + depends on ARCH_PNX4008 || ARCH_LPC32XX help Say Y here if to include support for the watchdog timer - in the PNX4008 processor. + in the PNX4008 or LPC32XX processor. This driver can be built as a module by choosing M. The module will be called pnx4008_wdt. -- cgit v1.2.3 From fbdd7144ceadd578bc2a875af1dabd67e80ba0d0 Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Mon, 20 Sep 2010 11:23:42 -0500 Subject: powerpc/watchdog: Allow the Book-E driver to be compiled as a module Register the __init and __exit functions in the PowerPC Book-E Watchdog driver as module entry/exit functions, and modify the Kconfig entry. Add a .release method for the PowerPC Book-E Watchdog driver, so that the watchdog is disabled when the driver is closed. Loosely based on original code from Jiang Yutang . Signed-off-by: Timur Tabi Signed-off-by: Kumar Gala --- drivers/watchdog/Kconfig | 5 ++++- drivers/watchdog/booke_wdt.c | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 24efd8ea41bb..a6812eb31fa1 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -957,9 +957,12 @@ config PIKA_WDT the Warp platform. config BOOKE_WDT - bool "PowerPC Book-E Watchdog Timer" + tristate "PowerPC Book-E Watchdog Timer" depends on BOOKE || 4xx ---help--- + Watchdog driver for PowerPC Book-E chips, such as the Freescale + MPC85xx SOCs and the IBM PowerPC 440. + Please see Documentation/watchdog/watchdog-api.txt for more information. diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c index 3d49671cdf5a..a9899981fd97 100644 --- a/drivers/watchdog/booke_wdt.c +++ b/drivers/watchdog/booke_wdt.c @@ -4,7 +4,7 @@ * Author: Matthew McClintock * Maintainer: Kumar Gala * - * Copyright 2005, 2008 Freescale Semiconductor Inc. + * Copyright 2005, 2008, 2010 Freescale Semiconductor Inc. * * 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 @@ -114,6 +114,27 @@ static void __booke_wdt_enable(void *data) mtspr(SPRN_TCR, val); } +/** + * booke_wdt_disable - disable the watchdog on the given CPU + * + * This function is called on each CPU. It disables the watchdog on that CPU. + * + * TCR[WRC] cannot be changed once it has been set to non-zero, but we can + * effectively disable the watchdog by setting its period to the maximum value. + */ +static void __booke_wdt_disable(void *data) +{ + u32 val; + + val = mfspr(SPRN_TCR); + val &= ~(TCR_WIE | WDTP_MASK); + mtspr(SPRN_TCR, val); + + /* clear status to make sure nothing is pending */ + __booke_wdt_ping(NULL); + +} + static ssize_t booke_wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { @@ -193,12 +214,21 @@ static int booke_wdt_open(struct inode *inode, struct file *file) return nonseekable_open(inode, file); } +static int booke_wdt_release(struct inode *inode, struct file *file) +{ + on_each_cpu(__booke_wdt_disable, NULL, 0); + booke_wdt_enabled = 0; + + return 0; +} + static const struct file_operations booke_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = booke_wdt_write, .unlocked_ioctl = booke_wdt_ioctl, .open = booke_wdt_open, + .release = booke_wdt_release, }; static struct miscdevice booke_wdt_miscdev = { @@ -237,4 +267,9 @@ static int __init booke_wdt_init(void) return ret; } -device_initcall(booke_wdt_init); + +module_init(booke_wdt_init); +module_exit(booke_wdt_exit); + +MODULE_DESCRIPTION("PowerPC Book-E watchdog driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From e0dc09ff9a28f37441c5e92a14de6abda8db49d6 Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Wed, 13 Oct 2010 14:19:36 -0500 Subject: powerpc/watchdog: Make default timeout for Book-E watchdog a Kconfig option The PowerPC Book-E watchdog driver (booke_wdt.c) defines a default timeout value in the code based on whether it's a Freescale Book-E part of not. Instead of having hard-coded values in the driver, make it a Kconfig option. As newer chips gets faster, the current default values become less appropriate, since the timeout sometimes occurs before the kernel finishes booting. Making the value a Kconfig option allows BSPs to configure a new value without requiring the wdt_period command-line parameter to be set. Signed-off-by: Timur Tabi Signed-off-by: Kumar Gala --- drivers/watchdog/Kconfig | 17 +++++++++++++++++ drivers/watchdog/booke_wdt.c | 8 +------- 2 files changed, 18 insertions(+), 7 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a6812eb31fa1..c356146bd712 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -966,6 +966,23 @@ config BOOKE_WDT Please see Documentation/watchdog/watchdog-api.txt for more information. +config BOOKE_WDT_DEFAULT_TIMEOUT + int "PowerPC Book-E Watchdog Timer Default Timeout" + depends on BOOKE_WDT + default 38 if FSL_BOOKE + range 0 63 if FSL_BOOKE + default 3 if !FSL_BOOKE + range 0 3 if !FSL_BOOKE + help + Select the default watchdog timer period to be used by the PowerPC + Book-E watchdog driver. A watchdog "event" occurs when the bit + position represented by this number transitions from zero to one. + + For Freescale Book-E processors, this is a number between 0 and 63. + For other Book-E processors, this is a number between 0 and 3. + + The value can be overidden by the wdt_period command-line parameter. + # PPC64 Architecture config WATCHDOG_RTAS diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c index a9899981fd97..d11ffb091b0d 100644 --- a/drivers/watchdog/booke_wdt.c +++ b/drivers/watchdog/booke_wdt.c @@ -33,14 +33,8 @@ * occur, and the final time the board will reset. */ -#ifdef CONFIG_FSL_BOOKE -#define WDT_PERIOD_DEFAULT 38 /* Ex. wdt_period=28 bus=333Mhz,reset=~40sec */ -#else -#define WDT_PERIOD_DEFAULT 3 /* Refer to the PPC40x and PPC4xx manuals */ -#endif /* for timing information */ - u32 booke_wdt_enabled; -u32 booke_wdt_period = WDT_PERIOD_DEFAULT; +u32 booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT; #ifdef CONFIG_FSL_BOOKE #define WDTP(x) ((((x)&0x3)<<30)|(((x)&0x3c)<<15)) -- cgit v1.2.3 From ee3e96583e42dcb4bd406ce4e5f824bd5bb80013 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Tue, 14 Sep 2010 02:47:28 +0200 Subject: watchdog: it87_wdt: Add support for IT8720F watchdog This simple patch adds support for a watchdog in IT8720F Super IO chip to it87_wdt driver. Signed-off-by: Ondrej Zajicek Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton --- drivers/watchdog/Kconfig | 9 +++++---- drivers/watchdog/it87_wdt.c | 17 +++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c356146bd712..cc53136122f9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -565,10 +565,11 @@ 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. + This is the driver for the hardware watchdog on the ITE + IT8716, IT8718, IT8720, 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. diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c index b709b3b2d1ef..455de7714618 100644 --- a/drivers/watchdog/it87_wdt.c +++ b/drivers/watchdog/it87_wdt.c @@ -12,7 +12,7 @@ * http://www.ite.com.tw/ * * Support of the watchdog timers, which are available on - * IT8716, IT8718, IT8726 and IT8712 (J,K version). + * IT8716, IT8718, IT8720, 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 @@ -45,7 +45,7 @@ #include -#define WATCHDOG_VERSION "1.12" +#define WATCHDOG_VERSION "1.13" #define WATCHDOG_NAME "IT87 WDT" #define PFX WATCHDOG_NAME ": " #define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" @@ -80,6 +80,7 @@ #define IT8712_ID 0x8712 #define IT8716_ID 0x8716 #define IT8718_ID 0x8718 +#define IT8720_ID 0x8720 #define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */ /* GPIO Configuration Registers LDN=0x07 */ @@ -92,7 +93,7 @@ #define WDT_CIRINT 0x80 #define WDT_MOUSEINT 0x40 #define WDT_KYBINT 0x20 -#define WDT_GAMEPORT 0x10 /* not it8718 */ +#define WDT_GAMEPORT 0x10 /* not in it8718, it8720 */ #define WDT_FORCE 0x02 #define WDT_ZERO 0x01 @@ -529,6 +530,7 @@ static struct notifier_block wdt_notifier = { static int __init it87_wdt_init(void) { int rc = 0; + int try_gameport = !nogameport; u16 chip_type; u8 chip_rev; unsigned long flags; @@ -542,9 +544,12 @@ static int __init it87_wdt_init(void) switch (chip_type) { case IT8716_ID: - case IT8718_ID: case IT8726_ID: break; + case IT8718_ID: + case IT8720_ID: + try_gameport = 0; + break; case IT8712_ID: if (chip_rev > 7) break; @@ -571,7 +576,7 @@ static int __init it87_wdt_init(void) superio_outb(0x00, WDTCTRL); /* First try to get Gameport support */ - if (chip_type != IT8718_ID && !nogameport) { + if (try_gameport) { superio_select(GAMEPORT); base = superio_inw(BASEREG); if (!base) { @@ -676,7 +681,7 @@ err_out_region: spin_unlock_irqrestore(&spinlock, flags); } err_out: - if (chip_type != IT8718_ID && !nogameport) { + if (try_gameport) { spin_lock_irqsave(&spinlock, flags); superio_enter(); superio_select(GAMEPORT); -- cgit v1.2.3 From dfb0b8eae1f78c7d0cf7c8abe6c84ff8cefde50e Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Tue, 14 Sep 2010 02:54:16 +0200 Subject: watchdog: it87_wdt: Add support for watchdogs with 8b timers This patch adds support for watchdogs with 8b timers, like ones in IT8702F and older revisions of IT8712F Super IO chip, to it87_wdt driver. This patch should be used after the patch 'it87_wdt: Add support for IT8720F watchdog'. Signed-off-by: Ondrej Zajicek Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton --- drivers/watchdog/Kconfig | 10 +++--- drivers/watchdog/it87_wdt.c | 81 ++++++++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 28 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index cc53136122f9..5a5c024482fa 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -565,11 +565,11 @@ 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, IT8720, 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. + This is the driver for the hardware watchdog on the ITE IT8702, + IT8712, IT8716, IT8718, IT8720, IT8726, IT8712 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. diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c index 455de7714618..dad29245a6a7 100644 --- a/drivers/watchdog/it87_wdt.c +++ b/drivers/watchdog/it87_wdt.c @@ -12,7 +12,7 @@ * http://www.ite.com.tw/ * * Support of the watchdog timers, which are available on - * IT8716, IT8718, IT8720, IT8726 and IT8712 (J,K version). + * IT8702, IT8712, IT8716, IT8718, IT8720 and IT8726. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -76,6 +76,7 @@ /* Chip Id numbers */ #define NO_DEV_ID 0xffff +#define IT8702_ID 0x8702 #define IT8705_ID 0x8705 #define IT8712_ID 0x8712 #define IT8716_ID 0x8716 @@ -133,7 +134,7 @@ #define WDTS_USE_GP 4 #define WDTS_EXPECTED 5 -static unsigned int base, gpact, ciract; +static unsigned int base, gpact, ciract, max_units; static unsigned long wdt_status; static DEFINE_SPINLOCK(spinlock); @@ -211,6 +212,33 @@ static inline void superio_outw(int val, int reg) outb(val, VAL); } +/* Internal function, should be called after superio_select(GPIO) */ +static void wdt_update_timeout(void) +{ + unsigned char cfg = WDT_KRST | WDT_PWROK; + int tm = timeout; + + if (testmode) + cfg = 0; + + if (tm <= max_units) + cfg |= WDT_TOV1; + else + tm /= 60; + + superio_outb(cfg, WDTCFG); + superio_outb(tm, WDTVALLSB); + if (max_units > 255) + superio_outb(tm>>8, WDTVALMSB); +} + +static int wdt_round_time(int t) +{ + t += 59; + t -= t % 60; + return t; +} + /* watchdog timer handling */ static void wdt_keepalive(void) @@ -235,12 +263,7 @@ static void wdt_start(void) 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); + wdt_update_timeout(); superio_exit(); spin_unlock_irqrestore(&spinlock, flags); @@ -256,8 +279,9 @@ static void wdt_stop(void) superio_select(GPIO); superio_outb(0x00, WDTCTRL); superio_outb(WDT_TOV1, WDTCFG); - superio_outb(0x00, WDTVALMSB); superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); superio_exit(); spin_unlock_irqrestore(&spinlock, flags); @@ -267,8 +291,8 @@ static void wdt_stop(void) * 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. + * The hardware device has a 8 or 16 bit watchdog timer (depends on + * chip version) that can be configured to count seconds or minutes. * * Used within WDIOC_SETTIMEOUT watchdog device ioctl. */ @@ -277,19 +301,19 @@ static int wdt_set_timeout(int t) { unsigned long flags; - if (t < 1 || t > 65535) + if (t < 1 || t > max_units * 60) return -EINVAL; - timeout = t; + if (t > max_units) + timeout = wdt_round_time(t); + else + 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); - + wdt_update_timeout(); superio_exit(); } spin_unlock_irqrestore(&spinlock, flags); @@ -535,6 +559,8 @@ static int __init it87_wdt_init(void) u8 chip_rev; unsigned long flags; + wdt_status = 0; + spin_lock_irqsave(&spinlock, flags); superio_enter(); chip_type = superio_inw(CHIPID); @@ -543,16 +569,21 @@ static int __init it87_wdt_init(void) spin_unlock_irqrestore(&spinlock, flags); switch (chip_type) { + case IT8702_ID: + max_units = 255; + break; + case IT8712_ID: + max_units = (chip_rev < 8) ? 255 : 65535; + break; case IT8716_ID: case IT8726_ID: + max_units = 65535; break; case IT8718_ID: case IT8720_ID: + max_units = 65535; try_gameport = 0; break; - case IT8712_ID: - if (chip_rev > 7) - break; case IT8705_ID: printk(KERN_ERR PFX "Unsupported Chip found, Chip %04x Revision %02x\n", @@ -628,13 +659,16 @@ static int __init it87_wdt_init(void) spin_unlock_irqrestore(&spinlock, flags); } - if (timeout < 1 || timeout > 65535) { + if (timeout < 1 || timeout > max_units * 60) { timeout = DEFAULT_TIMEOUT; printk(KERN_WARNING PFX "Timeout value out of range, use default %d sec\n", DEFAULT_TIMEOUT); } + if (timeout > max_units) + timeout = wdt_round_time(timeout); + rc = register_reboot_notifier(&wdt_notifier); if (rc) { printk(KERN_ERR PFX @@ -661,7 +695,7 @@ static int __init it87_wdt_init(void) outb(0x09, CIR_IER(base)); } - printk(KERN_INFO PFX "Chip it%04x revision %d initialized. " + 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); @@ -703,8 +737,9 @@ static void __exit it87_wdt_exit(void) superio_select(GPIO); superio_outb(0x00, WDTCTRL); superio_outb(0x00, WDTCFG); - superio_outb(0x00, WDTVALMSB); superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); if (test_bit(WDTS_USE_GP, &wdt_status)) { superio_select(GAMEPORT); superio_outb(gpact, ACTREG); -- cgit v1.2.3 From dee00abbbcab97b8ee3bbafb5e786dde83e26741 Mon Sep 17 00:00:00 2001 From: Giel van Schijndel Date: Mon, 4 Oct 2010 10:45:28 +0200 Subject: watchdog: f71808e_wdt: add support for the F71889FG Signed-off-by: Giel van Schijndel Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 4 ++-- drivers/watchdog/f71808e_wdt.c | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 5a5c024482fa..57b8a410697b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -409,11 +409,11 @@ config ALIM7101_WDT Most people will say N. config F71808E_WDT - tristate "Fintek F71808E and F71882FG Watchdog" + tristate "Fintek F71808E, F71882FG and F71889FG Watchdog" depends on X86 && EXPERIMENTAL help This is the driver for the hardware watchdog on the Fintek - F71808E and F71882FG Super I/O controllers. + F71808E, F71882FG and F71889FG Super I/O controllers. You can compile this driver directly into the kernel, or use it as a module. The module will be called f71808e_wdt. diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 7e5c266cda48..65e579635dba 100644 --- a/drivers/watchdog/f71808e_wdt.c +++ b/drivers/watchdog/f71808e_wdt.c @@ -308,6 +308,12 @@ static int watchdog_start(void) superio_set_bit(watchdog.sioaddr, 0x29, 1); break; + case f71889fg: + /* set pin 40 to WDTRST# */ + superio_outb(watchdog.sioaddr, 0x2b, + superio_inb(watchdog.sioaddr, 0x2b) & 0xcf); + break; + default: /* * 'default' label to shut up the compiler and catch @@ -708,8 +714,10 @@ static int __init f71808e_find(int sioaddr) case SIO_F71882_ID: watchdog.type = f71882fg; break; - case SIO_F71862_ID: case SIO_F71889_ID: + watchdog.type = f71889fg; + break; + case SIO_F71862_ID: /* These have a watchdog, though it isn't implemented (yet). */ err = -ENOSYS; goto exit; -- cgit v1.2.3 From b63aa731cd9e3fed7694a99f9c233f5f4b244f03 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Sat, 28 Aug 2010 22:03:45 +0200 Subject: watchdog: add support for Broadcom BCM63xx built-in watchdog This patch adds support for the Broadcom BCM63xx SoC built-in watchdog, it uses one of the BCM63xx hardware timer id. Signed-off-by: Miguel Gaio Signed-off-by: Florian Fainelli Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 10 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/bcm63xx_wdt.c | 350 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 drivers/watchdog/bcm63xx_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 57b8a410697b..4a291045ebac 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -917,6 +917,16 @@ config OCTEON_WDT from the first interrupt, it is then only poked when the device is written. +config BCM63XX_WDT + tristate "Broadcom BCM63xx hardware watchdog" + depends on BCM63XX + help + Watchdog driver for the built in watchdog hardware in Broadcom + BCM63xx SoC. + + To compile this driver as a loadable module, choose M here. + The module will be called bcm63xx_wdt. + # PARISC Architecture # POWERPC Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 8374503fcc6a..4b0ef386229d 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -109,6 +109,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o # MIPS Architecture obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o +obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o obj-$(CONFIG_INDYDOG) += indydog.o obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c new file mode 100644 index 000000000000..a1debc89356b --- /dev/null +++ b/drivers/watchdog/bcm63xx_wdt.c @@ -0,0 +1,350 @@ +/* + * Broadcom BCM63xx SoC watchdog driver + * + * Copyright (C) 2007, Miguel Gaio + * Copyright (C) 2008, Florian Fainelli + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PFX KBUILD_MODNAME + +#define WDT_HZ 50000000 /* Fclk */ +#define WDT_DEFAULT_TIME 30 /* seconds */ +#define WDT_MAX_TIME 256 /* seconds */ + +static struct { + void __iomem *regs; + struct timer_list timer; + int default_ticks; + unsigned long inuse; + atomic_t ticks; +} bcm63xx_wdt_device; + +static int expect_close; + +static int wdt_time = WDT_DEFAULT_TIME; +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) ")"); + +/* HW functions */ +static void bcm63xx_wdt_hw_start(void) +{ + bcm_writel(0xfffffffe, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG); + bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); + bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); +} + +static void bcm63xx_wdt_hw_stop(void) +{ + bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); + bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); +} + +static void bcm63xx_wdt_isr(void *data) +{ + struct pt_regs *regs = get_irq_regs(); + + die(PFX " fire", regs); +} + +static void bcm63xx_timer_tick(unsigned long unused) +{ + if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) { + bcm63xx_wdt_hw_start(); + mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ); + } else + printk(KERN_CRIT PFX ": watchdog will restart system\n"); +} + +static void bcm63xx_wdt_pet(void) +{ + atomic_set(&bcm63xx_wdt_device.ticks, wdt_time); +} + +static void bcm63xx_wdt_start(void) +{ + bcm63xx_wdt_pet(); + bcm63xx_timer_tick(0); +} + +static void bcm63xx_wdt_pause(void) +{ + del_timer_sync(&bcm63xx_wdt_device.timer); + bcm63xx_wdt_hw_stop(); +} + +static int bcm63xx_wdt_settimeout(int new_time) +{ + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) + return -EINVAL; + + wdt_time = new_time; + + return 0; +} + +static int bcm63xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse)) + return -EBUSY; + + bcm63xx_wdt_start(); + return nonseekable_open(inode, file); +} + +static int bcm63xx_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + bcm63xx_wdt_pause(); + else { + printk(KERN_CRIT PFX + ": Unexpected close, not stopping watchdog!\n"); + bcm63xx_wdt_start(); + } + clear_bit(0, &bcm63xx_wdt_device.inuse); + expect_close = 0; + return 0; +} + +static ssize_t bcm63xx_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + bcm63xx_wdt_pet(); + } + return len; +} + +static struct watchdog_info bcm63xx_wdt_info = { + .identity = PFX, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + + +static long bcm63xx_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, &bcm63xx_wdt_info, + sizeof(bcm63xx_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) { + bcm63xx_wdt_pause(); + retval = 0; + } + if (new_value & WDIOS_ENABLECARD) { + bcm63xx_wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + bcm63xx_wdt_pet(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + if (bcm63xx_wdt_settimeout(new_value)) + return -EINVAL; + + bcm63xx_wdt_pet(); + + case WDIOC_GETTIMEOUT: + return put_user(wdt_time, p); + + default: + return -ENOTTY; + + } +} + +static int bcm63xx_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + bcm63xx_wdt_pause(); + return NOTIFY_DONE; +} + +static const struct file_operations bcm63xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = bcm63xx_wdt_write, + .unlocked_ioctl = bcm63xx_wdt_ioctl, + .open = bcm63xx_wdt_open, + .release = bcm63xx_wdt_release, +}; + +static struct miscdevice bcm63xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bcm63xx_wdt_fops, +}; + +static struct notifier_block bcm63xx_wdt_notifier = { + .notifier_call = bcm63xx_wdt_notify_sys, +}; + + +static int bcm63xx_wdt_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + + setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resources\n"); + return -ENODEV; + } + + bcm63xx_wdt_device.regs = ioremap_nocache(r->start, r->end - r->start); + if (!bcm63xx_wdt_device.regs) { + dev_err(&pdev->dev, "failed to remap I/O resources\n"); + return -ENXIO; + } + + ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register wdt timer isr\n"); + goto unmap; + } + + if (bcm63xx_wdt_settimeout(wdt_time)) { + bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME); + dev_info(&pdev->dev, + ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n", + wdt_time); + } + + ret = register_reboot_notifier(&bcm63xx_wdt_notifier); + if (ret) { + dev_err(&pdev->dev, "failed to register reboot_notifier\n"); + goto unregister_timer; + } + + ret = misc_register(&bcm63xx_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register watchdog device\n"); + goto unregister_reboot_notifier; + } + + dev_info(&pdev->dev, " started, timer margin: %d sec\n", + WDT_DEFAULT_TIME); + + return 0; + +unregister_reboot_notifier: + unregister_reboot_notifier(&bcm63xx_wdt_notifier); +unregister_timer: + bcm63xx_timer_unregister(TIMER_WDT_ID); +unmap: + iounmap(bcm63xx_wdt_device.regs); + return ret; +} + +static int bcm63xx_wdt_remove(struct platform_device *pdev) +{ + if (!nowayout) + bcm63xx_wdt_pause(); + + misc_deregister(&bcm63xx_wdt_miscdev); + + iounmap(bcm63xx_wdt_device.regs); + + unregister_reboot_notifier(&bcm63xx_wdt_notifier); + bcm63xx_timer_unregister(TIMER_WDT_ID); + + return 0; +} + +static struct platform_driver bcm63xx_wdt = { + .probe = bcm63xx_wdt_probe, + .remove = bcm63xx_wdt_remove, + .driver = { + .name = "bcm63xx-wdt", + } +}; + +static int __init bcm63xx_wdt_init(void) +{ + return platform_driver_register(&bcm63xx_wdt); +} + +static void __exit bcm63xx_wdt_exit(void) +{ + platform_driver_unregister(&bcm63xx_wdt); +} + +module_init(bcm63xx_wdt_init); +module_exit(bcm63xx_wdt_exit); + +MODULE_AUTHOR("Miguel Gaio "); +MODULE_AUTHOR("Florian Fainelli "); +MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:bcm63xx-wdt"); -- cgit v1.2.3 From 4fc3680894ff5739e7474b6633e962bfbdf0d3d8 Mon Sep 17 00:00:00 2001 From: Wim Van Sebroeck Date: Thu, 2 Dec 2010 14:03:29 +0000 Subject: watchdog: it8712f_wdt: add note to Kconfig On some motherboards the it8712f watchdog does not work unless the game port was enabled. see Bug 13140. We therefor add a note to Kconfig. Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4a291045ebac..a5ad77ef4266 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -558,6 +558,9 @@ config IT8712F_WDT This is the driver for the built-in watchdog timer on the IT8712F Super I/0 chipset used on many motherboards. + If the driver does not work, then make sure that the game port in + the BIOS is enabled. + To compile this driver as a module, choose M here: the module will be called it8712f_wdt. -- cgit v1.2.3 From 9c67bea419c384561eeb84bdf251d521a3234e45 Mon Sep 17 00:00:00 2001 From: Benny Loenstrup Ammitzboell Date: Thu, 11 Nov 2010 16:08:41 +0100 Subject: watchdog: Add watchdog support for W83627DHG chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following adds watchdog support for the Winbond W83627DHG chip. I have tested it on a PQ7-M102XL (Intel Atom) board. Signed-off-by: Benny Lønstrup Ammitzbøll Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 9 +++++---- drivers/watchdog/w83627hf_wdt.c | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a5ad77ef4266..8a3aa2f050c8 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -722,14 +722,15 @@ config SMSC37B787_WDT Most people will say N. config W83627HF_WDT - tristate "W83627HF Watchdog Timer" + tristate "W83627HF/W83627DHG Watchdog Timer" depends on X86 ---help--- This is the driver for the hardware watchdog on the W83627HF chipset as used in Advantech PC-9578 and Tyan S2721-533 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. + (and likely others). The driver also supports the W83627DHG chip. + 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 w83627hf_wdt. diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index 0f5288df0091..48f2e0148bb4 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c @@ -42,7 +42,7 @@ #include -#define WATCHDOG_NAME "w83627hf/thf/hg WDT" +#define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT" #define PFX WATCHDOG_NAME ": " #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ @@ -89,7 +89,7 @@ static void w83627hf_select_wd_register(void) c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */ outb_p(0x2b, WDT_EFER); outb_p(c, WDT_EFDR); /* set GPIO3 to WDT0 */ - } else if (c == 0x88) { /* W83627EHF */ + } else if (c == 0x88 || c == 0xa0) { /* W83627EHF / W83627DHG */ outb_p(0x2d, WDT_EFER); /* select GPIO5 */ c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */ outb_p(0x2d, WDT_EFER); @@ -321,7 +321,7 @@ static int __init wdt_init(void) { int ret; - printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n"); + printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG/DHG Super I/O chip initialising.\n"); if (wdt_set_heartbeat(timeout)) { wdt_set_heartbeat(WATCHDOG_TIMEOUT); -- cgit v1.2.3 From e13752a1de02044bfda352cbc834e3c9541f148b Mon Sep 17 00:00:00 2001 From: Lutz Ballaschke Date: Wed, 12 Jan 2011 01:09:02 +0100 Subject: watchdog: f71808e_wdt: add F71862FG, F71869 to Kconfig Update Kconfig with the additional Fintek hardware that we support. Signed-off-by: Lutz Ballaschke Acked-by: Giel van Schijndel Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 8a3aa2f050c8..3c6607ddcd20 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -409,11 +409,11 @@ config ALIM7101_WDT Most people will say N. config F71808E_WDT - tristate "Fintek F71808E, F71882FG and F71889FG Watchdog" + tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" depends on X86 && EXPERIMENTAL help This is the driver for the hardware watchdog on the Fintek - F71808E, F71882FG and F71889FG Super I/O controllers. + F71808E, F71862FG, F71869, F71882FG and F71889FG Super I/O controllers. You can compile this driver directly into the kernel, or use it as a module. The module will be called f71808e_wdt. -- cgit v1.2.3 From 15e28bf130081a574192fb934b832ac7d07739f7 Mon Sep 17 00:00:00 2001 From: Priyanka Gupta Date: Mon, 25 Oct 2010 17:58:04 -0700 Subject: watchdog: Add support for sp5100 chipset TCO This driver adds /dev/watchdog support for the AMD sp5100 aka SB7x0 chipsets. It follows the same conventions found in other /dev/watchdog drivers. Signed-off-by: Priyanka Gupta Signed-off-by: Mike Waychison Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 11 + drivers/watchdog/Makefile | 1 + drivers/watchdog/sp5100_tco.c | 480 ++++++++++++++++++++++++++++++++++++++++++ drivers/watchdog/sp5100_tco.h | 41 ++++ 4 files changed, 533 insertions(+) create mode 100644 drivers/watchdog/sp5100_tco.c create mode 100644 drivers/watchdog/sp5100_tco.h (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3c6607ddcd20..c48faa5ade73 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -418,6 +418,17 @@ config F71808E_WDT You can compile this driver directly into the kernel, or use it as a module. The module will be called f71808e_wdt. +config SP5100_TCO + tristate "AMD/ATI SP5100 TCO Timer/Watchdog" + depends on X86 && PCI + ---help--- + Hardware watchdog driver for the AMD/ATI SP5100 chipset. The TCO + (Total Cost of Ownership) timer is a watchdog timer that will reboot + the machine after its expiration. The expiration time can be + configured with the "heartbeat" parameter. + + To compile this driver as a module, choose M here: the + module will be called sp5100_tco. config GEODE_WDT tristate "AMD Geode CS5535/CS5536 Watchdog" diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 4b0ef386229d..12e6c1e32671 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o +obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o obj-$(CONFIG_SC520_WDT) += sc520_wdt.o obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c new file mode 100644 index 000000000000..808372883e88 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.c @@ -0,0 +1,480 @@ +/* + * sp5100_tco : TCO timer driver for sp5100 chipsets + * + * (c) Copyright 2009 Google Inc., All Rights Reserved. + * + * Based on i8xx_tco.c: + * (c) Copyright 2000 kernel concepts , All Rights + * Reserved. + * http://www.kernelconcepts.de + * + * 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. + * + * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide" + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sp5100_tco.h" + +/* Module and version information */ +#define TCO_VERSION "0.01" +#define TCO_MODULE_NAME "SP5100 TCO timer" +#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION +#define PFX TCO_MODULE_NAME ": " + +/* internal variables */ +static void __iomem *tcobase; +static unsigned int pm_iobase; +static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ +static unsigned long timer_alive; +static char tco_expect_close; +static struct pci_dev *sp5100_tco_pci; + +/* the watchdog platform device */ +static struct platform_device *sp5100_tco_platform_device; + +/* module parameters */ + +#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */ +static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" + " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Some TCO specific functions + */ +static void tco_timer_start(void) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&tco_lock, flags); + val = readl(SP5100_WDT_CONTROL(tcobase)); + val |= SP5100_WDT_START_STOP_BIT; + writel(val, SP5100_WDT_CONTROL(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); +} + +static void tco_timer_stop(void) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&tco_lock, flags); + val = readl(SP5100_WDT_CONTROL(tcobase)); + val &= ~SP5100_WDT_START_STOP_BIT; + writel(val, SP5100_WDT_CONTROL(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); +} + +static void tco_timer_keepalive(void) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&tco_lock, flags); + val = readl(SP5100_WDT_CONTROL(tcobase)); + val |= SP5100_WDT_TRIGGER_BIT; + writel(val, SP5100_WDT_CONTROL(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); +} + +static int tco_timer_set_heartbeat(int t) +{ + unsigned long flags; + + if (t < 0 || t > 0xffff) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + spin_lock_irqsave(&tco_lock, flags); + writel(t, SP5100_WDT_COUNT(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); + + heartbeat = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int sp5100_tco_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + /* Reload and activate timer */ + tco_timer_start(); + tco_timer_keepalive(); + return nonseekable_open(inode, file); +} + +static int sp5100_tco_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. */ + if (tco_expect_close == 42) { + tco_timer_stop(); + } else { + printk(KERN_CRIT PFX + "Unexpected close, not stopping watchdog!\n"); + tco_timer_keepalive(); + } + clear_bit(0, &timer_alive); + tco_expect_close = 0; + return 0; +} + +static ssize_t sp5100_tco_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + tco_expect_close = 0; + + /* scan to see whether or not we got the magic character + */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + tco_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + tco_timer_keepalive(); + } + return len; +} + +static long sp5100_tco_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_options, retval = -EINVAL; + int new_heartbeat; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = TCO_MODULE_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + tco_timer_stop(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + tco_timer_start(); + tco_timer_keepalive(); + retval = 0; + } + return retval; + case WDIOC_KEEPALIVE: + tco_timer_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (tco_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + tco_timer_keepalive(); + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations sp5100_tco_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sp5100_tco_write, + .unlocked_ioctl = sp5100_tco_ioctl, + .open = sp5100_tco_open, + .release = sp5100_tco_release, +}; + +static struct miscdevice sp5100_tco_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sp5100_tco_fops, +}; + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might + * want to register another driver on the same PCI id. + */ +static struct pci_device_id sp5100_tco_pci_tbl[] = { + { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID, + PCI_ANY_ID, }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __devinit sp5100_tco_setupdevice(void) +{ + struct pci_dev *dev = NULL; + u32 val; + + /* Match the PCI device */ + for_each_pci_dev(dev) { + if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) { + sp5100_tco_pci = dev; + break; + } + } + + if (!sp5100_tco_pci) + return 0; + + /* Request the IO ports used by this driver */ + pm_iobase = SP5100_IO_PM_INDEX_REG; + if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + pm_iobase); + goto exit; + } + + /* Find the watchdog base address. */ + outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG); + val = inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG); + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG); + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG); + /* Low three bits of BASE0 are reserved. */ + val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8); + + tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); + if (tcobase == 0) { + printk(KERN_ERR PFX "failed to get tcobase address\n"); + goto unreg_region; + } + + /* Enable watchdog decode bit */ + pci_read_config_dword(sp5100_tco_pci, + SP5100_PCI_WATCHDOG_MISC_REG, + &val); + + val |= SP5100_PCI_WATCHDOG_DECODE_EN; + + pci_write_config_dword(sp5100_tco_pci, + SP5100_PCI_WATCHDOG_MISC_REG, + val); + + /* Enable Watchdog timer and set the resolution to 1 sec. */ + outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); + val = inb(SP5100_IO_PM_DATA_REG); + val |= SP5100_PM_WATCHDOG_SECOND_RES; + val &= ~SP5100_PM_WATCHDOG_DISABLE; + outb(val, SP5100_IO_PM_DATA_REG); + + /* Check that the watchdog action is set to reset the system. */ + val = readl(SP5100_WDT_CONTROL(tcobase)); + val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; + writel(val, SP5100_WDT_CONTROL(tcobase)); + + /* Set a reasonable heartbeat before we stop the timer */ + tco_timer_set_heartbeat(heartbeat); + + /* + * Stop the TCO before we change anything so we don't race with + * a zeroed timer. + */ + tco_timer_stop(); + + /* Done */ + return 1; + + iounmap(tcobase); +unreg_region: + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); +exit: + return 0; +} + +static int __devinit sp5100_tco_init(struct platform_device *dev) +{ + int ret; + u32 val; + + /* Check whether or not the hardware watchdog is there. If found, then + * set it up. + */ + if (!sp5100_tco_setupdevice()) + return -ENODEV; + + /* Check to see if last reboot was due to watchdog timeout */ + printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n", + readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ? + "" : "not "); + + /* Clear out the old status */ + val = readl(SP5100_WDT_CONTROL(tcobase)); + val &= ~SP5100_PM_WATCHDOG_FIRED; + writel(val, SP5100_WDT_CONTROL(tcobase)); + + /* + * Check that the heartbeat value is within it's range. + * If not, reset to the default. + */ + if (tco_timer_set_heartbeat(heartbeat)) { + heartbeat = WATCHDOG_HEARTBEAT; + tco_timer_set_heartbeat(heartbeat); + } + + ret = misc_register(&sp5100_tco_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=" + "%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto exit; + } + + clear_bit(0, &timer_alive); + + printk(KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec" + " (nowayout=%d)\n", + tcobase, heartbeat, nowayout); + + return 0; + +exit: + iounmap(tcobase); + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); + return ret; +} + +static void __devexit sp5100_tco_cleanup(void) +{ + /* Stop the timer before we leave */ + if (!nowayout) + tco_timer_stop(); + + /* Deregister */ + misc_deregister(&sp5100_tco_miscdev); + iounmap(tcobase); + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); +} + +static int __devexit sp5100_tco_remove(struct platform_device *dev) +{ + if (tcobase) + sp5100_tco_cleanup(); + return 0; +} + +static void sp5100_tco_shutdown(struct platform_device *dev) +{ + tco_timer_stop(); +} + +static struct platform_driver sp5100_tco_driver = { + .probe = sp5100_tco_init, + .remove = __devexit_p(sp5100_tco_remove), + .shutdown = sp5100_tco_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = TCO_MODULE_NAME, + }, +}; + +static int __init sp5100_tco_init_module(void) +{ + int err; + + printk(KERN_INFO PFX "SP5100 TCO WatchDog Timer Driver v%s\n", + TCO_VERSION); + + err = platform_driver_register(&sp5100_tco_driver); + if (err) + return err; + + sp5100_tco_platform_device = platform_device_register_simple( + TCO_MODULE_NAME, -1, NULL, 0); + if (IS_ERR(sp5100_tco_platform_device)) { + err = PTR_ERR(sp5100_tco_platform_device); + goto unreg_platform_driver; + } + + return 0; + +unreg_platform_driver: + platform_driver_unregister(&sp5100_tco_driver); + return err; +} + +static void __exit sp5100_tco_cleanup_module(void) +{ + platform_device_unregister(sp5100_tco_platform_device); + platform_driver_unregister(&sp5100_tco_driver); + printk(KERN_INFO PFX "SP5100 TCO Watchdog Module Unloaded.\n"); +} + +module_init(sp5100_tco_init_module); +module_exit(sp5100_tco_cleanup_module); + +MODULE_AUTHOR("Priyanka Gupta"); +MODULE_DESCRIPTION("TCO timer driver for SP5100 chipset"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h new file mode 100644 index 000000000000..a5a16cc90a34 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.h @@ -0,0 +1,41 @@ +/* + * sp5100_tco: TCO timer driver for sp5100 chipsets. + * + * (c) Copyright 2009 Google Inc., All Rights Reserved. + * + * TCO timer driver for sp5100 chipsets + */ + +/* + * Some address definitions for the Watchdog + */ + +#define SP5100_WDT_MEM_MAP_SIZE 0x08 +#define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */ +#define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ + +#define SP5100_WDT_START_STOP_BIT 1 +#define SP5100_WDT_TRIGGER_BIT (1 << 7) + +#define SP5100_PCI_WATCHDOG_MISC_REG 0x41 +#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3) + +#define SP5100_PM_IOPORTS_SIZE 0x02 + +/* These two IO registers are hardcoded and there doesn't seem to be a way to + * read them from a register. + */ +#define SP5100_IO_PM_INDEX_REG 0xCD6 +#define SP5100_IO_PM_DATA_REG 0xCD7 + +#define SP5100_PM_WATCHDOG_CONTROL 0x69 +#define SP5100_PM_WATCHDOG_BASE0 0x6C +#define SP5100_PM_WATCHDOG_BASE1 0x6D +#define SP5100_PM_WATCHDOG_BASE2 0x6E +#define SP5100_PM_WATCHDOG_BASE3 0x6F + +#define SP5100_PM_WATCHDOG_FIRED (1 << 1) +#define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) + +#define SP5100_PM_WATCHDOG_DISABLE 1 +#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) -- cgit v1.2.3 From 456c730153fe33134fe93742510a96e46a9217c4 Mon Sep 17 00:00:00 2001 From: Mike Waychison Date: Mon, 25 Oct 2010 17:58:05 -0700 Subject: watchdog: Add TCO support for nVidia chipsets This driver adds support for /dev/watchdog for boards using either the MCP51 or MCP55 chipsets. These are also known as the nForce 430 and nForce 550. This driver is likely to work on other chipsets as well, though those are the only two that have been tested. Signed-off-by: Mike Waychison Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 18 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/nv_tco.c | 512 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/watchdog/nv_tco.h | 64 ++++++ 4 files changed, 595 insertions(+) create mode 100644 drivers/watchdog/nv_tco.c create mode 100644 drivers/watchdog/nv_tco.h (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c48faa5ade73..db00fa9f6979 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -642,6 +642,24 @@ config PC87413_WDT Most people will say N. +config NV_TCO + tristate "nVidia TCO Timer/Watchdog" + depends on X86 && PCI + ---help--- + Hardware driver for the TCO timer built into the nVidia Hub family + (such as the MCP51). The TCO (Total Cost of Ownership) timer is a + watchdog timer that will reboot the machine after its second + expiration. The expiration time can be configured with the + "heartbeat" parameter. + + On some motherboards the driver may fail to reset the chipset's + NO_REBOOT flag which prevents the watchdog from rebooting the + machine. If this is the case you will get a kernel message like + "failed to reset NO_REBOOT flag, reboot disabled by hardware". + + To compile this driver as a module, choose M here: the + module will be called nv_tco. + config RDC321X_WDT tristate "RDC R-321x SoC watchdog" depends on X86_RDC321X diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 12e6c1e32671..cd77f5492f95 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o +obj-$(CONFIG_NV_TCO) += nv_tco.o obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o obj-$(CONFIG_SBC8360_WDT) += sbc8360.o diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c new file mode 100644 index 000000000000..1a50aa7079bf --- /dev/null +++ b/drivers/watchdog/nv_tco.c @@ -0,0 +1,512 @@ +/* + * nv_tco 0.01: TCO timer driver for NV chipsets + * + * (c) Copyright 2005 Google Inc., All Rights Reserved. + * + * Based off i8xx_tco.c: + * (c) Copyright 2000 kernel concepts , All Rights + * Reserved. + * http://www.kernelconcepts.de + * + * 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. + * + * TCO timer driver for NV chipsets + * based on softdog.c by Alan Cox + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nv_tco.h" + +/* Module and version information */ +#define TCO_VERSION "0.01" +#define TCO_MODULE_NAME "NV_TCO" +#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION +#define PFX TCO_MODULE_NAME ": " + +/* internal variables */ +static unsigned int tcobase; +static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ +static unsigned long timer_alive; +static char tco_expect_close; +static struct pci_dev *tco_pci; + +/* the watchdog platform device */ +static struct platform_device *nv_tco_platform_device; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2 t, so if t > 0x3f, so is + * tmrval=seconds_to_ticks(t). Check that the count in seconds isn't + * out of range on it's own (to avoid overflow in tmrval). + */ + if (t < 0 || t > 0x3f) + return -EINVAL; + tmrval = seconds_to_ticks(t); + + /* "Values of 0h-3h are ignored and should not be attempted" */ + if (tmrval > 0x3f || tmrval < 0x04) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + spin_lock_irqsave(&tco_lock, flags); + val = inb(TCO_TMR(tcobase)); + val &= 0xc0; + val |= tmrval; + outb(val, TCO_TMR(tcobase)); + val = inb(TCO_TMR(tcobase)); + + if ((val & 0x3f) != tmrval) + ret = -EINVAL; + spin_unlock_irqrestore(&tco_lock, flags); + + if (ret) + return ret; + + heartbeat = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int nv_tco_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + /* Reload and activate timer */ + tco_timer_keepalive(); + tco_timer_start(); + return nonseekable_open(inode, file); +} + +static int nv_tco_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer */ + if (tco_expect_close == 42) { + tco_timer_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping " + "watchdog!\n"); + tco_timer_keepalive(); + } + clear_bit(0, &timer_alive); + tco_expect_close = 0; + return 0; +} + +static ssize_t nv_tco_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* + * note: just in case someone wrote the magic character + * five months ago... + */ + tco_expect_close = 0; + + /* + * scan to see whether or not we got the magic + * character + */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + tco_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + tco_timer_keepalive(); + } + return len; +} + +static long nv_tco_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_options, retval = -EINVAL; + int new_heartbeat; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = TCO_MODULE_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + tco_timer_stop(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + tco_timer_keepalive(); + tco_timer_start(); + retval = 0; + } + return retval; + case WDIOC_KEEPALIVE: + tco_timer_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (tco_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + tco_timer_keepalive(); + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations nv_tco_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = nv_tco_write, + .unlocked_ioctl = nv_tco_ioctl, + .open = nv_tco_open, + .release = nv_tco_release, +}; + +static struct miscdevice nv_tco_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &nv_tco_fops, +}; + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ +static struct pci_device_id tco_pci_tbl[] = { + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, tco_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __init nv_tco_getdevice(void) +{ + struct pci_dev *dev = NULL; + u32 val; + + /* Find the PCI device */ + for_each_pci_dev(dev) { + if (pci_match_id(tco_pci_tbl, dev) != NULL) { + tco_pci = dev; + break; + } + } + + if (!tco_pci) + return 0; + + /* Find the base io port */ + pci_read_config_dword(tco_pci, 0x64, &val); + val &= 0xffff; + if (val == 0x0001 || val == 0x0000) { + /* Something is wrong here, bar isn't setup */ + printk(KERN_ERR PFX "failed to get tcobase address\n"); + return 0; + } + val &= 0xff00; + tcobase = val + 0x40; + + if (!request_region(tcobase, 0x10, "NV TCO")) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + tcobase); + return 0; + } + + /* Set a reasonable heartbeat before we stop the timer */ + tco_timer_set_heartbeat(30); + + /* + * Stop the TCO before we change anything so we don't race with + * a zeroed timer. + */ + tco_timer_keepalive(); + tco_timer_stop(); + + /* Disable SMI caused by TCO */ + if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + MCP51_SMI_EN(tcobase)); + goto out; + } + val = inl(MCP51_SMI_EN(tcobase)); + val &= ~MCP51_SMI_EN_TCO; + outl(val, MCP51_SMI_EN(tcobase)); + val = inl(MCP51_SMI_EN(tcobase)); + release_region(MCP51_SMI_EN(tcobase), 4); + if (val & MCP51_SMI_EN_TCO) { + printk(KERN_ERR PFX "Could not disable SMI caused by TCO\n"); + goto out; + } + + /* Check chipset's NO_REBOOT bit */ + pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); + val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT; + pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val); + pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val); + if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) { + printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot " + "disabled by hardware\n"); + goto out; + } + + return 1; +out: + release_region(tcobase, 0x10); + return 0; +} + +static int __devinit nv_tco_init(struct platform_device *dev) +{ + int ret; + + /* Check whether or not the hardware watchdog is there */ + if (!nv_tco_getdevice()) + return -ENODEV; + + /* Check to see if last reboot was due to watchdog timeout */ + printk(KERN_INFO PFX "Watchdog reboot %sdetected.\n", + inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not "); + + /* Clear out the old status */ + outl(TCO_STS_RESET, TCO_STS(tcobase)); + + /* + * Check that the heartbeat value is within it's range. + * If not, reset to the default. + */ + if (tco_timer_set_heartbeat(heartbeat)) { + heartbeat = WATCHDOG_HEARTBEAT; + tco_timer_set_heartbeat(heartbeat); + printk(KERN_INFO PFX "heartbeat value must be 2, All Rights + * Reserved. + * http://www.kernelconcepts.de + * + * 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 kernel concepts nor Nils Faerber admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 kernel concepts + * developed for + * Jentro AG, Haar/Munich (Germany) + * + * TCO timer driver for NV chipsets + * based on softdog.c by Alan Cox + */ + +/* + * Some address definitions for the TCO + */ + +#define TCO_RLD(base) ((base) + 0x00) /* TCO Timer Reload and Current Value */ +#define TCO_TMR(base) ((base) + 0x01) /* TCO Timer Initial Value */ + +#define TCO_STS(base) ((base) + 0x04) /* TCO Status Register */ +/* + * TCO Boot Status bit: set on TCO reset, reset by software or standby + * power-good (survives reboots), unfortunately this bit is never + * set. + */ +# define TCO_STS_BOOT_STS (1 << 9) +/* + * first and 2nd timeout status bits, these also survive a warm boot, + * and they work, so we use them. + */ +# define TCO_STS_TCO_INT_STS (1 << 1) +# define TCO_STS_TCO2TO_STS (1 << 10) +# define TCO_STS_RESET (TCO_STS_BOOT_STS | TCO_STS_TCO2TO_STS | \ + TCO_STS_TCO_INT_STS) + +#define TCO_CNT(base) ((base) + 0x08) /* TCO Control Register */ +# define TCO_CNT_TCOHALT (1 << 12) + +#define MCP51_SMBUS_SETUP_B 0xe8 +# define MCP51_SMBUS_SETUP_B_TCO_REBOOT (1 << 25) + +/* + * The SMI_EN register is at the base io address + 0x04, + * while TCOBASE is + 0x40. + */ +#define MCP51_SMI_EN(base) ((base) - 0x40 + 0x04) +# define MCP51_SMI_EN_TCO ((1 << 4) | (1 << 5)) -- cgit v1.2.3 From f8394f61c66f48b1fe9d6964ddce492d7f9a4cd9 Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Tue, 4 Jan 2011 21:28:19 +0100 Subject: watchdog: add driver for the Atheros AR71XX/AR724X/AR913X SoCs This patch adds a driver for the built-in hardware watchdog device of the Atheros AR71XX/AR724X/AR913X SoCs. Signed-off-by: Gabor Juhos Signed-off-by: Imre Kaloz Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 7 + drivers/watchdog/Makefile | 1 + drivers/watchdog/ath79_wdt.c | 305 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 drivers/watchdog/ath79_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index db00fa9f6979..68a9527f92d0 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -866,6 +866,13 @@ config SBC_EPX_C3_WATCHDOG # MIPS Architecture +config ATH79_WDT + tristate "Atheros AR71XX/AR724X/AR913X hardware watchdog" + depends on ATH79 + help + Hardware driver for the built-in watchdog timer on the Atheros + AR71XX/AR724X/AR913X SoCs. + config BCM47XX_WDT tristate "Broadcom BCM47xx Watchdog Timer" depends on BCM47XX diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index cd77f5492f95..9b6b33a02072 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -110,6 +110,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o # M68KNOMMU Architecture # MIPS Architecture +obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c new file mode 100644 index 000000000000..725c84bfdd76 --- /dev/null +++ b/drivers/watchdog/ath79_wdt.c @@ -0,0 +1,305 @@ +/* + * Atheros AR71XX/AR724X/AR913X built-in hardware watchdog timer. + * + * Copyright (C) 2008-2011 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * This driver was based on: drivers/watchdog/ixp4xx_wdt.c + * Author: Deepak Saxena + * Copyright 2004 (c) MontaVista, Software, Inc. + * + * which again was based on sa1100 driver, + * Copyright (C) 2000 Oleg Drokin + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRIVER_NAME "ath79-wdt" + +#define WDT_TIMEOUT 15 /* seconds */ + +#define WDOG_CTRL_LAST_RESET BIT(31) +#define WDOG_CTRL_ACTION_MASK 3 +#define WDOG_CTRL_ACTION_NONE 0 /* no action */ +#define WDOG_CTRL_ACTION_GPI 1 /* general purpose interrupt */ +#define WDOG_CTRL_ACTION_NMI 2 /* NMI */ +#define WDOG_CTRL_ACTION_FCR 3 /* full chip reset */ + +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 int timeout = WDT_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " + "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)"); + +static unsigned long wdt_flags; + +#define WDT_FLAGS_BUSY 0 +#define WDT_FLAGS_EXPECT_CLOSE 1 + +static struct clk *wdt_clk; +static unsigned long wdt_freq; +static int boot_status; +static int max_timeout; + +static inline void ath79_wdt_keepalive(void) +{ + ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout); +} + +static inline void ath79_wdt_enable(void) +{ + ath79_wdt_keepalive(); + ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR); +} + +static inline void ath79_wdt_disable(void) +{ + ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE); +} + +static int ath79_wdt_set_timeout(int val) +{ + if (val < 1 || val > max_timeout) + return -EINVAL; + + timeout = val; + ath79_wdt_keepalive(); + + return 0; +} + +static int ath79_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags)) + return -EBUSY; + + clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); + ath79_wdt_enable(); + + return nonseekable_open(inode, file); +} + +static int ath79_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags)) + ath79_wdt_disable(); + else { + pr_crit(DRIVER_NAME ": device closed unexpectedly, " + "watchdog timer will not stop!\n"); + ath79_wdt_keepalive(); + } + + clear_bit(WDT_FLAGS_BUSY, &wdt_flags); + clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); + + return 0; +} + +static ssize_t ath79_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + + if (c == 'V') + set_bit(WDT_FLAGS_EXPECT_CLOSE, + &wdt_flags); + } + } + + ath79_wdt_keepalive(); + } + + return len; +} + +static const struct watchdog_info ath79_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | WDIOF_CARDRESET, + .firmware_version = 0, + .identity = "ATH79 watchdog", +}; + +static long ath79_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int err; + int t; + + switch (cmd) { + case WDIOC_GETSUPPORT: + err = copy_to_user(argp, &ath79_wdt_info, + sizeof(ath79_wdt_info)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + err = put_user(0, p); + break; + + case WDIOC_GETBOOTSTATUS: + err = put_user(boot_status, p); + break; + + case WDIOC_KEEPALIVE: + ath79_wdt_keepalive(); + err = 0; + break; + + case WDIOC_SETTIMEOUT: + err = get_user(t, p); + if (err) + break; + + err = ath79_wdt_set_timeout(t); + if (err) + break; + + /* fallthrough */ + case WDIOC_GETTIMEOUT: + err = put_user(timeout, p); + break; + + default: + err = -ENOTTY; + break; + } + + return err; +} + +static const struct file_operations ath79_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ath79_wdt_write, + .unlocked_ioctl = ath79_wdt_ioctl, + .open = ath79_wdt_open, + .release = ath79_wdt_release, +}; + +static struct miscdevice ath79_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ath79_wdt_fops, +}; + +static int __devinit ath79_wdt_probe(struct platform_device *pdev) +{ + u32 ctrl; + int err; + + wdt_clk = clk_get(&pdev->dev, "wdt"); + if (IS_ERR(wdt_clk)) + return PTR_ERR(wdt_clk); + + err = clk_enable(wdt_clk); + if (err) + goto err_clk_put; + + wdt_freq = clk_get_rate(wdt_clk); + if (!wdt_freq) { + err = -EINVAL; + goto err_clk_disable; + } + + max_timeout = (0xfffffffful / wdt_freq); + if (timeout < 1 || timeout > max_timeout) { + timeout = max_timeout; + dev_info(&pdev->dev, + "timeout value must be 0 < timeout < %d, using %d\n", + max_timeout, timeout); + } + + ctrl = ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); + boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0; + + err = misc_register(&ath79_wdt_miscdev); + if (err) { + dev_err(&pdev->dev, + "unable to register misc device, err=%d\n", err); + goto err_clk_disable; + } + + return 0; + +err_clk_disable: + clk_disable(wdt_clk); +err_clk_put: + clk_put(wdt_clk); + return err; +} + +static int __devexit ath79_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&ath79_wdt_miscdev); + clk_disable(wdt_clk); + clk_put(wdt_clk); + return 0; +} + +static void ath97_wdt_shutdown(struct platform_device *pdev) +{ + ath79_wdt_disable(); +} + +static struct platform_driver ath79_wdt_driver = { + .remove = __devexit_p(ath79_wdt_remove), + .shutdown = ath97_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ath79_wdt_init(void) +{ + return platform_driver_probe(&ath79_wdt_driver, ath79_wdt_probe); +} +module_init(ath79_wdt_init); + +static void __exit ath79_wdt_exit(void) +{ + platform_driver_unregister(&ath79_wdt_driver); +} +module_exit(ath79_wdt_exit); + +MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver"); +MODULE_AUTHOR("Gabor Juhos Date: Wed, 3 Nov 2010 15:07:28 +0100 Subject: watchdog: Add MCF548x watchdog driver. Add watchdog driver for MCF548x. Signed-off-by: Philippe De Muyter Signed-off-by: Wim Van Sebroeck --- arch/m68k/include/asm/m548xgpt.h | 2 + drivers/watchdog/Kconfig | 7 +- drivers/watchdog/Makefile | 3 +- drivers/watchdog/m548x_wdt.c | 227 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 drivers/watchdog/m548x_wdt.c (limited to 'drivers/watchdog/Kconfig') diff --git a/arch/m68k/include/asm/m548xgpt.h b/arch/m68k/include/asm/m548xgpt.h index c8ef158a1c4e..33b2eef90f0a 100644 --- a/arch/m68k/include/asm/m548xgpt.h +++ b/arch/m68k/include/asm/m548xgpt.h @@ -59,11 +59,13 @@ #define MCF_GPT_GMS_GPIO_INPUT (0x00000000) #define MCF_GPT_GMS_GPIO_OUTLO (0x00000020) #define MCF_GPT_GMS_GPIO_OUTHI (0x00000030) +#define MCF_GPT_GMS_GPIO_MASK (0x00000030) #define MCF_GPT_GMS_TMS_DISABLE (0x00000000) #define MCF_GPT_GMS_TMS_INCAPT (0x00000001) #define MCF_GPT_GMS_TMS_OUTCAPT (0x00000002) #define MCF_GPT_GMS_TMS_PWM (0x00000003) #define MCF_GPT_GMS_TMS_GPIO (0x00000004) +#define MCF_GPT_GMS_TMS_MASK (0x00000007) /* Bit definitions and macros for MCF_GPT_GCIR */ #define MCF_GPT_GCIR_CNT(x) (((x)&0x0000FFFF)<<0) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 68a9527f92d0..2e2400e7322e 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -862,7 +862,12 @@ config SBC_EPX_C3_WATCHDOG # M68K Architecture -# M68KNOMMU Architecture +config M548x_WATCHDOG + tristate "MCF548x watchdog support" + depends on M548x + help + To compile this driver as a module, choose M here: the + module will be called m548x_wdt. # MIPS Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 9b6b33a02072..dd776651917c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -106,8 +106,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o # M32R Architecture # M68K Architecture - -# M68KNOMMU Architecture +obj-$(CONFIG_M548x_WATCHDOG) += m548x_wdt.o # MIPS Architecture obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o diff --git a/drivers/watchdog/m548x_wdt.c b/drivers/watchdog/m548x_wdt.c new file mode 100644 index 000000000000..cabbcfe1c847 --- /dev/null +++ b/drivers/watchdog/m548x_wdt.c @@ -0,0 +1,227 @@ +/* + * drivers/watchdog/m548x_wdt.c + * + * Watchdog driver for ColdFire MCF548x processors + * Copyright 2010 (c) Philippe De Muyter + * + * Adapted from the IXP4xx watchdog driver, which carries these notices: + * + * Author: Deepak Saxena + * + * Copyright 2004 (c) MontaVista, Software, Inc. + * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int nowayout = WATCHDOG_NOWAYOUT; +static unsigned int heartbeat = 30; /* (secs) Default is 0.5 minute */ +static unsigned long wdt_status; + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static void wdt_enable(void) +{ + unsigned int gms0; + + /* preserve GPIO usage, if any */ + gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); + if (gms0 & MCF_GPT_GMS_TMS_GPIO) + gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK + | MCF_GPT_GMS_OD); + else + gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD; + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); + __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) | + MCF_GPT_GCIR_CNT(0xffff), MCF_MBAR + MCF_GPT_GCIR0); + gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE; + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); +} + +static void wdt_disable(void) +{ + unsigned int gms0; + + /* disable watchdog */ + gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); + gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE); + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); +} + +static void wdt_keepalive(void) +{ + unsigned int gms0; + + gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); + gms0 |= MCF_GPT_GMS_OCPW(0xA5); + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); +} + +static int m548x_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 m548x_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_keepalive(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "Coldfire M548x Watchdog", +}; + +static long m548x_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: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_keepalive(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time <= 0 || time > 30) { + ret = -EINVAL; + break; + } + + heartbeat = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + } + return ret; +} + +static int m548x_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"); + wdt_keepalive(); + } + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static const struct file_operations m548x_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = m548x_wdt_write, + .unlocked_ioctl = m548x_wdt_ioctl, + .open = m548x_wdt_open, + .release = m548x_wdt_release, +}; + +static struct miscdevice m548x_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &m548x_wdt_fops, +}; + +static int __init m548x_wdt_init(void) +{ + if (!request_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4, + "Coldfire M548x Watchdog")) { + printk(KERN_WARNING + "Coldfire M548x Watchdog : I/O region busy\n"); + return -EBUSY; + } + printk(KERN_INFO "ColdFire watchdog driver is loaded.\n"); + + return misc_register(&m548x_wdt_miscdev); +} + +static void __exit m548x_wdt_exit(void) +{ + misc_deregister(&m548x_wdt_miscdev); + release_mem_region(MCF_MBAR + MCF_GPT_GCIR0, 4); +} + +module_init(m548x_wdt_init); +module_exit(m548x_wdt_exit); + +MODULE_AUTHOR("Philippe De Muyter "); +MODULE_DESCRIPTION("Coldfire M548x Watchdog"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- cgit v1.2.3