diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3defb9d..1d6b156 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -10,6 +10,12 @@ if WATCHDOG +menuconfig WATCHDOG_POLLER + bool "Watchdog periodic feeder support" + select POLLER + help + Provides support for periodic watchdog feeder. + config WATCHDOG_AR9344 bool "QCA AR9344" depends on SOC_QCA_AR9344 || SOC_QCA_AR9331 diff --git a/drivers/watchdog/ar9344_wdt.c b/drivers/watchdog/ar9344_wdt.c index d570cb4..4615288 100644 --- a/drivers/watchdog/ar9344_wdt.c +++ b/drivers/watchdog/ar9344_wdt.c @@ -41,20 +41,11 @@ static int ar9344_watchdog_set_timeout(struct watchdog *wd, unsigned timeout) { struct ar9344_wd *priv = to_ar9344_wd(wd); - u32 val, ctrl, rate, max_timout; - - rate = clk_get_rate(priv->clk); - max_timout = U32_MAX / rate; - - if (timeout > max_timout) { - dev_err(priv->dev, "timeout value out of range: %d > %d\n", - timeout, max_timout); - return -EINVAL; - } + u32 val, ctrl; if (timeout) { ctrl = AR9344_WD_CTRL_ACTION_FCR; - val = timeout * rate; + val = timeout * clk_get_rate(priv->clk); } else { ctrl = AR9344_WD_CTRL_ACTION_NONE; val = U32_MAX; @@ -95,7 +86,7 @@ priv->base = IOMEM(iores->start); priv->wd.set_timeout = ar9344_watchdog_set_timeout; - priv->wd.dev = dev; + priv->wd.hwdev = dev; priv->dev = dev; dev->priv = priv; @@ -111,6 +102,8 @@ clk_enable(priv->clk); + priv->wd.timeout_max = U32_MAX / clk_get_rate(priv->clk); + ret = watchdog_register(&priv->wd); if (ret) goto on_error; diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index 457d86f..d0c51ed 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -101,7 +101,7 @@ } priv->base = IOMEM(iores->start); priv->wd.set_timeout = bcm2835_wd_set_timeout; - priv->wd.dev = dev; + priv->wd.hwdev = dev; priv->dev = dev; if (IS_ENABLED(CONFIG_WATCHDOG_BCM2835)) { diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c index 03dc834..2ac5f8b 100644 --- a/drivers/watchdog/davinci_wdt.c +++ b/drivers/watchdog/davinci_wdt.c @@ -149,7 +149,7 @@ clk_enable(davinci_wdt->clk); davinci_wdt->wd.set_timeout = davinci_wdt_set_timeout; - davinci_wdt->wd.dev = dev; + davinci_wdt->wd.hwdev = dev; ret = watchdog_register(&davinci_wdt->wd); if (ret < 0) diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 8fd8c81..85e810f 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -73,7 +73,7 @@ struct dw_wdt *dw_wdt = to_dw_wdt(wdd); if (IS_ERR(dw_wdt->rst)) { - dev_warn(dw_wdt->wdd.dev, "No reset line. Will not stop.\n"); + dev_warn(dw_wdt->wdd.hwdev, "No reset line. Will not stop.\n"); return PTR_ERR(dw_wdt->rst); } @@ -157,7 +157,7 @@ wdd = &dw_wdt->wdd; wdd->name = "dw_wdt"; - wdd->dev = dev; + wdd->hwdev = dev; wdd->set_timeout = dw_wdt_set_timeout; ret = watchdog_register(wdd); diff --git a/drivers/watchdog/im28wd.c b/drivers/watchdog/im28wd.c index 1956fdb..2b233ed 100644 --- a/drivers/watchdog/im28wd.c +++ b/drivers/watchdog/im28wd.c @@ -133,9 +133,6 @@ struct imx28_wd *pwd = (struct imx28_wd *)to_imx28_wd(wd); void __iomem *base; - if (timeout > (ULONG_MAX / WDOG_TICK_RATE)) - return -EINVAL; - if (timeout) { writel(timeout * WDOG_TICK_RATE, pwd->regs + MXS_RTC_WATCHDOG); base = pwd->regs + MXS_RTC_SET_ADDR; @@ -199,7 +196,8 @@ return PTR_ERR(iores); priv->regs = IOMEM(iores->start); priv->wd.set_timeout = imx28_watchdog_set_timeout; - priv->wd.dev = dev; + priv->wd.timeout_max = ULONG_MAX / WDOG_TICK_RATE; + priv->wd.hwdev = dev; if (!(readl(priv->regs + MXS_RTC_STAT) & MXS_RTC_STAT_WD_PRESENT)) { rc = -ENODEV; diff --git a/drivers/watchdog/imxwd.c b/drivers/watchdog/imxwd.c index bd5e518..a66fae4 100644 --- a/drivers/watchdog/imxwd.c +++ b/drivers/watchdog/imxwd.c @@ -28,6 +28,7 @@ int (*set_timeout)(struct imx_wd *, unsigned); void (*soc_reset)(struct imx_wd *); int (*init)(struct imx_wd *); + unsigned int timeout_max; }; struct imx_wd { @@ -71,9 +72,6 @@ dev_dbg(priv->dev, "%s: %d\n", __func__, timeout); - if (timeout > 64) - return -EINVAL; - if (!timeout) { writew(IMX1_WDOG_WCR_WHALT, priv->base + IMX1_WDOG_WCR); return 0; @@ -102,9 +100,6 @@ dev_dbg(priv->dev, "%s: %d\n", __func__, timeout); - if (timeout > 128) - return -EINVAL; - if (timeout == 0) /* bit 2 (WDE) cannot be set to 0 again */ return -ENOSYS; @@ -218,7 +213,8 @@ priv->base = IOMEM(iores->start); priv->ops = ops; priv->wd.set_timeout = imx_watchdog_set_timeout; - priv->wd.dev = dev; + priv->wd.timeout_max = priv->ops->timeout_max; + priv->wd.hwdev = dev; priv->dev = dev; priv->ext_reset = of_property_read_bool(dev->device_node, @@ -259,11 +255,13 @@ .set_timeout = imx21_watchdog_set_timeout, .soc_reset = imx21_soc_reset, .init = imx21_wd_init, + .timeout_max = 128, }; static const struct imx_wd_ops imx1_wd_ops = { .set_timeout = imx1_watchdog_set_timeout, .soc_reset = imx1_soc_reset, + .timeout_max = 64, }; static __maybe_unused struct of_device_id imx_wdt_dt_ids[] = { diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c index 2802033..dd1fa3a 100644 --- a/drivers/watchdog/orion_wdt.c +++ b/drivers/watchdog/orion_wdt.c @@ -49,9 +49,6 @@ container_of(wd, struct orion_wdt_ddata, wd); u32 ctrl; - if (0xffffffff / CLKRATE < timeout) - return -EINVAL; - ctrl = readl(ddata->timer_base + TIMER_CTRL); if (timeout == 0) { @@ -88,7 +85,8 @@ ddata->wd.set_timeout = armada_xp_set_timeout; ddata->wd.name = "orion_wdt"; - ddata->wd.dev = dev; + ddata->wd.hwdev = dev; + ddata->wd.timeout_max = U32_MAX / CLKRATE; res_timer = dev_request_mem_resource(dev, 0); if (IS_ERR(res_timer)) { diff --git a/drivers/watchdog/wd_core.c b/drivers/watchdog/wd_core.c index 3a3f519..d330544 100644 --- a/drivers/watchdog/wd_core.c +++ b/drivers/watchdog/wd_core.c @@ -23,19 +23,134 @@ static const char *watchdog_name(struct watchdog *wd) { - if (wd->dev) - return dev_name(wd->dev); + if (wd->hwdev) + return dev_name(wd->hwdev); if (wd->name) return wd->name; return "unknown"; } +static int _watchdog_set_timeout(struct watchdog *wd, unsigned timeout) +{ + if (timeout > wd->timeout_max) + return -EINVAL; + + pr_debug("setting timeout on %s to %ds\n", watchdog_name(wd), timeout); + + return wd->set_timeout(wd, timeout); +} + +static int watchdog_set_cur(struct param_d *param, void *priv) +{ + struct watchdog *wd = priv; + + if (wd->timeout_cur > wd->timeout_max) + return -EINVAL; + + return 0; +} + +static void watchdog_poller_cb(void *priv); + +static void watchdog_poller_start(struct watchdog *wd) +{ + _watchdog_set_timeout(wd, wd->timeout_cur); + poller_call_async(&wd->poller, 500 * MSECOND, + watchdog_poller_cb, wd); + +} + +static void watchdog_poller_cb(void *priv) +{ + struct watchdog *wd = priv; + + if (wd->poller_enable) + watchdog_poller_start(wd); +} + +static int watchdog_set_poller(struct param_d *param, void *priv) +{ + struct watchdog *wd = priv; + + + if (wd->poller_enable) { + dev_info(&wd->dev, "enable watchdog poller\n"); + watchdog_poller_start(wd); + } else { + dev_info(&wd->dev, "disable watchdog poller\n"); + poller_async_cancel(&wd->poller); + } + + return 0; +} + +static int watchdog_register_poller(struct watchdog *wd) +{ + struct param_d *p; + int ret; + + ret = poller_async_register(&wd->poller); + if (ret) + return ret; + + p = dev_add_param_bool(&wd->dev, "autoping", watchdog_set_poller, + NULL, &wd->poller_enable, wd); + + return PTR_ERR_OR_ZERO(p); +} + +static int watchdog_register_dev(struct watchdog *wd, const char *name, int id) +{ + wd->dev.parent = wd->hwdev; + wd->dev.id = id; + strncpy(wd->dev.name, name, MAX_DRIVER_NAME); + + return register_device(&wd->dev); +} + int watchdog_register(struct watchdog *wd) { + struct param_d *p; + const char *alias; + int ret = 0; + + alias = of_alias_get(wd->hwdev->device_node); + if (alias) + ret = watchdog_register_dev(wd, alias, DEVICE_ID_SINGLE); + + if (!alias || ret) + ret = watchdog_register_dev(wd, "wdog", DEVICE_ID_DYNAMIC); + + if (ret) + return ret; + if (!wd->priority) wd->priority = WATCHDOG_DEFAULT_PRIORITY; + /* set some default sane value */ + if (!wd->timeout_max) + wd->timeout_max = 60 * 60 * 24; + + if (!wd->timeout_cur || wd->timeout_cur > wd->timeout_max) + wd->timeout_cur = wd->timeout_max; + + p = dev_add_param_uint32_ro(&wd->dev, "timeout_max", + &wd->timeout_max, "%u"); + if (IS_ERR(p)) + return PTR_ERR(p); + + p = dev_add_param_uint32(&wd->dev, "timeout_cur", watchdog_set_cur, NULL, + &wd->timeout_cur, "%u", wd); + if (IS_ERR(p)) + return PTR_ERR(p); + + if (IS_ENABLED(CONFIG_WATCHDOG_POLLER)) { + ret = watchdog_register_poller(wd); + if (ret) + return ret; + } + list_add_tail(&wd->list, &watchdog_list); pr_debug("registering watchdog %s with priority %d\n", watchdog_name(wd), @@ -47,6 +162,12 @@ int watchdog_deregister(struct watchdog *wd) { + if (IS_ENABLED(CONFIG_WATCHDOG_POLLER)) { + poller_async_cancel(&wd->poller); + poller_async_unregister(&wd->poller); + } + + unregister_device(&wd->dev); list_del(&wd->list); return 0; @@ -81,9 +202,7 @@ if (!wd) return -ENODEV; - pr_debug("setting timeout on %s to %ds\n", watchdog_name(wd), timeout); - - return wd->set_timeout(wd, timeout); + return _watchdog_set_timeout(wd, timeout); } EXPORT_SYMBOL(watchdog_set_timeout); diff --git a/include/watchdog.h b/include/watchdog.h index 3e8a487..0db4263 100644 --- a/include/watchdog.h +++ b/include/watchdog.h @@ -13,11 +13,18 @@ #ifndef INCLUDE_WATCHDOG_H # define INCLUDE_WATCHDOG_H +#include + struct watchdog { int (*set_timeout)(struct watchdog *, unsigned); const char *name; - struct device_d *dev; + struct device_d *hwdev; + struct device_d dev; unsigned int priority; + unsigned int timeout_max; + unsigned int timeout_cur; + unsigned int poller_enable; + struct poller_async poller; struct list_head list; };