watchdog: rzv2h_wdt: Access WDTDCR through CPG/MSSR syscon
Subsystems (5)
v9 cover watchdog: rzv2h_wdt: Access WDTDCR through CPG/MSSR syscon
From: Lad Prabhakar <[email protected]>
Hi all,
On RZ/T2H and RZ/N2H, the watchdog debug control registers (WDTDCR) are located in the CPG/MSSR SYS block rather than the watchdog register space. This series exposes those registers through the CPG/MSSR syscon and updates the watchdog driver and device trees to use that interface.
Normal start and stop operations use the syscon regmap. Since the watchdog restart handler runs in atomic context, the series also adds a lockless CPG helper for restarting the counter. Existing device trees using the separate WDTDCR register region remain supported.
The device trees for R9A09G077 and R9A09G087 are updated to identify each watchdog's WDTDCR through a renesas,sysc phandle.
v8->v9:
- Rebased on -next due to devm_kzalloc_obj change
- Added RB tag from Guenter for patch #2
- Dropped WDT driver patches as they are now queued in WDT branch
v7->v8:
- Dropped patch "watchdog: rzv2h: Drop enabling clocks in the restart handler" addressing Sashiko's comments
v6->v7:
- Added a new patch to introduce rzt2h_cpg_wdtdcr_count_start() function
- Added new patch to drop pm_runtime_resume_and_get() & pm_runtime_put_sync() calls while accessing the WDTDCR register.
- Dropped propagating WDTDCR access errors.
- Dropped converting WDTDCR handling to regmap as this would require resource address comparison to get WDT index to call rzt2h_cpg_wdtdcr_count_start().
- 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:
- Used pm_runtime_put_sync() in rzv2h_wdt_stop() function.
- rebased on top of next-20260922
v4->v5:
- patch #1 is new addressing Sashiko's comments to avoid potential deadlock
- patch #4 is new addressing Sashiko's comments
- Dropped checking rzt2h_wdt_wdtdcr_count_start() return value in restart path.
- Handled the regmap locking in the driver and introduced nolock version to be used in the restart path.
- Added review-by tag.
[0] https://lore.kernel.org/all/[email protected]/
[1] https://lore.kernel.org/all/[email protected]/
Cheers,
Prabhakar
Lad Prabhakar (4):
clk: renesas: r9a09g077: Add helper to start WDTDCR counter
watchdog: rzv2h_wdt: Add syscon based WDTDCR access
arm64: dts: renesas: r9a09g077: Use CPG/MSSR syscon for WDTDCR access
arm64: dts: renesas: r9a09g087: Use CPG/MSSR syscon for WDTDCR access
arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 24 +++---- arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 24 +++---- drivers/clk/renesas/r9a09g077-cpg.c | 35 ++++++++-- drivers/watchdog/rzv2h_wdt.c | 77 ++++++++++++++++++++-- include/linux/clk/renesas.h | 6 ++ 5 files changed, 130 insertions(+), 36 deletions(-)
Signature · 1 line
2.56.1
PATCH v9 1/4 clk: renesas: r9a09g077: Add helper to start WDTDCR counter
From: Lad Prabhakar <[email protected]>
The WDTDCRm registers are not suitable for access through the SYSC regmap from atomic or panic context, as regmap locking can deadlock when another CPU holding the lock has already stopped.
Add a lockless helper to clear WDTDCRm.WDTSTOPCTRL and start the watchdog counter using the existing SYSC register accessors.
Export the helper in the RZT2H_CPG namespace for use by the watchdog driver.
---v8->v9:
- Rebased on -next due to devm_kzalloc_obj change
v7->v8:
- No change
v6->v7:
- New patch
--- drivers/clk/renesas/r9a09g077-cpg.c | 35 ++++++++++++++++++++++++----- include/linux/clk/renesas.h | 6 +++++ 2 files changed, 35 insertions(+), 6 deletions(-)
drivers/clk/renesas/r9a09g077-cpg.c +29 -6
@@ -12,6 +12,7 @@#include <linux/clk-provider.h> #include <linux/clk/renesas.h> #include <linux/device.h> +#include <linux/export.h> #include <linux/init.h> #include <linux/io.h> #include <linux/iopoll.h>
@@ -103,10 +104,17 @@ MODULE_IMPORT_NS("RZV2H_CPG");#define RZT2H_SYSC_BLOCK (x) FIELD_GET(RZT2H_SYSC_BLOCK_MASK, x)#define RZT2H_SYSC_REG_OFFSET (x) FIELD_GET(RZT2H_SYSC_OFFSET_MASK, x) +/* WDT Debug Control Registers (WDTDCRm), in the block 1 SYSC region */ +#define RZT2H_WDTDCR (n) (RZT2H_SYSC_BLOCK_MASK | (0x5100 + (n) * 4)) +#define RZT2H_WDTDCR_WDTSTOPCTRL BIT(0) +#define RZT2H_WDT_MAX_INSTANCES 6 +struct r9a09g077_sysc_reg {void __iomem *base[2 ]; }; +static struct r9a09g077_sysc_reg *r9a09g077_sysc_regs; +enum rzt2h_clk_types {CLK_TYPE_RZT2H_DIV =CLK_TYPE_CUSTOM ,/* Clock with divider */ CLK_TYPE_RZT2H_MUX ,/* Clock with clock source selector */
@@ -963,10 +971,25 @@ static bool r9a09g077_writeable_readable_sysc(struct device *dev, unsigned int r } } +void rzt2h_cpg_wdtdcr_count_start (unsigned int wdt_index) +{ +struct r9a09g077_sysc_reg *sysc = r9a09g077_sysc_regs; +unsigned int reg, val; + +/* Sanity check: ensure sysc is initialized and wdt_index is valid */ +if (!sysc || wdt_index >=RZT2H_WDT_MAX_INSTANCES ) +return ; + + reg =RZT2H_WDTDCR (wdt_index); +r9a09g077_regmap_read (sysc, reg, &val); + val &= ~RZT2H_WDTDCR_WDTSTOPCTRL ; +r9a09g077_regmap_write (sysc, reg, val); +} +EXPORT_SYMBOL_NS_GPL (rzt2h_cpg_wdtdcr_count_start,"RZT2H_CPG" ); +static int r9a09g077_post_init (struct device *dev,struct cpg_mssr_pub *pub) { struct regmap_config *regmap_cfg __free(kfree) = kzalloc_obj(*regmap_cfg); -struct r9a09g077_sysc_reg *sysc_reg;struct regmap *regmap;struct resource res;int ret;
@@ -991,13 +1014,13 @@ static int r9a09g077_post_init(struct device *dev, struct cpg_mssr_pub *pub)if (!regmap_cfg)return -ENOMEM ; - sysc_reg =devm_kzalloc_obj (dev, *sysc_reg); -if (!sysc_reg) + r9a09g077_sysc_regs =devm_kzalloc_obj (dev, *r9a09g077_sysc_regs); +if (!r9a09g077_sysc_regs)return -ENOMEM ;/* Only allow access in the SYSC regions */ - sysc_reg->base[0 ] = pub->base0 +RZT2H_SYSC_OFFSET ; - sysc_reg->base[1 ] = pub->base1 +RZT2H_SYSC_OFFSET ; + r9a09g077_sysc_regs->base[0 ] = pub->base0 +RZT2H_SYSC_OFFSET ; + r9a09g077_sysc_regs->base[1 ] = pub->base1 +RZT2H_SYSC_OFFSET ; regmap_cfg->name ="rzt2h_sysc" ; regmap_cfg->reg_bits =32 ;
@@ -1008,7 +1031,7 @@ static int r9a09g077_post_init(struct device *dev, struct cpg_mssr_pub *pub) regmap_cfg->readable_reg = r9a09g077_writeable_readable_sysc; regmap_cfg->writeable_reg = r9a09g077_writeable_readable_sysc; - regmap =devm_regmap_init (dev, &r9a09g077_sys_regmap_bus, sysc_reg, regmap_cfg); + regmap =devm_regmap_init (dev, &r9a09g077_sys_regmap_bus, r9a09g077_sysc_regs, regmap_cfg);if (IS_ERR (regmap))return PTR_ERR (regmap);
include/linux/clk/renesas.h +6 -0
@@ -216,4 +216,10 @@ static inline bool rzv2h_cpg_get_pll_divs_pars(const struct rzv2h_pll_limits *li#define rzv2h_get_pll_pars rzv2h_cpg_get_pll_pars#define rzv2h_get_pll_divs_pars rzv2h_cpg_get_pll_divs_pars +#if defined(CONFIG_CLK_R9A09G077 ) || defined(CONFIG_CLK_R9A09G087 ) +void rzt2h_cpg_wdtdcr_count_start (unsigned int wdt_index); +#else +static inline void rzt2h_cpg_wdtdcr_count_start (unsigned int wdt_index) { } +#endif +#endif
Signature · 1 line
2.56.1
PATCH v9 2/4 watchdog: rzv2h_wdt: Add syscon based WDTDCR access
In reply to PrabhakarFrom: Lad Prabhakar <[email protected]>
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.
---v8->v9
- Added Reviewed-by tag from Guenter
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(-)
drivers/watchdog/rzv2h_wdt.c +71 -6
@@ -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);
Signature · 1 line
2.56.1
PATCH v9 3/4 arm64: dts: renesas: r9a09g077: Use CPG/MSSR syscon for WDTDCR access
In reply to PrabhakarFrom: Lad Prabhakar <[email protected]>
The WDTDCR registers for wdt0-wdt5 reside in the second register region of the CPG/MSSR block. This multi-function block is now exposed via a unified syscon regmap interface.
Replace the direct mapping of the individual WDTDCR registers with the new "renesas,sysc" phandle property pointing to the CPG/MSSR block syscon node.
---v8->v9
- No change
v7->v8:
- No change
v6->v7:
- No change
v5->v6:
- No change
v4->v5:
- Added review-by tag.
v3->v4:
- No change
v2->v3:
- Renamed the "renesas,sys" property to "renesas,sysc"
- Updated commit message
v1->v2:
- No change.
--- arch/arm64/boot/dts/renesas/r9a09g077.dtsi | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-)
arch/arm64/boot/dts/renesas/r9a09g077.dtsi +12 -12
@@ -327,61 +327,61 @@ channel1 { wdt0: watchdog@80082000 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80082000 0 0x400>, - <0 0x81295100 0 0x04>; + reg = <0 0x80082000 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 0>; status = "disabled"; }; wdt1: watchdog@80082400 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80082400 0 0x400>, - <0 0x81295104 0 0x04>; + reg = <0 0x80082400 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 1>; status = "disabled"; }; wdt2: watchdog@80082800 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80082800 0 0x400>, - <0 0x81295108 0 0x04>; + reg = <0 0x80082800 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 2>; status = "disabled"; }; wdt3: watchdog@80082c00 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80082c00 0 0x400>, - <0 0x8129510c 0 0x04>; + reg = <0 0x80082c00 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 3>; status = "disabled"; }; wdt4: watchdog@80083000 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80083000 0 0x400>, - <0 0x81295110 0 0x04>; + reg = <0 0x80083000 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 4>; status = "disabled"; }; wdt5: watchdog@80083400 { compatible = "renesas,r9a09g077-wdt"; - reg = <0 0x80083400 0 0x400>, - <0 0x81295114 0 0x04>; + reg = <0 0x80083400 0 0x400>; clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 5>; status = "disabled"; };
Signature · 1 line
2.56.1
PATCH v9 4/4 arm64: dts: renesas: r9a09g087: Use CPG/MSSR syscon for WDTDCR access
In reply to PrabhakarFrom: Lad Prabhakar <[email protected]>
The WDTDCR registers for wdt0-wdt5 reside in the second register region of the CPG/MSSR block. This multi-function block is now exposed via a unified syscon regmap interface.
Replace the direct mapping of the individual WDTDCR registers with the new "renesas,sysc" phandle property pointing to the CPG/MSSR block syscon node.
---v8->v9:
- No change
v7->v8:
- No change
v6->v7:
- No change
v5->v6:
- No change
v4->v5:
- Added review-by tag.
v3->v4:
- No change
v2->v3:
- Renamed the "renesas,sys" property to "renesas,sysc"
- Updated commit message
v1->v2:
- No change.
--- arch/arm64/boot/dts/renesas/r9a09g087.dtsi | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-)
arch/arm64/boot/dts/renesas/r9a09g087.dtsi +12 -12
@@ -327,61 +327,61 @@ channel1 { wdt0: watchdog@80082000 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80082000 0 0x400>, - <0 0x81295100 0 0x04>; + reg = <0 0x80082000 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 0>; status = "disabled"; }; wdt1: watchdog@80082400 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80082400 0 0x400>, - <0 0x81295104 0 0x04>; + reg = <0 0x80082400 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 1>; status = "disabled"; }; wdt2: watchdog@80082800 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80082800 0 0x400>, - <0 0x81295108 0 0x04>; + reg = <0 0x80082800 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 2>; status = "disabled"; }; wdt3: watchdog@80082c00 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80082c00 0 0x400>, - <0 0x8129510c 0 0x04>; + reg = <0 0x80082c00 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 3>; status = "disabled"; }; wdt4: watchdog@80083000 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80083000 0 0x400>, - <0 0x81295110 0 0x04>; + reg = <0 0x80083000 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 4>; status = "disabled"; }; wdt5: watchdog@80083400 { compatible = "renesas,r9a09g087-wdt", "renesas,r9a09g077-wdt"; - reg = <0 0x80083400 0 0x400>, - <0 0x81295114 0 0x04>; + reg = <0 0x80083400 0 0x400>; clocks = <&cpg CPG_CORE R9A09G087_CLK_PCLKL>; clock-names = "pclk"; power-domains = <&cpg>; + renesas,sysc = <&cpg 5>; status = "disabled"; };
Signature · 1 line
2.56.1