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 <[email protected]>
---
 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

Reply via email to