]> Gentwo Git Trees - linux/.git/commitdiff
pmdomain: mediatek: Add support for Hardware Voter power domains
authorAngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Thu, 25 Sep 2025 14:31:13 +0000 (16:31 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Wed, 19 Nov 2025 17:06:50 +0000 (18:06 +0100)
New generation SoCs like MT8196/MT6991 feature a new type of power
domains, managed by a Hardware Voter (HWV) helper (through a SoC
internal fixed-function MCU): this is used to collect votes from
both the AP and the various other remote processors present in the
SoC and transparently power on/off various power domains, avoiding
unpowered access of registers in various internal IPs from all of
the integrated remote processors (or from the AP...!).

Add a new power domain type and differentiate between the old
SCPSYS_MTCMOS_TYPE_DIRECT_CTL - where power domains are controlled
directly by and exclusively from the Application Processor, and
the new SCPSYS_MTCMOS_TYPE_HW_VOTER, where the power domains are
voted through the HWV.

With the two needing different handling, check the power domain
type and assign a different power_{off,on} callback for pm_genpd:
for this specific reason, also move the check for the SCPD cap
MTK_SCPD_KEEP_DEFAULT_OFF after the assignment, and use the
assigned power_on function instead of calling scpsys_power_on()
directly to make that work for both HW_VOTER and DIRECT_CTL.

Reviewed-by: NĂ­colas F. R. A. Prado <nfraprado@collabora.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/pmdomain/mediatek/mtk-pm-domains.c
drivers/pmdomain/mediatek/mtk-pm-domains.h

index 0ebe7379b94e50f357e30afae0803afad848e6c1..36767f740f57476f1e4e3e18665f017c06bcf3cf 100644 (file)
 #define MTK_POLL_DELAY_US              10
 #define MTK_POLL_TIMEOUT               USEC_PER_SEC
 
+#define MTK_HWV_POLL_DELAY_US          5
+#define MTK_HWV_POLL_TIMEOUT           (300 * USEC_PER_MSEC)
+
+#define MTK_HWV_PREPARE_DELAY_US       1
+#define MTK_HWV_PREPARE_TIMEOUT                (3 * USEC_PER_MSEC)
+
 #define PWR_RST_B_BIT                  BIT(0)
 #define PWR_ISO_BIT                    BIT(1)
 #define PWR_ON_BIT                     BIT(2)
@@ -48,6 +54,7 @@
 struct scpsys_domain {
        struct generic_pm_domain genpd;
        const struct scpsys_domain_data *data;
+       const struct scpsys_hwv_domain_data *hwv_data;
        struct scpsys *scpsys;
        int num_clks;
        struct clk_bulk_data *clks;
@@ -83,6 +90,32 @@ static bool scpsys_domain_is_on(struct scpsys_domain *pd)
        return status && status2;
 }
 
+static bool scpsys_hwv_domain_is_disable_done(struct scpsys_domain *pd)
+{
+       const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+       u32 regs[2] = { hwv->done, hwv->clr_sta };
+       u32 val[2];
+       u32 mask = BIT(hwv->setclr_bit);
+
+       regmap_multi_reg_read(pd->scpsys->base, regs, val, 2);
+
+       /* Disable is done when the bit is set in DONE, cleared in CLR_STA */
+       return (val[0] & mask) && !(val[1] & mask);
+}
+
+static bool scpsys_hwv_domain_is_enable_done(struct scpsys_domain *pd)
+{
+       const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+       u32 regs[3] = { hwv->done, hwv->en, hwv->set_sta };
+       u32 val[3];
+       u32 mask = BIT(hwv->setclr_bit);
+
+       regmap_multi_reg_read(pd->scpsys->base, regs, val, 3);
+
+       /* Enable is done when the bit is set in DONE and EN, cleared in SET_STA */
+       return (val[0] & mask) && (val[1] & mask) && !(val[2] & mask);
+}
+
 static int scpsys_sram_enable(struct scpsys_domain *pd)
 {
        u32 expected_ack, pdn_ack = pd->data->sram_pdn_ack_bits;
@@ -250,6 +283,137 @@ static int scpsys_regulator_disable(struct regulator *supply)
        return supply ? regulator_disable(supply) : 0;
 }
 
+static int scpsys_hwv_power_on(struct generic_pm_domain *genpd)
+{
+       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+       const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+       struct scpsys *scpsys = pd->scpsys;
+       u32 val;
+       int ret;
+
+       ret = scpsys_regulator_enable(pd->supply);
+       if (ret)
+               return ret;
+
+       ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks);
+       if (ret)
+               goto err_reg;
+
+       /* For HWV the subsys clocks refer to the HWV low power subsystem */
+       ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks);
+       if (ret)
+               goto err_disable_clks;
+
+       /* Make sure the HW Voter is idle and able to accept commands */
+       ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->done, val,
+                                             val & BIT(hwv->setclr_bit),
+                                             MTK_HWV_POLL_DELAY_US,
+                                             MTK_HWV_POLL_TIMEOUT);
+       if (ret) {
+               dev_err(scpsys->dev, "Failed to power on: HW Voter busy.\n");
+               goto err_disable_subsys_clks;
+       }
+
+       /*
+        * Instruct the HWV to power on the MTCMOS (power domain): after that,
+        * the same bit will be unset immediately by the hardware.
+        */
+       regmap_write(scpsys->base, hwv->set, BIT(hwv->setclr_bit));
+
+       /*
+        * Wait until the HWV sets the bit again, signalling that its internal
+        * state machine was started and it now processing the vote command.
+        */
+       ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->set, val,
+                                             val & BIT(hwv->setclr_bit),
+                                             MTK_HWV_PREPARE_DELAY_US,
+                                             MTK_HWV_PREPARE_TIMEOUT);
+       if (ret) {
+               dev_err(scpsys->dev, "Failed to power on: HW Voter not starting.\n");
+               goto err_disable_subsys_clks;
+       }
+
+       /* Wait for ACK, signalling that the MTCMOS was enabled */
+       ret = readx_poll_timeout_atomic(scpsys_hwv_domain_is_enable_done, pd, val, val,
+                                       MTK_HWV_POLL_DELAY_US, MTK_HWV_POLL_TIMEOUT);
+       if (ret) {
+               dev_err(scpsys->dev, "Failed to power on: HW Voter ACK timeout.\n");
+               goto err_disable_subsys_clks;
+       }
+
+       /* It's done! Disable the HWV low power subsystem clocks */
+       clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+
+       return 0;
+
+err_disable_subsys_clks:
+       clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+err_disable_clks:
+       clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
+err_reg:
+       scpsys_regulator_disable(pd->supply);
+       return ret;
+};
+
+static int scpsys_hwv_power_off(struct generic_pm_domain *genpd)
+{
+       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+       const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
+       struct scpsys *scpsys = pd->scpsys;
+       u32 val;
+       int ret;
+
+       ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks);
+       if (ret)
+               return ret;
+
+       /* Make sure the HW Voter is idle and able to accept commands */
+       ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->done, val,
+                                             val & BIT(hwv->setclr_bit),
+                                             MTK_HWV_POLL_DELAY_US,
+                                             MTK_HWV_POLL_TIMEOUT);
+       if (ret)
+               goto err_disable_subsys_clks;
+
+
+       /*
+        * Instruct the HWV to power off the MTCMOS (power domain): differently
+        * from poweron, the bit will be kept set.
+        */
+       regmap_write(scpsys->base, hwv->clr, BIT(hwv->setclr_bit));
+
+       /*
+        * Wait until the HWV clears the bit, signalling that its internal
+        * state machine was started and it now processing the clear command.
+        */
+       ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->clr, val,
+                                             !(val & BIT(hwv->setclr_bit)),
+                                             MTK_HWV_PREPARE_DELAY_US,
+                                             MTK_HWV_PREPARE_TIMEOUT);
+       if (ret)
+               goto err_disable_subsys_clks;
+
+       /* Poweroff needs 100us for the HW to stabilize */
+       udelay(100);
+
+       /* Wait for ACK, signalling that the MTCMOS was disabled */
+       ret = readx_poll_timeout_atomic(scpsys_hwv_domain_is_disable_done, pd, val, val,
+                                       MTK_HWV_POLL_DELAY_US, MTK_HWV_POLL_TIMEOUT);
+       if (ret)
+               goto err_disable_subsys_clks;
+
+       clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+       clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
+
+       scpsys_regulator_disable(pd->supply);
+
+       return 0;
+
+err_disable_subsys_clks:
+       clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks);
+       return ret;
+};
+
 static int scpsys_ctl_pwrseq_on(struct scpsys_domain *pd)
 {
        struct scpsys *scpsys = pd->scpsys;
@@ -514,6 +678,7 @@ static struct
 generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node)
 {
        const struct scpsys_domain_data *domain_data;
+       const struct scpsys_hwv_domain_data *hwv_domain_data;
        struct scpsys_domain *pd;
        struct property *prop;
        const char *clk_name;
@@ -529,14 +694,33 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
                return ERR_PTR(-EINVAL);
        }
 
-       if (id >= scpsys->soc_data->num_domains) {
-               dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id);
-               return ERR_PTR(-EINVAL);
-       }
+       switch (scpsys->soc_data->type) {
+       case SCPSYS_MTCMOS_TYPE_DIRECT_CTL:
+               if (id >= scpsys->soc_data->num_domains) {
+                       dev_err(scpsys->dev, "%pOF: invalid domain id %d\n", node, id);
+                       return ERR_PTR(-EINVAL);
+               }
+
+               domain_data = &scpsys->soc_data->domains_data[id];
+               hwv_domain_data = NULL;
 
-       domain_data = &scpsys->soc_data->domains_data[id];
-       if (domain_data->sta_mask == 0) {
-               dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
+               if (domain_data->sta_mask == 0) {
+                       dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
+                       return ERR_PTR(-EINVAL);
+               }
+
+               break;
+       case SCPSYS_MTCMOS_TYPE_HW_VOTER:
+               if (id >= scpsys->soc_data->num_hwv_domains) {
+                       dev_err(scpsys->dev, "%pOF: invalid HWV domain id %d\n", node, id);
+                       return ERR_PTR(-EINVAL);
+               }
+
+               domain_data = NULL;
+               hwv_domain_data = &scpsys->soc_data->hwv_domains_data[id];
+
+               break;
+       default:
                return ERR_PTR(-EINVAL);
        }
 
@@ -545,6 +729,7 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
                return ERR_PTR(-ENOMEM);
 
        pd->data = domain_data;
+       pd->hwv_data = hwv_domain_data;
        pd->scpsys = scpsys;
 
        if (MTK_SCPD_CAPS(pd, MTK_SCPD_DOMAIN_SUPPLY)) {
@@ -604,6 +789,31 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
                pd->subsys_clks[i].clk = clk;
        }
 
+       if (scpsys->domains[id]) {
+               ret = -EINVAL;
+               dev_err(scpsys->dev,
+                       "power domain with id %d already exists, check your device-tree\n", id);
+               goto err_put_subsys_clocks;
+       }
+
+       if (pd->data && pd->data->name)
+               pd->genpd.name = pd->data->name;
+       else if (pd->hwv_data && pd->hwv_data->name)
+               pd->genpd.name = pd->hwv_data->name;
+       else
+               pd->genpd.name = node->name;
+
+       if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_DIRECT_CTL) {
+               pd->genpd.power_off = scpsys_power_off;
+               pd->genpd.power_on = scpsys_power_on;
+       } else {
+               pd->genpd.power_off = scpsys_hwv_power_off;
+               pd->genpd.power_on = scpsys_hwv_power_on;
+
+               /* HW-Voter code can be invoked in atomic context */
+               pd->genpd.flags |= GENPD_FLAG_IRQ_SAFE;
+       }
+
        /*
         * Initially turn on all domains to make the domains usable
         * with !CONFIG_PM and to get the hardware in sync with the
@@ -615,7 +825,7 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
                        dev_warn(scpsys->dev,
                                 "%pOF: A default off power domain has been ON\n", node);
        } else {
-               ret = scpsys_power_on(&pd->genpd);
+               ret = pd->genpd.power_on(&pd->genpd);
                if (ret < 0) {
                        dev_err(scpsys->dev, "%pOF: failed to power on domain: %d\n", node, ret);
                        goto err_put_subsys_clocks;
@@ -625,21 +835,6 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
                        pd->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
        }
 
-       if (scpsys->domains[id]) {
-               ret = -EINVAL;
-               dev_err(scpsys->dev,
-                       "power domain with id %d already exists, check your device-tree\n", id);
-               goto err_put_subsys_clocks;
-       }
-
-       if (!pd->data->name)
-               pd->genpd.name = node->name;
-       else
-               pd->genpd.name = pd->data->name;
-
-       pd->genpd.power_off = scpsys_power_off;
-       pd->genpd.power_on = scpsys_power_on;
-
        if (MTK_SCPD_CAPS(pd, MTK_SCPD_ACTIVE_WAKEUP))
                pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
 
@@ -934,7 +1129,7 @@ static int scpsys_probe(struct platform_device *pdev)
        struct device_node *node;
        struct device *parent;
        struct scpsys *scpsys;
-       int ret;
+       int num_domains, ret;
 
        soc = of_device_get_match_data(&pdev->dev);
        if (!soc) {
@@ -942,7 +1137,9 @@ static int scpsys_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, soc->num_domains), GFP_KERNEL);
+       num_domains = soc->num_domains + soc->num_hwv_domains;
+
+       scpsys = devm_kzalloc(dev, struct_size(scpsys, domains, num_domains), GFP_KERNEL);
        if (!scpsys)
                return -ENOMEM;
 
index b2e3dee0383119ccbf0f0e1451108a9dd2eb84d0..df4bf013709bc862631c391389411e3de226be8a 100644 (file)
@@ -16,7 +16,9 @@
 #define MTK_SCPD_SRAM_PDN_INVERTED     BIT(9)
 #define MTK_SCPD_MODEM_PWRSEQ          BIT(10)
 #define MTK_SCPD_SKIP_RESET_B          BIT(11)
-#define MTK_SCPD_CAPS(_scpd, _x)       ((_scpd)->data->caps & (_x))
+#define MTK_SCPD_CAPS(_scpd, _x)       ((_scpd)->data ?                \
+                                        (_scpd)->data->caps & (_x) :   \
+                                        (_scpd)->hwv_data->caps & (_x))
 
 #define SPM_VDE_PWR_CON                        0x0210
 #define SPM_MFG_PWR_CON                        0x0214
@@ -124,6 +126,18 @@ enum scpsys_rtff_type {
        SCPSYS_RTFF_TYPE_MAX
 };
 
+/**
+ * enum scpsys_mtcmos_type - Type of power domain controller
+ * @SCPSYS_MTCMOS_TYPE_DIRECT_CTL: Power domains are controlled with direct access
+ * @SCPSYS_MTCMOS_TYPE_HW_VOTER:   Hardware-assisted voted power domain control
+ * @SCPSYS_MTCMOS_TYPE_MAX:        Number of supported power domain types
+ */
+enum scpsys_mtcmos_type {
+       SCPSYS_MTCMOS_TYPE_DIRECT_CTL = 0,
+       SCPSYS_MTCMOS_TYPE_HW_VOTER,
+       SCPSYS_MTCMOS_TYPE_MAX
+};
+
 /**
  * struct scpsys_domain_data - scp domain data for power on/off flow
  * @name: The name of the power domain.
@@ -152,11 +166,40 @@ struct scpsys_domain_data {
        int pwr_sta2nd_offs;
 };
 
+/**
+ * struct scpsys_hwv_domain_data - Hardware Voter power domain data
+ * @name:       Name of the power domain
+ * @set:        Offset of the HWV SET register
+ * @clr:        Offset of the HWV CLEAR register
+ * @done:       Offset of the HWV DONE register
+ * @en:         Offset of the HWV ENABLE register
+ * @set_sta:    Offset of the HWV SET STATUS register
+ * @clr_sta:    Offset of the HWV CLEAR STATUS register
+ * @setclr_bit: The SET/CLR bit to enable/disable the power domain
+ * @sta_bit:    The SET/CLR STA bit to check for on/off ACK
+ * @caps:       The flag for active wake-up action
+ */
+struct scpsys_hwv_domain_data {
+       const char *name;
+       u16 set;
+       u16 clr;
+       u16 done;
+       u16 en;
+       u16 set_sta;
+       u16 clr_sta;
+       u8 setclr_bit;
+       u8 sta_bit;
+       u16 caps;
+};
+
 struct scpsys_soc_data {
        const struct scpsys_domain_data *domains_data;
        int num_domains;
+       const struct scpsys_hwv_domain_data *hwv_domains_data;
+       int num_hwv_domains;
        enum scpsys_bus_prot_block *bus_prot_blocks;
        int num_bus_prot_blocks;
+       enum scpsys_mtcmos_type type;
 };
 
 #endif /* __SOC_MEDIATEK_MTK_PM_DOMAINS_H */