mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Jiaxing Hu <gahing@gahingwoo.com>
To: ulf.hansson@oss.qualcomm.com, ulfh@kernel.org, heiko@sntech.de
Cc: finley.xiao@rock-chips.com, shawn.lin@rock-chips.com,
	sebastian.reichel@collabora.com, vlwwwwww@gmail.com,
	leonard@lausen.nl, abel.vesa@oss.qualcomm.com,
	tomeu@tomeuvizoso.net, linux-pm@vger.kernel.org,
	linux-rockchip@lists.infradead.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v17 1/3] pmdomain: rockchip: switch off the subdomains of a regulator domain at probe
Date: Sat, 14 Nov 2026 16:04:59 +1300	[thread overview]
Message-ID: <733b921576828ca70a3f5048691400130f8a5dbc.1794615323.git.gahing@gahingwoo.com> (raw)
In-Reply-To: <cover.1794615323.git.gahing@gahingwoo.com>

A power domain that needs a regulator is switched off at probe, because
the state of its regulator is unknown at that point. Its subdomains are
added only after that, so a subdomain the bootloader left on stays
powered and not idled behind a parent that is off. genpd reads it as off
and never touches it again, and when the regulator core later disables
the regulator as unused, the bus hangs on the NIUs of those subdomains.

On the RK3576 this hangs boards that do not enable the NPU once PD_NPU
manages its supply (next patch): U-Boot leaves NPUTOP, NPU0 and NPU1 on,
and the SoC locks up when regulator_late_cleanup() disables vdd_npu_s0.

Mark a regulator domain and every domain below it as off when it is
added, and once the subtree is complete, switch it off from the leaves
up. If a subdomain fails to switch off, leave its parents on and warn.

Tested on a Radxa ROCK 4D with the NPU disabled in the DT. Without this
patch, all 14 boots left NPUTOP, NPU0 and NPU1 on, and one of them hung.
With it, all 10 boots switched them off, with no hang.

Reported-by: Vladimir Bely <vlwwwwww@gmail.com>
Closes: https://lore.kernel.org/all/20261113132323.159015-1-vlwwwwww@gmail.com/
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
 drivers/pmdomain/rockchip/pm-domains.c | 60 +++++++++++++++++++++++---
 1 file changed, 54 insertions(+), 6 deletions(-)

diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index 3699918d17d0..5cc2094e87cc 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -101,6 +101,7 @@ struct rockchip_pm_domain {
 	struct clk_bulk_data *clks;
 	struct device_node *node;
 	struct regulator *supply;
+	bool off_at_probe;
 };
 
 struct rockchip_pmu {
@@ -798,7 +799,8 @@ static void rockchip_pd_detach_dev(struct generic_pm_domain *genpd,
 }
 
 static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
-				      struct device_node *node)
+				      struct device_node *node,
+				      struct rockchip_pm_domain *parent)
 {
 	const struct rockchip_domain_info *pd_info;
 	struct rockchip_pm_domain *pd;
@@ -910,9 +912,16 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
 	 * the regulator state is unknown at probe time. Also the regulator
 	 * state cannot be checked, since that usually requires IP needing
 	 * (a different) power domain.
+	 *
+	 * The domains below such a domain have to start off too, or genpd
+	 * would see them off behind a parent that is off while the
+	 * bootloader left them on, and never idle them before the regulator
+	 * is cut. Only mark them here: the subdomains are not added yet, so
+	 * rockchip_pm_power_off_subtree() switches the whole subtree off,
+	 * children first, once it is complete.
 	 */
-	if (pd->info->need_regulator)
-		rockchip_pd_power(pd, false);
+	pd->off_at_probe = pd->info->need_regulator ||
+			   (parent && parent->off_at_probe);
 
 	pd->genpd.power_off = rockchip_pd_power_off;
 	pd->genpd.power_on = rockchip_pd_power_on;
@@ -922,7 +931,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
 	if (pd_info->active_wakeup)
 		pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
 	pm_genpd_init(&pd->genpd, NULL,
-		      !rockchip_pmu_domain_is_on(pd) ||
+		      pd->off_at_probe || !rockchip_pmu_domain_is_on(pd) ||
 		      (pd->info->mem_status_mask && !rockchip_pmu_domain_is_mem_on(pd)));
 
 	pmu->genpd_data.domains[id] = &pd->genpd;
@@ -986,6 +995,41 @@ static void rockchip_configure_pd_cnt(struct rockchip_pmu *pmu,
 	regmap_write(pmu->regmap, domain_reg_offset + 4, count);
 }
 
+/*
+ * Switch off the domains marked off_at_probe below and including @np,
+ * children before their parent. If a child fails to switch off, leave its
+ * ancestors on.
+ */
+static int rockchip_pm_power_off_subtree(struct rockchip_pmu *pmu,
+					 struct device_node *np)
+{
+	struct rockchip_pm_domain *pd;
+	u32 idx;
+	int ret;
+
+	for_each_child_of_node_scoped(np, child) {
+		ret = rockchip_pm_power_off_subtree(pmu, child);
+		if (ret)
+			return ret;
+	}
+
+	if (of_property_read_u32(np, "reg", &idx) ||
+	    idx >= pmu->genpd_data.num_domains ||
+	    !pmu->genpd_data.domains[idx])
+		return 0;
+
+	pd = to_rockchip_pd(pmu->genpd_data.domains[idx]);
+	if (!pd->off_at_probe)
+		return 0;
+
+	ret = rockchip_pd_power(pd, false);
+	if (ret)
+		dev_warn(pmu->dev, "failed to switch off %s at probe: %d\n",
+			 pd->genpd.name, ret);
+
+	return ret;
+}
+
 static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu,
 				     struct device_node *parent)
 {
@@ -1004,7 +1048,8 @@ static int rockchip_pm_add_subdomain(struct rockchip_pmu *pmu,
 		}
 		parent_domain = pmu->genpd_data.domains[idx];
 
-		error = rockchip_pm_add_one_domain(pmu, np);
+		error = rockchip_pm_add_one_domain(pmu, np,
+						   to_rockchip_pd(parent_domain));
 		if (error) {
 			dev_err(pmu->dev, "failed to handle node %pOFn: %d\n",
 				np, error);
@@ -1098,7 +1143,7 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev)
 	guard(mutex)(&dmc_pmu_mutex);
 
 	for_each_available_child_of_node_scoped(np, node) {
-		error = rockchip_pm_add_one_domain(pmu, node);
+		error = rockchip_pm_add_one_domain(pmu, node, NULL);
 		if (error) {
 			dev_err(dev, "failed to handle node %pOFn: %d\n",
 				node, error);
@@ -1111,6 +1156,9 @@ static int rockchip_pm_domain_probe(struct platform_device *pdev)
 				node, error);
 			goto err_out;
 		}
+
+		/* A domain left on is not fatal; carry on with the others. */
+		rockchip_pm_power_off_subtree(pmu, node);
 	}
 
 	if (error) {
-- 
2.43.0


  reply	other threads:[~2026-11-14  3:05 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14  3:04 [PATCH v17 0/3] pmdomain: rockchip: RK3576 NPU domain supply and settle delay Jiaxing Hu
2026-11-14  3:04 ` Jiaxing Hu [this message]
2026-11-14  3:05 ` [PATCH v17 2/3] pmdomain: rockchip: manage the RK3576 NPU domain supply Jiaxing Hu
2026-11-14  3:05 ` [PATCH v17 3/3] pmdomain: rockchip: settle RK3576 NPUTOP and VOP after power-on Jiaxing Hu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=733b921576828ca70a3f5048691400130f8a5dbc.1794615323.git.gahing@gahingwoo.com \
    --to=gahing@gahingwoo.com \
    --cc=abel.vesa@oss.qualcomm.com \
    --cc=finley.xiao@rock-chips.com \
    --cc=heiko@sntech.de \
    --cc=leonard@lausen.nl \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-rockchip@lists.infradead.org \
    --cc=sebastian.reichel@collabora.com \
    --cc=shawn.lin@rock-chips.com \
    --cc=tomeu@tomeuvizoso.net \
    --cc=ulf.hansson@oss.qualcomm.com \
    --cc=ulfh@kernel.org \
    --cc=vlwwwwww@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®