* [PATCH 0/3] watchdog: ma35d1: Add support for MA35D1 Watchdog
@ 2026-07-23 6:07 Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 1/3] dt-bindings: watchdog: Add MA35D1 Watchdog binding Zi-Yu Chen
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Zi-Yu Chen @ 2026-07-23 6:07 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Jacky Huang, Shan-Chun Hung, linux-watchdog, devicetree,
linux-arm-kernel, linux-kernel, Zi-Yu Chen
This patch series introduces watchdog timer support for the Nuvoton MA35D1
SoC platform.
The MA35D1 watchdog timer hardware provides a programmable countdown timer
capable of triggering a system reset upon timeout expiration or generating
an interrupt to wake up the system from low-power states.
Key features supported in this driver:
- Standard watchdog operations (start, stop, ping, set_timeout).
- Boot status detection to identify watchdog reset events (WDIOF_CARDRESET).
- Power management support including suspend/resume and wakeup functionality.
Series breakdown:
- Patch 1: Add DT binding documentation for Nuvoton MA35D1 watchdog.
- Patch 2: Add the watchdog driver implementation for MA35D1.
- Patch 3: Add the watchdog node in the MA35D1 SoC DeviceTree file.
Testing:
- Tested on the Nuvoton MA35D1 development board using standard watchdog
test utilities (e.g., watchdog-test from tools/testing/selftests/watchdog).
Zi-Yu Chen (3):
dt-bindings: watchdog: Add MA35D1 Watchdog binding
watchdog: Add Nuvoton MA35D1 watchdog driver support
arm64: dts: nuvoton: Add WDT node for MA35D1 SoC
.../bindings/watchdog/nuvoton,ma35d1-wdt.yaml | 55 +++
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 7 +
drivers/watchdog/Kconfig | 11 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/ma35d1_wdt.c | 357 ++++++++++++++++++
5 files changed, 431 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/nuvoton,ma35d1-wdt.yaml
create mode 100644 drivers/watchdog/ma35d1_wdt.c
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/3] dt-bindings: watchdog: Add MA35D1 Watchdog binding
2026-07-23 6:07 [PATCH 0/3] watchdog: ma35d1: Add support for MA35D1 Watchdog Zi-Yu Chen
@ 2026-07-23 6:07 ` Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC Zi-Yu Chen
2 siblings, 0 replies; 7+ messages in thread
From: Zi-Yu Chen @ 2026-07-23 6:07 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Jacky Huang, Shan-Chun Hung, linux-watchdog, devicetree,
linux-arm-kernel, linux-kernel, Zi-Yu Chen
Add device tree binding documentation for the watchdog timer (WDT)
controller found on Nuvoton MA35D1 SoC.
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
.../bindings/watchdog/nuvoton,ma35d1-wdt.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/nuvoton,ma35d1-wdt.yaml
diff --git a/Documentation/devicetree/bindings/watchdog/nuvoton,ma35d1-wdt.yaml b/Documentation/devicetree/bindings/watchdog/nuvoton,ma35d1-wdt.yaml
new file mode 100644
index 000000000000..aced7d77a7d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/nuvoton,ma35d1-wdt.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/watchdog/nuvoton,ma35d1-wdt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Nuvoton MA35D1 Watchdog Timer
+
+maintainers:
+ - Zi-Yu Chen <zychennvt@gmail.com>
+
+allOf:
+ - $ref: watchdog.yaml#
+
+properties:
+ compatible:
+ enum:
+ - nuvoton,ma35d1-wdt
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ wakeup-source: true
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ wdt1: watchdog@40440000 {
+ compatible = "nuvoton,ma35d1-wdt";
+ reg = <0x0 0x40440000 0x0 0x100>;
+ interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk WDT1_GATE>;
+ };
+ };
+...
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support
2026-07-23 6:07 [PATCH 0/3] watchdog: ma35d1: Add support for MA35D1 Watchdog Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 1/3] dt-bindings: watchdog: Add MA35D1 Watchdog binding Zi-Yu Chen
@ 2026-07-23 6:07 ` Zi-Yu Chen
2026-07-23 6:20 ` sashiko-bot
2026-07-23 6:07 ` [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC Zi-Yu Chen
2 siblings, 1 reply; 7+ messages in thread
From: Zi-Yu Chen @ 2026-07-23 6:07 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Jacky Huang, Shan-Chun Hung, linux-watchdog, devicetree,
linux-arm-kernel, linux-kernel, Zi-Yu Chen
Add driver support for the Watchdog Timer (WDT) integrated in
Nuvoton MA35D1 SoC. This driver provides standard watchdog functionality,
including timeout configuration, ping, and system reset support via
the watchdog framework.
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
drivers/watchdog/Kconfig | 11 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/ma35d1_wdt.c | 357 ++++++++++++++++++++++++++++++++++
3 files changed, 369 insertions(+)
create mode 100644 drivers/watchdog/ma35d1_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 08cb8612d41f..28d8d3d12c27 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -720,6 +720,17 @@ config STMP3XXX_RTC_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called stmp3xxx_rtc_wdt.
+config MA35D1_WDT
+ tristate "Nuvoton MA35D1 Watchdog Timer"
+ depends on ARCH_MA35 || COMPILE_TEST
+ select WATCHDOG_CORE
+ select MFD_SYSCON
+ help
+ Say Y here to include support for the watchdog timer
+ for the Nuvoton MA35D1 series microprosser.
+ To compile this driver as a module, choose M here: the
+ module will be called ma35d1_wdt.
+
config TS4800_WATCHDOG
tristate "TS-4800 Watchdog"
depends on HAS_IOMEM && OF
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index bc1d52220f22..a7889eb48c7f 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -104,6 +104,7 @@ 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
+obj-$(CONFIG_MA35D1_WDT) += ma35d1_wdt.o
# X86 (i386 + ia64 + x86_64) Architecture
obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
diff --git a/drivers/watchdog/ma35d1_wdt.c b/drivers/watchdog/ma35d1_wdt.c
new file mode 100644
index 000000000000..bd52847bb09b
--- /dev/null
+++ b/drivers/watchdog/ma35d1_wdt.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Nuvoton technology corporation.
+ *
+ * Author: Zi-Yu Chen <zychennvt@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/property.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define DRV_NAME "ma35d1-wdt"
+
+#define REG_WDT_CTL 0x00
+#define REG_WDT_RSTCNT 0x08
+
+#define TOUTSEL GENMASK(11, 8)
+#define WDTEN BIT(7)
+#define INTEN BIT(6)
+#define WKF BIT(5)
+#define WKEN BIT(4)
+#define IF BIT(3)
+#define RSTF BIT(2)
+#define RSTEN BIT(1)
+#define SYNC BIT(30)
+
+#define WDT_DEFAULT_TIMEOUT 32
+#define RESET_COUNTER 0x00005AA5
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+struct ma35d1_wdt_dev {
+ struct watchdog_device wdt_dev;
+ struct device *dev;
+ spinlock_t lock;
+ void __iomem *wdt_base;
+ struct clk *clk;
+ unsigned long clk_rate;
+ u32 irq;
+};
+
+static int ma35d1_wdt_wait_sync(struct ma35d1_wdt_dev *ma35d1_wdt)
+{
+ unsigned int val;
+
+ return readl_relaxed_poll_timeout_atomic(ma35d1_wdt->wdt_base +
+ REG_WDT_CTL,
+ val, !(val & SYNC), 10, 125);
+}
+
+/**
+ * ma35d1_wdt_stop - Stop the watchdog.
+ *
+ * @wdt_dev: watchdog device
+ *
+ * Read the contents of the CTL register, clear the WDTEN bit
+ * in the register and set the access key for successful write.
+ *
+ * Return: 0 on success, negative error otherwise.
+ */
+static int ma35d1_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
+
+ unsigned int val;
+ int ret;
+
+ guard(spinlock_irqsave)
+ (&ma35d1_wdt->lock);
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ val &= ~WDTEN;
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
+ if (ret) {
+ dev_err(ma35d1_wdt->dev, "Wait for WDTEN SYNC timeout!\n");
+ return ret;
+ }
+ return 0;
+}
+
+static int ma35d1_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel_relaxed(RESET_COUNTER, ma35d1_wdt->wdt_base + REG_WDT_RSTCNT);
+
+ return 0;
+}
+
+static int ma35d1_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long target_ticks;
+ unsigned int val, i;
+ static const uint8_t toutsel_shifts[] = { 4, 6, 8, 10, 12,
+ 14, 16, 18, 20 };
+
+ target_ticks = (unsigned long)timeout * ma35d1_wdt->clk_rate;
+
+ for (i = 0; i < ARRAY_SIZE(toutsel_shifts); i++) {
+ if ((1UL << toutsel_shifts[i]) >= target_ticks)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(toutsel_shifts))
+ i = ARRAY_SIZE(toutsel_shifts) - 1;
+
+ scoped_guard(spinlock_irqsave, &ma35d1_wdt->lock) {
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ val &= ~TOUTSEL;
+ val |= FIELD_PREP(TOUTSEL, i);
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ }
+
+ /* To avoid truncation errors (0 seconds) during division. */
+ wdt_dev->timeout = (1UL << toutsel_shifts[i]) / ma35d1_wdt->clk_rate;
+ if (wdt_dev->timeout == 0)
+ wdt_dev->timeout = 1;
+
+ dev_dbg(ma35d1_wdt->dev, "wdt_dev->timeout = %d\n", wdt_dev->timeout);
+
+ return 0;
+}
+
+static int ma35d1_wdt_start(struct watchdog_device *wdt_dev)
+{
+ unsigned int val;
+
+ struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = ma35d1_wdt->wdt_base;
+ int ret;
+
+ ret = ma35d1_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ if (ret < 0)
+ return ret;
+
+ guard(spinlock_irqsave)
+ (&ma35d1_wdt->lock);
+ val = readl_relaxed(wdt_base + REG_WDT_CTL);
+ val |= (WDTEN | RSTEN);
+
+ writel_relaxed(val, wdt_base + REG_WDT_CTL);
+ writel_relaxed(RESET_COUNTER, wdt_base + REG_WDT_RSTCNT);
+
+ return 0;
+}
+
+static const struct watchdog_info ma35d1_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
+ WDIOF_CARDRESET,
+};
+
+static const struct watchdog_ops ma35d1_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ma35d1_wdt_start,
+ .stop = ma35d1_wdt_stop,
+ .ping = ma35d1_wdt_ping,
+ .set_timeout = ma35d1_wdt_set_timeout,
+};
+
+static irqreturn_t ma35d1_wdt_isr(int irq, void *dev_id)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = dev_id;
+ unsigned int val;
+
+ /* Clear the flag if set */
+ guard(spinlock)
+ (&ma35d1_wdt->lock);
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+
+ return IRQ_HANDLED;
+}
+
+static int ma35d1_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ma35d1_wdt_dev *ma35d1_wdt;
+ unsigned long clk_rate, val;
+ int ret;
+
+ ma35d1_wdt = devm_kzalloc(dev, sizeof(*ma35d1_wdt), GFP_KERNEL);
+ if (!ma35d1_wdt)
+ return -ENOMEM;
+
+ spin_lock_init(&ma35d1_wdt->lock);
+ platform_set_drvdata(pdev, ma35d1_wdt);
+
+ ma35d1_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ma35d1_wdt->wdt_base))
+ return PTR_ERR(ma35d1_wdt->wdt_base);
+
+ ma35d1_wdt->clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(ma35d1_wdt->clk))
+ return dev_err_probe(dev, PTR_ERR(ma35d1_wdt->clk),
+ "Can't get Watchdog clock\n");
+
+ clk_rate = clk_get_rate(ma35d1_wdt->clk);
+ if (!clk_rate)
+ return -EINVAL;
+ ma35d1_wdt->clk_rate = clk_rate;
+
+ ma35d1_wdt->irq = platform_get_irq(pdev, 0);
+ if (ma35d1_wdt->irq < 0)
+ return dev_err_probe(dev, ma35d1_wdt->irq,
+ "failed to get irq\n");
+
+ ma35d1_wdt->wdt_dev.info = &ma35d1_wdt_info;
+ ma35d1_wdt->wdt_dev.ops = &ma35d1_wdt_ops;
+ ma35d1_wdt->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT;
+ ma35d1_wdt->wdt_dev.min_timeout = 1;
+ ma35d1_wdt->wdt_dev.max_hw_heartbeat_ms = (1U << 20) * 1000 / clk_rate;
+ ma35d1_wdt->wdt_dev.parent = dev;
+
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ if (val & RSTF) {
+ ma35d1_wdt->wdt_dev.bootstatus = WDIOF_CARDRESET;
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ }
+
+ watchdog_set_drvdata(&ma35d1_wdt->wdt_dev, ma35d1_wdt);
+ watchdog_set_nowayout(&ma35d1_wdt->wdt_dev, nowayout);
+ watchdog_init_timeout(&ma35d1_wdt->wdt_dev, timeout, &pdev->dev);
+
+ ret = devm_request_irq(dev, ma35d1_wdt->irq, ma35d1_wdt_isr, 0,
+ dev_name(dev), ma35d1_wdt);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot claim IRQ %d\n",
+ ma35d1_wdt->irq);
+
+ if (device_property_read_bool(dev, "wakeup-source")) {
+ ret = devm_device_init_wakeup(dev);
+ if (ret)
+ return ret;
+
+ ret = dev_pm_set_wake_irq(dev, ma35d1_wdt->irq);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_watchdog_register_device(dev, &ma35d1_wdt->wdt_dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ma35d1_wdt_suspend(struct device *dev)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&ma35d1_wdt->wdt_dev)) {
+ u32 val;
+ int ret;
+
+ guard(spinlock_irqsave)
+ (&ma35d1_wdt->lock);
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+
+ if (device_may_wakeup(dev)) {
+ val &= ~RSTEN;
+ val |= (INTEN | WKEN);
+ } else {
+ val &= ~(WDTEN | RSTEN);
+ }
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
+ if (ret) {
+ dev_err(ma35d1_wdt->dev,
+ "Wait for WDTEN SYNC timeout!\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ma35d1_wdt_resume(struct device *dev)
+{
+ struct ma35d1_wdt_dev *ma35d1_wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&ma35d1_wdt->wdt_dev)) {
+ u32 val;
+ int ret;
+
+ guard(spinlock_irqsave)
+ (&ma35d1_wdt->lock);
+ val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
+
+ if (device_may_wakeup(dev)) {
+ val |= RSTEN;
+ val &= ~(INTEN | WKEN);
+ } else {
+ val |= (WDTEN | RSTEN);
+ }
+ writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
+ writel_relaxed(RESET_COUNTER,
+ ma35d1_wdt->wdt_base + REG_WDT_RSTCNT);
+ ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
+ if (ret) {
+ dev_err(ma35d1_wdt->dev,
+ "Wait for WDTEN SYNC timeout!\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ma35d1_wdt_pm_ops, ma35d1_wdt_suspend,
+ ma35d1_wdt_resume);
+
+static const struct of_device_id ma35d1_wdt_dt_ids[] = {
+ { .compatible = "nuvoton,ma35d1-wdt" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ma35d1_wdt_dt_ids);
+
+static struct platform_driver ma35d1_wdt_driver = {
+ .probe = ma35d1_wdt_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = pm_ptr(&ma35d1_wdt_pm_ops),
+ .of_match_table = ma35d1_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(ma35d1_wdt_driver);
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(
+ nowayout,
+ "Watchdog cannot be stopped once started (default=" __MODULE_STRING(
+ WATCHDOG_NOWAYOUT) ")");
+
+MODULE_AUTHOR("Zi-Yu Chen <zychennvt@gmail.com>");
+MODULE_DESCRIPTION("Nuvoton MA35D1 Watchdog Timer Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC
2026-07-23 6:07 [PATCH 0/3] watchdog: ma35d1: Add support for MA35D1 Watchdog Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 1/3] dt-bindings: watchdog: Add MA35D1 Watchdog binding Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support Zi-Yu Chen
@ 2026-07-23 6:07 ` Zi-Yu Chen
2026-07-23 6:18 ` sashiko-bot
2 siblings, 1 reply; 7+ messages in thread
From: Zi-Yu Chen @ 2026-07-23 6:07 UTC (permalink / raw)
To: Wim Van Sebroeck, Guenter Roeck, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Jacky Huang, Shan-Chun Hung, linux-watchdog, devicetree,
linux-arm-kernel, linux-kernel, Zi-Yu Chen
Add Watchdog Timer (WDT) device node to ma35d1.dtsi.
Signed-off-by: Zi-Yu Chen <zychennvt@gmail.com>
---
arch/arm64/boot/dts/nuvoton/ma35d1.dtsi | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
index 7228ad4735b5..14fd17cfdc5a 100644
--- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
+++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
@@ -82,6 +82,13 @@ soc {
#size-cells = <2>;
ranges;
+ wdt1: watchdog@40440000 {
+ compatible = "nuvoton,ma35d1-wdt";
+ reg = <0x0 0x40440000 0x0 0x100>;
+ interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk WDT1_GATE>;
+ };
+
sys: system-management@40460000 {
compatible = "nuvoton,ma35d1-reset", "syscon";
reg = <0x0 0x40460000 0x0 0x200>;
--
2.34.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC
2026-07-23 6:07 ` [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC Zi-Yu Chen
@ 2026-07-23 6:18 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-07-23 6:18 UTC (permalink / raw)
To: Zi-Yu Chen; +Cc: linux-watchdog, robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Error check for platform_get_irq() is always false due to u32 type mismatch.
- [High] Resource leak due to missing device-managed variant for wake IRQ setup.
--
--- Patch [3]: [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC ---
commit c2bd64766eedbf6c5de5bc325d96283f217c6464
Author: Zi-Yu Chen <zychennvt@gmail.com>
arm64: dts: nuvoton: Add WDT node for MA35D1 SoC
Add Watchdog Timer (WDT) device node to ma35d1.dtsi.
> diff --git a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> index 7228ad4735b5c..14fd17cfdc5ae 100644
> --- a/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> +++ b/arch/arm64/boot/dts/nuvoton/ma35d1.dtsi
> @@ -82,6 +82,13 @@ soc {
> #size-cells = <2>;
> ranges;
>
> + wdt1: watchdog@40440000 {
> + compatible = "nuvoton,ma35d1-wdt";
[Severity: High]
Does adding this device node trigger a probe regression if platform_get_irq()
returns a negative error code like -EPROBE_DEFER?
Looking at ma35d1_wdt_probe() in drivers/watchdog/ma35d1_wdt.c:
ma35d1_wdt->irq = platform_get_irq(pdev, 0);
if (ma35d1_wdt->irq < 0)
return dev_err_probe(dev, ma35d1_wdt->irq,
"failed to get irq\n");
If ma35d1_wdt->irq is a u32, the negative error code is implicitly cast to a
large positive number, making the check evaluate to false.
Will this cause probe errors to be ignored and devm_request_irq() to be
called with an invalid IRQ number, resulting in -EINVAL and breaking the probe
deferral mechanism?
[Severity: High]
Could this also lead to a resource leak when setting the wake IRQ?
Looking further in ma35d1_wdt_probe() in drivers/watchdog/ma35d1_wdt.c:
ret = dev_pm_set_wake_irq(dev, ma35d1_wdt->irq);
if (ret)
return ret;
Since dev_pm_set_wake_irq() is used instead of the device-managed variant
devm_pm_set_wake_irq(), and the driver relies on devm_ interfaces without a
.remove callback, will the wake IRQ remain attached to the device structure if
the driver is unbound or a subsequent probe step fails?
Can this leave a dangling wake IRQ and potentially cause a use-after-free
when the device is destroyed?
> + reg = <0x0 0x40440000 0x0 0x100>;
> + interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk WDT1_GATE>;
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723060755.1596858-1-zychennvt@gmail.com?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support
2026-07-23 6:07 ` [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support Zi-Yu Chen
@ 2026-07-23 6:20 ` sashiko-bot
2026-07-23 14:07 ` Guenter Roeck
0 siblings, 1 reply; 7+ messages in thread
From: sashiko-bot @ 2026-07-23 6:20 UTC (permalink / raw)
To: Zi-Yu Chen; +Cc: linux-watchdog, devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] The `ma35d1_wdt->dev` pointer is never initialized, leading to NULL pointer dereferences in logging functions.
- [High] Read-modify-write operations on REG_WDT_CTL inadvertently clear W1C (write-one-to-clear) status flags.
- [High] Missing clock domain synchronization (`ma35d1_wdt_wait_sync`) after writes to `REG_WDT_CTL` can lead to dropped register writes.
- [High] Driver overwrites `wdt_dev->timeout`, breaking the software heartbeat extension.
- [High] Missing cleanup for wake IRQ allocated by dev_pm_set_wake_irq(), leading to a memory leak and permanent device unbind/bind failures.
- [High] The driver fails to check if the watchdog was already enabled by the bootloader, causing unexpected system reboots during boot.
- [Medium] Unsigned integer comparison against zero renders IRQ error handling dead code.
- [Medium] Integer overflow in `target_ticks` calculation triggers unexpected system resets for large timeouts.
- [Low] Unnecessary dependency: selects `MFD_SYSCON` without using it.
--
--- Patch [2]: [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support ---
commit 3456ece7880176f0928364babfa03dcf9243b968
Author: Zi-Yu Chen <zychennvt@gmail.com>
watchdog: Add Nuvoton MA35D1 watchdog driver support
Add driver support for the Watchdog Timer (WDT) integrated in
Nuvoton MA35D1 SoC. This driver provides standard watchdog functionality,
including timeout configuration, ping, and system reset support via
the watchdog framework.
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 08cb8612d41fe..28d8d3d12c27f 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -720,6 +720,17 @@ config STMP3XXX_RTC_WATCHDOG
> To compile this driver as a module, choose M here: the
> module will be called stmp3xxx_rtc_wdt.
>
> +config MA35D1_WDT
> + tristate "Nuvoton MA35D1 Watchdog Timer"
> + depends on ARCH_MA35 || COMPILE_TEST
> + select WATCHDOG_CORE
> + select MFD_SYSCON
[Severity: Low]
Is select MFD_SYSCON necessary here?
The driver maps its MMIO region directly and doesn't appear to use MFD or
syscon APIs.
[ ... ]
> --- /dev/null
> +++ b/drivers/watchdog/ma35d1_wdt.c
[ ... ]
> +static int ma35d1_wdt_set_timeout(struct watchdog_device *wdt_dev,
> + unsigned int timeout)
> +{
> + struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
> + unsigned long target_ticks;
> + unsigned int val, i;
> + static const uint8_t toutsel_shifts[] = { 4, 6, 8, 10, 12,
> + 14, 16, 18, 20 };
> +
> + target_ticks = (unsigned long)timeout * ma35d1_wdt->clk_rate;
[Severity: Medium]
Can this calculation overflow on 32-bit platforms where unsigned long is
32 bits?
If a user configures a large timeout, timeout * clk_rate might truncate
before it can be clamped, resulting in a tiny hardware timeout.
> +
> + for (i = 0; i < ARRAY_SIZE(toutsel_shifts); i++) {
> + if ((1UL << toutsel_shifts[i]) >= target_ticks)
> + break;
> + }
> +
> + if (i == ARRAY_SIZE(toutsel_shifts))
> + i = ARRAY_SIZE(toutsel_shifts) - 1;
> +
> + scoped_guard(spinlock_irqsave, &ma35d1_wdt->lock) {
> + val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
> + val &= ~TOUTSEL;
> + val |= FIELD_PREP(TOUTSEL, i);
> + writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
> + }
[Severity: High]
Does this read-modify-write sequence accidentally clear write-one-to-clear
status flags like IF, WKF, or RSTF?
Writing back a 1 to these bits when they are concurrently set by hardware
would acknowledge and clear them.
> +
> + /* To avoid truncation errors (0 seconds) during division. */
> + wdt_dev->timeout = (1UL << toutsel_shifts[i]) / ma35d1_wdt->clk_rate;
[Severity: High]
Does overwriting wdt_dev->timeout with the truncated hardware timeout
break the software heartbeat extension?
If userspace requests a timeout larger than the hardware maximum, the core
delegates to the software ping worker. Modifying wdt_dev->timeout here
causes the core to adopt the small timeout instead.
> + if (wdt_dev->timeout == 0)
> + wdt_dev->timeout = 1;
> +
> + dev_dbg(ma35d1_wdt->dev, "wdt_dev->timeout = %d\n", wdt_dev->timeout);
> +
> + return 0;
> +}
> +
> +static int ma35d1_wdt_start(struct watchdog_device *wdt_dev)
> +{
> + unsigned int val;
> +
> + struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
> + void __iomem *wdt_base = ma35d1_wdt->wdt_base;
> + int ret;
> +
> + ret = ma35d1_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
> + if (ret < 0)
> + return ret;
> +
> + guard(spinlock_irqsave)
> + (&ma35d1_wdt->lock);
> + val = readl_relaxed(wdt_base + REG_WDT_CTL);
> + val |= (WDTEN | RSTEN);
> +
> + writel_relaxed(val, wdt_base + REG_WDT_CTL);
[Severity: High]
Can the back-to-back writes to REG_WDT_CTL drop register updates?
ma35d1_wdt_set_timeout() writes to REG_WDT_CTL but doesn't call
ma35d1_wdt_wait_sync() afterwards. When ma35d1_wdt_start() immediately
writes to REG_WDT_CTL again, it might happen before the synchronization
bit clears.
[ ... ]
> +static int ma35d1_wdt_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct ma35d1_wdt_dev *ma35d1_wdt;
> + unsigned long clk_rate, val;
> + int ret;
> +
> + ma35d1_wdt = devm_kzalloc(dev, sizeof(*ma35d1_wdt), GFP_KERNEL);
> + if (!ma35d1_wdt)
> + return -ENOMEM;
[Severity: High]
It looks like ma35d1_wdt->dev is never assigned to dev during probe.
If a synchronization timeout occurs later in ma35d1_wdt_stop(), will
dev_err(ma35d1_wdt->dev, ...) dereference this uninitialized pointer?
[ ... ]
> + ma35d1_wdt->irq = platform_get_irq(pdev, 0);
> + if (ma35d1_wdt->irq < 0)
> + return dev_err_probe(dev, ma35d1_wdt->irq,
> + "failed to get irq\n");
[Severity: Medium]
Since the irq field in struct ma35d1_wdt_dev is declared as an unsigned u32,
will this < 0 check ever evaluate to true?
If platform_get_irq() returns a negative error code like -ENXIO, the error
handling would be skipped and proceed to devm_request_irq() with a huge
positive IRQ number.
[ ... ]
> + if (device_property_read_bool(dev, "wakeup-source")) {
> + ret = devm_device_init_wakeup(dev);
> + if (ret)
> + return ret;
> +
> + ret = dev_pm_set_wake_irq(dev, ma35d1_wdt->irq);
> + if (ret)
> + return ret;
> + }
[Severity: High]
Because the driver uses dev_pm_set_wake_irq() without a devm_ wrapper and
lacks a remove function, does this leak the PM wake IRQ structure?
If the driver is unbound and rebound via sysfs, the attached wake IRQ is
never cleaned up, which could cause subsequent binds to fail.
[ ... ]
> + val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
> + if (val & RSTF) {
> + ma35d1_wdt->wdt_dev.bootstatus = WDIOF_CARDRESET;
> + writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
> + }
> +
> + watchdog_set_drvdata(&ma35d1_wdt->wdt_dev, ma35d1_wdt);
[Severity: High]
Does this code need to check the WDTEN bit to handle a running watchdog
handed off by the bootloader?
If the hardware is already ticking and WDOG_HW_RUNNING is not set, the
watchdog core won't know to ping the hardware before userspace connects,
which could lead to spurious resets during boot.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260723060755.1596858-1-zychennvt@gmail.com?part=2
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support
2026-07-23 6:20 ` sashiko-bot
@ 2026-07-23 14:07 ` Guenter Roeck
0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2026-07-23 14:07 UTC (permalink / raw)
To: sashiko-reviews, Zi-Yu Chen; +Cc: linux-watchdog, devicetree, conor+dt, robh
On 7/22/26 23:20, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
> - [High] The `ma35d1_wdt->dev` pointer is never initialized, leading to NULL pointer dereferences in logging functions.
> - [High] Read-modify-write operations on REG_WDT_CTL inadvertently clear W1C (write-one-to-clear) status flags.
> - [High] Missing clock domain synchronization (`ma35d1_wdt_wait_sync`) after writes to `REG_WDT_CTL` can lead to dropped register writes.
> - [High] Driver overwrites `wdt_dev->timeout`, breaking the software heartbeat extension.
> - [High] Missing cleanup for wake IRQ allocated by dev_pm_set_wake_irq(), leading to a memory leak and permanent device unbind/bind failures.
> - [High] The driver fails to check if the watchdog was already enabled by the bootloader, causing unexpected system reboots during boot.
> - [Medium] Unsigned integer comparison against zero renders IRQ error handling dead code.
> - [Medium] Integer overflow in `target_ticks` calculation triggers unexpected system resets for large timeouts.
> - [Low] Unnecessary dependency: selects `MFD_SYSCON` without using it.
Please fix or explain why the issues reported by Sashiko are not real.
Thanks,
Guenter
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-23 14:07 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 6:07 [PATCH 0/3] watchdog: ma35d1: Add support for MA35D1 Watchdog Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 1/3] dt-bindings: watchdog: Add MA35D1 Watchdog binding Zi-Yu Chen
2026-07-23 6:07 ` [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support Zi-Yu Chen
2026-07-23 6:20 ` sashiko-bot
2026-07-23 14:07 ` Guenter Roeck
2026-07-23 6:07 ` [PATCH 3/3] arm64: dts: nuvoton: Add WDT node for MA35D1 SoC Zi-Yu Chen
2026-07-23 6:18 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox