From 7aa31ee9ec92915926e74731378c009c9cc04928 Mon Sep 17 00:00:00 2001 From: Li Qiang Date: Sun, 28 Sep 2025 16:33:32 +0800 Subject: via_wdt: fix critical boot hang due to unnamed resource allocation The VIA watchdog driver uses allocate_resource() to reserve a MMIO region for the watchdog control register. However, the allocated resource was not given a name, which causes the kernel resource tree to contain an entry marked as "" under /proc/iomem on x86 platforms. During boot, this unnamed resource can lead to a critical hang because subsequent resource lookups and conflict checks fail to handle the invalid entry properly. Signed-off-by: Li Qiang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/via_wdt.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c index d647923d68fe..f55576392651 100644 --- a/drivers/watchdog/via_wdt.c +++ b/drivers/watchdog/via_wdt.c @@ -165,6 +165,7 @@ static int wdt_probe(struct pci_dev *pdev, dev_err(&pdev->dev, "cannot enable PCI device\n"); return -ENODEV; } + wdt_res.name = "via_wdt"; /* * Allocate a MMIO region which contains watchdog control register -- cgit v1.2.3 From 0eb54296dc784de757724055983d32b03c9e2df1 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 10 Oct 2025 16:03:14 +0800 Subject: watchdog: aspeed: Support variable number of reset mask registers Starting from the AST2600 platform, the SoC design has become more complex, with an increased number of reset mask registers. To support this, introduce a new field 'num_reset_masks' in the 'aspeed_wdt_config' structure to specify the number of reset mask registers per platform. This change removes the need for hardcoded platform-specific logic and improves scalability for future SoCs. Signed-off-by: Chin-Ting Kuo Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/aspeed_wdt.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index 837e15701c0e..d23e565f30a8 100644 --- a/drivers/watchdog/aspeed_wdt.c +++ b/drivers/watchdog/aspeed_wdt.c @@ -35,6 +35,7 @@ struct aspeed_wdt_config { u32 irq_shift; u32 irq_mask; struct aspeed_wdt_scu scu; + u32 num_reset_masks; }; struct aspeed_wdt { @@ -66,6 +67,7 @@ static const struct aspeed_wdt_config ast2500_config = { .wdt_reset_mask = 0x1, .wdt_reset_mask_shift = 2, }, + .num_reset_masks = 1, }; static const struct aspeed_wdt_config ast2600_config = { @@ -78,6 +80,7 @@ static const struct aspeed_wdt_config ast2600_config = { .wdt_reset_mask = 0xf, .wdt_reset_mask_shift = 16, }, + .num_reset_masks = 2, }; static const struct of_device_id aspeed_wdt_of_table[] = { @@ -479,11 +482,11 @@ static int aspeed_wdt_probe(struct platform_device *pdev) set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); } - if ((of_device_is_compatible(np, "aspeed,ast2500-wdt")) || - (of_device_is_compatible(np, "aspeed,ast2600-wdt"))) { + if (!of_device_is_compatible(np, "aspeed,ast2400-wdt")) { u32 reset_mask[2]; - size_t nrstmask = of_device_is_compatible(np, "aspeed,ast2600-wdt") ? 2 : 1; + size_t nrstmask = wdt->cfg->num_reset_masks; u32 reg = readl(wdt->base + WDT_RESET_WIDTH); + int i; reg &= wdt->cfg->ext_pulse_width_mask; if (of_property_read_bool(np, "aspeed,ext-active-high")) @@ -503,9 +506,8 @@ static int aspeed_wdt_probe(struct platform_device *pdev) ret = of_property_read_u32_array(np, "aspeed,reset-mask", reset_mask, nrstmask); if (!ret) { - writel(reset_mask[0], wdt->base + WDT_RESET_MASK1); - if (nrstmask > 1) - writel(reset_mask[1], wdt->base + WDT_RESET_MASK2); + for (i = 0; i < nrstmask; i++) + writel(reset_mask[i], wdt->base + WDT_RESET_MASK1 + i * 4); } } -- cgit v1.2.3 From 13e86646710dd5e1ec68798ab44773e314612d83 Mon Sep 17 00:00:00 2001 From: Chin-Ting Kuo Date: Fri, 10 Oct 2025 16:03:15 +0800 Subject: watchdog: aspeed: Add support for AST2700 platform Add AST2700 platform support to the ASPEED watchdog driver. This includes a new per-platform configuration with SCU reset status register at SCU1_070 and support for 5 reset mask registers. Signed-off-by: Chin-Ting Kuo Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/aspeed_wdt.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index d23e565f30a8..c9e79851504c 100644 --- a/drivers/watchdog/aspeed_wdt.c +++ b/drivers/watchdog/aspeed_wdt.c @@ -83,10 +83,24 @@ static const struct aspeed_wdt_config ast2600_config = { .num_reset_masks = 2, }; +static const struct aspeed_wdt_config ast2700_config = { + .ext_pulse_width_mask = 0xfffff, + .irq_shift = 0, + .irq_mask = GENMASK(31, 10), + .scu = { + .compatible = "aspeed,ast2700-scu0", + .reset_status_reg = 0x70, + .wdt_reset_mask = 0xf, + .wdt_reset_mask_shift = 0, + }, + .num_reset_masks = 5, +}; + static const struct of_device_id aspeed_wdt_of_table[] = { { .compatible = "aspeed,ast2400-wdt", .data = &ast2400_config }, { .compatible = "aspeed,ast2500-wdt", .data = &ast2500_config }, { .compatible = "aspeed,ast2600-wdt", .data = &ast2600_config }, + { .compatible = "aspeed,ast2700-wdt", .data = &ast2700_config }, { }, }; MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table); @@ -483,7 +497,7 @@ static int aspeed_wdt_probe(struct platform_device *pdev) } if (!of_device_is_compatible(np, "aspeed,ast2400-wdt")) { - u32 reset_mask[2]; + u32 reset_mask[5]; size_t nrstmask = wdt->cfg->num_reset_masks; u32 reg = readl(wdt->base + WDT_RESET_WIDTH); int i; -- cgit v1.2.3 From babe81b06158da7e845d19fae0a9205baf2b211d Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 17 Oct 2025 12:15:49 +0200 Subject: watchdog: renesas_wwdt: add driver This driver adds support for the Renesas Window Watchdog Timer (WWDT). Because it can only be setup once after boot and we cannot know if this already happened in early boot stages, it is mandated that the firmware configures the watchdog. Linux then adapts according to the given setup. Note that this watchdog only reports an overflow to the Error Control Module (ECM) and does not reset the SoC on its own. Signed-off-by: Wolfram Sang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 8 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/renesas_wwdt.c | 163 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 drivers/watchdog/renesas_wwdt.c (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 05008d937e40..792d0d831336 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -969,6 +969,14 @@ config RENESAS_WDT This driver adds watchdog support for the integrated watchdogs in the Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT). +config RENESAS_WWDT + tristate "Renesas Window WWDT Watchdog" + depends on ARCH_RENESAS || COMPILE_TEST + select WATCHDOG_CORE + help + This driver adds watchdog support for a window timer found in some + Renesas R-Car Gen3 and later SoCs. + config RENESAS_RZAWDT tristate "Renesas RZ/A WDT Watchdog" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index b680e4d3c1bc..ba52099b1253 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -85,6 +85,7 @@ obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o +obj-$(CONFIG_RENESAS_WWDT) += renesas_wwdt.o obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o obj-$(CONFIG_RENESAS_RZN1WDT) += rzn1_wdt.o obj-$(CONFIG_RENESAS_RZG2LWDT) += rzg2l_wdt.o diff --git a/drivers/watchdog/renesas_wwdt.c b/drivers/watchdog/renesas_wwdt.c new file mode 100644 index 000000000000..b250913c349a --- /dev/null +++ b/drivers/watchdog/renesas_wwdt.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Renesas Window Watchdog Timer (WWDT) + * + * The WWDT can only be setup once after boot. Because we cannot know if this + * already happened in early boot stages, it is mandated that the firmware + * configures the watchdog. Linux then adapts according to the given setup. + * Note that this watchdog reports in the default configuration an overflow to + * the Error Control Module which then decides further actions. Or the WWDT is + * configured to generate an interrupt. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WDTA0WDTE 0x00 +#define WDTA0RUN BIT(7) +#define WDTA0_KEY 0x2c + +#define WDTA0MD 0x0c +#define WDTA0OVF(x) FIELD_GET(GENMASK(6, 4), x) +#define WDTA0WIE BIT(3) +#define WDTA0ERM BIT(2) +#define WDTA0WS(x) FIELD_GET(GENMASK(1, 0), x) + +struct wwdt_priv { + void __iomem *base; + struct watchdog_device wdev; +}; + +static int wwdt_start(struct watchdog_device *wdev) +{ + struct wwdt_priv *priv = container_of(wdev, struct wwdt_priv, wdev); + + writeb(WDTA0RUN | WDTA0_KEY, priv->base + WDTA0WDTE); + return 0; +} + +static const struct watchdog_info wwdt_ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_ALARMONLY, + .identity = "Renesas Window Watchdog", +}; + +static const struct watchdog_ops wwdt_ops = { + .owner = THIS_MODULE, + .start = wwdt_start, +}; + +static irqreturn_t wwdt_error_irq(int irq, void *dev_id) +{ + struct device *dev = dev_id; + + dev_warn(dev, "Watchdog timed out\n"); + return IRQ_HANDLED; +} + +static irqreturn_t wwdt_pretimeout_irq(int irq, void *dev_id) +{ + struct watchdog_device *wdev = dev_id; + + watchdog_notify_pretimeout(wdev); + return IRQ_HANDLED; +} + +static int wwdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct wwdt_priv *priv; + struct watchdog_device *wdev; + struct clk *clk; + unsigned long rate; + unsigned int interval, window_size; + int ret; + u8 val; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + clk = devm_clk_get(dev, "cnt"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + rate = clk_get_rate(clk); + if (!rate) + return -EINVAL; + + wdev = &priv->wdev; + + val = readb(priv->base + WDTA0WDTE); + if (val & WDTA0RUN) + set_bit(WDOG_HW_RUNNING, &wdev->status); + + val = readb(priv->base + WDTA0MD); + interval = 1 << (9 + WDTA0OVF(val)); + /* size of the closed(!) window per mille */ + window_size = 250 * (3 - WDTA0WS(val)); + + wdev->info = &wwdt_ident; + wdev->ops = &wwdt_ops; + wdev->parent = dev; + wdev->min_hw_heartbeat_ms = window_size * interval / rate; + wdev->max_hw_heartbeat_ms = 1000 * interval / rate; + wdev->timeout = DIV_ROUND_UP(wdev->max_hw_heartbeat_ms, 1000); + watchdog_set_nowayout(wdev, true); + + if (!(val & WDTA0ERM)) { + ret = platform_get_irq_byname(pdev, "error"); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(dev, ret, NULL, wwdt_error_irq, + IRQF_ONESHOT, NULL, dev); + if (ret < 0) + return ret; + } + + if (val & WDTA0WIE) { + ret = platform_get_irq_byname(pdev, "pretimeout"); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(dev, ret, NULL, wwdt_pretimeout_irq, + IRQF_ONESHOT, NULL, wdev); + if (ret < 0) + return ret; + } + + devm_watchdog_register_device(dev, wdev); + + return 0; +} + +static const struct of_device_id renesas_wwdt_ids[] = { + { .compatible = "renesas,rcar-gen3-wwdt", }, + { .compatible = "renesas,rcar-gen4-wwdt", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, renesas_wwdt_ids); + +static struct platform_driver renesas_wwdt_driver = { + .driver = { + .name = "renesas_wwdt", + .of_match_table = renesas_wwdt_ids, + }, + .probe = wwdt_probe, +}; +module_platform_driver(renesas_wwdt_driver); + +MODULE_DESCRIPTION("Renesas Window Watchdog (WWDT) Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wolfram Sang "); -- cgit v1.2.3 From f0a4bf61f1a6080d0d2452c7d19066eba0e227b7 Mon Sep 17 00:00:00 2001 From: Zoe Gates Date: Mon, 27 Oct 2025 22:14:50 -0500 Subject: watchdog/diag288: Fix module comment typos Correct spelling and capitalizaion in the header comment so the documentation reads cleanly. Signed-off-by: Zoe Gates Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/diag288_wdt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/diag288_wdt.c b/drivers/watchdog/diag288_wdt.c index 887d5a6c155b..656b937f7653 100644 --- a/drivers/watchdog/diag288_wdt.c +++ b/drivers/watchdog/diag288_wdt.c @@ -6,10 +6,10 @@ * to CP. * * The command can be altered using the module parameter "cmd". This is - * not recommended because it's only supported on z/VM but not whith LPAR. + * not recommended because it's only supported on z/VM but not with LPAR. * - * On LPAR, the watchdog will always trigger a system restart. the module - * paramter cmd is meaningless here. + * On LPAR, the watchdog will always trigger a system restart. The module + * parameter cmd is meaningless here. * * * Copyright IBM Corp. 2004, 2013 -- cgit v1.2.3 From f909b3d4f1abc87486c3ff3b71b0df4bd862d9df Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Fri, 7 Nov 2025 14:01:27 +0800 Subject: watchdog: loongson1: Add missing MODULE_PARM_DESC Add documentation for module_param so that they're visible with modinfo command. Signed-off-by: Binbin Zhou Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/loongson1_wdt.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/loongson1_wdt.c b/drivers/watchdog/loongson1_wdt.c index 0587ff44d3a1..8502263b0d6f 100644 --- a/drivers/watchdog/loongson1_wdt.c +++ b/drivers/watchdog/loongson1_wdt.c @@ -18,10 +18,14 @@ #define DEFAULT_HEARTBEAT 30 static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0444); +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); static unsigned int heartbeat; -module_param(heartbeat, uint, 0444); +module_param(heartbeat, uint, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" + __MODULE_STRING(DEFAULT_HEARTBEAT) ")"); struct ls1x_wdt_drvdata { void __iomem *base; -- cgit v1.2.3 From 6121d0b8891a67b6a2924ace6caae364438ab5d7 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Fri, 7 Nov 2025 14:01:28 +0800 Subject: watchdog: loongson1: Simplify ls1x_wdt_probe code Remove meaningless output to simplify ls1x_wdt_probe(). Signed-off-by: Binbin Zhou Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/loongson1_wdt.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/loongson1_wdt.c b/drivers/watchdog/loongson1_wdt.c index 8502263b0d6f..781f01f1f888 100644 --- a/drivers/watchdog/loongson1_wdt.c +++ b/drivers/watchdog/loongson1_wdt.c @@ -108,11 +108,11 @@ static int ls1x_wdt_probe(struct platform_device *pdev) struct ls1x_wdt_drvdata *drvdata; struct watchdog_device *ls1x_wdt; unsigned long clk_rate; - int err; drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) return -ENOMEM; + platform_set_drvdata(pdev, drvdata); drvdata->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(drvdata->base)) @@ -139,15 +139,7 @@ static int ls1x_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(ls1x_wdt, nowayout); watchdog_set_drvdata(ls1x_wdt, drvdata); - err = devm_watchdog_register_device(dev, &drvdata->wdt); - if (err) - return err; - - platform_set_drvdata(pdev, drvdata); - - dev_info(dev, "Loongson1 Watchdog driver registered\n"); - - return 0; + return devm_watchdog_register_device(dev, &drvdata->wdt); } #ifdef CONFIG_OF -- cgit v1.2.3 From 9d8ca99d60a1f058c6aba0241c00550ec8356119 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Fri, 7 Nov 2025 14:01:29 +0800 Subject: watchdog: loongson1: Drop CONFIG_OF The general recommendation is to not use of_match_ptr() or CONFIG_OF ifdef. Drop them. Signed-off-by: Binbin Zhou Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/loongson1_wdt.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/loongson1_wdt.c b/drivers/watchdog/loongson1_wdt.c index 781f01f1f888..255198cbf5bf 100644 --- a/drivers/watchdog/loongson1_wdt.c +++ b/drivers/watchdog/loongson1_wdt.c @@ -142,20 +142,18 @@ static int ls1x_wdt_probe(struct platform_device *pdev) return devm_watchdog_register_device(dev, &drvdata->wdt); } -#ifdef CONFIG_OF static const struct of_device_id ls1x_wdt_dt_ids[] = { { .compatible = "loongson,ls1b-wdt", }, { .compatible = "loongson,ls1c-wdt", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ls1x_wdt_dt_ids); -#endif static struct platform_driver ls1x_wdt_driver = { .probe = ls1x_wdt_probe, .driver = { .name = "ls1x-wdt", - .of_match_table = of_match_ptr(ls1x_wdt_dt_ids), + .of_match_table = ls1x_wdt_dt_ids, }, }; -- cgit v1.2.3 From e0c50cddbd942338cbd6029ddbbcef4268a30718 Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Fri, 7 Nov 2025 14:01:51 +0800 Subject: watchdog: loongson1: Add Loongson-2k0300 watchdog support According to the manual, the Loongson-2K0300 watchdog is similar to the Loongson-1, except for some register offsets and inconsistent register bit definitions. Separate definitions via driver_data suffice. Co-developed-by: Xiaochuang Mao Signed-off-by: Xiaochuang Mao Signed-off-by: Binbin Zhou Reviewed-by: Huacai Chen Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 4 +-- drivers/watchdog/loongson1_wdt.c | 73 +++++++++++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 792d0d831336..d3b9df7d466b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1984,10 +1984,10 @@ config LANTIQ_WDT config LOONGSON1_WDT tristate "Loongson1 SoC hardware watchdog" - depends on MACH_LOONGSON32 || COMPILE_TEST + depends on MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST select WATCHDOG_CORE help - Hardware driver for the Loongson1 SoC Watchdog Timer. + Hardware driver for the Loongson family Watchdog Timer. config RALINK_WDT tristate "Ralink SoC watchdog" diff --git a/drivers/watchdog/loongson1_wdt.c b/drivers/watchdog/loongson1_wdt.c index 255198cbf5bf..2417519717cc 100644 --- a/drivers/watchdog/loongson1_wdt.c +++ b/drivers/watchdog/loongson1_wdt.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2016 Yang Ling + * Copyright (C) 2025 Binbin Zhou */ #include @@ -10,10 +11,8 @@ #include #include -/* Loongson 1 Watchdog Register Definitions */ +/* Loongson Watchdog Register Definitions */ #define WDT_EN 0x0 -#define WDT_TIMER 0x4 -#define WDT_SET 0x8 #define DEFAULT_HEARTBEAT 30 @@ -27,18 +26,37 @@ module_param(heartbeat, uint, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" __MODULE_STRING(DEFAULT_HEARTBEAT) ")"); +struct ls1x_wdt_pdata { + u32 timer_offset; + u32 set_offset; + u32 wdt_en_bit; +}; + +static const struct ls1x_wdt_pdata ls1b_wdt_pdata = { + .timer_offset = 0x4, + .set_offset = 0x8, + .wdt_en_bit = BIT(0), +}; + +static const struct ls1x_wdt_pdata ls2k0300_wdt_pdata = { + .timer_offset = 0x8, + .set_offset = 0x4, + .wdt_en_bit = BIT(1), +}; + struct ls1x_wdt_drvdata { void __iomem *base; struct clk *clk; unsigned long clk_rate; struct watchdog_device wdt; + const struct ls1x_wdt_pdata *pdata; }; static int ls1x_wdt_ping(struct watchdog_device *wdt_dev) { struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writel(0x1, drvdata->base + WDT_SET); + writel(0x1, drvdata->base + drvdata->pdata->set_offset); return 0; } @@ -53,7 +71,7 @@ static int ls1x_wdt_set_timeout(struct watchdog_device *wdt_dev, wdt_dev->timeout = timeout; counts = drvdata->clk_rate * min(timeout, max_hw_heartbeat); - writel(counts, drvdata->base + WDT_TIMER); + writel(counts, drvdata->base + drvdata->pdata->timer_offset); return 0; } @@ -62,7 +80,7 @@ static int ls1x_wdt_start(struct watchdog_device *wdt_dev) { struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writel(0x1, drvdata->base + WDT_EN); + writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN); return 0; } @@ -70,8 +88,10 @@ static int ls1x_wdt_start(struct watchdog_device *wdt_dev) static int ls1x_wdt_stop(struct watchdog_device *wdt_dev) { struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + u32 val = readl(drvdata->base + WDT_EN); - writel(0x0, drvdata->base + WDT_EN); + val &= ~(drvdata->pdata->wdt_en_bit); + writel(val, drvdata->base + WDT_EN); return 0; } @@ -81,9 +101,9 @@ static int ls1x_wdt_restart(struct watchdog_device *wdt_dev, { struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - writel(0x1, drvdata->base + WDT_EN); - writel(0x1, drvdata->base + WDT_TIMER); - writel(0x1, drvdata->base + WDT_SET); + writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN); + writel(0x1, drvdata->base + drvdata->pdata->timer_offset); + writel(0x1, drvdata->base + drvdata->pdata->set_offset); return 0; } @@ -114,6 +134,8 @@ static int ls1x_wdt_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, drvdata); + drvdata->pdata = of_device_get_match_data(dev); + drvdata->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(drvdata->base)) return PTR_ERR(drvdata->base); @@ -142,9 +164,32 @@ static int ls1x_wdt_probe(struct platform_device *pdev) return devm_watchdog_register_device(dev, &drvdata->wdt); } +static int ls1x_wdt_resume(struct device *dev) +{ + struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev); + + if (watchdog_active(&data->wdt)) + ls1x_wdt_start(&data->wdt); + + return 0; +} + +static int ls1x_wdt_suspend(struct device *dev) +{ + struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev); + + if (watchdog_active(&data->wdt)) + ls1x_wdt_stop(&data->wdt); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ls1x_wdt_pm_ops, ls1x_wdt_suspend, ls1x_wdt_resume); + static const struct of_device_id ls1x_wdt_dt_ids[] = { - { .compatible = "loongson,ls1b-wdt", }, - { .compatible = "loongson,ls1c-wdt", }, + { .compatible = "loongson,ls1b-wdt", .data = &ls1b_wdt_pdata }, + { .compatible = "loongson,ls1c-wdt", .data = &ls1b_wdt_pdata }, + { .compatible = "loongson,ls2k0300-wdt", .data = &ls2k0300_wdt_pdata }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ls1x_wdt_dt_ids); @@ -154,11 +199,13 @@ static struct platform_driver ls1x_wdt_driver = { .driver = { .name = "ls1x-wdt", .of_match_table = ls1x_wdt_dt_ids, + .pm = pm_ptr(&ls1x_wdt_pm_ops), }, }; module_platform_driver(ls1x_wdt_driver); MODULE_AUTHOR("Yang Ling "); -MODULE_DESCRIPTION("Loongson1 Watchdog Driver"); +MODULE_AUTHOR("Binbin Zhou "); +MODULE_DESCRIPTION("Loongson Watchdog Driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 25c0b472eab8379683d4eef681185c104bed8ffd Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Thu, 13 Nov 2025 10:30:32 +0800 Subject: watchdog: wdat_wdt: Fix ACPI table leak in probe function wdat_wdt_probe() calls acpi_get_table() to obtain the WDAT ACPI table but never calls acpi_put_table() on any paths. This causes a permanent ACPI table memory leak. Add a single cleanup path which calls acpi_put_table() to ensure the ACPI table is always released. Fixes: 058dfc767008 ("ACPI / watchdog: Add support for WDAT hardware watchdog") Suggested-by: Guenter Roeck Signed-off-by: Haotian Zhang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/wdat_wdt.c | 64 ++++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c index 650fdc7996e1..dd3c2d69c9df 100644 --- a/drivers/watchdog/wdat_wdt.c +++ b/drivers/watchdog/wdat_wdt.c @@ -326,19 +326,27 @@ static int wdat_wdt_probe(struct platform_device *pdev) return -ENODEV; wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL); - if (!wdat) - return -ENOMEM; + if (!wdat) { + ret = -ENOMEM; + goto out_put_table; + } regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs), GFP_KERNEL); - if (!regs) - return -ENOMEM; + if (!regs) { + ret = -ENOMEM; + goto out_put_table; + } /* WDAT specification wants to have >= 1ms period */ - if (tbl->timer_period < 1) - return -EINVAL; - if (tbl->min_count > tbl->max_count) - return -EINVAL; + if (tbl->timer_period < 1) { + ret = -EINVAL; + goto out_put_table; + } + if (tbl->min_count > tbl->max_count) { + ret = -EINVAL; + goto out_put_table; + } wdat->period = tbl->timer_period; wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000); @@ -355,15 +363,20 @@ static int wdat_wdt_probe(struct platform_device *pdev) res = &pdev->resource[i]; if (resource_type(res) == IORESOURCE_MEM) { reg = devm_ioremap_resource(dev, res); - if (IS_ERR(reg)) - return PTR_ERR(reg); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + goto out_put_table; + } } else if (resource_type(res) == IORESOURCE_IO) { reg = devm_ioport_map(dev, res->start, 1); - if (!reg) - return -ENOMEM; + if (!reg) { + ret = -ENOMEM; + goto out_put_table; + } } else { dev_err(dev, "Unsupported resource\n"); - return -EINVAL; + ret = -EINVAL; + goto out_put_table; } regs[i] = reg; @@ -385,8 +398,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) } instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL); - if (!instr) - return -ENOMEM; + if (!instr) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(&instr->node); instr->entry = entries[i]; @@ -417,7 +432,8 @@ static int wdat_wdt_probe(struct platform_device *pdev) if (!instr->reg) { dev_err(dev, "I/O resource not found\n"); - return -EINVAL; + ret = -EINVAL; + goto out_put_table; } instructions = wdat->instructions[action]; @@ -425,8 +441,10 @@ static int wdat_wdt_probe(struct platform_device *pdev) instructions = devm_kzalloc(dev, sizeof(*instructions), GFP_KERNEL); - if (!instructions) - return -ENOMEM; + if (!instructions) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(instructions); wdat->instructions[action] = instructions; @@ -443,7 +461,7 @@ static int wdat_wdt_probe(struct platform_device *pdev) ret = wdat_wdt_enable_reboot(wdat); if (ret) - return ret; + goto out_put_table; platform_set_drvdata(pdev, wdat); @@ -460,12 +478,16 @@ static int wdat_wdt_probe(struct platform_device *pdev) ret = wdat_wdt_set_timeout(&wdat->wdd, timeout); if (ret) - return ret; + goto out_put_table; watchdog_set_nowayout(&wdat->wdd, nowayout); watchdog_stop_on_reboot(&wdat->wdd); watchdog_stop_on_unregister(&wdat->wdd); - return devm_watchdog_register_device(dev, &wdat->wdd); + ret = devm_watchdog_register_device(dev, &wdat->wdd); + +out_put_table: + acpi_put_table((struct acpi_table_header *)tbl); + return ret; } static int wdat_wdt_suspend_noirq(struct device *dev) -- cgit v1.2.3 From 5bcc5786a0cfa9249ccbe539833040a6285d0de3 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Wed, 5 Nov 2025 16:42:20 +0800 Subject: watchdog: starfive: Fix resource leak in probe error path If pm_runtime_put_sync() fails after watchdog_register_device() succeeds, the probe function jumps to err_exit without unregistering the watchdog device. This leaves the watchdog registered in the subsystem while the driver fails to load, resulting in a resource leak. Add a new error label err_unregister_wdt to properly unregister the watchdog device. Fixes: 8bc22a2f1bf0 ("watchdog: starfive: Check pm_runtime_enabled() before decrementing usage counter") Signed-off-by: Haotian Zhang Reviewed-by: Wim Van Sebroeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/starfive-wdt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index 355918d62f63..ed71d3960a0f 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -500,12 +500,14 @@ static int starfive_wdt_probe(struct platform_device *pdev) if (pm_runtime_enabled(&pdev->dev)) { ret = pm_runtime_put_sync(&pdev->dev); if (ret) - goto err_exit; + goto err_unregister_wdt; } } return 0; +err_unregister_wdt: + watchdog_unregister_device(&wdt->wdd); err_exit: starfive_wdt_disable_clock(wdt); pm_runtime_disable(&pdev->dev); -- cgit v1.2.3