From c9353ae1c69ba13ec9d29ab4f2dae881c2c06a58 Mon Sep 17 00:00:00 2001 From: Jamie Iles Date: Mon, 24 Jan 2011 12:19:12 +0000 Subject: [PATCH] watchdog: add support for the Synopsys DesignWare WDT The Synopsys DesignWare watchdog is found in several ARM based systems and provides a choice of 16 timeout periods depending on the clock input. The watchdog cannot be disabled once started. Signed-off-by: Jamie Iles Acked-by: Viresh Kumar Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 1 + drivers/watchdog/dw_wdt.c | 376 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 386 insertions(+) create mode 100644 drivers/watchdog/dw_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 21d816e9dfa5..de78d0ab041f 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -186,6 +186,15 @@ config SA1100_WATCHDOG To compile this driver as a module, choose M here: the module will be called sa1100_wdt. +config DW_WATCHDOG + tristate "Synopsys DesignWare watchdog" + depends on ARM && HAVE_CLK + help + Say Y here if to include support for the Synopsys DesignWare + watchdog timer found in many ARM chips. + To compile this driver as a module, choose M here: the + module will be called dw_wdt. + config MPCORE_WATCHDOG tristate "MPcore watchdog" depends on HAVE_ARM_TWD diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ed26f7094e47..b2ddff704ae0 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o +obj-$(CONFIG_DW_WATCHDOG) += dw_wdt.o obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c new file mode 100644 index 000000000000..f10f8c0abba4 --- /dev/null +++ b/drivers/watchdog/dw_wdt.c @@ -0,0 +1,376 @@ +/* + * Copyright 2010-2011 Picochip Ltd., Jamie Iles + * http://www.picochip.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This file implements a driver for the Synopsys DesignWare watchdog device + * in the many ARM subsystems. The watchdog has 16 different timeout periods + * and these are a function of the input clock frequency. + * + * The DesignWare watchdog cannot be stopped once it has been started so we + * use a software timer to implement a ping that will keep the watchdog alive. + * If we receive an expected close for the watchdog then we keep the timer + * running, otherwise the timer is stopped and the watchdog will expire. + */ +#define pr_fmt(fmt) "dw_wdt: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WDOG_CONTROL_REG_OFFSET 0x00 +#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 +#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 +#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 +#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c +#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 + +/* The maximum TOP (timeout period) value that can be set in the watchdog. */ +#define DW_WDT_MAX_TOP 15 + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " + "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define WDT_TIMEOUT (HZ / 2) + +static struct { + spinlock_t lock; + void __iomem *regs; + struct clk *clk; + unsigned long in_use; + unsigned long next_heartbeat; + struct timer_list timer; + int expect_close; +} dw_wdt; + +static inline int dw_wdt_is_enabled(void) +{ + return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) & + WDOG_CONTROL_REG_WDT_EN_MASK; +} + +static inline int dw_wdt_top_in_seconds(unsigned top) +{ + /* + * There are 16 possible timeout values in 0..15 where the number of + * cycles is 2 ^ (16 + i) and the watchdog counts down. + */ + return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk); +} + +static int dw_wdt_get_top(void) +{ + int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; + + return dw_wdt_top_in_seconds(top); +} + +static inline void dw_wdt_set_next_heartbeat(void) +{ + dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ; +} + +static int dw_wdt_set_top(unsigned top_s) +{ + int i, top_val = DW_WDT_MAX_TOP; + + /* + * Iterate over the timeout values until we find the closest match. We + * always look for >=. + */ + for (i = 0; i <= DW_WDT_MAX_TOP; ++i) + if (dw_wdt_top_in_seconds(i) >= top_s) { + top_val = i; + break; + } + + /* Set the new value in the watchdog. */ + writel(top_val, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + + dw_wdt_set_next_heartbeat(); + + return dw_wdt_top_in_seconds(top_val); +} + +static void dw_wdt_keepalive(void) +{ + writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + + WDOG_COUNTER_RESTART_REG_OFFSET); +} + +static void dw_wdt_ping(unsigned long data) +{ + if (time_before(jiffies, dw_wdt.next_heartbeat) || + (!nowayout && !dw_wdt.in_use)) { + dw_wdt_keepalive(); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + } else + pr_crit("keepalive missed, machine will reset\n"); +} + +static int dw_wdt_open(struct inode *inode, struct file *filp) +{ + if (test_and_set_bit(0, &dw_wdt.in_use)) + return -EBUSY; + + /* Make sure we don't get unloaded. */ + __module_get(THIS_MODULE); + + spin_lock(&dw_wdt.lock); + if (!dw_wdt_is_enabled()) { + /* + * The watchdog is not currently enabled. Set the timeout to + * the maximum and then start it. + */ + dw_wdt_set_top(DW_WDT_MAX_TOP); + writel(WDOG_CONTROL_REG_WDT_EN_MASK, + dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + } + + dw_wdt_set_next_heartbeat(); + + spin_unlock(&dw_wdt.lock); + + return nonseekable_open(inode, filp); +} + +ssize_t dw_wdt_write(struct file *filp, const char __user *buf, size_t len, + loff_t *offset) +{ + if (!len) + return 0; + + if (!nowayout) { + size_t i; + + dw_wdt.expect_close = 0; + + for (i = 0; i < len; ++i) { + char c; + + if (get_user(c, buf + i)) + return -EFAULT; + + if (c == 'V') { + dw_wdt.expect_close = 1; + break; + } + } + } + + dw_wdt_set_next_heartbeat(); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + + return len; +} + +static u32 dw_wdt_time_left(void) +{ + return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) / + clk_get_rate(dw_wdt.clk); +} + +static const struct watchdog_info dw_wdt_ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .identity = "Synopsys DesignWare Watchdog", +}; + +static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + unsigned long val; + int timeout; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info *)arg, &dw_wdt_ident, + sizeof(dw_wdt_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int *)arg); + + case WDIOC_KEEPALIVE: + dw_wdt_set_next_heartbeat(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + timeout = dw_wdt_set_top(val); + return put_user(timeout , (int __user *)arg); + + case WDIOC_GETTIMEOUT: + return put_user(dw_wdt_get_top(), (int __user *)arg); + + case WDIOC_GETTIMELEFT: + /* Get the time left until expiry. */ + if (get_user(val, (int __user *)arg)) + return -EFAULT; + return put_user(dw_wdt_time_left(), (int __user *)arg); + + default: + return -ENOTTY; + } +} + +static int dw_wdt_release(struct inode *inode, struct file *filp) +{ + clear_bit(0, &dw_wdt.in_use); + + if (!dw_wdt.expect_close) { + del_timer(&dw_wdt.timer); + + if (!nowayout) + pr_crit("unexpected close, system will reboot soon\n"); + else + pr_crit("watchdog cannot be disabled, system will reboot soon\n"); + } + + dw_wdt.expect_close = 0; + + return 0; +} + +#ifdef CONFIG_PM +static int dw_wdt_suspend(struct device *dev) +{ + clk_disable(dw_wdt.clk); + + return 0; +} + +static int dw_wdt_resume(struct device *dev) +{ + int err = clk_enable(dw_wdt.clk); + + if (err) + return err; + + dw_wdt_keepalive(); + + return 0; +} + +static const struct dev_pm_ops dw_wdt_pm_ops = { + .suspend = dw_wdt_suspend, + .resume = dw_wdt_resume, +}; +#endif /* CONFIG_PM */ + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = dw_wdt_open, + .write = dw_wdt_write, + .unlocked_ioctl = dw_wdt_ioctl, + .release = dw_wdt_release +}; + +static struct miscdevice dw_wdt_miscdev = { + .fops = &wdt_fops, + .name = "watchdog", + .minor = WATCHDOG_MINOR, +}; + +static int __devinit dw_wdt_drv_probe(struct platform_device *pdev) +{ + int ret; + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!mem) + return -EINVAL; + + if (!devm_request_mem_region(&pdev->dev, mem->start, resource_size(mem), + "dw_wdt")) + return -ENOMEM; + + dw_wdt.regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!dw_wdt.regs) + return -ENOMEM; + + dw_wdt.clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dw_wdt.clk)) + return PTR_ERR(dw_wdt.clk); + + ret = clk_enable(dw_wdt.clk); + if (ret) + goto out_put_clk; + + spin_lock_init(&dw_wdt.lock); + + ret = misc_register(&dw_wdt_miscdev); + if (ret) + goto out_disable_clk; + + dw_wdt_set_next_heartbeat(); + setup_timer(&dw_wdt.timer, dw_wdt_ping, 0); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + + return 0; + +out_disable_clk: + clk_disable(dw_wdt.clk); +out_put_clk: + clk_put(dw_wdt.clk); + + return ret; +} + +static int __devexit dw_wdt_drv_remove(struct platform_device *pdev) +{ + misc_deregister(&dw_wdt_miscdev); + + clk_disable(dw_wdt.clk); + clk_put(dw_wdt.clk); + + return 0; +} + +static struct platform_driver dw_wdt_driver = { + .probe = dw_wdt_drv_probe, + .remove = __devexit_p(dw_wdt_drv_remove), + .driver = { + .name = "dw_wdt", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &dw_wdt_pm_ops, +#endif /* CONFIG_PM */ + }, +}; + +static int __init dw_wdt_watchdog_init(void) +{ + return platform_driver_register(&dw_wdt_driver); +} +module_init(dw_wdt_watchdog_init); + +static void __exit dw_wdt_watchdog_exit(void) +{ + platform_driver_unregister(&dw_wdt_driver); +} +module_exit(dw_wdt_watchdog_exit); + +MODULE_AUTHOR("Jamie Iles"); +MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);