diff options
author | Guenter Roeck <linux@roeck-us.net> | 2016-02-28 22:12:19 +0100 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2016-03-16 21:11:21 +0100 |
commit | f29a72c24ad4927027e77e4eed431a61bc8335b2 (patch) | |
tree | 23f485d7364555aa700d468f7aaaba951814cc36 /drivers/watchdog/dw_wdt.c | |
parent | watchdog: Add support for minimum time between heartbeats (diff) | |
download | linux-f29a72c24ad4927027e77e4eed431a61bc8335b2.tar.xz linux-f29a72c24ad4927027e77e4eed431a61bc8335b2.zip |
watchdog: dw_wdt: Convert to use watchdog infrastructure
Convert driver to use watchdog infrastructure. This includes
infrastructure support to handle watchdog keepalive if the watchdog
is running while the watchdog device is closed.
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Tested-by: Douglas Anderson <dianders@chromium.org>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog/dw_wdt.c')
-rw-r--r-- | drivers/watchdog/dw_wdt.c | 323 |
1 files changed, 116 insertions, 207 deletions
diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 8fefa4ad46d4..2acb51cf5504 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -12,9 +12,8 @@ * 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. + * do not implement a stop function. The watchdog core will continue to send + * heartbeat requests after the watchdog device has been closed. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -22,12 +21,9 @@ #include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> -#include <linux/device.h> #include <linux/err.h> -#include <linux/fs.h> #include <linux/io.h> #include <linux/kernel.h> -#include <linux/miscdevice.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/notifier.h> @@ -35,8 +31,6 @@ #include <linux/pm.h> #include <linux/platform_device.h> #include <linux/reboot.h> -#include <linux/timer.h> -#include <linux/uaccess.h> #include <linux/watchdog.h> #define WDOG_CONTROL_REG_OFFSET 0x00 @@ -57,53 +51,50 @@ module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -#define WDT_TIMEOUT (HZ / 2) - -static struct { +struct dw_wdt { void __iomem *regs; struct clk *clk; - unsigned long in_use; - unsigned long next_heartbeat; - struct timer_list timer; - int expect_close; struct notifier_block restart_handler; -} dw_wdt; + struct watchdog_device wdd; +}; + +#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd) -static inline int dw_wdt_is_enabled(void) +static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt) { - return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) & + 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) +static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, 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 (1U << (16 + top)) / clk_get_rate(dw_wdt.clk); + return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk); } -static int dw_wdt_get_top(void) +static int dw_wdt_get_top(struct dw_wdt *dw_wdt) { - int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; + int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; - return dw_wdt_top_in_seconds(top); + return dw_wdt_top_in_seconds(dw_wdt, top); } -static inline void dw_wdt_set_next_heartbeat(void) +static int dw_wdt_ping(struct watchdog_device *wdd) { - dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ; -} + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); -static void dw_wdt_keepalive(void) -{ - writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + + writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); + + return 0; } -static int dw_wdt_set_top(unsigned top_s) +static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s) { + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); int i, top_val = DW_WDT_MAX_TOP; /* @@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s) * always look for >=. */ for (i = 0; i <= DW_WDT_MAX_TOP; ++i) - if (dw_wdt_top_in_seconds(i) >= top_s) { + if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) { top_val = i; break; } @@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s) * effectively get a pat of the watchdog right here. */ writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT, - dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); - /* - * Add an explicit pat to handle versions of the watchdog that - * don't have TOPINIT. This won't hurt on versions that have - * it. - */ - dw_wdt_keepalive(); + wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val); - dw_wdt_set_next_heartbeat(); + return 0; +} + +static int dw_wdt_start(struct watchdog_device *wdd) +{ + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); + + dw_wdt_set_timeout(wdd, wdd->timeout); - return dw_wdt_top_in_seconds(top_val); + set_bit(WDOG_HW_RUNNING, &wdd->status); + + writel(WDOG_CONTROL_REG_WDT_EN_MASK, + dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); + + return 0; } static int dw_wdt_restart_handle(struct notifier_block *this, - unsigned long mode, void *cmd) + unsigned long mode, void *cmd) { + struct dw_wdt *dw_wdt; u32 val; - writel(0, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); - val = readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + dw_wdt = container_of(this, struct dw_wdt, restart_handler); + + writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); if (val & WDOG_CONTROL_REG_WDT_EN_MASK) - writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + - WDOG_COUNTER_RESTART_REG_OFFSET); + writel(WDOG_COUNTER_RESTART_KICK_VALUE, + dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); else writel(WDOG_CONTROL_REG_WDT_EN_MASK, - dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); /* wait for reset to assert... */ mdelay(500); @@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this, return NOTIFY_DONE; } -static void dw_wdt_ping(unsigned long data) +static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd) { - 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); - - if (!dw_wdt_is_enabled()) { - /* - * The watchdog is not currently enabled. Set the timeout to - * something reasonable and then start it. - */ - dw_wdt_set_top(DW_WDT_DEFAULT_SECONDS); - writel(WDOG_CONTROL_REG_WDT_EN_MASK, - dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); - } - - dw_wdt_set_next_heartbeat(); - - return nonseekable_open(inode, filp); -} - -static 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; - } - } - } + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); - dw_wdt_set_next_heartbeat(); - dw_wdt_keepalive(); - 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); + return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) / + clk_get_rate(dw_wdt->clk); } static const struct watchdog_info dw_wdt_ident = { @@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = { .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((void __user *)arg, &dw_wdt_ident, - sizeof(dw_wdt_ident)) ? -EFAULT : 0; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, (int __user *)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; -} +static const struct watchdog_ops dw_wdt_ops = { + .owner = THIS_MODULE, + .start = dw_wdt_start, + .ping = dw_wdt_ping, + .set_timeout = dw_wdt_set_timeout, + .get_timeleft = dw_wdt_get_timeleft, +}; #ifdef CONFIG_PM_SLEEP static int dw_wdt_suspend(struct device *dev) { - clk_disable_unprepare(dw_wdt.clk); + struct dw_wdt *dw_wdt = dev_get_drvdata(dev); + + clk_disable_unprepare(dw_wdt->clk); return 0; } static int dw_wdt_resume(struct device *dev) { - int err = clk_prepare_enable(dw_wdt.clk); + struct dw_wdt *dw_wdt = dev_get_drvdata(dev); + int err = clk_prepare_enable(dw_wdt->clk); if (err) return err; - dw_wdt_keepalive(); + dw_wdt_ping(&dw_wdt->wdd); return 0; } @@ -312,67 +206,82 @@ static int dw_wdt_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume); -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 dw_wdt_drv_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct watchdog_device *wdd; + struct dw_wdt *dw_wdt; + struct resource *mem; int ret; - struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(dw_wdt.regs)) - return PTR_ERR(dw_wdt.regs); + dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL); + if (!dw_wdt) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dw_wdt->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(dw_wdt->regs)) + return PTR_ERR(dw_wdt->regs); - dw_wdt.clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(dw_wdt.clk)) - return PTR_ERR(dw_wdt.clk); + dw_wdt->clk = devm_clk_get(dev, NULL); + if (IS_ERR(dw_wdt->clk)) + return PTR_ERR(dw_wdt->clk); - ret = clk_prepare_enable(dw_wdt.clk); + ret = clk_prepare_enable(dw_wdt->clk); if (ret) return ret; - ret = misc_register(&dw_wdt_miscdev); + wdd = &dw_wdt->wdd; + wdd->info = &dw_wdt_ident; + wdd->ops = &dw_wdt_ops; + wdd->min_timeout = 1; + wdd->max_hw_heartbeat_ms = + dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000; + wdd->parent = dev; + + watchdog_set_drvdata(wdd, dw_wdt); + watchdog_set_nowayout(wdd, nowayout); + watchdog_init_timeout(wdd, 0, dev); + + /* + * If the watchdog is already running, use its already configured + * timeout. Otherwise use the default or the value provided through + * devicetree. + */ + if (dw_wdt_is_enabled(dw_wdt)) { + wdd->timeout = dw_wdt_get_top(dw_wdt); + set_bit(WDOG_HW_RUNNING, &wdd->status); + } else { + wdd->timeout = DW_WDT_DEFAULT_SECONDS; + watchdog_init_timeout(wdd, 0, dev); + } + + platform_set_drvdata(pdev, dw_wdt); + + ret = watchdog_register_device(wdd); if (ret) goto out_disable_clk; - dw_wdt.restart_handler.notifier_call = dw_wdt_restart_handle; - dw_wdt.restart_handler.priority = 128; - ret = register_restart_handler(&dw_wdt.restart_handler); + dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle; + dw_wdt->restart_handler.priority = 128; + ret = register_restart_handler(&dw_wdt->restart_handler); if (ret) pr_warn("cannot register restart handler\n"); - 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_unprepare(dw_wdt.clk); - + clk_disable_unprepare(dw_wdt->clk); return ret; } static int dw_wdt_drv_remove(struct platform_device *pdev) { - unregister_restart_handler(&dw_wdt.restart_handler); - - misc_deregister(&dw_wdt_miscdev); + struct dw_wdt *dw_wdt = platform_get_drvdata(pdev); - clk_disable_unprepare(dw_wdt.clk); + unregister_restart_handler(&dw_wdt->restart_handler); + watchdog_unregister_device(&dw_wdt->wdd); + clk_disable_unprepare(dw_wdt->clk); return 0; } |