[PATCH v8 5/7] watchdog: rzv2h_wdt: Add syscon based WDTDCR access

From: Prabhakar

Date: Thu Oct 01 2026 - 13:00:12 EST


From: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>

On RZ/T2H and RZ/N2H, WDTDCR is located in the SYS block rather than the
WDT register space. Allow the WDT driver to access it through the syscon
regmap exposed by the CPG driver. Retain support for the existing second
WDT register region for compatibility with older device trees.

The syscon regmap uses locks and cannot be used from the watchdog restart
handler, which runs in atomic context. Use the CPG helper for this path so
WDTDCR can be accessed without taking a lock, while continuing to use the
syscon regmap for normal start and stop operations.

Keep the WDTDCR start and stop helpers as void and treat the regmap
operations as best effort. Propagating errors from these MMIO-backed
accesses would complicate the control flow and error handling, while
the restart path in particular should make a best effort to restart
the system.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx>
---
v7->v8:
- No change

v6->v7:
- Made use of rzt2h_cpg_wdtdcr_count_start() in restart handler
- Added if regmap is available, use it for WDTDCR access
- Updated commit message

v5->v6:
- No change

v4->v5:
- Code changes due to regmap_lock introduction.

v3->v4:
- No change

v2->v3:
- Made use of the new "renesas,sysc" phandle-array property to access the
WDTDCR register via the CPG/MSSR syscon node.
- Updated commit message

v1->v2:
- No change.
---
drivers/watchdog/rzv2h_wdt.c | 77 +++++++++++++++++++++++++++++++++---
1 file changed, 71 insertions(+), 6 deletions(-)

diff --git a/drivers/watchdog/rzv2h_wdt.c b/drivers/watchdog/rzv2h_wdt.c
index e8a40482d580..70d2b133c5aa 100644
--- a/drivers/watchdog/rzv2h_wdt.c
+++ b/drivers/watchdog/rzv2h_wdt.c
@@ -5,18 +5,23 @@
* Copyright (C) 2024 Renesas Electronics Corporation.
*/
#include <linux/clk.h>
+#include <linux/clk/renesas.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/stringify.h>
#include <linux/units.h>
#include <linux/watchdog.h>

+MODULE_IMPORT_NS("RZT2H_CPG");
+
#define WDTRR 0x00 /* WDT Refresh Register RW, 8 */
#define WDTCR 0x02 /* WDT Control Register RW, 16 */
#define WDTSR 0x04 /* WDT Status Register RW, 16 */
@@ -44,6 +49,11 @@

#define WDT_DEFAULT_TIMEOUT 60U

+#define RZT2H_WDT_MAX_INSTANCES 6
+
+#define RZT2H_SYS_BLOCK1_BASE (BIT(16) | 0x5100)
+#define RZT2H_WDTDCR_OFFSET(n) (RZT2H_SYS_BLOCK1_BASE + (n) * 4)
+
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
@@ -64,6 +74,12 @@ struct rzv2h_of_data {
bool wdtdcr;
};

+struct rzv2h_sysc_wdtdcr {
+ struct regmap *regmap;
+ unsigned int offset;
+ unsigned int wdt_index;
+};
+
struct rzv2h_wdt_priv {
void __iomem *base;
void __iomem *wdtdcr;
@@ -72,6 +88,7 @@ struct rzv2h_wdt_priv {
struct reset_control *rstc;
struct watchdog_device wdev;
const struct rzv2h_of_data *of_data;
+ struct rzv2h_sysc_wdtdcr sysc;
};

static int rzv2h_wdt_ping(struct watchdog_device *wdev)
@@ -90,18 +107,50 @@ static int rzv2h_wdt_ping(struct watchdog_device *wdev)

static void rzt2h_wdt_wdtdcr_count_stop(struct rzv2h_wdt_priv *priv)
{
- u32 reg = readl(priv->wdtdcr + WDTDCR);
+ struct rzv2h_sysc_wdtdcr *sysc = &priv->sysc;
+ u32 reg;
+
+ if (sysc->regmap) {
+ /* Ignore the theoretical regmap error */
+ regmap_set_bits(sysc->regmap, sysc->offset, WDTDCR_WDTSTOPCTRL);
+ return;
+ }

+ reg = readl(priv->wdtdcr + WDTDCR);
writel(reg | WDTDCR_WDTSTOPCTRL, priv->wdtdcr + WDTDCR);
}

static void rzt2h_wdt_wdtdcr_count_start(struct rzv2h_wdt_priv *priv)
{
- u32 reg = readl(priv->wdtdcr + WDTDCR);
+ struct rzv2h_sysc_wdtdcr *sysc = &priv->sysc;
+ u32 reg;
+
+ if (sysc->regmap) {
+ /* Ignore the theoretical regmap error */
+ regmap_clear_bits(sysc->regmap, sysc->offset, WDTDCR_WDTSTOPCTRL);
+ return;
+ }

+ reg = readl(priv->wdtdcr + WDTDCR);
writel(reg & ~WDTDCR_WDTSTOPCTRL, priv->wdtdcr + WDTDCR);
}

+/*
+ * Restart runs in atomic context, so the syscon regmap must not be used.
+ * Go through the CPG helper, which writes the register directly.
+ */
+static void rzt2h_wdt_wdtdcr_count_start_atomic(struct rzv2h_wdt_priv *priv)
+{
+ struct rzv2h_sysc_wdtdcr *sysc = &priv->sysc;
+
+ if (sysc->regmap) {
+ rzt2h_cpg_wdtdcr_count_start(sysc->wdt_index);
+ return;
+ }
+
+ rzt2h_wdt_wdtdcr_count_start(priv);
+}
+
static void rzv2h_wdt_setup(struct watchdog_device *wdev, u16 wdtcr)
{
struct rzv2h_wdt_priv *priv = watchdog_get_drvdata(wdev);
@@ -234,7 +283,7 @@ static int rzv2h_wdt_restart(struct watchdog_device *wdev,
WDTCR_RPES_75 | WDTCR_TOPS_1024);

if (priv->of_data->wdtdcr)
- rzt2h_wdt_wdtdcr_count_start(priv);
+ rzt2h_wdt_wdtdcr_count_start_atomic(priv);

rzv2h_wdt_ping(wdev);

@@ -255,9 +304,25 @@ static const struct watchdog_ops rzv2h_wdt_ops = {
static int rzt2h_wdt_wdtdcr_init(struct platform_device *pdev,
struct rzv2h_wdt_priv *priv)
{
- priv->wdtdcr = devm_platform_ioremap_resource(pdev, 1);
- if (IS_ERR(priv->wdtdcr))
- return PTR_ERR(priv->wdtdcr);
+ struct device_node *np = dev_of_node(&pdev->dev);
+
+ if (of_property_present(np, "renesas,sysc")) {
+ struct rzv2h_sysc_wdtdcr *sysc = &priv->sysc;
+
+ sysc->regmap = syscon_regmap_lookup_by_phandle_args(np, "renesas,sysc",
+ 1, &sysc->wdt_index);
+ if (IS_ERR(sysc->regmap))
+ return PTR_ERR(sysc->regmap);
+
+ if (sysc->wdt_index >= RZT2H_WDT_MAX_INSTANCES)
+ return -EINVAL;
+
+ sysc->offset = RZT2H_WDTDCR_OFFSET(sysc->wdt_index);
+ } else {
+ priv->wdtdcr = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(priv->wdtdcr))
+ return PTR_ERR(priv->wdtdcr);
+ }

rzt2h_wdt_wdtdcr_count_stop(priv);

--
2.55.0