[PATCH v9 4/4] watchdog: aaeon: Add watchdog driver for SRG-IMX8P MCU

From: Thomas Perrot (Schneider Electric)

Date: Thu Oct 29 2026 - 10:54:50 EST


Add watchdog driver for the Aaeon SRG-IMX8P embedded controller,
which exposes the MCU's watchdog timer.

The MCU's hardware heartbeat is fixed at 25 seconds, so the watchdog
core pings the MCU in the background to provide the default timeout
of 240 seconds. The software timeout can be changed via the
WDIOC_SETTIMEOUT ioctl, the DT timeout-sec property of the MCU node,
or the timeout module parameter. As the hardware cannot expire earlier
than its heartbeat, timeouts below 25 seconds are rejected.

CONFIG_WATCHDOG_NOWAYOUT is honoured, so the watchdog cannot be stopped
once started when it is enabled.

Co-developed-by: Jérémie Dautheribes (Schneider Electric) <jeremie.dautheribes@xxxxxxxxxxx>
Signed-off-by: Jérémie Dautheribes (Schneider Electric) <jeremie.dautheribes@xxxxxxxxxxx>
Signed-off-by: Thomas Perrot (Schneider Electric) <thomas.perrot@xxxxxxxxxxx>
Acked-by: Guenter Roeck <linux@xxxxxxxxxxxx>
---
MAINTAINERS | 1 +
drivers/watchdog/Kconfig | 10 +++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/aaeon_mcu_wdt.c | 171 +++++++++++++++++++++++++++++++++++++++
4 files changed, 183 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 4844d50327c8..d8bb162ddcff 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -194,6 +194,7 @@ S: Maintained
F: Documentation/devicetree/bindings/mfd/aaeon,srg-imx8p-mcu.yaml
F: drivers/gpio/gpio-aaeon-mcu.c
F: drivers/mfd/aaeon-mcu.c
+F: drivers/watchdog/aaeon_mcu_wdt.c
F: include/linux/mfd/aaeon-mcu.h

AAEON UPBOARD FPGA MFD DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f7d0a0c2c0ef..9ffc2ade9bf0 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -449,6 +449,16 @@ config SL28CPLD_WATCHDOG

# ARM Architecture

+config AAEON_MCU_WATCHDOG
+ tristate "Aaeon MCU Watchdog"
+ depends on MFD_AAEON_MCU
+ select WATCHDOG_CORE
+ help
+ Select this option to enable watchdog timer support for the Aaeon
+ SRG-IMX8P onboard microcontroller (MCU). This driver provides
+ watchdog functionality through the MCU, allowing system monitoring
+ and automatic recovery from system hangs.
+
config AIROHA_WATCHDOG
tristate "Airoha EN7581 Watchdog"
depends on ARCH_AIROHA || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 9ac406677aba..09c07a29a2f4 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
# ALPHA Architecture

