From 151d4c1b2fbf7d9604a5f4de1bcb80a53a9ead1c Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Mon, 27 Apr 2026 18:48:02 -0700 Subject: watchdog: remove driver for integrated WDT of ZFx86 486-based SoC The machzwd driver supports the integrated watchdog of the ZF Micro ZFx86 SoC, which contains a 486-compatible core [1]. Since 486 support was removed in commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), the driver is no longer useful, Remove it. [1] https://www.zfmicro.com/zfx86.html Signed-off-by: Ethan Nelson-Moore Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260428014806.35400-1-enelsonmoore@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 13 -- drivers/watchdog/Makefile | 1 - drivers/watchdog/machzwd.c | 452 --------------------------------------------- 3 files changed, 466 deletions(-) delete mode 100644 drivers/watchdog/machzwd.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index dc78729ba2a5..97e320721cff 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1712,19 +1712,6 @@ config W83977F_WDT To compile this driver as a module, choose M here: the module will be called w83977f_wdt. -config MACHZ_WDT - tristate "ZF MachZ Watchdog" - depends on (X86 || COMPILE_TEST) && HAS_IOPORT - help - If you are using a ZF Micro MachZ processor, say Y here, otherwise - N. This is the driver for the watchdog timer built-in on that - processor using ZF-Logic interface. This watchdog simply watches - your kernel to make sure it doesn't freeze, and if it does, it - reboots your computer after a certain amount of time. - - To compile this driver as a module, choose M here: the - module will be called machzwd. - config SBC_EPX_C3_WATCHDOG tristate "Winsystems SBC EPX-C3 watchdog" depends on (X86 || COMPILE_TEST) && HAS_IOPORT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index d2fb16b9f9ce..2961ca141c61 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -146,7 +146,6 @@ obj-$(CONFIG_VIA_WDT) += via_wdt.o obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o -obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o obj-$(CONFIG_INTEL_OC_WATCHDOG) += intel_oc_wdt.o diff --git a/drivers/watchdog/machzwd.c b/drivers/watchdog/machzwd.c deleted file mode 100644 index 0ae8e5bc10ae..000000000000 --- a/drivers/watchdog/machzwd.c +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * MachZ ZF-Logic Watchdog Timer driver for Linux - * - * The author does NOT admit liability nor provide warranty for - * any of this software. This material is provided "AS-IS" in - * the hope that it may be useful for others. - * - * Author: Fernando Fuganti - * - * Based on sbc60xxwdt.c by Jakob Oestergaard - * - * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the - * following periods: - * wd#1 - 2 seconds; - * wd#2 - 7.2 ms; - * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or - * a system RESET and it starts wd#2 that unconditionally will RESET - * the system when the counter reaches zero. - * - * 14-Dec-2001 Matt Domsch - * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* ports */ -#define ZF_IOBASE 0x218 -#define INDEX 0x218 -#define DATA_B 0x219 -#define DATA_W 0x21A -#define DATA_D 0x21A - -/* indexes */ /* size */ -#define ZFL_VERSION 0x02 /* 16 */ -#define CONTROL 0x10 /* 16 */ -#define STATUS 0x12 /* 8 */ -#define COUNTER_1 0x0C /* 16 */ -#define COUNTER_2 0x0E /* 8 */ -#define PULSE_LEN 0x0F /* 8 */ - -/* controls */ -#define ENABLE_WD1 0x0001 -#define ENABLE_WD2 0x0002 -#define RESET_WD1 0x0010 -#define RESET_WD2 0x0020 -#define GEN_SCI 0x0100 -#define GEN_NMI 0x0200 -#define GEN_SMI 0x0400 -#define GEN_RESET 0x0800 - - -/* utilities */ - -#define WD1 0 -#define WD2 1 - -#define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } -#define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } -#define zf_get_ZFL_version() zf_readw(ZFL_VERSION) - - -static unsigned short zf_readw(unsigned char port) -{ - outb(port, INDEX); - return inw(DATA_W); -} - - -MODULE_AUTHOR("Fernando Fuganti "); -MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); -MODULE_LICENSE("GPL"); - -static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, - "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -#define PFX "machzwd" - -static const struct watchdog_info zf_info = { - .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "ZF-Logic watchdog", -}; - - -/* - * action refers to action taken when watchdog resets - * 0 = GEN_RESET - * 1 = GEN_SMI - * 2 = GEN_NMI - * 3 = GEN_SCI - * defaults to GEN_RESET (0) - */ -static int action; -module_param(action, int, 0); -MODULE_PARM_DESC(action, "after watchdog resets, generate: " - "0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); - -static void zf_ping(struct timer_list *unused); - -static int zf_action = GEN_RESET; -static unsigned long zf_is_open; -static char zf_expect_close; -static DEFINE_SPINLOCK(zf_port_lock); -static DEFINE_TIMER(zf_timer, zf_ping); -static unsigned long next_heartbeat; - - -/* timeout for user land heart beat (10 seconds) */ -#define ZF_USER_TIMEO (HZ*10) - -/* timeout for hardware watchdog (~500ms) */ -#define ZF_HW_TIMEO (HZ/2) - -/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ -#define ZF_CTIMEOUT 0xffff - -#ifndef ZF_DEBUG -#define dprintk(format, args...) -#else -#define dprintk(format, args...) \ - pr_debug(":%s:%d: " format, __func__, __LINE__ , ## args) -#endif - - -static inline void zf_set_status(unsigned char new) -{ - zf_writeb(STATUS, new); -} - - -/* CONTROL register functions */ - -static inline unsigned short zf_get_control(void) -{ - return zf_readw(CONTROL); -} - -static inline void zf_set_control(unsigned short new) -{ - zf_writew(CONTROL, new); -} - - -/* WD#? counter functions */ -/* - * Just set counter value - */ - -static inline void zf_set_timer(unsigned short new, unsigned char n) -{ - switch (n) { - case WD1: - zf_writew(COUNTER_1, new); - fallthrough; - case WD2: - zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); - fallthrough; - default: - return; - } -} - -/* - * stop hardware timer - */ -static void zf_timer_off(void) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - /* stop internal ping */ - timer_delete_sync(&zf_timer); - - spin_lock_irqsave(&zf_port_lock, flags); - /* stop watchdog timer */ - ctrl_reg = zf_get_control(); - ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ - ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - pr_info("Watchdog timer is now disabled\n"); -} - - -/* - * start hardware timer - */ -static void zf_timer_on(void) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - spin_lock_irqsave(&zf_port_lock, flags); - - zf_writeb(PULSE_LEN, 0xff); - - zf_set_timer(ZF_CTIMEOUT, WD1); - - /* user land ping */ - next_heartbeat = jiffies + ZF_USER_TIMEO; - - /* start the timer for internal ping */ - mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); - - /* start watchdog timer */ - ctrl_reg = zf_get_control(); - ctrl_reg |= (ENABLE_WD1|zf_action); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - pr_info("Watchdog timer is now enabled\n"); -} - - -static void zf_ping(struct timer_list *unused) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - zf_writeb(COUNTER_2, 0xff); - - if (time_before(jiffies, next_heartbeat)) { - dprintk("time_before: %ld\n", next_heartbeat - jiffies); - /* - * reset event is activated by transition from 0 to 1 on - * RESET_WD1 bit and we assume that it is already zero... - */ - - spin_lock_irqsave(&zf_port_lock, flags); - ctrl_reg = zf_get_control(); - ctrl_reg |= RESET_WD1; - zf_set_control(ctrl_reg); - - /* ...and nothing changes until here */ - ctrl_reg &= ~(RESET_WD1); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); - } else - pr_crit("I will reset your machine\n"); -} - -static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, - loff_t *ppos) -{ - /* See if we got the magic character */ - if (count) { - /* - * no need to check for close confirmation - * no way to disable watchdog ;) - */ - if (!nowayout) { - size_t ofs; - /* - * note: just in case someone wrote the magic character - * five months ago... - */ - zf_expect_close = 0; - - /* now scan */ - for (ofs = 0; ofs != count; ofs++) { - char c; - if (get_user(c, buf + ofs)) - return -EFAULT; - if (c == 'V') { - zf_expect_close = 42; - dprintk("zf_expect_close = 42\n"); - } - } - } - - /* - * Well, anyhow someone wrote to us, - * we should return that favour - */ - next_heartbeat = jiffies + ZF_USER_TIMEO; - dprintk("user ping at %ld\n", jiffies); - } - return count; -} - -static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - switch (cmd) { - case WDIOC_GETSUPPORT: - if (copy_to_user(argp, &zf_info, sizeof(zf_info))) - return -EFAULT; - break; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_KEEPALIVE: - zf_ping(NULL); - break; - default: - return -ENOTTY; - } - return 0; -} - -static int zf_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &zf_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); - zf_timer_on(); - return stream_open(inode, file); -} - -static int zf_close(struct inode *inode, struct file *file) -{ - if (zf_expect_close == 42) - zf_timer_off(); - else { - timer_delete(&zf_timer); - pr_err("device file closed unexpectedly. Will not stop the WDT!\n"); - } - clear_bit(0, &zf_is_open); - zf_expect_close = 0; - return 0; -} - -/* - * Notifier for system down - */ - -static int zf_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - zf_timer_off(); - return NOTIFY_DONE; -} - -static const struct file_operations zf_fops = { - .owner = THIS_MODULE, - .write = zf_write, - .unlocked_ioctl = zf_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .open = zf_open, - .release = zf_close, -}; - -static struct miscdevice zf_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &zf_fops, -}; - - -/* - * The device needs to learn about soft shutdowns in order to - * turn the timebomb registers off. - */ -static struct notifier_block zf_notifier = { - .notifier_call = zf_notify_sys, -}; - -static void __init zf_show_action(int act) -{ - static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" }; - - pr_info("Watchdog using action = %s\n", str[act]); -} - -static int __init zf_init(void) -{ - int ret; - - pr_info("MachZ ZF-Logic Watchdog driver initializing\n"); - - ret = zf_get_ZFL_version(); - if (!ret || ret == 0xffff) { - pr_warn("no ZF-Logic found\n"); - return -ENODEV; - } - - if (action <= 3 && action >= 0) - zf_action = zf_action >> action; - else - action = 0; - - zf_show_action(action); - - if (!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")) { - pr_err("cannot reserve I/O ports at %d\n", ZF_IOBASE); - ret = -EBUSY; - goto no_region; - } - - ret = register_reboot_notifier(&zf_notifier); - if (ret) { - pr_err("can't register reboot notifier (err=%d)\n", ret); - goto no_reboot; - } - - ret = misc_register(&zf_miscdev); - if (ret) { - pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); - goto no_misc; - } - - zf_set_status(0); - zf_set_control(0); - - return 0; - -no_misc: - unregister_reboot_notifier(&zf_notifier); -no_reboot: - release_region(ZF_IOBASE, 3); -no_region: - return ret; -} - - -static void __exit zf_exit(void) -{ - zf_timer_off(); - - misc_deregister(&zf_miscdev); - unregister_reboot_notifier(&zf_notifier); - release_region(ZF_IOBASE, 3); -} - -module_init(zf_init); -module_exit(zf_exit); -- cgit v1.2.3 From 3b3a80dacd6e730196b45cae142970aa46c87c50 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 22 Apr 2026 10:27:37 +0200 Subject: watchdog: qcom: Unify user-visible "Qualcomm" name Various names for Qualcomm as a company are used in user-visible config options: QCOM, Qualcomm and Qualcomm Technologies. Switch to unified "Qualcomm" so it will be easier for users to identify the options when for example running menuconfig. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260422082736.81981-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 97e320721cff..e19872c0ae61 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -882,7 +882,7 @@ config TEGRA_WATCHDOG module will be called tegra_wdt. config QCOM_WDT - tristate "QCOM watchdog" + tristate "Qualcomm watchdog" depends on HAS_IOMEM depends on ARCH_QCOM || COMPILE_TEST select WATCHDOG_CORE @@ -1093,7 +1093,7 @@ config SPRD_WATCHDOG by Spreadtrum system. config PM8916_WATCHDOG - tristate "QCOM PM8916 pmic watchdog" + tristate "Qualcomm PM8916 pmic watchdog" depends on OF && MFD_SPMI_PMIC select WATCHDOG_CORE help -- cgit v1.2.3 From 158dd300b07b0d39e9fb791a4ad1bc68c8588530 Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 8 Apr 2026 14:54:38 +0530 Subject: watchdog: qcom: add support to get the bootstatus from IMEM When the system boots up after a watchdog reset, the EXPIRED_STATUS bit in the WDT_STS register is cleared. To identify if the system was restarted due to WDT expiry, XBL update the information in the IMEM region. Update the driver to read the restart reason from IMEM and populate the bootstatus accordingly. With the CONFIG_WATCHDOG_SYSFS enabled, user can extract the information as below: cat /sys/devices/platform/soc@0/f410000.watchdog/watchdog/watchdog0/bootstatus 32 For backward compatibility, keep the EXPIRED_STATUS bit check. Add a new function qcom_wdt_get_bootstatus() to read the restart reason from IMEM. Reviewed-by: Konrad Dybcio Signed-off-by: Kathiravan Thirumoorthy Link: https://lore.kernel.org/r/20260408-wdt_reset_reason-v10-2-caf66786329f@oss.qualcomm.com Signed-off-by: Guenter Roeck --- drivers/watchdog/qcom-wdt.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index dfaac5995c84..49bd04841f0c 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ struct qcom_wdt_match_data { const u32 *offset; bool pretimeout; u32 max_tick_count; + u32 wdt_reason_val; }; struct qcom_wdt { @@ -185,6 +187,7 @@ static const struct qcom_wdt_match_data match_data_ipq5424 = { .offset = reg_offset_data_kpss, .pretimeout = true, .max_tick_count = 0xFFFFFU, + .wdt_reason_val = 5, }; static const struct qcom_wdt_match_data match_data_kpss = { @@ -193,6 +196,40 @@ static const struct qcom_wdt_match_data match_data_kpss = { .max_tick_count = 0xFFFFFU, }; +static int qcom_wdt_get_bootstatus(struct device *dev, struct qcom_wdt *wdt, + u32 val) +{ + struct device_node *imem; + struct resource res; + void __iomem *addr; + int ret; + + imem = of_parse_phandle(dev->of_node, "sram", 0); + if (!imem) { + /* Read the EXPIRED_STATUS bit as a fallback */ + if (readl(wdt_addr(wdt, WDT_STS)) & 1) + wdt->wdd.bootstatus = WDIOF_CARDRESET; + + return 0; + } + + ret = of_address_to_resource(imem, 0, &res); + of_node_put(imem); + if (ret) + return ret; + + addr = ioremap(res.start, resource_size(&res)); + if (!addr) + return -ENOMEM; + + if (readl(addr) == val) + wdt->wdd.bootstatus = WDIOF_CARDRESET; + + iounmap(addr); + + return 0; +} + static int qcom_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -273,8 +310,9 @@ static int qcom_wdt_probe(struct platform_device *pdev) wdt->wdd.parent = dev; wdt->layout = data->offset; - if (readl(wdt_addr(wdt, WDT_STS)) & 1) - wdt->wdd.bootstatus = WDIOF_CARDRESET; + ret = qcom_wdt_get_bootstatus(dev, wdt, data->wdt_reason_val); + if (ret) + dev_err(dev, "failed to get the bootstatus, %d\n", ret); /* * If 'timeout-sec' unspecified in devicetree, assume a 30 second -- cgit v1.2.3 From 4f675f036cd5e9cfbe6df5f24f8338158af96a4b Mon Sep 17 00:00:00 2001 From: Yingjie Gao Date: Thu, 2 Apr 2026 15:16:17 +0800 Subject: watchdog: sp5100_tco: Use EFCH MMIO for newer Hygon FCH Commit 009637de1f65 ("watchdog: sp5100_tco: support Hygon FCH/SCH (Server Controller Hub)") added Hygon vendor matching to the efch layout selection, but newer Hygon 0x790b SMBus devices still need the efch_mmio path. The efch_mmio path enables EFCH_PM_DECODEEN_WDT_TMREN before probing the watchdog MMIO block. If firmware leaves that bit clear and the driver picks the legacy efch path instead, probe falls back to the alternate window and fails with "Watchdog hardware is disabled". Select efch_mmio for Hygon 0x790b devices with revision 0x51 or later, matching the equivalent AMD behavior and allowing the watchdog to initialize on those systems. Fixes: 009637de1f65 ("watchdog: sp5100_tco: support Hygon FCH/SCH (Server Controller Hub)") Signed-off-by: Yingjie Gao Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260402071617.634563-2-gaoyingjie@uniontech.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sp5100_tco.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 2bd3dc25cb03..7e99c3b1f367 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -92,7 +92,8 @@ static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev) dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && dev->revision < 0x40) { return sp5100; - } else if (dev->vendor == PCI_VENDOR_ID_AMD && + } else if ((dev->vendor == PCI_VENDOR_ID_AMD || + dev->vendor == PCI_VENDOR_ID_HYGON) && sp5100_tco_pci->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS && sp5100_tco_pci->revision >= AMD_ZEN_SMBUS_PCI_REV) { return efch_mmio; -- cgit v1.2.3 From 07d690db9fc741649f98506e492d5c10d1060b80 Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Tue, 24 Mar 2026 12:48:45 +0100 Subject: watchdog: rzn1: Use dev_err_probe() In the probe() function the following pattern is present several times: if (err) { dev_err(dev, ...); return err; } Replace them by dev_err_probe() calls. Signed-off-by: Herve Codina (Schneider Electric) Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260324114849.242755-3-herve.codina@bootlin.com Signed-off-by: Guenter Roeck --- drivers/watchdog/rzn1_wdt.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/rzn1_wdt.c b/drivers/watchdog/rzn1_wdt.c index 96fd04fbc2a2..0b57540692f9 100644 --- a/drivers/watchdog/rzn1_wdt.c +++ b/drivers/watchdog/rzn1_wdt.c @@ -122,22 +122,16 @@ static int rzn1_wdt_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, rzn1_wdt_irq, 0, np->name, wdt); - if (ret) { - dev_err(dev, "failed to request irq %d\n", irq); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "failed to request irq %d\n", irq); clk = devm_clk_get_enabled(dev, NULL); - if (IS_ERR(clk)) { - dev_err(dev, "failed to get the clock\n"); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get the clock\n"); clk_rate = clk_get_rate(clk); - if (!clk_rate) { - dev_err(dev, "failed to get the clock rate\n"); - return -EINVAL; - } + if (!clk_rate) + return dev_err_probe(dev, -EINVAL, "failed to get the clock rate\n"); wdt->clk_rate_khz = clk_rate / 1000; wdt->wdtdev.info = &rzn1_wdt_info; -- cgit v1.2.3 From cde09b031da871bc88ab693cf5ac503574a06639 Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Tue, 24 Mar 2026 12:48:44 +0100 Subject: watchdog: rzn1: Fix reverse xmas tree declaration Variables declared in probe() don't follow the reverse xmas tree convention. Fix the declaration in order to follow the convention. Signed-off-by: Herve Codina (Schneider Electric) Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260324114849.242755-2-herve.codina@bootlin.com Signed-off-by: Guenter Roeck --- drivers/watchdog/rzn1_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/rzn1_wdt.c b/drivers/watchdog/rzn1_wdt.c index 0b57540692f9..48d5afef62a5 100644 --- a/drivers/watchdog/rzn1_wdt.c +++ b/drivers/watchdog/rzn1_wdt.c @@ -101,10 +101,10 @@ static const struct watchdog_ops rzn1_wdt_ops = { static int rzn1_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct rzn1_watchdog *wdt; struct device_node *np = dev->of_node; - struct clk *clk; + struct rzn1_watchdog *wdt; unsigned long clk_rate; + struct clk *clk; int ret; int irq; -- cgit v1.2.3 From 2963bb3d5acf9f8478ed5a8b07ff8b8a211cc25d Mon Sep 17 00:00:00 2001 From: Jose Javier Rodriguez Barbarin Date: Mon, 23 Mar 2026 12:24:13 +0100 Subject: watchdog: menz069_wdt: drop unneeded MODULE_ALIAS Since commit 1f4ea4838b13 ("mcb: Add missing modpost build support") the MODULE_ALIAS() is redundant as the module alias is now automatically generated from the MODULE_DEVICE_TABLE(). Remove the explicit alias. No functional change intended. Reviewed-by: Jorge Sanjuan Garcia Signed-off-by: Jose Javier Rodriguez Barbarin Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260323112413.21923-2-dev-josejavier.rodriguez@duagon.com Signed-off-by: Guenter Roeck --- drivers/watchdog/menz69_wdt.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/menz69_wdt.c b/drivers/watchdog/menz69_wdt.c index 6e5e4e5c0b56..3fe23451135d 100644 --- a/drivers/watchdog/menz69_wdt.c +++ b/drivers/watchdog/menz69_wdt.c @@ -163,5 +163,4 @@ module_mcb_driver(men_z069_driver); MODULE_AUTHOR("Johannes Thumshirn "); MODULE_DESCRIPTION("Watchdog driver for the MEN z069 IP-Core"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("mcb:16z069"); MODULE_IMPORT_NS("MCB"); -- cgit v1.2.3 From d8207d1e9a36b1a8a461581b2d4073ea28335603 Mon Sep 17 00:00:00 2001 From: Steve Wahl Date: Wed, 18 Mar 2026 10:50:05 -0500 Subject: watchdog/hpwdt: Refine hpwdt message for UV platform The watchdog hardware the hpwdt driver uses was added to the UV platform for UV_5, but the logging mentioned by this driver was not added to the BIOS. When the watchdog fires, the printed message had the administrators and developers looking for non-existent log files, and confused about whether a watchdog actually tripped. Change the message that prints on UV platforms so it doesn't send the user looking for non-existent logs. To aid in any future debugging, include all 8 bits of the NMISTAT register in the output, not just the two bits being used to determine this was "mynmi". And provide names to the bits in NMISTAT so the code is easier to understand. Signed-off-by: Steve Wahl Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260318155005.90271-1-steve.wahl@hpe.com Signed-off-by: Guenter Roeck --- drivers/watchdog/hpwdt.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 2a848c35c14d..8af1fad2de0b 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -23,6 +23,7 @@ #include #ifdef CONFIG_HPWDT_NMI_DECODING #include +#include #endif #include @@ -159,24 +160,31 @@ static int hpwdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req) return 0; } -static int hpwdt_my_nmi(void) -{ - return ioread8(hpwdt_nmistat) & 0x6; -} +#define NMISTAT_EASR BIT(0) +#define NMISTAT_EWDOG BIT(1) +#define NMISTAT_RTRAP BIT(2) +#define NMISTAT_DBELL BIT(3) +#define NMISTAT_EMSWDG BIT(4) +#define NMISTAT_GPI BIT(5) +#define NMISTAT_NMIOUT BIT(7) /* * NMI Handler */ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) { - unsigned int mynmi = hpwdt_my_nmi(); - static char panic_msg[] = + u8 nmistat = ioread8(hpwdt_nmistat); + bool mynmi = (nmistat & (NMISTAT_EWDOG | NMISTAT_RTRAP)) != 0; + static char panic_msg_default[] = "00: An NMI occurred. Depending on your system the reason " "for the NMI is logged in any one of the following resources:\n" "1. Integrated Management Log (IML)\n" "2. OA Syslog\n" "3. OA Forward Progress Log\n" "4. iLO Event Log"; + static char panic_msg_uv[] = + "00: A watchdog NMI occurred."; + char *panic_msg = is_uv_system() ? panic_msg_uv : panic_msg_default; if (ulReason == NMI_UNKNOWN && !mynmi) return NMI_DONE; @@ -190,7 +198,7 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) hpwdt_ping_ticks(SECS_TO_TICKS(val)); } - hex_byte_pack(panic_msg, mynmi); + hex_byte_pack(panic_msg, nmistat); nmi_panic(regs, panic_msg); return NMI_HANDLED; -- cgit v1.2.3 From 5abb70c8f7582950e490823f4a94a3597ada9d83 Mon Sep 17 00:00:00 2001 From: Flavio Suligoi Date: Mon, 23 Mar 2026 13:52:04 +0100 Subject: watchdog: gpio_wdt: add ACPI support The gpio_wdt device driver uses the device property APIs, so it is firmware agnostic. For this reason we can now add the ACPI support in Kconfig. In this way it can be used seamlessly in ACPI and DT systems. For example, a typical GPIO watchdog device configuration, in an ACPI SSDT table, could be: Device (WDOG) { Name (_HID, "WDOG0001") Name (_CID, "PRP0001") Name (_UID, One) Name (_CRS, ResourceTemplate () { GpioIo (Exclusive, PullNone, 0, 0, IoRestrictionOutputOnly, "\\_SB.GPI0", 0, ResourceConsumer, ,) { 3 } }) Method (_STA, 0, NotSerialized) { Return (0x0F) } Name (_DSD, Package (2) { ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package (5) { Package () { "compatible", Package() { "linux,wdt-gpio" } }, Package () { "hw_algo", "toggle" }, Package () { "gpios", Package () { ^WDOG, 0, 0, 0 } }, Package () { "hw_margin_ms", 2000 }, Package () { "always-running", 1 }, }, }) } Signed-off-by: Flavio Suligoi Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260323125204.164785-2-f.suligoi@asem.it Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index e19872c0ae61..8de43b614bb7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -250,7 +250,7 @@ config DA9062_WATCHDOG config GPIO_WATCHDOG tristate "Watchdog device controlled through GPIO-line" - depends on OF_GPIO + depends on (ACPI && GPIOLIB) || OF_GPIO select WATCHDOG_CORE help If you say yes here you get support for watchdog device -- cgit v1.2.3 From ca316e145c35d59bf1c0b48ff4e38091e19b80a8 Mon Sep 17 00:00:00 2001 From: Hrishabh Rajput Date: Wed, 11 Mar 2026 11:16:31 +0530 Subject: watchdog: Add driver for Gunyah Watchdog On Qualcomm SoCs running under the Gunyah hypervisor, access to watchdog through MMIO is not available on all platforms. Depending on the hypervisor configuration, the watchdog is either fully emulated or exposed via ARM's SMC Calling Conventions (SMCCC) through the Vendor Specific Hypervisor Service Calls space. Add driver to support the SMC-based watchdog provided by the Gunyah Hypervisor. Device registration is done in the QCOM SCM driver after checks to restrict the watchdog initialization to Qualcomm devices running under Gunyah. Gunyah watchdog is not a hardware but an SMC-based vendor-specific hypervisor interface provided by the Gunyah hypervisor. The design involving QCOM SCM driver for registering the platform device has been devised to avoid adding non-hardware nodes to devicetree. Tested-by: Shivendra Pratap Tested-by: Neil Armstrong Tested-by: Mukesh Ojha Reviewed-by: Guenter Roeck Reviewed-by: Dmitry Baryshkov Signed-off-by: Hrishabh Rajput Signed-off-by: Pavankumar Kondeti Link: https://lore.kernel.org/r/20260311-gunyah_watchdog-v8-2-4c1c0689de22@oss.qualcomm.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 13 +++ drivers/watchdog/Makefile | 1 + drivers/watchdog/gunyah_wdt.c | 261 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 drivers/watchdog/gunyah_wdt.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 8de43b614bb7..3397c82ded9c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2341,4 +2341,17 @@ config KEEMBAY_WATCHDOG To compile this driver as a module, choose M here: the module will be called keembay_wdt. +config GUNYAH_WATCHDOG + tristate "Qualcomm Gunyah Watchdog" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAVE_ARM_SMCCC + select WATCHDOG_CORE + help + Say Y here to include support for watchdog timer provided by the + Gunyah hypervisor. The driver uses ARM SMC Calling Convention (SMCCC) + to interact with Gunyah Watchdog. + + To compile this driver as a module, choose M here: the + module will be called gunyah_wdt. + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 2961ca141c61..ff1dc6fd5867 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o obj-$(CONFIG_MARVELL_GTI_WDT) += marvell_gti_wdt.o +obj-$(CONFIG_GUNYAH_WATCHDOG) += gunyah_wdt.o # X86 (i386 + ia64 + x86_64) Architecture obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o diff --git a/drivers/watchdog/gunyah_wdt.c b/drivers/watchdog/gunyah_wdt.c new file mode 100644 index 000000000000..49dfef459e84 --- /dev/null +++ b/drivers/watchdog/gunyah_wdt.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define GUNYAH_WDT_SMCCC_CALL_VAL(func_id) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYP, func_id) + +/* SMCCC function IDs for watchdog operations */ +#define GUNYAH_WDT_CONTROL GUNYAH_WDT_SMCCC_CALL_VAL(0x0005) +#define GUNYAH_WDT_STATUS GUNYAH_WDT_SMCCC_CALL_VAL(0x0006) +#define GUNYAH_WDT_PING GUNYAH_WDT_SMCCC_CALL_VAL(0x0007) +#define GUNYAH_WDT_SET_TIME GUNYAH_WDT_SMCCC_CALL_VAL(0x0008) + +/* + * Control values for GUNYAH_WDT_CONTROL. + * Bit 0 is used to enable or disable the watchdog. If this bit is set, + * then the watchdog is enabled and vice versa. + * Bit 1 should always be set to 1 as this bit is reserved in Gunyah and + * it's expected to be 1. + */ +#define WDT_CTRL_ENABLE (BIT(1) | BIT(0)) +#define WDT_CTRL_DISABLE BIT(1) + +enum gunyah_error { + GUNYAH_ERROR_OK = 0, + GUNYAH_ERROR_UNIMPLEMENTED = -1, + GUNYAH_ERROR_ARG_INVAL = 1, +}; + +/** + * gunyah_error_remap() - Remap Gunyah hypervisor errors into a Linux error code + * @gunyah_error: Gunyah hypercall return value + */ +static inline int gunyah_error_remap(enum gunyah_error gunyah_error) +{ + switch (gunyah_error) { + case GUNYAH_ERROR_OK: + return 0; + case GUNYAH_ERROR_UNIMPLEMENTED: + return -EOPNOTSUPP; + default: + return -EINVAL; + } +} + +static int gunyah_wdt_call(unsigned long func_id, unsigned long arg1, + unsigned long arg2) +{ + struct arm_smccc_res res; + + arm_smccc_1_1_smc(func_id, arg1, arg2, &res); + return gunyah_error_remap(res.a0); +} + +static int gunyah_wdt_start(struct watchdog_device *wdd) +{ + unsigned int timeout_ms; + struct device *dev = wdd->parent; + int ret; + + ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); + if (ret && watchdog_active(wdd)) { + dev_err(dev, "%s: Failed to stop gunyah wdt %d\n", __func__, ret); + return ret; + } + + timeout_ms = wdd->timeout * 1000; + ret = gunyah_wdt_call(GUNYAH_WDT_SET_TIME, timeout_ms, timeout_ms); + if (ret) { + dev_err(dev, "%s: Failed to set timeout for gunyah wdt %d\n", + __func__, ret); + return ret; + } + + ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0); + if (ret) + dev_err(dev, "%s: Failed to start gunyah wdt %d\n", __func__, ret); + + return ret; +} + +static int gunyah_wdt_stop(struct watchdog_device *wdd) +{ + return gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); +} + +static int gunyah_wdt_ping(struct watchdog_device *wdd) +{ + return gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0); +} + +static int gunyah_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout_sec) +{ + wdd->timeout = timeout_sec; + + if (watchdog_active(wdd)) + return gunyah_wdt_start(wdd); + + return 0; +} + +static int gunyah_wdt_get_time_since_last_ping(void) +{ + struct arm_smccc_res res; + + arm_smccc_1_1_smc(GUNYAH_WDT_STATUS, 0, 0, &res); + if (res.a0) + return gunyah_error_remap(res.a0); + + return res.a2 / 1000; +} + +static unsigned int gunyah_wdt_get_timeleft(struct watchdog_device *wdd) +{ + int seconds_since_last_ping; + + seconds_since_last_ping = gunyah_wdt_get_time_since_last_ping(); + if (seconds_since_last_ping < 0 || + seconds_since_last_ping > wdd->timeout) + return 0; + + return wdd->timeout - seconds_since_last_ping; +} + +static int gunyah_wdt_restart(struct watchdog_device *wdd, + unsigned long action, void *data) +{ + /* Set timeout to 1ms and send a ping */ + gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); + gunyah_wdt_call(GUNYAH_WDT_SET_TIME, 1, 1); + gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0); + gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0); + + /* Wait to make sure reset occurs */ + mdelay(100); + + return 0; +} + +static const struct watchdog_info gunyah_wdt_info = { + .identity = "Gunyah Watchdog", + .options = WDIOF_SETTIMEOUT + | WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops gunyah_wdt_ops = { + .owner = THIS_MODULE, + .start = gunyah_wdt_start, + .stop = gunyah_wdt_stop, + .ping = gunyah_wdt_ping, + .set_timeout = gunyah_wdt_set_timeout, + .get_timeleft = gunyah_wdt_get_timeleft, + .restart = gunyah_wdt_restart +}; + +static int gunyah_wdt_probe(struct platform_device *pdev) +{ + struct watchdog_device *wdd; + struct device *dev = &pdev->dev; + int ret; + + ret = gunyah_wdt_call(GUNYAH_WDT_STATUS, 0, 0); + if (ret == -EOPNOTSUPP) + return -ENODEV; + + if (ret) + return dev_err_probe(dev, ret, "status check failed\n"); + + wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL); + if (!wdd) + return -ENOMEM; + + wdd->info = &gunyah_wdt_info; + wdd->ops = &gunyah_wdt_ops; + wdd->parent = dev; + + /* + * Although Gunyah expects 16-bit unsigned int values as timeout values + * in milliseconds, values above 0x8000 are reserved. This limits the + * max timeout value to 32 seconds. + */ + wdd->max_timeout = 32; /* seconds */ + wdd->min_timeout = 1; /* seconds */ + wdd->timeout = wdd->max_timeout; + + gunyah_wdt_stop(wdd); + platform_set_drvdata(pdev, wdd); + watchdog_set_restart_priority(wdd, 0); + + return devm_watchdog_register_device(dev, wdd); +} + +static void gunyah_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + + gunyah_wdt_stop(wdd); +} + +static int gunyah_wdt_suspend(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + gunyah_wdt_stop(wdd); + + return 0; +} + +static int gunyah_wdt_resume(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + gunyah_wdt_start(wdd); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(gunyah_wdt_pm_ops, gunyah_wdt_suspend, gunyah_wdt_resume); + +/* + * Gunyah watchdog is a vendor-specific hypervisor interface provided by the + * Gunyah hypervisor. Using QCOM SCM driver to detect Gunyah watchdog SMCCC + * hypervisor service and register platform device when the service is available + * allows this driver to operate independently of the devicetree and avoids + * adding the non-hardware nodes to the devicetree. + */ +static const struct platform_device_id gunyah_wdt_id[] = { + { .name = "gunyah-wdt" }, + {} +}; +MODULE_DEVICE_TABLE(platform, gunyah_wdt_id); + +static struct platform_driver gunyah_wdt_driver = { + .driver = { + .name = "gunyah-wdt", + .pm = pm_sleep_ptr(&gunyah_wdt_pm_ops), + }, + .id_table = gunyah_wdt_id, + .probe = gunyah_wdt_probe, + .remove = gunyah_wdt_remove, +}; + +module_platform_driver(gunyah_wdt_driver); + +MODULE_DESCRIPTION("Gunyah Watchdog Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From e8bc610b14a99d24b0916635102cc52ba41def9b Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 2 Mar 2026 17:03:09 +0530 Subject: watchdog: sama5d4_wdt: Fix WDDIS detection on SAM9X60 and SAMA7G5 The driver hardcoded AT91_WDT_WDDIS (bit 15) in wdt_enabled and the probe initial state readout. SAM9X60 and SAMA7G5 use bit 12 (AT91_SAM9X60_WDDIS), causing incorrect WDDIS detection. Introduce a per-device wddis_mask field to select the correct WDDIS bit based on the compatible string. Fixes: 266da53c35fc ("watchdog: sama5d4: readout initial state") Co-developed-by: Andrei Simion Signed-off-by: Andrei Simion Signed-off-by: Balakrishnan Sambath Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/20260302113310.133989-2-balakrishnan.s@microchip.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sama5d4_wdt.c | 48 ++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/sama5d4_wdt.c b/drivers/watchdog/sama5d4_wdt.c index 13e72918338a..704b786cc2ec 100644 --- a/drivers/watchdog/sama5d4_wdt.c +++ b/drivers/watchdog/sama5d4_wdt.c @@ -30,6 +30,7 @@ struct sama5d4_wdt { void __iomem *reg_base; u32 mr; u32 ir; + u32 wddis_mask; unsigned long last_ping; bool need_irq; bool sam9x60_support; @@ -48,7 +49,10 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -#define wdt_enabled (!(wdt->mr & AT91_WDT_WDDIS)) +static inline bool wdt_enabled(struct sama5d4_wdt *wdt) +{ + return !(wdt->mr & wdt->wddis_mask); +} #define wdt_read(wdt, field) \ readl_relaxed((wdt)->reg_base + (field)) @@ -81,12 +85,9 @@ static int sama5d4_wdt_start(struct watchdog_device *wdd) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); - if (wdt->sam9x60_support) { + if (wdt->sam9x60_support) writel_relaxed(wdt->ir, wdt->reg_base + AT91_SAM9X60_IER); - wdt->mr &= ~AT91_SAM9X60_WDDIS; - } else { - wdt->mr &= ~AT91_WDT_WDDIS; - } + wdt->mr &= ~wdt->wddis_mask; wdt_write(wdt, AT91_WDT_MR, wdt->mr); return 0; @@ -96,12 +97,9 @@ static int sama5d4_wdt_stop(struct watchdog_device *wdd) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); - if (wdt->sam9x60_support) { + if (wdt->sam9x60_support) writel_relaxed(wdt->ir, wdt->reg_base + AT91_SAM9X60_IDR); - wdt->mr |= AT91_SAM9X60_WDDIS; - } else { - wdt->mr |= AT91_WDT_WDDIS; - } + wdt->mr |= wdt->wddis_mask; wdt_write(wdt, AT91_WDT_MR, wdt->mr); return 0; @@ -117,7 +115,7 @@ static int sama5d4_wdt_ping(struct watchdog_device *wdd) } static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd, - unsigned int timeout) + unsigned int timeout) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); u32 value = WDT_SEC2TICKS(timeout); @@ -140,8 +138,8 @@ static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd, * If the watchdog is enabled, then the timeout can be updated. Else, * wait that the user enables it. */ - if (wdt_enabled) - wdt_write(wdt, AT91_WDT_MR, wdt->mr & ~AT91_WDT_WDDIS); + if (wdt_enabled(wdt)) + wdt_write(wdt, AT91_WDT_MR, wdt->mr & ~wdt->wddis_mask); wdd->timeout = timeout; @@ -184,10 +182,7 @@ static int of_sama5d4_wdt_init(struct device_node *np, struct sama5d4_wdt *wdt) { const char *tmp; - if (wdt->sam9x60_support) - wdt->mr = AT91_SAM9X60_WDDIS; - else - wdt->mr = AT91_WDT_WDDIS; + wdt->mr = wdt->wddis_mask; if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) && !strcmp(tmp, "software")) @@ -213,15 +208,11 @@ static int sama5d4_wdt_init(struct sama5d4_wdt *wdt) * If the watchdog is already running, we can safely update it. * Else, we have to disable it properly. */ - if (!wdt_enabled) { + if (!wdt_enabled(wdt)) { reg = wdt_read(wdt, AT91_WDT_MR); - if (wdt->sam9x60_support && (!(reg & AT91_SAM9X60_WDDIS))) - wdt_write_nosleep(wdt, AT91_WDT_MR, - reg | AT91_SAM9X60_WDDIS); - else if (!wdt->sam9x60_support && - (!(reg & AT91_WDT_WDDIS))) + if (!(reg & wdt->wddis_mask)) wdt_write_nosleep(wdt, AT91_WDT_MR, - reg | AT91_WDT_WDDIS); + reg | wdt->wddis_mask); } if (wdt->sam9x60_support) { @@ -273,6 +264,9 @@ static int sama5d4_wdt_probe(struct platform_device *pdev) of_device_is_compatible(dev->of_node, "microchip,sama7g5-wdt")) wdt->sam9x60_support = true; + wdt->wddis_mask = wdt->sam9x60_support ? AT91_SAM9X60_WDDIS + : AT91_WDT_WDDIS; + watchdog_set_drvdata(wdd, wdt); regs = devm_platform_ioremap_resource(pdev, 0); @@ -306,8 +300,8 @@ static int sama5d4_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(wdd, wdt_timeout, dev); reg = wdt_read(wdt, AT91_WDT_MR); - if (!(reg & AT91_WDT_WDDIS)) { - wdt->mr &= ~AT91_WDT_WDDIS; + if (!(reg & wdt->wddis_mask)) { + wdt->mr &= ~wdt->wddis_mask; set_bit(WDOG_HW_RUNNING, &wdd->status); } -- cgit v1.2.3 From bdd918c4e4516bd2eadac712b2d2e6d29445bd2e Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Mon, 2 Mar 2026 12:08:53 -0600 Subject: watchdog: bcm2835_wdt: Switch to new sys-off handler API Kernel now supports chained power-off handlers. Use devm_register_sys_off_handler() that registers a power-off handler. Legacy pm_power_off() will be removed once all drivers and archs are converted to the new sys-off API. Signed-off-by: Andrew Davis Reviewed-by: Guenter Roeck Reviewed-by: Florian Fainelli Link: https://lore.kernel.org/r/20260302180853.224112-1-afd@ti.com Signed-off-by: Guenter Roeck --- drivers/watchdog/bcm2835_wdt.c | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index 9fcfee63905b..6fd8b1b8e386 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -19,6 +19,7 @@ #include #include #include +#include #define PM_RSTC 0x1c #define PM_RSTS 0x20 @@ -49,8 +50,6 @@ struct bcm2835_wdt { spinlock_t lock; }; -static struct bcm2835_wdt *bcm2835_power_off_wdt; - static unsigned int heartbeat; static bool nowayout = WATCHDOG_NOWAYOUT; @@ -150,9 +149,9 @@ static struct watchdog_device bcm2835_wdt_wdd = { * indicate to bootcode.bin not to reboot, then most of the chip will be * powered off. */ -static void bcm2835_power_off(void) +static int bcm2835_power_off(struct sys_off_data *data) { - struct bcm2835_wdt *wdt = bcm2835_power_off_wdt; + struct bcm2835_wdt *wdt = data->cb_data; u32 val; /* @@ -166,6 +165,8 @@ static void bcm2835_power_off(void) /* Continue with normal reset mechanism */ __bcm2835_restart(wdt); + + return NOTIFY_DONE; } static int bcm2835_wdt_probe(struct platform_device *pdev) @@ -206,28 +207,17 @@ static int bcm2835_wdt_probe(struct platform_device *pdev) if (err) return err; - if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) { - if (!pm_power_off) { - pm_power_off = bcm2835_power_off; - bcm2835_power_off_wdt = wdt; - } else { - dev_info(dev, "Poweroff handler already present!\n"); - } - } + if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) + devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + bcm2835_power_off, wdt); dev_info(dev, "Broadcom BCM2835 watchdog timer"); return 0; } -static void bcm2835_wdt_remove(struct platform_device *pdev) -{ - if (pm_power_off == bcm2835_power_off) - pm_power_off = NULL; -} - static struct platform_driver bcm2835_wdt_driver = { .probe = bcm2835_wdt_probe, - .remove = bcm2835_wdt_remove, .driver = { .name = "bcm2835-wdt", }, -- cgit v1.2.3 From 96b3cfc3b8ad0524d12fed1e08bc5df3ff345f64 Mon Sep 17 00:00:00 2001 From: Felix Gu Date: Mon, 23 Feb 2026 19:59:20 +0800 Subject: watchdog: sprd_wdt: Remove redundant sprd_wdt_disable() on register failure The driver uses devm_add_action_or_reset() to register sprd_wdt_disable() as a managed cleanup action. When devm_watchdog_register_device() fails, the devm core will invoke the cleanup action automatically. The explicit sprd_wdt_disable() call in the error path is therefore redundant and results in adouble cleanup. Fixes: 78d9bfad2e89 ("watchdog: sprd_wdt: Convert to use device managed functions and other improvements") Signed-off-by: Felix Gu Reviewed-by: Guenter Roeck Reviewed-by: Baolin Wang Link: https://lore.kernel.org/r/20260223-sprd_wdt-v1-1-2e71f9a76ecb@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sprd_wdt.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/sprd_wdt.c b/drivers/watchdog/sprd_wdt.c index 4e689b6ff141..aacf04616fef 100644 --- a/drivers/watchdog/sprd_wdt.c +++ b/drivers/watchdog/sprd_wdt.c @@ -320,10 +320,9 @@ static int sprd_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&wdt->wdd, 0, dev); ret = devm_watchdog_register_device(dev, &wdt->wdd); - if (ret) { - sprd_wdt_disable(wdt); + if (ret) return ret; - } + platform_set_drvdata(pdev, wdt); return 0; -- cgit v1.2.3 From 79bbc356d537969332ddd0bf3b990438224e013b Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Sun, 22 Feb 2026 18:24:16 -0500 Subject: watchdog: pic32-wdt: allow driver to be compiled on all architectures with COMPILE_TEST This driver currently only supports builds against a PIC32 target, or with COMPILE_TEST on MIPS. Now that commit 5aa5879eeebb ("watchdog: pic32-wdt: update include to use pic32.h from platform_data") is merged, it's possible to compile this driver on other architectures. To avoid future breakage of this driver in the future, let's update the Kconfig so that it can be built with COMPILE_TEST enabled on all architectures. Signed-off-by: Brian Masney Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260222-watchdog-pic32-v1-1-a2538aa528d1@redhat.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3397c82ded9c..ae8c90fa194a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1998,7 +1998,7 @@ config MT7621_WDT config PIC32_WDT tristate "Microchip PIC32 hardware watchdog" select WATCHDOG_CORE - depends on MACH_PIC32 || (MIPS && COMPILE_TEST) + depends on MACH_PIC32 || COMPILE_TEST help Watchdog driver for the built in watchdog hardware in a PIC32. -- cgit v1.2.3 From 96bbd6217953d371617bf2131f0805ed7885a5d9 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Sun, 22 Feb 2026 18:24:17 -0500 Subject: watchdog: pic32-dmt: allow driver to be compiled on all architectures with COMPILE_TEST This driver currently only supports builds against a PIC32 target, or with COMPILE_TEST on MIPS. Now that commit 0f8a61ca78d6 ("watchdog: pic32-dmt: update include to use pic32.h from platform_data") is merged, it's possible to compile this driver on other architectures. To avoid future breakage of this driver in the future, let's update the Kconfig so that it can be built with COMPILE_TEST enabled on all architectures. Signed-off-by: Brian Masney Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260222-watchdog-pic32-v1-2-a2538aa528d1@redhat.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index ae8c90fa194a..d58a0bd0bdeb 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2011,7 +2011,7 @@ config PIC32_WDT config PIC32_DMT tristate "Microchip PIC32 Deadman Timer" select WATCHDOG_CORE - depends on MACH_PIC32 || (MIPS && COMPILE_TEST) + depends on MACH_PIC32 || COMPILE_TEST help Watchdog driver for PIC32 instruction fetch counting timer. This specific timer is typically be used in mission critical and safety -- cgit v1.2.3 From aed1abf31ea3f13b8ac8aa0f6c1078a9bc7b9564 Mon Sep 17 00:00:00 2001 From: Judith Mendez Date: Fri, 6 Feb 2026 17:42:55 -0600 Subject: watchdog: rti_wdt: Add reaction control This configures the reaction between NMI and reset for WWD. On K3 SoCs other than AM62L SoC [0], watchdog reset output is routed to the ESM module which can subsequently route the signal to safety master or SoC reset. On AM62L, the watchdog reset output is routed to the SoC HW reset block. So, add a new compatible for AM62L to add SoC data and configure reaction to reset instead of NMI. [0] https://www.ti.com/product/AM62L Signed-off-by: Judith Mendez Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20260206234255.380530-3-jm@ti.com Signed-off-by: Guenter Roeck --- drivers/watchdog/rti_wdt.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c index be7d7db47591..c3c7715140ea 100644 --- a/drivers/watchdog/rti_wdt.c +++ b/drivers/watchdog/rti_wdt.c @@ -35,7 +35,8 @@ #define RTIWWDRXCTRL 0xa4 #define RTIWWDSIZECTRL 0xa8 -#define RTIWWDRX_NMI 0xa +#define RTIWWDRXN_RST 0x5 +#define RTIWWDRXN_NMI 0xa #define RTIWWDSIZE_50P 0x50 #define RTIWWDSIZE_25P 0x500 @@ -63,22 +64,29 @@ static int heartbeat; +struct rti_wdt_data { + bool nmi; +}; + /* * struct to hold data for each WDT device * @base - base io address of WD device * @freq - source clock frequency of WDT * @wdd - hold watchdog device as is in WDT core + * @nmi - Set if this WDT instance supports generating NMI */ struct rti_wdt_device { void __iomem *base; unsigned long freq; struct watchdog_device wdd; + bool nmi; }; static int rti_wdt_start(struct watchdog_device *wdd) { u32 timer_margin; struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd); + u8 reaction; int ret; ret = pm_runtime_resume_and_get(wdd->parent); @@ -101,8 +109,13 @@ static int rti_wdt_start(struct watchdog_device *wdd) */ wdd->min_hw_heartbeat_ms = 520 * wdd->timeout + MAX_HW_ERROR; - /* Generate NMI when wdt expires */ - writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL); + /* When WDT expires, generate NMI or reset if NMI not supported */ + if (wdt->nmi) + reaction = RTIWWDRXN_NMI; + else + reaction = RTIWWDRXN_RST; + + writel_relaxed(reaction, wdt->base + RTIWWDRXCTRL); /* Open window size 50%; this is the largest window size available */ writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL); @@ -210,6 +223,7 @@ static int rti_wdt_probe(struct platform_device *pdev) { int ret = 0; struct device *dev = &pdev->dev; + const struct rti_wdt_data *data; struct watchdog_device *wdd; struct rti_wdt_device *wdt; struct clk *clk; @@ -254,6 +268,14 @@ static int rti_wdt_probe(struct platform_device *pdev) wdd->timeout = DEFAULT_HEARTBEAT; wdd->parent = dev; + data = device_get_match_data(dev); + if (!data) { + ret = -ENODEV; + goto err_iomap; + } + + wdt->nmi = data->nmi; + watchdog_set_drvdata(wdd, wdt); watchdog_set_nowayout(wdd, 1); watchdog_set_restart_priority(wdd, 128); @@ -361,8 +383,17 @@ static void rti_wdt_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +static const struct rti_wdt_data rti_wdt_j7_data = { + .nmi = true, +}; + +static const struct rti_wdt_data rti_wdt_am62l_data = { + .nmi = false, +}; + static const struct of_device_id rti_wdt_of_match[] = { - { .compatible = "ti,j7-rti-wdt", }, + { .compatible = "ti,j7-rti-wdt", .data = &rti_wdt_j7_data }, + { .compatible = "ti,am62l-rti-wdt", .data = &rti_wdt_am62l_data }, {}, }; MODULE_DEVICE_TABLE(of, rti_wdt_of_match); -- cgit v1.2.3 From 1f5b29add64e7c1500530079bbbe359cad28cb63 Mon Sep 17 00:00:00 2001 From: Ranjani Vaidyanathan Date: Fri, 6 Feb 2026 08:23:32 +0800 Subject: watchdog: imx7ulp_wdt: Keep WDOG running until A55 enters WFI on i.MX94 On i.MX94, watchdog sources clock from bus clock that will be always on during the lifecycle of Linux. There is a Low Power Clock Gating(LPCG) between the bus clock and watchdog, but the LPCG is not exported for software to control, it is hardware automatically controlled. When Cortex-A55 executes WFI during suspend flow, the LPCG will automatically gate off the clock to stop watchdog and resume clock when Cortex-A55 is woke up. So watchdog could always be alive to protect Linux, except Cortex-A platform WFI is executed in Linux suspend flow. Introduce a new hardware feature flag to indicate CPU low-power-mode auto clock gating support, and use it to avoid stopping the watchdog during suspend when LPCG can safely keep it running. Add i.MX94-specific watchdog hardware data and DT compatible entry to enable this behavior. Signed-off-by: Ranjani Vaidyanathan [peng.fan@nxp.com: rewrite commit log for clarity] Signed-off-by: Peng Fan Reviewed-by: Guenter Roeck Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20260206-imx94-wdog-v2-1-4dd725faec1f@nxp.com Signed-off-by: Guenter Roeck --- drivers/watchdog/imx7ulp_wdt.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/imx7ulp_wdt.c b/drivers/watchdog/imx7ulp_wdt.c index 03479110453c..855dc9d5083a 100644 --- a/drivers/watchdog/imx7ulp_wdt.c +++ b/drivers/watchdog/imx7ulp_wdt.c @@ -56,6 +56,7 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" struct imx_wdt_hw_feature { bool prescaler_enable; bool post_rcs_wait; + bool cpu_lpm_auto_cg; u32 wdog_clock_rate; }; @@ -360,7 +361,7 @@ static int __maybe_unused imx7ulp_wdt_suspend_noirq(struct device *dev) { struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); - if (watchdog_active(&imx7ulp_wdt->wdd)) + if (watchdog_active(&imx7ulp_wdt->wdd) && !imx7ulp_wdt->hw->cpu_lpm_auto_cg) imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); clk_disable_unprepare(imx7ulp_wdt->clk); @@ -408,10 +409,17 @@ static const struct imx_wdt_hw_feature imx93_wdt_hw = { .wdog_clock_rate = 125, }; +static const struct imx_wdt_hw_feature imx94_wdt_hw = { + .prescaler_enable = true, + .wdog_clock_rate = 125, + .cpu_lpm_auto_cg = true, +}; + static const struct of_device_id imx7ulp_wdt_dt_ids[] = { { .compatible = "fsl,imx7ulp-wdt", .data = &imx7ulp_wdt_hw, }, { .compatible = "fsl,imx8ulp-wdt", .data = &imx8ulp_wdt_hw, }, { .compatible = "fsl,imx93-wdt", .data = &imx93_wdt_hw, }, + { .compatible = "fsl,imx94-wdt", .data = &imx94_wdt_hw, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); -- cgit v1.2.3 From 10820327b9386c77aaa8c5c3c3690a7443baf9b9 Mon Sep 17 00:00:00 2001 From: Balakrishnan Sambath Date: Mon, 2 Mar 2026 17:03:10 +0530 Subject: watchdog: at91sam9_wdt.h: Document WDDIS bit position per SoC family AT91_WDT_WDDIS (bit 15) applies to SAMA5/AT91SAM9261 and AT91_SAM9X60_WDDIS (bit 12) to SAM9X60/SAMA7G5/SAM9X75. Update comments to reflect this and add SAMA7G5 and SAM9X75 datasheet references to the file header. Signed-off-by: Balakrishnan Sambath Reviewed-by: Alexandre Belloni Link: https://lore.kernel.org/r/20260302113310.133989-3-balakrishnan.s@microchip.com Signed-off-by: Guenter Roeck --- drivers/watchdog/at91sam9_wdt.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/at91sam9_wdt.h b/drivers/watchdog/at91sam9_wdt.h index 298d545df1a1..2020694f8f6f 100644 --- a/drivers/watchdog/at91sam9_wdt.h +++ b/drivers/watchdog/at91sam9_wdt.h @@ -9,6 +9,8 @@ * Watchdog Timer (WDT) - System peripherals regsters. * Based on AT91SAM9261 datasheet revision D. * Based on SAM9X60 datasheet. + * Based on SAMA7G5 datasheet. + * Based on SAM9X75 datasheet. * */ @@ -27,10 +29,10 @@ #define AT91_SAM9X60_PERIODRST BIT(4) /* Period Reset */ #define AT91_SAM9X60_RPTHRST BIT(5) /* Minimum Restart Period */ #define AT91_WDT_WDFIEN BIT(12) /* Fault Interrupt Enable */ -#define AT91_SAM9X60_WDDIS BIT(12) /* Watchdog Disable */ +#define AT91_SAM9X60_WDDIS BIT(12) /* Watchdog Disable (SAM9X60, SAMA7G5, SAM9X75) */ #define AT91_WDT_WDRSTEN BIT(13) /* Reset Processor */ #define AT91_WDT_WDRPROC BIT(14) /* Timer Restart */ -#define AT91_WDT_WDDIS BIT(15) /* Watchdog Disable */ +#define AT91_WDT_WDDIS BIT(15) /* Watchdog Disable (SAMA5, AT91SAM9261) */ #define AT91_WDT_WDD (0xfffUL << 16) /* Delta Value */ #define AT91_WDT_SET_WDD(x) (((x) << 16) & AT91_WDT_WDD) #define AT91_WDT_WDDBGHLT BIT(28) /* Debug Halt */ -- cgit v1.2.3 From e4e0848ad046da9304e0656a71f74ddfbb316e98 Mon Sep 17 00:00:00 2001 From: CL Wang Date: Thu, 15 Jan 2026 16:14:43 +0800 Subject: watchdog: atcwdt200: Add driver for Andes ATCWDT200 Add support for the Andes ATCWDT200 watchdog timer. The driver implements programmable reset and interrupt timers, and includes automatic detection of the supported IntTime bit-width. Integrated with the Linux watchdog framework, it supports basic operations including start, stop, ping, timeout configuration, and system reset via the restart handler. Signed-off-by: CL Wang Link: https://lore.kernel.org/r/20260115081444.2452357-3-cl634@andestech.com Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 1 + drivers/watchdog/atcwdt200_wdt.c | 580 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 590 insertions(+) create mode 100644 drivers/watchdog/atcwdt200_wdt.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d58a0bd0bdeb..eb8eb307e04d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2119,6 +2119,15 @@ config WATCHDOG_RTAS # RISC-V Architecture +config ATCWDT200_WATCHDOG + tristate "Andes ATCWDT200 Watchdog support" + depends on ARCH_ANDES || COMPILE_TEST + help + Driver for the Andes ATCWDT200 watchdog timer. It provides access to + programmable reset and interrupt counters with clock-source dependent + timing. The driver automatically detects the supported IntTime bit-width + and is fully integrated with the Linux Watchdog Framework. + config STARFIVE_WATCHDOG tristate "StarFive Watchdog support" depends on ARCH_STARFIVE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ff1dc6fd5867..a4341b251bdf 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -198,6 +198,7 @@ obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o # RISC-V Architecture +obj-$(CONFIG_ATCWDT200_WATCHDOG) += atcwdt200_wdt.o obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o # S390 Architecture diff --git a/drivers/watchdog/atcwdt200_wdt.c b/drivers/watchdog/atcwdt200_wdt.c new file mode 100644 index 000000000000..8e3b18aea368 --- /dev/null +++ b/drivers/watchdog/atcwdt200_wdt.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Andes ATCWDT200 watchdog timer driver. + * + * Copyright (C) 2025 Andes Technology Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register definitions */ +#define REG_CTRL 0x10 +#define REG_RESTART 0x14 +#define REG_WRITE_EN 0x18 +#define REG_STATUS 0x1C + +/* Control Register */ +#define CTRL_RST_TIME_MSK GENMASK(10, 8) +#define CTRL_RST_TIME_SET(x) FIELD_PREP(CTRL_RST_TIME_MSK, x) +#define CTRL_INT_TIME_MSK GENMASK(7, 4) +#define CTRL_INT_TIME_SET(x) FIELD_PREP(CTRL_INT_TIME_MSK, x) +#define CTRL_INT_TIME_GET(x) FIELD_GET(CTRL_INT_TIME_MSK, x) +#define CTRL_RST_EN BIT(3) +#define CTRL_CLK_SEL BIT(1) +#define CTRL_CLK_SEL_PCLK 1 +#define CTRL_CLK_SEL_SET(x) FIELD_PREP(CTRL_CLK_SEL, x) +#define CTRL_WDT_EN BIT(0) + +/* Restart Register */ +#define RESTART_MAGIC 0xCAFE + +/* Write Enable Register */ +#define WRITE_EN_MAGIC 0x5AA5 + +/* Status Register */ +#define STATUS_INT_EXPIRED BIT(1) + +/* The default timeout value in seconds */ +#define ATCWDT_TIMEOUT 4 + +/* Define the array size for each timer type */ +#define TMR_SZ_RST 8 +#define TMR_SZ_INT_16 8 +#define TMR_SZ_INT_32 16 + +#define DRV_NAME "atcwdt200" +/** + * enum timer_type - Supported timer types for ATCWDT200 watchdog driver + * @TMR_RST: Reset timer (non-interrupt). + * @TMR_INT_16: 16-bit interrupt timer supported by hardware. + * @TMR_INT_32: 32-bit interrupt timer supported by hardware. + * @TMR_UNKNOWN: Timer type cannot be determined. + */ +enum timer_type { + TMR_RST, + TMR_INT_16, + TMR_INT_32, + TMR_UNKNOWN +}; + +static unsigned int timeout = ATCWDT_TIMEOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; + +/** + * struct atcwdt_drv - ATCWDT200 watchdog driver private data + * @wdt_dev: Watchdog device used by the watchdog framework. + * @regmap: Register map for accessing hardware registers. + * @clk: Hardware clock used by the watchdog timer. + * @lock: Spinlock protecting register accesses and driver state. + * @clk_freq: Input clock frequency of the ATCWDT200. + * @clk_src: Selected clock source for the watchdog timer. + * @int_timer_type: Detected interrupt timer type (16-bit, 32-bit, or unknown). + */ +struct atcwdt_drv { + struct watchdog_device wdt_dev; + struct regmap *regmap; + struct clk *clk; + spinlock_t lock; + unsigned int clk_freq; + unsigned char clk_src; + unsigned char int_timer_type; +}; + +static const struct watchdog_info atcwdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +/** + * atcwdt_get_index - Get the interval value for the specified timer type + * @index: The index of the interval in the array + * @timer_type: The type of timer, which can be TMR_RST, TMR_INT_16, or + * TMR_INT_32. + * + * This function retrieves the interval value based on the timer type and + * ensures the index stays within the valid range for the given timer type. + * For TMR_RST: + * - The maximum array size is 8 (index range: 0-7). + * For TMR_INT_16: + * - The maximum array size is 8 (index range: 0-7). + * For TMR_INT_32: + * - The maximum array size is 16 (index range: 0-15). + * + * If the index exceeds the maximum array size, the function will return + * the last element of the respective array. + */ +static inline unsigned char atcwdt_get_index(unsigned char index, + enum timer_type timer_type) +{ + static const unsigned char rst_timer_interval[TMR_SZ_RST] = { + 7, 8, 9, 10, 11, 12, 13, 14}; + static const unsigned char int_timer_interval[TMR_SZ_INT_32] = { + 6, 8, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 25, 27, 29, 31}; + unsigned char array_index; + + if (timer_type == TMR_RST) { + array_index = min(index, TMR_SZ_RST - 1); + return rst_timer_interval[array_index]; + } + + if (timer_type == TMR_INT_32) + array_index = min(index, TMR_SZ_INT_32 - 1); + else + array_index = min(index, TMR_SZ_INT_16 - 1); + + return int_timer_interval[array_index]; +} + +/** + * atcwdt_get_clock_period - Calculate the closest clock period based on a + * given tick count + * @tick: The target tick count to match + * @timer_type: The type of timer, which can be TMR_RST, TMR_INT_16, or + * TMR_INT_32. + * @index: Pointer to store the index of the selected parameter + * + * This function calculates the closest clock period to the given tick count + * by iterating through the timer parameters and selecting the one that + * minimizes the difference between the target tick count and the calculated + * clock period. The function determines the index of the closest parameter + * and returns the difference between the target tick count and the selected + * clock period. + * + * Return: The difference between the target tick count and the selected + * clock period. + */ +static long long atcwdt_get_clock_period(long long tick, + enum timer_type timer_type, + unsigned char *index) +{ + long long result; + unsigned char size; + char i; + + if (timer_type == TMR_RST) + size = TMR_SZ_RST; + else if (timer_type == TMR_INT_32) + size = TMR_SZ_INT_32; + else + size = TMR_SZ_INT_16; + + *index = size - 1; + for (i = 0; i < size; i++) { + result = tick - (1LL << atcwdt_get_index(i, timer_type)); + + if (result <= 1) { + *index = i; + break; + } + } + + return result; +} + +/** + * atcwdt_get_timeout_params - Calculate optimal parameters for Watchdog Timer + * @drv_data: Pointer to the Watchdog driver data structure + * @timeout: Desired timeout value (in seconds) + * @int_timer_params: Pointer to store the calculated interrupt timer + * parameter index + * @rst_timer_params: Pointer to store the calculated reset timer parameter + * index + * + * This function calculates the optimal parameter combination for the + * interrupt timer and reset timer of the Watchdog Timer to achieve a + * timeout value closest to, but not less than the specified timeout. + * + * Algorithm: + * 1. The parameters for both the interrupt timer and reset timer are + * predefined as a series of options represented as powers of 2. + * 2. The function first determines the interrupt timer's parameter index + * that provides a time closest to and not exceeding the desired timeout. + * 3. Based on the selected interrupt timer, it calculates the required + * reset timer parameter to ensure the total timeout matches the target. + * + * Return: The calculated parameter indices are stored in the provided + * pointers. + */ +static void atcwdt_get_timeout_params(struct atcwdt_drv *drv_data, + unsigned int timeout, + unsigned char *int_timer_params, + unsigned char *rst_timer_params) +{ + long long rest_time_ms; + long long result; + long long tick; + unsigned char rst_index; + unsigned char int_index; + unsigned char above; + unsigned char below; + + tick = (long long)timeout * drv_data->clk_freq; + result = atcwdt_get_clock_period(tick, + drv_data->int_timer_type, + &above); + if (result == 0 || above == 0) { + *int_timer_params = above; + *rst_timer_params = 0; + return; + } + below = above - 1; + + int_index = atcwdt_get_index(below, drv_data->int_timer_type); + rest_time_ms = timeout * 1000LL + - div64_s64(1000LL << int_index, drv_data->clk_freq); + + result = atcwdt_get_clock_period(rest_time_ms * drv_data->clk_freq, + TMR_RST, + &rst_index); + + if (result > 1) { + *int_timer_params = above; + *rst_timer_params = 0; + } else { + *int_timer_params = below; + *rst_timer_params = rst_index; + } +} + +/** + * atcwdt_get_int_timer_type - Get the supported interrupt timer type. + * @drv_data: Pointer to the watchdog driver data structure. + * + * This function tests the writable bits in the IntTime field of the control + * register to determine the interrupt timer type supported by the hardware. + * + * Note: This function must only be called when the ATCWDT200 watchdog is + * disabled. If the watchdog is enabled, this function returns TMR_UNKNOWN. + * + * Returns: The interrupt timer type supported by the hardware. + */ +static int atcwdt_get_int_timer_type(struct atcwdt_drv *drv_data) +{ + struct device *dev = drv_data->wdt_dev.parent; + unsigned int val; + int ret = 0; + + spin_lock(&drv_data->lock); + regmap_read(drv_data->regmap, REG_CTRL, &val); + if (val & CTRL_WDT_EN) { + spin_unlock(&drv_data->lock); + return TMR_UNKNOWN; + } + + /* + * Configures the IntTime field with the maximum mask value + * (CTRL_INT_TIME_MSK), reads its value from the control register + * to identify the maximum writable bits. + */ + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_write(drv_data->regmap, REG_CTRL, CTRL_INT_TIME_MSK); + regmap_read(drv_data->regmap, REG_CTRL, &val); + spin_unlock(&drv_data->lock); + + val = CTRL_INT_TIME_GET(val); + switch (val) { + case 7: + drv_data->int_timer_type = TMR_INT_16; + break; + case 15: + drv_data->int_timer_type = TMR_INT_32; + break; + default: + drv_data->int_timer_type = TMR_UNKNOWN; + ret = dev_err_probe(dev, -ENODEV, + "Failed to detect interrupt timer type\n"); + } + + return ret; +} + +static int atcwdt_ping(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_write(drv_data->regmap, REG_RESTART, RESTART_MAGIC); + regmap_update_bits(drv_data->regmap, REG_STATUS, STATUS_INT_EXPIRED, + STATUS_INT_EXPIRED); + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + unsigned int value; + unsigned char rst_val; + unsigned char int_val; + + wdt_dev->timeout = timeout; + atcwdt_get_timeout_params(drv_data, timeout, &int_val, &rst_val); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + + value = CTRL_RST_TIME_SET(rst_val) | + CTRL_INT_TIME_SET(int_val) | + CTRL_CLK_SEL_SET(drv_data->clk_src); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_TIME_MSK | + CTRL_INT_TIME_MSK | + CTRL_CLK_SEL, + value); + + spin_unlock(&drv_data->lock); + atcwdt_ping(wdt_dev); + + return 0; +} + +static int atcwdt_start(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + atcwdt_set_timeout(wdt_dev, wdt_dev->timeout); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + CTRL_RST_EN | CTRL_WDT_EN); + + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_stop(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + 0); + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + atcwdt_set_timeout(wdt_dev, 0); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + CTRL_RST_EN | CTRL_WDT_EN); + spin_unlock(&drv_data->lock); + + return 0; +} + +static const struct watchdog_ops atcwdt_ops = { + .owner = THIS_MODULE, + .start = atcwdt_start, + .stop = atcwdt_stop, + .ping = atcwdt_ping, + .set_timeout = atcwdt_set_timeout, + .restart = atcwdt_restart, +}; + +static int atcwdt_init_resource(struct platform_device *pdev, + struct atcwdt_drv *drv_data) +{ + struct device *dev = &pdev->dev; + void __iomem *base; + const struct regmap_config cfg = { + .name = "atcwdt", + .reg_bits = 32, + .val_bits = 32, + .cache_type = REGCACHE_NONE, + .reg_stride = 4, + .max_register = REG_STATUS, + }; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), + "Failed to ioremap I/O resource\n"); + + drv_data->regmap = devm_regmap_init_mmio(dev, base, &cfg); + if (IS_ERR(drv_data->regmap)) + return dev_err_probe(dev, PTR_ERR(drv_data->regmap), + "Failed to create regmap\n"); + + return 0; +} + +static int atcwdt_enable_clk(struct atcwdt_drv *drv_data) +{ + struct device *dev = drv_data->wdt_dev.parent; + unsigned int val; + int clk_src; + + drv_data->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(drv_data->clk)) + return dev_err_probe(dev, PTR_ERR(drv_data->clk), + "Failed to get watchdog clock\n"); + + drv_data->clk_freq = clk_get_rate(drv_data->clk); + if (!drv_data->clk_freq) + return dev_err_probe(dev, -EINVAL, + "Failed to get clock rate\n"); + + clk_src = device_property_read_u32(dev, "andestech,clock-source", &val); + drv_data->clk_src = (!clk_src && val != 0) ? CTRL_CLK_SEL_PCLK : 0; + + return 0; +} + +static int atcwdt_init_wdt_device(struct device *dev, + struct atcwdt_drv *drv_data) +{ + struct watchdog_device *wdd = &drv_data->wdt_dev; + + wdd->parent = dev; + wdd->info = &atcwdt_info; + wdd->ops = &atcwdt_ops; + wdd->timeout = ATCWDT_TIMEOUT; + wdd->min_timeout = 1; + + watchdog_set_nowayout(wdd, nowayout); + watchdog_set_drvdata(wdd, drv_data); + + return 0; +} + +static void atcwdt_calc_max_timeout(struct atcwdt_drv *drv_data) +{ + unsigned char rst_idx = atcwdt_get_index(0xFF, TMR_RST); + unsigned char int_idx = atcwdt_get_index(0xFF, + drv_data->int_timer_type); + + drv_data->wdt_dev.max_timeout = + ((1U << rst_idx) + (1U << int_idx)) / drv_data->clk_freq; +} + +static int atcwdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atcwdt_drv *drv_data; + int ret; + + drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + platform_set_drvdata(pdev, drv_data); + spin_lock_init(&drv_data->lock); + + ret = atcwdt_init_wdt_device(dev, drv_data); + if (ret) + return ret; + + ret = atcwdt_init_resource(pdev, drv_data); + if (ret) + return ret; + + ret = atcwdt_enable_clk(drv_data); + if (ret) + return ret; + + ret = atcwdt_get_int_timer_type(drv_data); + if (ret) + return ret; + + atcwdt_calc_max_timeout(drv_data); + + ret = devm_watchdog_register_device(dev, &drv_data->wdt_dev); + + return ret; +} + +static int atcwdt_suspend(struct device *dev) +{ + struct atcwdt_drv *drv_data = dev_get_drvdata(dev); + + if (watchdog_active(&drv_data->wdt_dev)) { + atcwdt_stop(&drv_data->wdt_dev); + clk_disable_unprepare(drv_data->clk); + } + + return 0; +} + +static int atcwdt_resume(struct device *dev) +{ + struct atcwdt_drv *drv_data = dev_get_drvdata(dev); + int ret = 0; + + if (watchdog_active(&drv_data->wdt_dev)) { + ret = clk_prepare_enable(drv_data->clk); + if (ret) + return ret; + atcwdt_start(&drv_data->wdt_dev); + atcwdt_ping(&drv_data->wdt_dev); + } + + return ret; +} + +static const struct of_device_id atcwdt_match[] = { + { .compatible = "andestech,ae350-wdt" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atcwdt_match); + +static DEFINE_SIMPLE_DEV_PM_OPS(atcwdt_pm_ops, atcwdt_suspend, atcwdt_resume); + +static struct platform_driver atcwdt_driver = { + .probe = atcwdt_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = atcwdt_match, + .pm = pm_sleep_ptr(&atcwdt_pm_ops), + }, +}; + +module_platform_driver(atcwdt_driver); + +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(ATCWDT_TIMEOUT) ")"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("CL Wang "); +MODULE_DESCRIPTION("Andes ATCWDT200 Watchdog timer driver"); -- cgit v1.2.3 From 7b25feb19ce8073193fff6a05a43b5a5c2854b4b Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Tue, 28 Apr 2026 08:49:44 -0400 Subject: watchdog: lenovo_se10_wdt: Add support for SE10 Gen 2 platform The Lenovo SE10 Gen 2 platform uses a watchdog chip from the same family. Watchdog functionality is the same, so update the driver with the new chip ID. Add the Gen 2 MTM's to enable support on the platform. Tested on SE10 G2. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20260428124954.1193450-1-mpearson-lenovo@squebb.ca Signed-off-by: Guenter Roeck --- drivers/watchdog/lenovo_se10_wdt.c | 66 +++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/lenovo_se10_wdt.c b/drivers/watchdog/lenovo_se10_wdt.c index cd0500e5080b..99ff01af4124 100644 --- a/drivers/watchdog/lenovo_se10_wdt.c +++ b/drivers/watchdog/lenovo_se10_wdt.c @@ -178,7 +178,7 @@ static int se10_wdt_probe(struct platform_device *pdev) return -EBUSY; chip_id = get_chipID(); - if (chip_id != 0x5632) { + if (chip_id != 0x5632 && chip_id != 0x5652) { release_region(CFG_PORT, CFG_SIZE); return -ENODEV; } @@ -280,6 +280,70 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { }, .callback = se10_create_platform_device, }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13LJ"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13LK"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S1"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S2"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S3"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S4"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S5"), + }, + .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S6"), + }, + .callback = se10_create_platform_device, + }, {} }; MODULE_DEVICE_TABLE(dmi, se10_dmi_table); -- cgit v1.2.3 From 44c94a7e11a52e915731458a636537bc4d13cb95 Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Tue, 5 May 2026 11:26:16 +0200 Subject: watchdog: Prefix WDT with ICS for clarity `wdt.rst` is only about the Watchdog from "Industrial Computer Source" (ICS). Change the title and rename the file to better express this. Add missing SPDX license identifier `GPL-2.0-or-later` same as code to silence `checkpatch`. Fix wrong link to sample driver in drivers/watchdog/smsc37b787_wdt.c. Signed-off-by: Philipp Hahn Link: https://lore.kernel.org/r/5a71979d8e8ab8e0a30de33f6aa2540b3b5dc1ee.1777972790.git.phahn-oss@avm.de Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 10 ++++++---- drivers/watchdog/smsc37b787_wdt.c | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index eb8eb307e04d..4c204951bc95 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2266,10 +2266,11 @@ config MIXCOMWD Most people will say N. config WDT - tristate "WDT Watchdog timer" + tristate "ICS WDT500P/501P Watchdog timer" depends on ISA help - If you have a WDT500P or WDT501P watchdog board, say Y here, + If you have an Industrial Computer Source (ICS) WDT500P or WDT501P + watchdog board, say Y here, otherwise N. It is not possible to probe for this board, which means that you have to inform the kernel about the IO port and IRQ that is needed (you can do this via the io and irq parameters) @@ -2300,10 +2301,11 @@ config PCIPCWATCHDOG Most people will say N. config WDTPCI - tristate "PCI-WDT500/501 Watchdog timer" + tristate "ICS PCI-WDT500/501 Watchdog timer" depends on PCI && HAS_IOPORT help - If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. + If you have an Industrial Computer Source (ICS) PCI-WDT500/501 watchdog + board, say Y here, otherwise N. If you have a PCI-WDT501 watchdog board then you can enable the temperature sensor by setting the type parameter to 501. diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c index 3011e1af00f9..9f2c2ab4e2f1 100644 --- a/drivers/watchdog/smsc37b787_wdt.c +++ b/drivers/watchdog/smsc37b787_wdt.c @@ -36,7 +36,7 @@ * mknod /dev/watchdog c 10 130 * * For an example userspace keep-alive daemon, see: - * Documentation/watchdog/wdt.rst + * samples/watchdog/watchdog-simple.c */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -- cgit v1.2.3 From 3394e894880a60338a2981dd688fd25e88d79667 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Mon, 4 May 2026 14:01:42 -0400 Subject: watchdog: lenovo_se10_wdt: Fix use-after-free and resource leak risk Review by sashiko.dev highlighted potential use after free and resource leak instances. Set se10_pdev to null to prevent use after free Remove DMI call back and instead directly call se10_create_platform_device. Handle error cases appropriately Link: https://sashiko.dev/#/patchset/20260428124954.1193450-1-mpearson-lenovo%40squebb.ca Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20260504180159.999189-1-mpearson-lenovo@squebb.ca Signed-off-by: Guenter Roeck --- drivers/watchdog/lenovo_se10_wdt.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/lenovo_se10_wdt.c b/drivers/watchdog/lenovo_se10_wdt.c index 99ff01af4124..503e220263f9 100644 --- a/drivers/watchdog/lenovo_se10_wdt.c +++ b/drivers/watchdog/lenovo_se10_wdt.c @@ -224,7 +224,7 @@ static struct platform_driver se10_wdt_driver = { .probe = se10_wdt_probe, }; -static int se10_create_platform_device(const struct dmi_system_id *id) +static int se10_create_platform_device(void) { int err; @@ -233,9 +233,10 @@ static int se10_create_platform_device(const struct dmi_system_id *id) return -ENOMEM; err = platform_device_add(se10_pdev); - if (err) + if (err) { platform_device_put(se10_pdev); - + se10_pdev = NULL; + } return err; } @@ -246,7 +247,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NH"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -254,7 +254,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NJ"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -262,7 +261,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NK"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -270,7 +268,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NL"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -278,7 +275,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NM"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -286,7 +282,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13LJ"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -294,7 +289,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13LK"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -302,7 +296,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S1"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -310,7 +303,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S2"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -318,7 +310,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S3"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -326,7 +317,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S4"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -334,7 +324,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S5"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10-G2", @@ -342,7 +331,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "13S6"), }, - .callback = se10_create_platform_device, }, {} }; @@ -350,10 +338,20 @@ MODULE_DEVICE_TABLE(dmi, se10_dmi_table); static int __init se10_wdt_init(void) { + int err; + if (!dmi_check_system(se10_dmi_table)) return -ENODEV; - return platform_driver_register(&se10_wdt_driver); + err = platform_driver_register(&se10_wdt_driver); + if (err) + return err; + + err = se10_create_platform_device(); + if (err) + platform_driver_unregister(&se10_wdt_driver); + + return err; } static void __exit se10_wdt_exit(void) -- cgit v1.2.3 From 550c3bcdda0e9e3b3258b2fa9da040a96db058fb Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 5 May 2026 09:23:43 -0700 Subject: watchdog: Remove AMD Elan SC520 processor watchdog driver AMD Elan support was removed from the upstream kernel with commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"). Its watchdog driver can no longer be enabled except for test builds. Remove it. Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 13 -- drivers/watchdog/Makefile | 1 - drivers/watchdog/sc520_wdt.c | 430 ------------------------------------------- 3 files changed, 444 deletions(-) delete mode 100644 drivers/watchdog/sc520_wdt.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4c204951bc95..d1d2cdbd1995 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1270,19 +1270,6 @@ config GEODE_WDT You can compile this driver directly into the kernel, or use it as a module. The module will be called geodewdt. -config SC520_WDT - tristate "AMD Elan SC520 processor Watchdog" - depends on MELAN || COMPILE_TEST - help - This is the driver for the hardware watchdog built in to the - AMD "Elan" SC520 microcomputer commonly used in embedded systems. - This watchdog simply watches your kernel to make sure it doesn't - freeze, and if it does, it reboots your computer after a certain - amount of time. - - You can compile this driver directly into the kernel, or use - it as a module. The module will be called sc520_wdt. - config SBC_FITPC2_WATCHDOG tristate "Compulab SBC-FITPC2 watchdog" depends on (X86 || COMPILE_TEST) && HAS_IOPORT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index a4341b251bdf..bc1d52220f22 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -117,7 +117,6 @@ obj-$(CONFIG_EXAR_WDT) += exar_wdt.o obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o -obj-$(CONFIG_SC520_WDT) += sc520_wdt.o obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o obj-$(CONFIG_IB700_WDT) += ib700wdt.o diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c deleted file mode 100644 index 005f62e4a4fb..000000000000 --- a/drivers/watchdog/sc520_wdt.c +++ /dev/null @@ -1,430 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * AMD Elan SC520 processor Watchdog Timer driver - * - * Based on acquirewdt.c by Alan Cox, - * and sbc60xxwdt.c by Jakob Oestergaard - * - * The authors do NOT admit liability nor provide warranty for - * any of this software. This material is provided "AS-IS" in - * the hope that it may be useful for others. - * - * (c) Copyright 2001 Scott Jennings - * 9/27 - 2001 [Initial release] - * - * Additional fixes Alan Cox - * - Fixed formatting - * - Removed debug printks - * - Fixed SMP built kernel deadlock - * - Switched to private locks not lock_kernel - * - Used ioremap/writew/readw - * - Added NOWAYOUT support - * 4/12 - 2002 Changes by Rob Radez - * - Change comments - * - Eliminate fop_llseek - * - Change CONFIG_WATCHDOG_NOWAYOUT semantics - * - Add KERN_* tags to printks - * - fix possible wdt_is_open race - * - Report proper capabilities in watchdog_info - * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, - * GETTIMEOUT, SETOPTIONS} ioctls - * 09/8 - 2003 Changes by Wim Van Sebroeck - * - cleanup of trailing spaces - * - added extra printk's for startup problems - * - use module_param - * - made timeout (the emulated heartbeat) a module_param - * - made the keepalive ping an internal subroutine - * 3/27 - 2004 Changes by Sean Young - * - set MMCR_BASE to 0xfffef000 - * - CBAR does not need to be read - * - removed debugging printks - * - * This WDT driver is different from most other Linux WDT - * drivers in that the driver will ping the watchdog by itself, - * because this particular WDT has a very short timeout (1.6 - * seconds) and it would be insane to count on any userspace - * daemon always getting scheduled within that time frame. - * - * This driver uses memory mapped IO, and spinlock. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* - * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) - * - * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s - * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s - * - * We will program the SC520 watchdog for a timeout of 2.01s. - * If we reset the watchdog every ~250ms we should be safe. - */ - -#define WDT_INTERVAL (HZ/4+1) - -/* - * We must not require too good response from the userspace daemon. - * Here we require the userspace daemon to send us a heartbeat - * char to /dev/watchdog every 30 seconds. - */ - -#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ -static int timeout = WATCHDOG_TIMEOUT; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, - "Watchdog timeout in seconds. (1 <= timeout <= 3600, default=" - __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); - -static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, - "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -/* - * AMD Elan SC520 - Watchdog Timer Registers - */ -#define MMCR_BASE 0xfffef000 /* The default base address */ -#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ - -/* WDT Control Register bit definitions */ -#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ -#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ -#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ -#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ -#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ -#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ - -static __u16 __iomem *wdtmrctl; - -static void wdt_timer_ping(struct timer_list *); -static DEFINE_TIMER(timer, wdt_timer_ping); -static unsigned long next_heartbeat; -static unsigned long wdt_is_open; -static char wdt_expect_close; -static DEFINE_SPINLOCK(wdt_spinlock); - -/* - * Whack the dog - */ - -static void wdt_timer_ping(struct timer_list *unused) -{ - /* If we got a heartbeat pulse within the WDT_US_INTERVAL - * we agree to ping the WDT - */ - if (time_before(jiffies, next_heartbeat)) { - /* Ping the WDT */ - spin_lock(&wdt_spinlock); - writew(0xAAAA, wdtmrctl); - writew(0x5555, wdtmrctl); - spin_unlock(&wdt_spinlock); - - /* Re-set the timer interval */ - mod_timer(&timer, jiffies + WDT_INTERVAL); - } else - pr_warn("Heartbeat lost! Will not ping the watchdog\n"); -} - -/* - * Utility routines - */ - -static void wdt_config(int writeval) -{ - unsigned long flags; - - /* buy some time (ping) */ - spin_lock_irqsave(&wdt_spinlock, flags); - readw(wdtmrctl); /* ensure write synchronization */ - writew(0xAAAA, wdtmrctl); - writew(0x5555, wdtmrctl); - /* unlock WDT = make WDT configuration register writable one time */ - writew(0x3333, wdtmrctl); - writew(0xCCCC, wdtmrctl); - /* write WDT configuration register */ - writew(writeval, wdtmrctl); - spin_unlock_irqrestore(&wdt_spinlock, flags); -} - -static int wdt_startup(void) -{ - next_heartbeat = jiffies + (timeout * HZ); - - /* Start the timer */ - mod_timer(&timer, jiffies + WDT_INTERVAL); - - /* Start the watchdog */ - wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); - - pr_info("Watchdog timer is now enabled\n"); - return 0; -} - -static int wdt_turnoff(void) -{ - /* Stop the timer */ - timer_delete_sync(&timer); - - /* Stop the watchdog */ - wdt_config(0); - - pr_info("Watchdog timer is now disabled...\n"); - return 0; -} - -static int wdt_keepalive(void) -{ - /* user land ping */ - next_heartbeat = jiffies + (timeout * HZ); - return 0; -} - -static int wdt_set_heartbeat(int t) -{ - if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ - return -EINVAL; - - timeout = t; - return 0; -} - -/* - * /dev/watchdog handling - */ - -static ssize_t fop_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - /* See if we got the magic character 'V' and reload the timer */ - if (count) { - if (!nowayout) { - size_t ofs; - - /* note: just in case someone wrote the magic character - * five months ago... */ - wdt_expect_close = 0; - - /* now scan */ - for (ofs = 0; ofs != count; ofs++) { - char c; - if (get_user(c, buf + ofs)) - return -EFAULT; - if (c == 'V') - wdt_expect_close = 42; - } - } - - /* Well, anyhow someone wrote to us, we should - return that favour */ - wdt_keepalive(); - } - return count; -} - -static int fop_open(struct inode *inode, struct file *file) -{ - /* Just in case we're already talking to someone... */ - if (test_and_set_bit(0, &wdt_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); - - /* Good, fire up the show */ - wdt_startup(); - return stream_open(inode, file); -} - -static int fop_close(struct inode *inode, struct file *file) -{ - if (wdt_expect_close == 42) - wdt_turnoff(); - else { - pr_crit("Unexpected close, not stopping watchdog!\n"); - wdt_keepalive(); - } - clear_bit(0, &wdt_is_open); - wdt_expect_close = 0; - return 0; -} - -static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - static const struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT - | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "SC520", - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_SETOPTIONS: - { - int new_options, retval = -EINVAL; - - if (get_user(new_options, p)) - return -EFAULT; - - if (new_options & WDIOS_DISABLECARD) { - wdt_turnoff(); - retval = 0; - } - - if (new_options & WDIOS_ENABLECARD) { - wdt_startup(); - retval = 0; - } - - return retval; - } - case WDIOC_KEEPALIVE: - wdt_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - { - int new_timeout; - - if (get_user(new_timeout, p)) - return -EFAULT; - - if (wdt_set_heartbeat(new_timeout)) - return -EINVAL; - - wdt_keepalive(); - } - fallthrough; - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - default: - return -ENOTTY; - } -} - -static const struct file_operations wdt_fops = { - .owner = THIS_MODULE, - .write = fop_write, - .open = fop_open, - .release = fop_close, - .unlocked_ioctl = fop_ioctl, - .compat_ioctl = compat_ptr_ioctl, -}; - -static struct miscdevice wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &wdt_fops, -}; - -/* - * Notifier for system down - */ - -static int wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - wdt_turnoff(); - return NOTIFY_DONE; -} - -/* - * The WDT needs to learn about soft shutdowns in order to - * turn the timebomb registers off. - */ - -static struct notifier_block wdt_notifier = { - .notifier_call = wdt_notify_sys, -}; - -static void __exit sc520_wdt_unload(void) -{ - if (!nowayout) - wdt_turnoff(); - - /* Deregister */ - misc_deregister(&wdt_miscdev); - unregister_reboot_notifier(&wdt_notifier); - iounmap(wdtmrctl); -} - -static int __init sc520_wdt_init(void) -{ - int rc = -EBUSY; - - /* Check that the timeout value is within it's range ; - if not reset to the default */ - if (wdt_set_heartbeat(timeout)) { - wdt_set_heartbeat(WATCHDOG_TIMEOUT); - pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n", - WATCHDOG_TIMEOUT); - } - - wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2); - if (!wdtmrctl) { - pr_err("Unable to remap memory\n"); - rc = -ENOMEM; - goto err_out_region2; - } - - rc = register_reboot_notifier(&wdt_notifier); - if (rc) { - pr_err("cannot register reboot notifier (err=%d)\n", rc); - goto err_out_ioremap; - } - - rc = misc_register(&wdt_miscdev); - if (rc) { - pr_err("cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, rc); - goto err_out_notifier; - } - - pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", - timeout, nowayout); - - return 0; - -err_out_notifier: - unregister_reboot_notifier(&wdt_notifier); -err_out_ioremap: - iounmap(wdtmrctl); -err_out_region2: - return rc; -} - -module_init(sc520_wdt_init); -module_exit(sc520_wdt_unload); - -MODULE_AUTHOR("Scott and Bill Jennings"); -MODULE_DESCRIPTION( - "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 63e07a8ecb1ea9ac5cdcb267244716d38c942b30 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 6 May 2026 10:16:00 +0200 Subject: watchdog: convert the Kconfig dependency on OF_GPIO to OF OF_GPIO is selected automatically on all OF systems. Any symbols it controls also provide stubs so there's really no reason to select it explicitly. We could simply remove the dependency but in order to avoid a new symbol popping up for everyone in make config - just convert it to requiring CONFIG_OF. Reviewed-by: Guenter Roeck Signed-off-by: Bartosz Golaszewski Link: https://lore.kernel.org/r/20260506081600.4921-1-bartosz.golaszewski@oss.qualcomm.com [groeck: Resolved conflict; updated dependencies to require OF _or_ ACPI] Signed-off-by: Guenter Roeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d1d2cdbd1995..a8fc1d7a5031 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -250,7 +250,7 @@ config DA9062_WATCHDOG config GPIO_WATCHDOG tristate "Watchdog device controlled through GPIO-line" - depends on (ACPI && GPIOLIB) || OF_GPIO + depends on GPIOLIB && (ACPI || OF) select WATCHDOG_CORE help If you say yes here you get support for watchdog device -- cgit v1.2.3 From fafd036b80a7c85aa86763e70a1a608aa398a144 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 7 May 2026 12:24:08 +0200 Subject: watchdog: rzn1: remove now obsolete interrupt support Previously, it was overlooked that the watchdog could reset the system directly. So, a workaround using the interrupt which called emergency_restart() was implemented. We now configure the controller when booting properly to allow watchdog resets directly. Thus, remove the interrupt workaround. Signed-off-by: Wolfram Sang Reviewed-by: Herve Codina Link: https://lore.kernel.org/r/20260507102410.43384-4-wsa+renesas@sang-engineering.com Signed-off-by: Guenter Roeck --- drivers/watchdog/rzn1_wdt.c | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/rzn1_wdt.c b/drivers/watchdog/rzn1_wdt.c index 48d5afef62a5..4fdc5363ba98 100644 --- a/drivers/watchdog/rzn1_wdt.c +++ b/drivers/watchdog/rzn1_wdt.c @@ -79,14 +79,6 @@ static int rzn1_wdt_start(struct watchdog_device *w) return 0; } -static irqreturn_t rzn1_wdt_irq(int irq, void *_wdt) -{ - pr_crit("RZN1 Watchdog. Initiating system reboot\n"); - emergency_restart(); - - return IRQ_HANDLED; -} - static struct watchdog_info rzn1_wdt_info = { .identity = "RZ/N1 Watchdog", .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, @@ -101,12 +93,10 @@ static const struct watchdog_ops rzn1_wdt_ops = { static int rzn1_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; struct rzn1_watchdog *wdt; unsigned long clk_rate; struct clk *clk; int ret; - int irq; wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) @@ -116,15 +106,6 @@ static int rzn1_wdt_probe(struct platform_device *pdev) if (IS_ERR(wdt->base)) return PTR_ERR(wdt->base); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_irq(dev, irq, rzn1_wdt_irq, 0, - np->name, wdt); - if (ret) - return dev_err_probe(dev, ret, "failed to request irq %d\n", irq); - clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) return dev_err_probe(dev, PTR_ERR(clk), "failed to get the clock\n"); -- cgit v1.2.3 From 14ca4868886f2188401fe06cd7bf01a330b3fb99 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 31 Dec 2025 13:07:21 +0100 Subject: watchdog: apple: Add "apple,t8103-wdt" compatible After discussion with the devicetree maintainers we agreed to not extend lists with the generic compatible "apple,wdt" anymore [1]. Use "apple,t8103-wdt" as base compatible as it is the SoC the driver and bindings were written for. [1]: https://lore.kernel.org/asahi/12ab93b7-1fc2-4ce0-926e-c8141cfe81bf@kernel.org/ Fixes: 4ed224aeaf66 ("watchdog: Add Apple SoC watchdog driver") Cc: stable@vger.kernel.org Reviewed-by: Neal Gompa Signed-off-by: Janne Grunau Link: https://lore.kernel.org/r/20251231-watchdog-apple-t8103-base-compat-v1-1-1702a02e0c45@jannau.net Signed-off-by: Guenter Roeck --- drivers/watchdog/apple_wdt.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c index 66a158f67a71..6b9b0f9b05ce 100644 --- a/drivers/watchdog/apple_wdt.c +++ b/drivers/watchdog/apple_wdt.c @@ -218,6 +218,7 @@ static int apple_wdt_suspend(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(apple_wdt_pm_ops, apple_wdt_suspend, apple_wdt_resume); static const struct of_device_id apple_wdt_of_match[] = { + { .compatible = "apple,t8103-wdt" }, { .compatible = "apple,wdt" }, {}, }; -- cgit v1.2.3 From 05d871c5194bef01b525ca9289efb736bbfd6ece Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 15 May 2026 23:23:50 +0200 Subject: watchdog: realtek-otto: prevent PHASE2 underflows For small pretimeout values, ((timeout - pretimeout) / tick) might be rounded up to the same value as (timeout / tick). As a result, the number of PHASE2 ticks may be zero, causing an underflow when subtracting 1 to configure the hardware. While this results in a longer-than-expected time to system reset, the duration of PHASE1 and minimum ping interval for the watchdog would still be correct. As the watchdog core ensures pretimeout is strictly less than timeout, ceil(timeout / tick) is strictly greater than floor(pretimeout / tick) and the number of PHASE1 ticks cannot be 0. So instead of rounding up the number of PHASE1 ticks, we can round down the number of PHASE2 ticks, maintaining the current behavior while avoiding underflows. The original helper function is now inlined, as it doesn't save any duplication anymore. Signed-off-by: Sander Vanheule Link: https://lore.kernel.org/r/20260515212351.752054-2-sander@svanheule.net Signed-off-by: Guenter Roeck --- drivers/watchdog/realtek_otto_wdt.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/realtek_otto_wdt.c b/drivers/watchdog/realtek_otto_wdt.c index 2c30ddd574c5..1495e8a8cbb5 100644 --- a/drivers/watchdog/realtek_otto_wdt.c +++ b/drivers/watchdog/realtek_otto_wdt.c @@ -114,12 +114,6 @@ static int otto_wdt_tick_ms(struct otto_wdt_ctrl *ctrl, int prescale) * the value stored in those fields. This means each phase will run for at least * one tick, so small values need to be clamped to correctly reflect the timeout. */ -static inline unsigned int div_round_ticks(unsigned int val, unsigned int tick_duration, - unsigned int min_ticks) -{ - return max(min_ticks, DIV_ROUND_UP(val, tick_duration)); -} - static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned int timeout, unsigned int pretimeout) { @@ -140,9 +134,9 @@ static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned in return -EINVAL; tick_ms = otto_wdt_tick_ms(ctrl, prescale); - total_ticks = div_round_ticks(timeout_ms, tick_ms, 2); - phase1_ticks = div_round_ticks(timeout_ms - pretimeout_ms, tick_ms, 1); - phase2_ticks = total_ticks - phase1_ticks; + total_ticks = max(2, DIV_ROUND_UP(timeout_ms, tick_ms)); + phase2_ticks = max(1, pretimeout_ms / tick_ms); + phase1_ticks = total_ticks - phase2_ticks; prescale_next++; } while (phase1_ticks > OTTO_WDT_PHASE_TICKS_MAX -- cgit v1.2.3 From 1147eb0dc29bd33aa3499f9d02777d4165587575 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 15 May 2026 23:23:51 +0200 Subject: watchdog: realtek-otto: enable clock before using I/O As the watchdog is normally on the same bus as the UART peripheral, the bootloader will have ensured the bus' clock is up and running before the watchdog driver is probed. Nevertheless, let's do things the right way and enable the watchdog's clock before performing I/O accesses. Signed-off-by: Sander Vanheule Link: https://lore.kernel.org/r/20260515212351.752054-3-sander@svanheule.net Signed-off-by: Guenter Roeck --- drivers/watchdog/realtek_otto_wdt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/realtek_otto_wdt.c b/drivers/watchdog/realtek_otto_wdt.c index 1495e8a8cbb5..01b3ef89bacf 100644 --- a/drivers/watchdog/realtek_otto_wdt.c +++ b/drivers/watchdog/realtek_otto_wdt.c @@ -296,15 +296,15 @@ static int otto_wdt_probe(struct platform_device *pdev) if (IS_ERR(ctrl->base)) return PTR_ERR(ctrl->base); + ret = otto_wdt_probe_clk(ctrl); + if (ret) + return ret; + /* Clear any old interrupts and reset initial state */ iowrite32(OTTO_WDT_INTR_PHASE_1 | OTTO_WDT_INTR_PHASE_2, ctrl->base + OTTO_WDT_REG_INTR); iowrite32(OTTO_WDT_CTRL_DEFAULT, ctrl->base + OTTO_WDT_REG_CTRL); - ret = otto_wdt_probe_clk(ctrl); - if (ret) - return ret; - ctrl->irq_phase1 = platform_get_irq_byname(pdev, "phase1"); if (ctrl->irq_phase1 < 0) return ctrl->irq_phase1; -- cgit v1.2.3 From c574bdb524095d24169e229b2e3b9318c72e733a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 18 May 2026 19:19:01 +0200 Subject: watchdog: ziirave_wdt: Use named initializers for struct i2c_device_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. This patch doesn't modify the compiled arrays, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://lore.kernel.org/r/20260518171901.904094-2-u.kleine-koenig@baylibre.com Signed-off-by: Guenter Roeck --- drivers/watchdog/ziirave_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index 5c6e3fa001d8..f3bb935c08c2 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -718,7 +718,7 @@ static void ziirave_wdt_remove(struct i2c_client *client) } static const struct i2c_device_id ziirave_wdt_id[] = { - { "rave-wdt" }, + { .name = "rave-wdt" }, { } }; MODULE_DEVICE_TABLE(i2c, ziirave_wdt_id); -- cgit v1.2.3 From c5be907887350812624f8bac4671a0f7e6c22c92 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 29 May 2026 14:20:23 -0700 Subject: watchdog: core: clean up some comments Fix some grammar typos and bulleted kernel-doc comment format. Signed-off-by: Randy Dunlap Signed-off-by: Guenter Roeck --- drivers/watchdog/watchdog_core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 8300520688d0..866874b26819 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -55,9 +55,9 @@ MODULE_PARM_DESC(stop_on_reboot, "Stop watchdogs on reboot (0=keep watching, 1=s * for example when it's impossible to disable it. To do so, * raising the initcall level of the watchdog driver is a solution. * But in such case, the miscdev is maybe not ready (subsys_initcall), and - * watchdog_core need miscdev to register the watchdog as a char device. + * watchdog_core needs miscdev to register the watchdog as a char device. * - * The deferred registration infrastructure offer a way for the watchdog + * The deferred registration infrastructure offers a way for the watchdog * subsystem to register a watchdog properly, even before miscdev is ready. */ @@ -222,11 +222,11 @@ static int watchdog_pm_notifier(struct notifier_block *nb, unsigned long mode, * watchdog_set_restart_priority - Change priority of restart handler * @wdd: watchdog device * @priority: priority of the restart handler, should follow these guidelines: - * 0: use watchdog's restart function as last resort, has limited restart - * capabilies - * 128: default restart handler, use if no other handler is expected to be + * * 0: use watchdog's restart function as last resort, has limited restart + * capabilities + * * 128: default restart handler, use if no other handler is expected to be * available and/or if restart is sufficient to restart the entire system - * 255: preempt all other handlers + * * 255: preempt all other handlers * * If a wdd->ops->restart function is provided when watchdog_register_device is * called, it will be registered as a restart handler with the priority given -- cgit v1.2.3 From 975599591fefd356fc67f9d7675f425bcf3eabf5 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 29 May 2026 14:20:24 -0700 Subject: watchdog: dev: convert to kernel-doc comments Convert multiple functions to kernel-doc format. Signed-off-by: Randy Dunlap Signed-off-by: Guenter Roeck --- drivers/watchdog/watchdog_dev.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 834f65f4b59a..d7895009a2de 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -176,7 +176,7 @@ static int __watchdog_ping(struct watchdog_device *wdd) return err; } -/* +/** * watchdog_ping - ping the watchdog * @wdd: The watchdog device to ping * @@ -236,7 +236,7 @@ static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer) return HRTIMER_NORESTART; } -/* +/** * watchdog_start - wrapper to start the watchdog * @wdd: The watchdog device to start * @@ -279,7 +279,7 @@ static int watchdog_start(struct watchdog_device *wdd) return err; } -/* +/** * watchdog_stop - wrapper to stop the watchdog * @wdd: The watchdog device to stop * @@ -319,7 +319,7 @@ static int watchdog_stop(struct watchdog_device *wdd) return err; } -/* +/** * watchdog_get_status - wrapper to get the watchdog status * @wdd: The watchdog device to get the status from * @@ -356,7 +356,7 @@ static unsigned int watchdog_get_status(struct watchdog_device *wdd) return status; } -/* +/** * watchdog_set_timeout - set the watchdog timer timeout * @wdd: The watchdog device to set the timeout for * @timeout: Timeout to set in seconds @@ -391,7 +391,7 @@ static int watchdog_set_timeout(struct watchdog_device *wdd, return err; } -/* +/** * watchdog_set_pretimeout - set the watchdog timer pretimeout * @wdd: The watchdog device to set the timeout for * @timeout: pretimeout to set in seconds @@ -417,7 +417,7 @@ static int watchdog_set_pretimeout(struct watchdog_device *wdd, return err; } -/* +/** * watchdog_get_timeleft - wrapper to get the time left before a reboot * @wdd: The watchdog device to get the remaining time from * @timeleft: The time that's left @@ -659,7 +659,7 @@ __ATTRIBUTE_GROUPS(wdt); #define wdt_groups NULL #endif -/* +/** * watchdog_ioctl_op - call the watchdog drivers ioctl op if defined * @wdd: The watchdog device to do the ioctl on * @cmd: Watchdog command @@ -678,7 +678,7 @@ static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd, return wdd->ops->ioctl(wdd, cmd, arg); } -/* +/** * watchdog_write - writes to the watchdog * @file: File from VFS * @data: User address of data @@ -732,7 +732,7 @@ static ssize_t watchdog_write(struct file *file, const char __user *data, return len; } -/* +/** * watchdog_ioctl - handle the different ioctl's for the watchdog device * @file: File handle to the device * @cmd: Watchdog command @@ -845,7 +845,7 @@ out_ioctl: return err; } -/* +/** * watchdog_open - open the /dev/watchdog* devices * @inode: Inode of device * @file: File handle to device @@ -923,7 +923,7 @@ static void watchdog_core_data_release(struct device *dev) kfree(wd_data); } -/* +/** * watchdog_release - release the watchdog device * @inode: Inode of device * @file: File handle to device @@ -932,7 +932,7 @@ static void watchdog_core_data_release(struct device *dev) * stop the watchdog when we have received the magic char (and nowayout * was not set), else the watchdog will keep running. * - * Always returns 0. + * Returns: Always 0. */ static int watchdog_release(struct inode *inode, struct file *file) { @@ -1004,7 +1004,7 @@ static const struct class watchdog_class = { .dev_groups = wdt_groups, }; -/* +/** * watchdog_cdev_register - register watchdog character device * @wdd: Watchdog device * @@ -1105,7 +1105,7 @@ static int watchdog_cdev_register(struct watchdog_device *wdd) return 0; } -/* +/** * watchdog_cdev_unregister - unregister watchdog character device * @wdd: Watchdog device * -- cgit v1.2.3 From a298c7302ee9584a7a1ac1e8acbede8d98ab51a4 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Mon, 1 Jun 2026 15:20:05 -0400 Subject: watchdog: unregister PM notifier on watchdog unregister watchdog_register_device() registers wdd->pm_nb when WDOG_NO_PING_ON_SUSPEND is set, but watchdog_unregister_device() does not remove it. This leaves an embedded notifier block on the PM notifier chain after the watchdog device has been unregistered. A later suspend/resume notification can then call watchdog_pm_notifier() with a stale watchdog_device pointer, or at minimum after wdd->wd_data has been cleared by watchdog_dev_unregister(). Unregister the PM notifier before tearing down the watchdog device. Fixes: 60bcd91aafd2 ("watchdog: introduce watchdog_dev_suspend/resume") Signed-off-by: Yuho Choi Link: https://lore.kernel.org/r/20260601192005.1970805-1-dbgh9129@gmail.com Signed-off-by: Guenter Roeck --- drivers/watchdog/watchdog_core.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 866874b26819..f4097aefaf49 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -391,6 +391,9 @@ static void __watchdog_unregister_device(struct watchdog_device *wdd) if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) unregister_reboot_notifier(&wdd->reboot_nb); + if (test_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status)) + unregister_pm_notifier(&wdd->pm_nb); + watchdog_dev_unregister(wdd); ida_free(&watchdog_ida, wdd->id); } -- cgit v1.2.3 From 54c726fbb53341a2553bf234d519d0cb22a2ce3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 10 Jun 2026 10:48:22 +0200 Subject: watchdog: sc1200: Drop unused assignment of pnp_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver explicitly sets the .driver_data member of struct pnp_device_id to zero without relying on that value. Drop this unused assignments. While touching this array simplify the list terminator. This patch doesn't modify the compiled array, only its representation in source form benefits. The former was confirmed with builds on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://lore.kernel.org/r/0793f81a854f9e5880ad38f54c8583b3d56e5d60.1781081216.git.u.kleine-koenig@baylibre.com Signed-off-by: Guenter Roeck --- drivers/watchdog/sc1200wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c index 76a58715f665..1d3300c98d43 100644 --- a/drivers/watchdog/sc1200wdt.c +++ b/drivers/watchdog/sc1200wdt.c @@ -339,8 +339,8 @@ static int __init sc1200wdt_probe(void) static const struct pnp_device_id scl200wdt_pnp_devices[] = { /* National Semiconductor PC87307/PC97307 watchdog component */ - {.id = "NSC0800", .driver_data = 0}, - {.id = ""}, + { .id = "NSC0800" }, + { } }; static int scl200wdt_pnp_probe(struct pnp_dev *dev, -- cgit v1.2.3