From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id CEBA9C531C7 for ; Thu, 23 Jul 2026 06:08:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Reply-To:Content-Type:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=6x2aOBje5frBTxDxT/OoHyn8yJc/gXKzhFg2I8WT5vE=; b=UsFjs3x8ND4gmgPL0+R3dlnPwt Zk1wYEYB/YGbImRSDUxd3k293J5QPmysRslIvQ9tYUx53VA2QWNDZQvWAq77k7TJRTRTkSwkx7oVR MoG0AoitMy9ENEwln8raGH+Kpl+kEGneTk4Ju/qZEF5GVccci4gBczVDJz2nBlIPx9TPm8rOzD18t Si1eNHZQqoG7N+QoWzWtt20tTxUSqhWYsQ6G7EIIeJB1T64jHyi82z1wScojryXgPHRDvk5iJUPD1 iTMhmPEL0eZQSh+3GmSFwMYLvB32qeEUV7cYGR6W5YJsyBz7RBUGULxx8bXj+gmG47cHfdoyWyNET d2uBs8+A==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1wmmbg-0000000DWO0-2nWb; Thu, 23 Jul 2026 06:08:32 +0000 Received: from mail-pl1-x629.google.com ([2607:f8b0:4864:20::629]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1wmmbb-0000000DWMV-43jA for linux-arm-kernel@lists.infradead.org; Thu, 23 Jul 2026 06:08:29 +0000 Received: by mail-pl1-x629.google.com with SMTP id d9443c01a7336-2caea3f742bso3568175ad.0 for ; Wed, 22 Jul 2026 23:08:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784786907; x=1785391707; darn=lists.infradead.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6x2aOBje5frBTxDxT/OoHyn8yJc/gXKzhFg2I8WT5vE=; b=aABT0Z1PKxH7+zuO9JpGtiCpyopaQLSGxu304de1u268w21ljtZ4LO7Till7xnIJqI arTJNtUXbRbtwBZVLbXePG+ecFwfSvw1FF5J8WKFQDaQ/gzzqb56bNSJL2XhfxUMXBFJ zNBE5pkPvStwD23HCYRRhJ0MXS+cpFG9sTalEeAorSLBzKwlHQNGLtmLCaCo8tY4V0Yf j3NU2gbaynra1G9NO80XPqwZeoIql9w32c7kMtmQN8NKjDNtwHs39j3BfilMZ8sISjis cKuQr2d4GLUJQiMCXDOM94NhTg4w16fQdm2aTIspnapP8EGLhZX6BjwUungJ2my+U1fv 8qzg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784786907; x=1785391707; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6x2aOBje5frBTxDxT/OoHyn8yJc/gXKzhFg2I8WT5vE=; b=cPavzSUR79+8qxPQ+Jk1rRVWfe10W7aGnpe4kSGsxDbQvyz1DRX09DVNoX7sLgr9/x 6R1947+1MrKsBFYxl+TcCzCvzSarBif9Z4X2to7JCLNoxJlw40cT+6Yz2t8o6j/uz5GA glEdIFNP/i3OWWg53g5a2XY+sa/ZJpo4HS0potMrfEwi+QrfyW5BWE8hcMow2fvHXnrl z4ztukV+YK9YRbRxOceejp8XEQdMcWqqD8n7gAZGBJdmdsV06IR5k9rGpltKWn3Q8JiG WpNL4YGWKHxCFotF8RAPiGQqzjl5TTchpngUd7VpB4E8ceDbcf7H48dmvOX9oOszLooc ISzg== X-Forwarded-Encrypted: i=1; AHgh+RpRe01FsXhHfeCjE2y25E4BBq1toaBvUYHOXBoLk22VGzr8X0vD7H+SnEQI4MOaA6ft8f4LFmsQOQdCAEhBlahI@lists.infradead.org X-Gm-Message-State: AOJu0YwZ4BrkGXtQ5y96Fb06BjzyRTTQKkAhTExKip4PE67541x4CEok jaVAnoVhioQiMueqsXDotIULEDAJIqwclgAY5TtHHlWoSFU6HRVW6lHb X-Gm-Gg: AR+sD13SaP4MIudcr/7xoNMdS3ju6L1EWgyj2ZVnku21WFb1eNrDsg52rZ7VVueNXZT w8cT1/CkY/jHGj3aHm3/S4u0eKauq/bBQMUIRvnWIp39xvVRgZFToJnH96s5vfCjpxpKEqhCHF0 6uxHXfu6KqdIRO3pmZklUgrnMbSM9D+3Sw12ynYr4bO/UfRXrHdbiJWncSGTwRbHoVgdHfi/J87 BWudDoFFl/O/YIIDbuNJJfAkTNA+bTZc9+QK1xiMXznKne3fz7Aue8Vzn3Ph3ooYQMM1JUEJRaL SMogqq6YrGTXpvDRrTsqa58RFAMTRArS+UXBwTqaZOgfOkmBKDGF1HyezkfDTdubyh9eQJoJNjY WRAaj0BmkC+bXvbeX/ESCcivJ2Iz0woW6jjV/5VtqYBD1IvpBaPdWE8vo0hlbCZ6XHqVSTjSxM/ /IF82xqnVPl5aOOiJfZWJQLCBt7J52yFCXryx/gSqAR81UZm6XeRwoD0cAzqld X-Received: by 2002:a17:902:cf42:b0:2c9:d298:6c06 with SMTP id d9443c01a7336-2cfa7091618mr23349475ad.25.1784786907130; Wed, 22 Jul 2026 23:08:27 -0700 (PDT) Received: from localhost.localdomain (60-250-196-139.hinet-ip.hinet.net. [60.250.196.139]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cf8f390dd2sm26071285ad.79.2026.07.22.23.08.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Jul 2026 23:08:26 -0700 (PDT) From: Zi-Yu Chen To: Wim Van Sebroeck , Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Jacky Huang , Shan-Chun Hung , linux-watchdog@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Zi-Yu Chen Subject: [PATCH 2/3] watchdog: Add Nuvoton MA35D1 watchdog driver support Date: Thu, 23 Jul 2026 14:07:53 +0800 Message-Id: <20260723060755.1596858-3-zychennvt@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260723060755.1596858-1-zychennvt@gmail.com> References: <20260723060755.1596858-1-zychennvt@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.9.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260722_230828_034188_62DCBB4A X-CRM114-Status: GOOD ( 31.08 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Nuvoton MA35D1 Watchdog Timer Driver"); +MODULE_LICENSE("GPL"); -- 2.34.1