From: Julien Stephan Date: Thu, 9 Jul 2026 12:58:18 +0000 (+0200) Subject: power: domain: mediatek: add support for the power-controller model X-Git-Tag: v2026.10-rc1~17^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a81e1f8a7cf2aae4304b74b818c8da90bec1c02e;p=thirdparty%2Fu-boot.git power: domain: mediatek: add support for the power-controller model 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 Link: https://patch.msgid.link/20260709-mt8188-add-power-domain-v2-12-589ace7d30e2@baylibre.com Signed-off-by: David Lechner --- 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 #include #include +#include +#include #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 #include #include #include @@ -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;