# ARM Architecture
+obj-$(CONFIG_AAEON_MCU_WATCHDOG) += aaeon_mcu_wdt.o
obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o
obj-$(CONFIG_ARM_SBSA_WATCHDOG) += sbsa_gwdt.o
obj-$(CONFIG_ARMADA_37XX_WATCHDOG) += armada_37xx_wdt.o
diff --git a/drivers/watchdog/aaeon_mcu_wdt.c b/drivers/watchdog/aaeon_mcu_wdt.c
new file mode 100644
index 000000000000..beded3d6326f
--- /dev/null
+++ b/drivers/watchdog/aaeon_mcu_wdt.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Aaeon MCU Watchdog driver
+ *
+ * Copyright (C) 2026 Bootlin
+ * Author: Jérémie Dautheribes <jeremie.dautheribes@xxxxxxxxxxx>
+ * Author: Thomas Perrot <thomas.perrot@xxxxxxxxxxx>
+ */
+
+#include <linux/mfd/aaeon-mcu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define AAEON_MCU_PING_WDT 0x73
+
+#define AAEON_MCU_WDT_TIMEOUT 240
+#define AAEON_MCU_WDT_HEARTBEAT_MS 25000
+/* The MCU cannot expire earlier than its fixed heartbeat. */
+#define AAEON_MCU_WDT_MIN_TIMEOUT (AAEON_MCU_WDT_HEARTBEAT_MS / 1000)
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+struct aaeon_mcu_wdt {
+ struct watchdog_device wdt;
+ struct regmap *regmap;
+};
+
+static int aaeon_mcu_wdt_cmd(struct aaeon_mcu_wdt *data, u8 opcode, u8 arg)
+{
+ return regmap_write(data->regmap, AAEON_MCU_REG(opcode, arg), 0);
+}
+
+static int aaeon_mcu_wdt_start(struct watchdog_device *wdt)
+{
+ struct aaeon_mcu_wdt *data = watchdog_get_drvdata(wdt);
+
+ return aaeon_mcu_wdt_cmd(data, AAEON_MCU_CONTROL_WDT_OPCODE, 0x01);
+}
+
+static int aaeon_mcu_wdt_status(struct watchdog_device *wdt, bool *enabled)
+{
+ struct aaeon_mcu_wdt *data = watchdog_get_drvdata(wdt);
+ unsigned int rsp;
+ int ret;
+
+ ret = regmap_read(data->regmap,
+ AAEON_MCU_REG(AAEON_MCU_CONTROL_WDT_OPCODE, 0x02),
+ &rsp);
+ if (ret)
+ return ret;
+
+ *enabled = rsp == 0x01;
+ return 0;
+}
+
+static int aaeon_mcu_wdt_stop(struct watchdog_device *wdt)
+{
+ struct aaeon_mcu_wdt *data = watchdog_get_drvdata(wdt);
+
+ return aaeon_mcu_wdt_cmd(data, AAEON_MCU_CONTROL_WDT_OPCODE, 0x00);
+}
+
+static int aaeon_mcu_wdt_ping(struct watchdog_device *wdt)
+{
+ struct aaeon_mcu_wdt *data = watchdog_get_drvdata(wdt);
+
+ return aaeon_mcu_wdt_cmd(data, AAEON_MCU_PING_WDT, 0x00);
+}
+
+static const struct watchdog_info aaeon_mcu_wdt_info = {
+ .identity = "Aaeon MCU Watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT
+};
+
+static const struct watchdog_ops aaeon_mcu_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = aaeon_mcu_wdt_start,
+ .stop = aaeon_mcu_wdt_stop,
+ .ping = aaeon_mcu_wdt_ping,
+};
+
+static int aaeon_mcu_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdt;
+ struct aaeon_mcu_wdt *data;
+ bool enabled;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!data->regmap)
+ return -ENODEV;
+
+ wdt = &data->wdt;
+ wdt->parent = dev;
+ wdt->info = &aaeon_mcu_wdt_info;
+ wdt->ops = &aaeon_mcu_wdt_ops;
+ /*
+ * The MCU firmware has a fixed hardware timeout of 25 seconds that
+ * cannot be changed. The watchdog core handles automatic pinging to
+ * support software timeouts longer than the hardware limit. The default
+ * software timeout of 240 seconds can be overridden via the DT
+ * timeout-sec property of the MCU node or the timeout module
+ * parameter.
+ */
+ wdt->timeout = AAEON_MCU_WDT_TIMEOUT;
+ wdt->min_timeout = AAEON_MCU_WDT_MIN_TIMEOUT;
+ wdt->max_hw_heartbeat_ms = AAEON_MCU_WDT_HEARTBEAT_MS;
+ watchdog_init_timeout(wdt, timeout, dev->parent);
+
+ watchdog_set_drvdata(wdt, data);
+ watchdog_set_nowayout(wdt, WATCHDOG_NOWAYOUT);
+ watchdog_stop_on_reboot(wdt);
+ watchdog_stop_ping_on_suspend(wdt);
+ dev_set_drvdata(dev, wdt);
+
+ ret = aaeon_mcu_wdt_status(wdt, &enabled);
+ if (ret)
+ return ret;
+
+ if (enabled)
+ set_bit(WDOG_HW_RUNNING, &wdt->status);
+
+ return devm_watchdog_register_device(dev, wdt);
+}
+
+static int aaeon_mcu_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdt) || watchdog_hw_running(wdt))
+ return aaeon_mcu_wdt_stop(wdt);
+
+ return 0;
+}
+
+static int aaeon_mcu_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdt) || watchdog_hw_running(wdt))
+ return aaeon_mcu_wdt_start(wdt);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aaeon_mcu_wdt_pm_ops,
+ aaeon_mcu_wdt_suspend, aaeon_mcu_wdt_resume);
+
+static struct platform_driver aaeon_mcu_wdt_driver = {
+ .driver = {
+ .name = "aaeon-mcu-wdt",
+ .pm = pm_sleep_ptr(&aaeon_mcu_wdt_pm_ops),
+ },
+ .probe = aaeon_mcu_wdt_probe,
+};
+
+module_platform_driver(aaeon_mcu_wdt_driver);
+
+MODULE_ALIAS("platform:aaeon-mcu-wdt");
+MODULE_DESCRIPTION("Aaeon MCU Watchdog Driver");
+MODULE_AUTHOR("Jérémie Dautheribes <jeremie.dautheribes@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");

--
2.56.0