Commit f29a72c2 authored by Guenter Roeck's avatar Guenter Roeck Committed by Wim Van Sebroeck

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: default avatarGuenter Roeck <linux@roeck-us.net>
Tested-by: default avatarDouglas Anderson <dianders@chromium.org>
Signed-off-by: default avatarWim Van Sebroeck <wim@iguana.be>
parent 15013ad8
...@@ -350,6 +350,7 @@ config SA1100_WATCHDOG ...@@ -350,6 +350,7 @@ config SA1100_WATCHDOG
config DW_WATCHDOG config DW_WATCHDOG
tristate "Synopsys DesignWare watchdog" tristate "Synopsys DesignWare watchdog"
depends on HAS_IOMEM depends on HAS_IOMEM
select WATCHDOG_CORE
help help
Say Y here if to include support for the Synopsys DesignWare Say Y here if to include support for the Synopsys DesignWare
watchdog timer found in many chips. watchdog timer found in many chips.
......
...@@ -12,9 +12,8 @@ ...@@ -12,9 +12,8 @@
* and these are a function of the input clock frequency. * and these are a function of the input clock frequency.
* *
* The DesignWare watchdog cannot be stopped once it has been started so we * 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. * do not implement a stop function. The watchdog core will continue to send
* If we receive an expected close for the watchdog then we keep the timer * heartbeat requests after the watchdog device has been closed.
* running, otherwise the timer is stopped and the watchdog will expire.
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
...@@ -22,12 +21,9 @@ ...@@ -22,12 +21,9 @@
#include <linux/bitops.h> #include <linux/bitops.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/fs.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/notifier.h> #include <linux/notifier.h>
...@@ -35,8 +31,6 @@ ...@@ -35,8 +31,6 @@
#include <linux/pm.h> #include <linux/pm.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/timer.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#define WDOG_CONTROL_REG_OFFSET 0x00 #define WDOG_CONTROL_REG_OFFSET 0x00
...@@ -57,53 +51,50 @@ module_param(nowayout, bool, 0); ...@@ -57,53 +51,50 @@ module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#define WDT_TIMEOUT (HZ / 2) struct dw_wdt {
static struct {
void __iomem *regs; void __iomem *regs;
struct clk *clk; struct clk *clk;
unsigned long in_use;
unsigned long next_heartbeat;
struct timer_list timer;
int expect_close;
struct notifier_block restart_handler; 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; 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 * There are 16 possible timeout values in 0..15 where the number of
* cycles is 2 ^ (16 + i) and the watchdog counts down. * 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); 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; int i, top_val = DW_WDT_MAX_TOP;
/* /*
...@@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s) ...@@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s)
* always look for >=. * always look for >=.
*/ */
for (i = 0; i <= DW_WDT_MAX_TOP; ++i) 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; top_val = i;
break; break;
} }
...@@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s) ...@@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s)
* effectively get a pat of the watchdog right here. * effectively get a pat of the watchdog right here.
*/ */
writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT, 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);
/* wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);
* Add an explicit pat to handle versions of the watchdog that
* don't have TOPINIT. This won't hurt on versions that have return 0;
* it. }
*/
dw_wdt_keepalive(); static int dw_wdt_start(struct watchdog_device *wdd)
{
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
dw_wdt_set_next_heartbeat(); dw_wdt_set_timeout(wdd, wdd->timeout);
set_bit(WDOG_HW_RUNNING, &wdd->status);
writel(WDOG_CONTROL_REG_WDT_EN_MASK,
dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
return dw_wdt_top_in_seconds(top_val); return 0;
} }
static int dw_wdt_restart_handle(struct notifier_block *this, 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; u32 val;
writel(0, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); dw_wdt = container_of(this, struct dw_wdt, restart_handler);
val = readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
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) if (val & WDOG_CONTROL_REG_WDT_EN_MASK)
writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + writel(WDOG_COUNTER_RESTART_KICK_VALUE,
WDOG_COUNTER_RESTART_REG_OFFSET); dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
else else
writel(WDOG_CONTROL_REG_WDT_EN_MASK, 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... */ /* wait for reset to assert... */
mdelay(500); mdelay(500);
...@@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this, ...@@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this,
return NOTIFY_DONE; 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) struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
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(); return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
dw_wdt_keepalive(); clk_get_rate(dw_wdt->clk);
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 = { static const struct watchdog_info dw_wdt_ident = {
...@@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = { ...@@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = {
.identity = "Synopsys DesignWare Watchdog", .identity = "Synopsys DesignWare Watchdog",
}; };
static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) static const struct watchdog_ops dw_wdt_ops = {
{ .owner = THIS_MODULE,
unsigned long val; .start = dw_wdt_start,
int timeout; .ping = dw_wdt_ping,
.set_timeout = dw_wdt_set_timeout,
switch (cmd) { .get_timeleft = dw_wdt_get_timeleft,
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;
}
#ifdef CONFIG_PM_SLEEP #ifdef CONFIG_PM_SLEEP
static int dw_wdt_suspend(struct device *dev) 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; return 0;
} }
static int dw_wdt_resume(struct device *dev) 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) if (err)
return err; return err;
dw_wdt_keepalive(); dw_wdt_ping(&dw_wdt->wdd);
return 0; return 0;
} }
...@@ -312,67 +206,82 @@ static int dw_wdt_resume(struct device *dev) ...@@ -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 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) 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; int ret;
struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem); dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
if (IS_ERR(dw_wdt.regs)) if (!dw_wdt)
return PTR_ERR(dw_wdt.regs); return -ENOMEM;
dw_wdt.clk = devm_clk_get(&pdev->dev, NULL); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (IS_ERR(dw_wdt.clk)) dw_wdt->regs = devm_ioremap_resource(dev, mem);
return PTR_ERR(dw_wdt.clk); if (IS_ERR(dw_wdt->regs))
return PTR_ERR(dw_wdt->regs);
ret = clk_prepare_enable(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);
if (ret) if (ret)
return 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) if (ret)
goto out_disable_clk; goto out_disable_clk;
dw_wdt.restart_handler.notifier_call = dw_wdt_restart_handle; dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle;
dw_wdt.restart_handler.priority = 128; dw_wdt->restart_handler.priority = 128;
ret = register_restart_handler(&dw_wdt.restart_handler); ret = register_restart_handler(&dw_wdt->restart_handler);
if (ret) if (ret)
pr_warn("cannot register restart handler\n"); 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; return 0;
out_disable_clk: out_disable_clk:
clk_disable_unprepare(dw_wdt.clk); clk_disable_unprepare(dw_wdt->clk);
return ret; return ret;
} }
static int dw_wdt_drv_remove(struct platform_device *pdev) static int dw_wdt_drv_remove(struct platform_device *pdev)
{ {
unregister_restart_handler(&dw_wdt.restart_handler); struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
misc_deregister(&dw_wdt_miscdev);
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; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment