[PATCH v2 12/14] power: domain: mediatek: add support for the power-controller model
Julien Stephan
jstephan at baylibre.com
Thu Jul 9 14:58:18 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