Merge tag 'watchdog-for-v7.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull watchdog fixes from Guenter Roeck:

 - core: Do not start hrtimer when pretimeout is zero

 - msc313e: Various fixes for issues reported by Sashiko

 - MAINTAINERS: Update URI for watchdog tree

 - sunxi_wdt: preserve boot-enabled watchdog

* tag 'watchdog-for-v7.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging:
  watchdog: msc313e: Sync timeout value if WDT was running at boot
  watchdog: msc313e: Fix undefined behavior
  watchdog: msc313e: Fix spurious reset on suspend
  watchdog: msc313e: Enable clock before accessing hardware registers
  watchdog: msc313e: Fix clock leak and spurious timer in settimeout()
  watchdog: msc313e: Avoid division by zero
  watchdog: fix hrtimer start when pretimeout is zero
  MAINTAINERS: Update URI for watchdog tree
  watchdog: msc313e: Fix NULL pointer dereference in PM callbacks
  watchdog: sunxi_wdt: preserve boot-enabled watchdog
This commit is contained in:
Linus Torvalds
2026-09-10 09:11:17 -07:00
4 changed files with 106 additions and 16 deletions
+1 -1
View File
@@ -29357,7 +29357,7 @@ M: Guenter Roeck <linux@roeck-us.net>
L: linux-watchdog@vger.kernel.org
S: Maintained
W: http://www.linux-watchdog.org/
T: git git://www.linux-watchdog.org/linux-watchdog.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git
F: Documentation/devicetree/bindings/watchdog/
F: Documentation/watchdog/
F: drivers/watchdog/
+60 -14
View File
@@ -31,20 +31,36 @@ struct msc313e_wdt_priv {
struct clk *clk;
};
static u32 msc313e_wdt_get_hw_timeout(struct msc313e_wdt_priv *priv)
{
u16 low, high;
low = readw(priv->base + REG_WDT_MAX_PRD_L);
high = readw(priv->base + REG_WDT_MAX_PRD_H);
return ((u32)high << 16) | low;
}
static void msc313e_wdt_set_hw_timeout(struct msc313e_wdt_priv *priv,
unsigned int timeout)
{
u32 t = timeout * clk_get_rate(priv->clk);
writew(t & 0xffff, priv->base + REG_WDT_MAX_PRD_L);
writew((t >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H);
writew(1, priv->base + REG_WDT_CLR);
}
static int msc313e_wdt_start(struct watchdog_device *wdev)
{
struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev);
u32 timeout;
int err;
err = clk_prepare_enable(priv->clk);
if (err)
return err;
timeout = wdev->timeout * clk_get_rate(priv->clk);
writew(timeout & 0xffff, priv->base + REG_WDT_MAX_PRD_L);
writew((timeout >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H);
writew(1, priv->base + REG_WDT_CLR);
msc313e_wdt_set_hw_timeout(priv, wdev->timeout);
return 0;
}
@@ -69,9 +85,13 @@ static int msc313e_wdt_stop(struct watchdog_device *wdev)
static int msc313e_wdt_settimeout(struct watchdog_device *wdev, unsigned int new_time)
{
struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev);
wdev->timeout = new_time;
return msc313e_wdt_start(wdev);
if (watchdog_hw_running(wdev) || watchdog_active(wdev))
msc313e_wdt_set_hw_timeout(priv, wdev->timeout);
return 0;
}
static const struct watchdog_info msc313e_wdt_ident = {
@@ -97,6 +117,8 @@ static int msc313e_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct msc313e_wdt_priv *priv;
unsigned long rate;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -116,27 +138,51 @@ static int msc313e_wdt_probe(struct platform_device *pdev)
priv->wdev.ops = &msc313e_wdt_ops,
priv->wdev.parent = dev;
priv->wdev.min_timeout = MSC313E_WDT_MIN_TIMEOUT;
priv->wdev.max_timeout = U32_MAX / clk_get_rate(priv->clk);
rate = clk_get_rate(priv->clk);
if (!rate)
return -EINVAL;
priv->wdev.max_timeout = U32_MAX / rate;
priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT;
/* If the period is non-zero the WDT is running */
if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16))
set_bit(WDOG_HW_RUNNING, &priv->wdev.status);
watchdog_set_drvdata(&priv->wdev, priv);
platform_set_drvdata(pdev, priv);
watchdog_init_timeout(&priv->wdev, timeout, dev);
watchdog_stop_on_reboot(&priv->wdev);
watchdog_stop_on_unregister(&priv->wdev);
watchdog_stop_ping_on_suspend(&priv->wdev);
return devm_watchdog_register_device(dev, &priv->wdev);
ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
/* If the period is non-zero the WDT is running */
if (msc313e_wdt_get_hw_timeout(priv)) {
msc313e_wdt_set_hw_timeout(priv, priv->wdev.timeout);
set_bit(WDOG_HW_RUNNING, &priv->wdev.status);
/*
* Keep the clock enabled. The watchdog core will skip the next
* start() and a future stop() will balance the CCF reference
* count.
*/
} else {
clk_disable_unprepare(priv->clk);
}
ret = devm_watchdog_register_device(dev, &priv->wdev);
/* If the WDT is running and anything goes wrong, disable the clock. */
if (ret && test_bit(WDOG_HW_RUNNING, &priv->wdev.status))
clk_disable_unprepare(priv->clk);
return ret;
}
static int __maybe_unused msc313e_wdt_suspend(struct device *dev)
{
struct msc313e_wdt_priv *priv = dev_get_drvdata(dev);
if (watchdog_active(&priv->wdev))
if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev))
msc313e_wdt_stop(&priv->wdev);
return 0;
@@ -146,7 +192,7 @@ static int __maybe_unused msc313e_wdt_resume(struct device *dev)
{
struct msc313e_wdt_priv *priv = dev_get_drvdata(dev);
if (watchdog_active(&priv->wdev))
if (watchdog_active(&priv->wdev) || watchdog_hw_running(&priv->wdev))
msc313e_wdt_start(&priv->wdev);
return 0;
+44 -1
View File
@@ -128,6 +128,38 @@ static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
return 0;
}
static bool sunxi_wdt_is_running(struct watchdog_device *wdt_dev)
{
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
return readl(sunxi_wdt->wdt_base + regs->wdt_mode) & WDT_MODE_EN;
}
static unsigned int sunxi_wdt_get_timeout(struct watchdog_device *wdt_dev)
{
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
unsigned int timeout;
u32 interval;
interval = readl(sunxi_wdt->wdt_base + regs->wdt_mode);
interval >>= regs->wdt_timeout_shift;
interval &= WDT_TIMEOUT_MASK;
/* Round the 0.5-second interval up to the minimum representable timeout. */
if (!interval)
return WDT_MIN_TIMEOUT;
for (timeout = WDT_MIN_TIMEOUT;
timeout < ARRAY_SIZE(wdt_timeout_map); timeout++) {
if (wdt_timeout_map[timeout] == interval)
return timeout;
}
/* Reserved interval encoding. */
return 0;
}
static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
unsigned int timeout)
{
@@ -259,6 +291,7 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sunxi_wdt_dev *sunxi_wdt;
unsigned int running_timeout;
int err;
sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
@@ -286,7 +319,17 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
if (sunxi_wdt_is_running(&sunxi_wdt->wdt_dev)) {
running_timeout = sunxi_wdt_get_timeout(&sunxi_wdt->wdt_dev);
if (running_timeout)
sunxi_wdt->wdt_dev.timeout = running_timeout;
err = sunxi_wdt_start(&sunxi_wdt->wdt_dev);
if (err)
return err;
set_bit(WDOG_HW_RUNNING, &sunxi_wdt->wdt_dev.status);
}
watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
@@ -30,6 +30,7 @@ void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd)
void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd)
{
if (!(wdd->info->options & WDIOF_PRETIMEOUT) &&
wdd->pretimeout &&
!watchdog_pretimeout_invalid(wdd, wdd->pretimeout))
hrtimer_start(&wdd->wd_data->pretimeout_timer,
ktime_set(wdd->timeout - wdd->pretimeout, 0),