[PATCH 10/12] power: domain: mediatek: add support for the power-controller model

Julien Stephan jstephan at baylibre.com
Wed Jul 8 15:07:16 CEST 2026


The current driver handles a flat scpsys node with a single, fixed set
of power status registers and one infracfg bus-protection mask. Newer
MediaTek SoCs describe their domains as child nodes of a power
controller and need a richer model:

  - per-domain power status register offsets (pwr_sta_offs /
    pwr_sta2nd_offs), falling back to the legacy SPM_PWR_STATUS
    registers when not set;
  - an ordered list of bus-protection steps (scpsys_bus_prot_data /
    BUS_PROT_WR), each with its own set/clear/status register, applied
    on power-off and released in reverse on power-on;
  - per-domain clocks and an optional parent power domain.

Add mtk_power_controller_probe(), which walks the controller's subnodes,
resolves each domain's id, clocks and infracfg, and builds the domain
tree. The existing flat probe (mtk_scpsys_probe) is left unchanged.

No functional change for the existing MT7623/MT7629 drivers.

Signed-off-by: Julien Stephan <jstephan at baylibre.com>
---
 drivers/power/domain/mtk-power-domain.c | 333 ++++++++++++++++++++++++++++++--
 drivers/power/domain/mtk-power-domain.h |  38 ++++
 2 files changed, 357 insertions(+), 14 deletions(-)

diff --git a/drivers/power/domain/mtk-power-domain.c b/drivers/power/domain/mtk-power-domain.c
index 3b0a90d3f94..9eef58d2d57 100644
--- a/drivers/power/domain/mtk-power-domain.c
+++ b/drivers/power/domain/mtk-power-domain.c
@@ -11,6 +11,8 @@
 #include <asm/io.h>
 #include <linux/err.h>
 #include <linux/iopoll.h>
+#include <dm/device_compat.h>
+#include <dm/devres.h>
 
 #include "mtk-power-domain.h"
 
@@ -41,14 +43,79 @@ static int mtk_infracfg_clear_bus_protection(void __iomem *infracfg,
 				  !(val & mask), 100);
 }
 
+static int _scpsys_bus_protect_enable(const struct mtk_scpsys_bus_prot_data *bpd,
+				      void __iomem *reg)
+{
+	u32 val = 0, mask = bpd->bus_prot_mask;
+
+	if (!mask)
+		return 0;
+
+	if (bpd->bus_prot_reg_update)
+		clrsetbits_le32(reg + bpd->bus_prot_set, mask, mask);
+	else
+		writel(mask, reg + bpd->bus_prot_set);
+
+	return readl_poll_timeout(reg + bpd->bus_prot_sta, val, (val & mask) == mask, 1000);
+}
+
+static int _scpsys_bus_protect_disable(const struct mtk_scpsys_bus_prot_data *bpd,
+				       void __iomem *reg)
+{
+	u32 val = 0, mask = bpd->bus_prot_mask;
+
+	if (!mask)
+		return 0;
+
+	if (bpd->bus_prot_reg_update)
+		clrbits_le32(reg + bpd->bus_prot_clr, mask);
+	else
+		writel(mask, reg + bpd->bus_prot_clr);
+
+	if (bpd->ignore_clr_ack)
+		return 0;
+
+	return readl_poll_timeout(reg + bpd->bus_prot_sta, val, !(val & mask), 1000);
+}
+
+static int scpsys_bus_protect_enable(const struct mtk_scpsys_bus_prot_data *bpd,
+				     int bpd_size, void __iomem *reg)
+{
+	int ret, i;
+
+	for (i = 0; i < bpd_size; i++) {
+		ret = _scpsys_bus_protect_enable(&bpd[i], reg);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scpsys_bus_protect_disable(const struct mtk_scpsys_bus_prot_data *bpd,
+				      int bpd_size, void __iomem *reg)
+{
+	int i, ret;
+
+	for (i = bpd_size - 1; i >= 0; i--) {
+		ret = _scpsys_bus_protect_disable(&bpd[i], reg);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static int mtk_scpsys_domain_is_on(struct power_domain *power_domain)
 {
 	struct mtk_scpsys *scpsys = dev_get_priv(power_domain->dev);
-	const struct mtk_scp_domain_data *data = &scpsys->soc_data->data[power_domain->id];
-	u32 sta = readl(scpsys->base + SPM_PWR_STATUS) &
-		data->sta_mask;
-	u32 sta2 = readl(scpsys->base + SPM_PWR_STATUS_2ND) &
-		data->sta_mask;
+	const struct mtk_scp_domain_data *data = scpsys->domains[power_domain->id].data;
+	u32 spm_pwr_status = data->pwr_sta_offs ?: SPM_PWR_STATUS;
+	u32 spm_pwr_status_2nd = data->pwr_sta2nd_offs ?: SPM_PWR_STATUS_2ND;
+	u32 sta, sta2;
+
+	sta = readl(scpsys->base + spm_pwr_status) & data->sta_mask;
+	sta2 = readl(scpsys->base + spm_pwr_status_2nd) & data->sta_mask;
 
 	/*
 	 * A domain is on when both status bits are set. If only one is set
@@ -65,12 +132,36 @@ static int mtk_scpsys_domain_is_on(struct power_domain *power_domain)
 static int mtk_scpsys_power_on(struct power_domain *power_domain)
 {
 	struct mtk_scpsys *scpsys = dev_get_priv(power_domain->dev);
-	const struct mtk_scp_domain_data *data = &scpsys->soc_data->data[power_domain->id];
-	void __iomem *ctl_addr = scpsys->base + data->ctl_offs;
-	u32 pdn_ack = data->sram_pdn_ack_bits;
+	struct mtk_scp_domain *domain;
+	const struct mtk_scp_domain_data *data;
+	void __iomem *ctl_addr;
+	void __iomem *infracfg;
+	u32 pdn_ack;
 	u32 val;
 	int ret, tmp;
 
+	if (power_domain->id >= scpsys->soc_data->num_domains)
+		return -EINVAL;
+
+	domain = &scpsys->domains[power_domain->id];
+	data = domain->data;
+	if (!data)
+		return -EINVAL;
+
+	ctl_addr = scpsys->base + data->ctl_offs;
+	infracfg = domain->infracfg ? domain->infracfg : scpsys->infracfg;
+	pdn_ack = data->sram_pdn_ack_bits;
+
+	if (domain->has_pd) {
+		ret = power_domain_on(&domain->parent_pd);
+		if (ret)
+			return ret;
+	}
+
+	ret = clk_enable_bulk(&domain->clks);
+	if (ret)
+		return ret;
+
 	writel(SPM_EN, scpsys->base);
 
 	val = readl(ctl_addr);
@@ -94,6 +185,10 @@ static int mtk_scpsys_power_on(struct power_domain *power_domain)
 	val |= PWR_RST_B_BIT;
 	writel(val, ctl_addr);
 
+	ret = clk_enable_bulk(&domain->subsys_clks);
+	if (ret)
+		return ret;
+
 	val &= ~data->sram_pdn_bits;
 	writel(val, ctl_addr);
 
@@ -102,11 +197,14 @@ static int mtk_scpsys_power_on(struct power_domain *power_domain)
 		return ret;
 
 	if (data->bus_prot_mask) {
-		ret = mtk_infracfg_clear_bus_protection(scpsys->infracfg,
+		ret = mtk_infracfg_clear_bus_protection(infracfg,
 							data->bus_prot_mask);
 		if (ret)
 			return ret;
 	}
+	ret = scpsys_bus_protect_disable(data->bp_infracfg, SPM_MAX_BUS_PROT_DATA, infracfg);
+	if (ret < 0)
+		return ret;
 
 	return 0;
 }
@@ -114,19 +212,37 @@ static int mtk_scpsys_power_on(struct power_domain *power_domain)
 static int mtk_scpsys_power_off(struct power_domain *power_domain)
 {
 	struct mtk_scpsys *scpsys = dev_get_priv(power_domain->dev);
-	const struct mtk_scp_domain_data *data = &scpsys->soc_data->data[power_domain->id];
-	void __iomem *ctl_addr = scpsys->base + data->ctl_offs;
-	u32 pdn_ack = data->sram_pdn_ack_bits;
+	struct mtk_scp_domain *domain;
+	const struct mtk_scp_domain_data *data;
+	void __iomem *ctl_addr;
+	void __iomem *infracfg;
+	u32 pdn_ack;
 	u32 val;
 	int ret, tmp;
 
+	if (power_domain->id >= scpsys->soc_data->num_domains)
+		return -EINVAL;
+
+	domain = &scpsys->domains[power_domain->id];
+	data = domain->data;
+	if (!data)
+		return -EINVAL;
+
+	ctl_addr = scpsys->base + data->ctl_offs;
+	infracfg = domain->infracfg ?: scpsys->infracfg;
+	pdn_ack = data->sram_pdn_ack_bits;
+
 	if (data->bus_prot_mask) {
-		ret = mtk_infracfg_set_bus_protection(scpsys->infracfg,
+		ret = mtk_infracfg_set_bus_protection(infracfg,
 						      data->bus_prot_mask);
 		if (ret)
 			return ret;
 	}
 
+	ret = scpsys_bus_protect_enable(data->bp_infracfg, SPM_MAX_BUS_PROT_DATA, infracfg);
+	if (ret < 0)
+		return ret;
+
 	val = readl(ctl_addr);
 	val |= data->sram_pdn_bits;
 	writel(val, ctl_addr);
@@ -136,6 +252,10 @@ static int mtk_scpsys_power_off(struct power_domain *power_domain)
 	if (ret < 0)
 		return ret;
 
+	ret = clk_disable_bulk(&domain->subsys_clks);
+	if (ret)
+		return ret;
+
 	val |= PWR_ISO_BIT;
 	writel(val, ctl_addr);
 
@@ -155,6 +275,10 @@ static int mtk_scpsys_power_off(struct power_domain *power_domain)
 	if (ret < 0)
 		return ret;
 
+	ret = clk_disable_bulk(&domain->clks);
+	if (ret)
+		return ret;
+
 	return 0;
 }
 
@@ -164,7 +288,7 @@ int mtk_scpsys_probe(struct udevice *dev)
 	struct mtk_scpsys *scpsys = dev_get_priv(dev);
 	struct regmap *regmap;
 	struct clk_bulk bulk;
-	int err;
+	int err, i;
 
 	scpsys->base = dev_read_addr_ptr(dev);
 	if (!scpsys->base)
@@ -172,6 +296,14 @@ int mtk_scpsys_probe(struct udevice *dev)
 
 	scpsys->soc_data = (const struct mtk_scp_soc_data *)dev_get_driver_data(dev);
 
+	scpsys->domains = devm_kcalloc(dev, scpsys->soc_data->num_domains,
+				       sizeof(*scpsys->domains), GFP_KERNEL);
+	if (!scpsys->domains)
+		return -ENOMEM;
+
+	for (i = 0; i < scpsys->soc_data->num_domains; i++)
+		scpsys->domains[i].data = &scpsys->soc_data->data[i];
+
 	/* get corresponding syscon phandle */
 	err = dev_read_phandle_with_args(dev, "infracfg", NULL, 0, 0, &args);
 	if (err)
@@ -195,6 +327,179 @@ int mtk_scpsys_probe(struct udevice *dev)
 	return clk_enable_bulk(&bulk);
 }
 
+static int mtk_scpsys_add_one_domain(struct udevice *dev, ofnode node, int parent_id)
+{
+	struct mtk_scpsys *scpsys = dev_get_priv(dev);
+	struct ofnode_phandle_args args;
+	struct mtk_scp_domain *domain;
+	struct regmap *regmap;
+	const char *clk_name;
+	int i, ret, num_clks;
+	u32 id;
+
+	ret = ofnode_read_u32(node, "reg", &id);
+	if (ret) {
+		dev_err(dev, "%s: failed to retrieve domain id from reg: %d\n",
+			ofnode_get_name(node), ret);
+		return ret;
+	}
+
+	if (id >= scpsys->soc_data->num_domains) {
+		dev_err(dev, "%s: invalid domain id %d\n", ofnode_get_name(node), id);
+		return -EINVAL;
+	}
+
+	domain = &scpsys->domains[id];
+	domain->data = &scpsys->soc_data->data[id];
+
+	if (parent_id >= 0) {
+		domain->has_pd = true;
+		domain->parent_pd.dev = dev;
+		domain->parent_pd.id = parent_id;
+	}
+
+	if (ofnode_read_bool(node, "mediatek,infracfg")) {
+		ret = ofnode_parse_phandle_with_args(node, "mediatek,infracfg", NULL, 0, 0, &args);
+		if (ret)
+			return ret;
+
+		regmap = syscon_node_to_regmap(args.node);
+		if (IS_ERR(regmap))
+			return PTR_ERR(regmap);
+
+		domain->infracfg = regmap_get_range(regmap, 0);
+
+		/* enable Infra DCM */
+		if (domain->infracfg)
+			setbits_le32(domain->infracfg + INFRA_TOPDCM_CTRL,
+				     DCM_TOP_EN);
+	}
+
+	num_clks = ofnode_read_string_count(node, "clock-names");
+	for (i = 0; i < num_clks; i++) {
+		ret = ofnode_read_string_index(node, "clock-names", i, &clk_name);
+		if (ret) {
+			dev_err(dev, "%s: failed to retrieve clock-names at index %i: %d\n",
+				ofnode_get_name(node), i, ret);
+			return ret;
+		}
+
+		if (strchr(clk_name, '-'))
+			domain->subsys_clks.count++;
+		else
+			domain->clks.count++;
+	}
+
+	if (domain->clks.count) {
+		domain->clks.clks = devm_kcalloc(dev, domain->clks.count,
+						 sizeof(struct clk), GFP_KERNEL);
+		if (!domain->clks.clks)
+			return -ENOMEM;
+	}
+
+	if (domain->subsys_clks.count) {
+		domain->subsys_clks.clks = devm_kcalloc(dev,
+							domain->subsys_clks.count,
+							sizeof(struct clk), GFP_KERNEL);
+		if (!domain->subsys_clks.clks)
+			return -ENOMEM;
+	}
+
+	for (i = 0; i < domain->clks.count; i++) {
+		ret = clk_get_by_index_nodev(node, i, &domain->clks.clks[i]);
+		if (ret < 0) {
+			dev_err(dev, "%s: failed to get clk at index %d: %d\n",
+				ofnode_get_name(node), i, ret);
+			goto err_put_clocks;
+		}
+	}
+
+	for (i = 0; i < domain->subsys_clks.count; i++) {
+		ret = clk_get_by_index_nodev(node, i + domain->clks.count,
+					     &domain->subsys_clks.clks[i]);
+		if (ret < 0) {
+			dev_err(dev, "%s: failed to get subsys clk at index %d: %d\n",
+				ofnode_get_name(node), i + domain->clks.count, ret);
+			goto err_put_subsys_clocks;
+		}
+	}
+
+	return 0;
+
+err_put_subsys_clocks:
+	clk_release_all(domain->subsys_clks.clks, domain->subsys_clks.count);
+	domain->subsys_clks.count = 0;
+err_put_clocks:
+	clk_release_all(domain->clks.clks, domain->clks.count);
+	domain->clks.count = 0;
+	domain->data = NULL;
+	return ret;
+}
+
+static int mtk_scpsys_add_subdomain(struct udevice *dev, ofnode node)
+{
+	ofnode subnode;
+	int ret;
+	u32 id;
+
+	ret = ofnode_read_u32(node, "reg", &id);
+	if (ret) {
+		dev_err(dev, "%s: failed to get domain id\n", ofnode_get_name(node));
+		return ret;
+	}
+
+	ofnode_for_each_subnode(subnode, node) {
+		ret = mtk_scpsys_add_one_domain(dev, subnode, id);
+		if (ret) {
+			dev_err(dev, "failed to add child domain: %s\n",
+				ofnode_get_name(subnode));
+			continue;
+		}
+
+		ret = mtk_scpsys_add_subdomain(dev, subnode);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int mtk_power_controller_probe(struct udevice *dev)
+{
+	struct mtk_scpsys *scpsys = dev_get_priv(dev);
+	ofnode subnode;
+	int ret;
+
+	scpsys->base = dev_read_addr_ptr(dev_get_parent(dev));
+	if (!scpsys->base)
+		return -ENOENT;
+
+	scpsys->soc_data = (const struct mtk_scp_soc_data *)dev_get_driver_data(dev);
+
+	scpsys->domains = devm_kcalloc(dev, scpsys->soc_data->num_domains,
+				       sizeof(*scpsys->domains), GFP_KERNEL);
+	if (!scpsys->domains)
+		return -ENOMEM;
+
+	dev_for_each_subnode(subnode, dev) {
+		ret = mtk_scpsys_add_one_domain(dev, subnode, -1);
+		if (ret) {
+			dev_err(dev, "failed to add child domain: %s\n",
+				ofnode_get_name(subnode));
+			continue;
+		}
+
+		ret = mtk_scpsys_add_subdomain(dev, subnode);
+		if (ret) {
+			dev_err(dev, "failed to add sub domain: %s\n",
+				ofnode_get_name(subnode));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
 const struct power_domain_ops mtk_power_domain_ops = {
 	.off = mtk_scpsys_power_off,
 	.on = mtk_scpsys_power_on,
diff --git a/drivers/power/domain/mtk-power-domain.h b/drivers/power/domain/mtk-power-domain.h
index 689ec4d1657..5bf0ca850ec 100644
--- a/drivers/power/domain/mtk-power-domain.h
+++ b/drivers/power/domain/mtk-power-domain.h
@@ -9,6 +9,7 @@
 #ifndef __MTK_POWER_DOMAIN_H
 #define __MTK_POWER_DOMAIN_H
 
+#include <clk.h>
 #include <power-domain-uclass.h>
 #include <linux/bitops.h>
 #include <linux/compiler.h>
@@ -59,14 +60,28 @@ struct udevice;
 
 #define DCM_TOP_EN		BIT(0)
 
+#define SPM_MAX_BUS_PROT_DATA	6
+
 struct mtk_scp_domain;
 
+struct mtk_scpsys_bus_prot_data {
+	u32 bus_prot_mask;
+	u32 bus_prot_set;
+	u32 bus_prot_clr;
+	u32 bus_prot_sta;
+	bool bus_prot_reg_update;
+	bool ignore_clr_ack;
+};
+
 struct mtk_scp_domain_data {
 	u32 sta_mask;
 	int ctl_offs;
 	u32 sram_pdn_bits;
 	u32 sram_pdn_ack_bits;
 	u32 bus_prot_mask;
+	const struct mtk_scpsys_bus_prot_data bp_infracfg[SPM_MAX_BUS_PROT_DATA];
+	int pwr_sta_offs;
+	int pwr_sta2nd_offs;
 };
 
 struct mtk_scp_soc_data {
@@ -80,13 +95,36 @@ static const struct mtk_scp_soc_data _name##_scp_soc_data = {	\
 	.num_domains = ARRAY_SIZE(_domains),			\
 }
 
+struct mtk_scp_domain {
+	const struct mtk_scp_domain_data *data;
+	void __iomem *infracfg;
+	struct clk_bulk clks;
+	struct clk_bulk subsys_clks;
+	bool has_pd;
+	struct power_domain parent_pd;
+};
+
 struct mtk_scpsys {
 	void __iomem *base;
 	void __iomem *infracfg;
 	const struct mtk_scp_soc_data *soc_data;
+	struct mtk_scp_domain *domains;
 };
 
+#define _BUS_PROT(_mask, _set, _clr, _sta, _update, _ignore) {	\
+	.bus_prot_mask = (_mask),				\
+	.bus_prot_set = (_set),					\
+	.bus_prot_clr = (_clr),					\
+	.bus_prot_sta = (_sta),					\
+	.bus_prot_reg_update = (_update),			\
+	.ignore_clr_ack = (_ignore),				\
+}
+
+#define BUS_PROT_WR(_mask, _set, _clr, _sta)			\
+	_BUS_PROT(_mask, _set, _clr, _sta, false, false)
+
 int mtk_scpsys_probe(struct udevice *dev);
+int mtk_power_controller_probe(struct udevice *dev);
 
 extern const struct power_domain_ops mtk_power_domain_ops;
 

-- 
2.54.0



More information about the U-Boot mailing list