From 3b7685da41838eda57f56e804014091769a93700 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 22 Sep 2025 10:21:07 +0800 Subject: pmdomain: core: Allow power-off for out-of-band wakeup-capable devices Currently, if a device is configured as a system wakeup source, the PM domain core avoids powering off its power domain during system-wide suspend. However, this can lead to unnecessary power consumption, especially for devices whose wakeup logic resides in an always-on domain, i.e., devices with out-of-band wakeup capability. To address this, add a check for device_out_band_wakeup() in genpd_finish_suspend(). If the device supports out-of-band wakeup, its power domain can be safely powered off, just like regular devices without wakeup enabled. And same check in genpd_finish_resume(). This change improves power efficiency without compromising wakeup functionality. Signed-off-by: Peng Fan Reviewed-by: Dhruva Gole Signed-off-by: Ulf Hansson --- drivers/pmdomain/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c index 61c2277c9ce3..4925bc1c4410 100644 --- a/drivers/pmdomain/core.c +++ b/drivers/pmdomain/core.c @@ -1545,7 +1545,8 @@ static int genpd_finish_suspend(struct device *dev, if (ret) return ret; - if (device_awake_path(dev) && genpd_is_active_wakeup(genpd)) + if (device_awake_path(dev) && genpd_is_active_wakeup(genpd) && + !device_out_band_wakeup(dev)) return 0; if (genpd->dev_ops.stop && genpd->dev_ops.start && @@ -1600,7 +1601,8 @@ static int genpd_finish_resume(struct device *dev, if (IS_ERR(genpd)) return -EINVAL; - if (device_awake_path(dev) && genpd_is_active_wakeup(genpd)) + if (device_awake_path(dev) && genpd_is_active_wakeup(genpd) && + !device_out_band_wakeup(dev)) return resume_noirq(dev); genpd_lock(genpd); -- cgit v1.2.3 From 72b0a7b34b40cbc877edf1f70520953767fb4583 Mon Sep 17 00:00:00 2001 From: Jishnu Prakash Date: Wed, 24 Sep 2025 16:17:08 -0700 Subject: pmdomain: qcom: rpmhpd: Add RPMh power domain support for Kaanapali Add the RPMh power domains present in Kaanapali SoCs. Also increase the maximum allowed number of levels for ARC resources from 16 to 32, as needed starting on the Kaanapali SoC where the ARC vote registers have been expanded from 4 to 5 bits. Signed-off-by: Jishnu Prakash Signed-off-by: Jingyi Wang Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Signed-off-by: Ulf Hansson --- drivers/pmdomain/qcom/rpmhpd.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/qcom/rpmhpd.c b/drivers/pmdomain/qcom/rpmhpd.c index 4faa8a256186..a8b37037c6fe 100644 --- a/drivers/pmdomain/qcom/rpmhpd.c +++ b/drivers/pmdomain/qcom/rpmhpd.c @@ -19,7 +19,7 @@ #define domain_to_rpmhpd(domain) container_of(domain, struct rpmhpd, pd) -#define RPMH_ARC_MAX_LEVELS 16 +#define RPMH_ARC_MAX_LEVELS 32 /** * struct rpmhpd - top level RPMh power domain resource data structure @@ -595,6 +595,31 @@ static const struct rpmhpd_desc sm8750_desc = { .num_pds = ARRAY_SIZE(sm8750_rpmhpds), }; +/* KAANAPALI RPMH powerdomains */ +static struct rpmhpd *kaanapali_rpmhpds[] = { + [RPMHPD_CX] = &cx, + [RPMHPD_CX_AO] = &cx_ao, + [RPMHPD_EBI] = &ebi, + [RPMHPD_GFX] = &gfx, + [RPMHPD_GMXC] = &gmxc, + [RPMHPD_LCX] = &lcx, + [RPMHPD_LMX] = &lmx, + [RPMHPD_MX] = &mx, + [RPMHPD_MX_AO] = &mx_ao, + [RPMHPD_MMCX] = &mmcx, + [RPMHPD_MMCX_AO] = &mmcx_ao, + [RPMHPD_MSS] = &mss, + [RPMHPD_MXC] = &mxc, + [RPMHPD_MXC_AO] = &mxc_ao, + [RPMHPD_NSP] = &nsp, + [RPMHPD_NSP2] = &nsp2, +}; + +static const struct rpmhpd_desc kaanapali_desc = { + .rpmhpds = kaanapali_rpmhpds, + .num_pds = ARRAY_SIZE(kaanapali_rpmhpds), +}; + /* QDU1000/QRU1000 RPMH powerdomains */ static struct rpmhpd *qdu1000_rpmhpds[] = { [QDU1000_CX] = &cx, @@ -767,6 +792,7 @@ static const struct rpmhpd_desc qcs615_desc = { static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,glymur-rpmhpd", .data = &glymur_desc }, + { .compatible = "qcom,kaanapali-rpmhpd", .data = &kaanapali_desc }, { .compatible = "qcom,milos-rpmhpd", .data = &milos_desc }, { .compatible = "qcom,qcs615-rpmhpd", .data = &qcs615_desc }, { .compatible = "qcom,qcs8300-rpmhpd", .data = &qcs8300_desc }, -- cgit v1.2.3 From 88914db077b6c2920b29a3ec76109a2fd3cf8d38 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 25 Sep 2025 16:31:13 +0200 Subject: pmdomain: mediatek: Add support for Hardware Voter power domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 247 ++++++++++++++++++++++++++--- drivers/pmdomain/mediatek/mtk-pm-domains.h | 45 +++++- 2 files changed, 266 insertions(+), 26 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index 0ebe7379b94e..36767f740f57 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -31,6 +31,12 @@ #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; diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.h b/drivers/pmdomain/mediatek/mtk-pm-domains.h index b2e3dee03831..df4bf013709b 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.h +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.h @@ -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 */ -- cgit v1.2.3 From 8e98badec1d5e1ab7755a2cdfd092c8623f86ee5 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 25 Sep 2025 16:31:14 +0200 Subject: pmdomain: mediatek: Add support for secure HWCCF infra power on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some SoCs, like the MediaTek Dimensity 9400 (MT6991), have granular power controls and will disable power to the infracfg to save power when the platform is in deeper sleep states (or when no IP in the the infracfg macro-block is in use). These chips also cannot control the infracfg power states directly via AP register writes as those are protected by the secure world. Add a new MTK_SCPD_INFRA_PWR_CTL cap and, if present, make a call to the secure world to poweron the infracfg block, as the HWV IP resides in there, when executing HWV domains power sequences. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 40 ++++++++++++++++++++++++++++-- drivers/pmdomain/mediatek/mtk-pm-domains.h | 1 + 2 files changed, 39 insertions(+), 2 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index 36767f740f57..f400b0c6b5fd 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "mt6735-pm-domains.h" #include "mt6795-pm-domains.h" @@ -51,6 +52,8 @@ #define PWR_RTFF_SAVE_FLAG BIT(27) #define PWR_RTFF_UFS_CLK_DIS BIT(28) +#define MTK_SIP_KERNEL_HWCCF_CONTROL MTK_SIP_SMC_CMD(0x540) + struct scpsys_domain { struct generic_pm_domain genpd; const struct scpsys_domain_data *data; @@ -116,6 +119,15 @@ static bool scpsys_hwv_domain_is_enable_done(struct scpsys_domain *pd) return (val[0] & mask) && (val[1] & mask) && !(val[2] & mask); } +static int scpsys_sec_infra_power_on(bool on) +{ + struct arm_smccc_res res; + unsigned long cmd = on ? 1 : 0; + + arm_smccc_smc(MTK_SIP_KERNEL_HWCCF_CONTROL, cmd, 0, 0, 0, 0, 0, 0, &res); + return res.a0; +} + static int scpsys_sram_enable(struct scpsys_domain *pd) { u32 expected_ack, pdn_ack = pd->data->sram_pdn_ack_bits; @@ -291,9 +303,15 @@ static int scpsys_hwv_power_on(struct generic_pm_domain *genpd) u32 val; int ret; + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) { + ret = scpsys_sec_infra_power_on(true); + if (ret) + return ret; + } + ret = scpsys_regulator_enable(pd->supply); if (ret) - return ret; + goto err_infra; ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks); if (ret) @@ -344,6 +362,9 @@ static int scpsys_hwv_power_on(struct generic_pm_domain *genpd) /* It's done! Disable the HWV low power subsystem clocks */ clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks); + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) + scpsys_sec_infra_power_on(false); + return 0; err_disable_subsys_clks: @@ -352,6 +373,9 @@ err_disable_clks: clk_bulk_disable_unprepare(pd->num_clks, pd->clks); err_reg: scpsys_regulator_disable(pd->supply); +err_infra: + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) + scpsys_sec_infra_power_on(false); return ret; }; @@ -363,9 +387,15 @@ static int scpsys_hwv_power_off(struct generic_pm_domain *genpd) u32 val; int ret; + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) { + ret = scpsys_sec_infra_power_on(true); + if (ret) + return ret; + } + ret = clk_bulk_prepare_enable(pd->num_subsys_clks, pd->subsys_clks); if (ret) - return ret; + goto err_infra; /* Make sure the HW Voter is idle and able to accept commands */ ret = regmap_read_poll_timeout_atomic(scpsys->base, hwv->done, val, @@ -407,10 +437,16 @@ static int scpsys_hwv_power_off(struct generic_pm_domain *genpd) scpsys_regulator_disable(pd->supply); + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) + scpsys_sec_infra_power_on(false); + return 0; err_disable_subsys_clks: clk_bulk_disable_unprepare(pd->num_subsys_clks, pd->subsys_clks); +err_infra: + if (MTK_SCPD_CAPS(pd, MTK_SCPD_INFRA_PWR_CTL)) + scpsys_sec_infra_power_on(false); return ret; }; diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.h b/drivers/pmdomain/mediatek/mtk-pm-domains.h index df4bf013709b..36adcfca80c6 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.h +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.h @@ -16,6 +16,7 @@ #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_INFRA_PWR_CTL BIT(12) #define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data ? \ (_scpd)->data->caps & (_x) : \ (_scpd)->hwv_data->caps & (_x)) -- cgit v1.2.3 From 5437b2813f8f8ed02ec2bca78de08a35fbaea85c Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 25 Sep 2025 16:31:15 +0200 Subject: pmdomain: mediatek: Add support for MT8196 SCPSYS power domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new SPM bus protection block and add support for both the direct control and HW Voter control SCPSYS power domains found in the MT8196 and MT6991 SoCs. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mt8196-pm-domains.h | 386 ++++++++++++++++++++++++++ drivers/pmdomain/mediatek/mtk-pm-domains.c | 18 +- drivers/pmdomain/mediatek/mtk-pm-domains.h | 3 + 3 files changed, 404 insertions(+), 3 deletions(-) create mode 100644 drivers/pmdomain/mediatek/mt8196-pm-domains.h (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mt8196-pm-domains.h b/drivers/pmdomain/mediatek/mt8196-pm-domains.h new file mode 100644 index 000000000000..ce8d594c46f8 --- /dev/null +++ b/drivers/pmdomain/mediatek/mt8196-pm-domains.h @@ -0,0 +1,386 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025 Collabora Ltd + * AngeloGioacchino Del Regno + */ + +#ifndef __SOC_MEDIATEK_MT8196_PM_DOMAINS_H +#define __SOC_MEDIATEK_MT8196_PM_DOMAINS_H + +#include "mtk-pm-domains.h" +#include + +/* + * MT8196 and MT6991 power domain support + */ + +/* INFRA TOP_AXI registers */ +#define MT8196_TOP_AXI_PROT_EN_SET 0x4 +#define MT8196_TOP_AXI_PROT_EN_CLR 0x8 +#define MT8196_TOP_AXI_PROT_EN_STA 0xc + #define MT8196_TOP_AXI_PROT_EN_SLEEP0_MD BIT(29) + +#define MT8196_TOP_AXI_PROT_EN_1_SET 0x24 +#define MT8196_TOP_AXI_PROT_EN_1_CLR 0x28 +#define MT8196_TOP_AXI_PROT_EN_1_STA 0x2c + #define MT8196_TOP_AXI_PROT_EN_1_SLEEP1_MD BIT(0) + +/* SPM BUS_PROTECT registers */ +#define MT8196_SPM_BUS_PROTECT_CON_SET 0xdc +#define MT8196_SPM_BUS_PROTECT_CON_CLR 0xe0 +#define MT8196_SPM_BUS_PROTECT_RDY 0x208 + #define MT8196_SPM_PROT_EN_BUS_CONN BIT(1) + #define MT8196_SPM_PROT_EN_BUS_SSUSB_DP_PHY_P0 BIT(6) + #define MT8196_SPM_PROT_EN_BUS_SSUSB_P0 BIT(7) + #define MT8196_SPM_PROT_EN_BUS_SSUSB_P1 BIT(8) + #define MT8196_SPM_PROT_EN_BUS_SSUSB_P23 BIT(9) + #define MT8196_SPM_PROT_EN_BUS_SSUSB_PHY_P2 BIT(10) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC0 BIT(13) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC1 BIT(14) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_MAC2 BIT(15) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY0 BIT(16) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY1 BIT(17) + #define MT8196_SPM_PROT_EN_BUS_PEXTP_PHY2 BIT(18) + #define MT8196_SPM_PROT_EN_BUS_AUDIO BIT(19) + #define MT8196_SPM_PROT_EN_BUS_ADSP_TOP BIT(21) + #define MT8196_SPM_PROT_EN_BUS_ADSP_INFRA BIT(22) + #define MT8196_SPM_PROT_EN_BUS_ADSP_AO BIT(23) + #define MT8196_SPM_PROT_EN_BUS_MM_PROC BIT(24) + +/* PWR_CON registers */ +#define MT8196_PWR_ACK BIT(30) +#define MT8196_PWR_ACK_2ND BIT(31) + +static enum scpsys_bus_prot_block scpsys_bus_prot_blocks_mt8196[] = { + BUS_PROT_BLOCK_INFRA, BUS_PROT_BLOCK_SPM +}; + +static const struct scpsys_domain_data scpsys_domain_data_mt8196[] = { + [MT8196_POWER_DOMAIN_MD] = { + .name = "md", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe00, + .pwr_sta_offs = 0xe00, + .pwr_sta2nd_offs = 0xe00, + .ext_buck_iso_offs = 0xefc, + .ext_buck_iso_mask = GENMASK(1, 0), + .bp_cfg = { + BUS_PROT_WR_IGN(INFRA, MT8196_TOP_AXI_PROT_EN_SLEEP0_MD, + MT8196_TOP_AXI_PROT_EN_SET, + MT8196_TOP_AXI_PROT_EN_CLR, + MT8196_TOP_AXI_PROT_EN_STA), + BUS_PROT_WR_IGN(INFRA, MT8196_TOP_AXI_PROT_EN_1_SLEEP1_MD, + MT8196_TOP_AXI_PROT_EN_1_SET, + MT8196_TOP_AXI_PROT_EN_1_CLR, + MT8196_TOP_AXI_PROT_EN_1_STA), + }, + .caps = MTK_SCPD_MODEM_PWRSEQ | MTK_SCPD_EXT_BUCK_ISO | + MTK_SCPD_SKIP_RESET_B | MTK_SCPD_KEEP_DEFAULT_OFF, + }, + [MT8196_POWER_DOMAIN_CONN] = { + .name = "conn", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe04, + .pwr_sta_offs = 0xe04, + .pwr_sta2nd_offs = 0xe04, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_CONN, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_KEEP_DEFAULT_OFF, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_SSUSB_DP_PHY_P0] = { + .name = "ssusb-dp-phy-p0", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe18, + .pwr_sta_offs = 0xe18, + .pwr_sta2nd_offs = 0xe18, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_DP_PHY_P0, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_ALWAYS_ON, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_SSUSB_P0] = { + .name = "ssusb-p0", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe1c, + .pwr_sta_offs = 0xe1c, + .pwr_sta2nd_offs = 0xe1c, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P0, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_ALWAYS_ON, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_SSUSB_P1] = { + .name = "ssusb-p1", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe20, + .pwr_sta_offs = 0xe20, + .pwr_sta2nd_offs = 0xe20, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P1, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_ALWAYS_ON, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_SSUSB_P23] = { + .name = "ssusb-p23", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe24, + .pwr_sta_offs = 0xe24, + .pwr_sta2nd_offs = 0xe24, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_P23, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_KEEP_DEFAULT_OFF, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_SSUSB_PHY_P2] = { + .name = "ssusb-phy-p2", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe28, + .pwr_sta_offs = 0xe28, + .pwr_sta2nd_offs = 0xe28, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_SSUSB_PHY_P2, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_KEEP_DEFAULT_OFF, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_PEXTP_MAC0] = { + .name = "pextp-mac0", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe34, + .pwr_sta_offs = 0xe34, + .pwr_sta2nd_offs = 0xe34, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC0, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_PEXTP_MAC1] = { + .name = "pextp-mac1", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe38, + .pwr_sta_offs = 0xe38, + .pwr_sta2nd_offs = 0xe38, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC1, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_PEXTP_MAC2] = { + .name = "pextp-mac2", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe3c, + .pwr_sta_offs = 0xe3c, + .pwr_sta2nd_offs = 0xe3c, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_MAC2, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_PEXTP_PHY0] = { + .name = "pextp-phy0", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe40, + .pwr_sta_offs = 0xe40, + .pwr_sta2nd_offs = 0xe40, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY0, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_PEXTP_PHY1] = { + .name = "pextp-phy1", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe44, + .pwr_sta_offs = 0xe44, + .pwr_sta2nd_offs = 0xe44, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY1, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_PEXTP_PHY2] = { + .name = "pextp-phy2", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe48, + .pwr_sta_offs = 0xe48, + .pwr_sta2nd_offs = 0xe48, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_PEXTP_PHY2, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_PCIE_PHY, + }, + [MT8196_POWER_DOMAIN_AUDIO] = { + .name = "audio", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe4c, + .pwr_sta_offs = 0xe4c, + .pwr_sta2nd_offs = 0xe4c, + .sram_pdn_bits = BIT(8), + .sram_pdn_ack_bits = BIT(12), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_AUDIO, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_ADSP_TOP_DORMANT] = { + .name = "adsp-top-dormant", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe54, + .pwr_sta_offs = 0xe54, + .pwr_sta2nd_offs = 0xe54, + /* Note: This is not managing powerdown (pdn), but sleep instead (slp) */ + .sram_pdn_bits = BIT(9), + .sram_pdn_ack_bits = BIT(13), + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_TOP, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_SRAM_ISO | MTK_SCPD_SRAM_PDN_INVERTED, + }, + [MT8196_POWER_DOMAIN_ADSP_INFRA] = { + .name = "adsp-infra", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe58, + .pwr_sta_offs = 0xe58, + .pwr_sta2nd_offs = 0xe58, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_INFRA, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_ALWAYS_ON, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, + [MT8196_POWER_DOMAIN_ADSP_AO] = { + .name = "adsp-ao", + .sta_mask = MT8196_PWR_ACK, + .sta2nd_mask = MT8196_PWR_ACK_2ND, + .ctl_offs = 0xe5c, + .pwr_sta_offs = 0xe5c, + .pwr_sta2nd_offs = 0xe5c, + .bp_cfg = { + BUS_PROT_WR_IGN(SPM, MT8196_SPM_PROT_EN_BUS_ADSP_AO, + MT8196_SPM_BUS_PROTECT_CON_SET, + MT8196_SPM_BUS_PROTECT_CON_CLR, + MT8196_SPM_BUS_PROTECT_RDY), + }, + .caps = MTK_SCPD_ALWAYS_ON, + .rtff_type = SCPSYS_RTFF_TYPE_GENERIC, + }, +}; + +static const struct scpsys_hwv_domain_data scpsys_hwv_domain_data_mt8196[] = { + [MT8196_POWER_DOMAIN_MM_PROC_DORMANT] = { + .name = "mm-proc-dormant", + .set = 0x0218, + .clr = 0x021c, + .done = 0x141c, + .en = 0x1410, + .set_sta = 0x146c, + .clr_sta = 0x1470, + .setclr_bit = 0, + .caps = MTK_SCPD_ALWAYS_ON, + }, + [MT8196_POWER_DOMAIN_SSR] = { + .name = "ssrsys", + .set = 0x0218, + .clr = 0x021c, + .done = 0x141c, + .en = 0x1410, + .set_sta = 0x146c, + .clr_sta = 0x1470, + .setclr_bit = 1, + }, +}; + +static const struct scpsys_soc_data mt8196_scpsys_data = { + .domains_data = scpsys_domain_data_mt8196, + .num_domains = ARRAY_SIZE(scpsys_domain_data_mt8196), + .bus_prot_blocks = scpsys_bus_prot_blocks_mt8196, + .num_bus_prot_blocks = ARRAY_SIZE(scpsys_bus_prot_blocks_mt8196), + .type = SCPSYS_MTCMOS_TYPE_DIRECT_CTL, +}; + +static const struct scpsys_soc_data mt8196_scpsys_hwv_data = { + .hwv_domains_data = scpsys_hwv_domain_data_mt8196, + .num_hwv_domains = ARRAY_SIZE(scpsys_hwv_domain_data_mt8196), + .type = SCPSYS_MTCMOS_TYPE_HW_VOTER, +}; + +#endif /* __SOC_MEDIATEK_MT8196_PM_DOMAINS_H */ diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index f400b0c6b5fd..18f0b9b960d9 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -27,6 +27,7 @@ #include "mt8188-pm-domains.h" #include "mt8192-pm-domains.h" #include "mt8195-pm-domains.h" +#include "mt8196-pm-domains.h" #include "mt8365-pm-domains.h" #define MTK_POLL_DELAY_US 10 @@ -81,13 +82,16 @@ struct scpsys { static bool scpsys_domain_is_on(struct scpsys_domain *pd) { struct scpsys *scpsys = pd->scpsys; - u32 status, status2; + u32 mask = pd->data->sta_mask; + u32 status, status2, mask2; + + mask2 = pd->data->sta2nd_mask ? pd->data->sta2nd_mask : mask; regmap_read(scpsys->base, pd->data->pwr_sta_offs, &status); - status &= pd->data->sta_mask; + status &= mask; regmap_read(scpsys->base, pd->data->pwr_sta2nd_offs, &status2); - status2 &= pd->data->sta_mask; + status2 &= mask2; /* A domain is on when both status bits are set. */ return status && status2; @@ -1150,6 +1154,14 @@ static const struct of_device_id scpsys_of_match[] = { .compatible = "mediatek,mt8195-power-controller", .data = &mt8195_scpsys_data, }, + { + .compatible = "mediatek,mt8196-power-controller", + .data = &mt8196_scpsys_data, + }, + { + .compatible = "mediatek,mt8196-hwv-scp-power-controller", + .data = &mt8196_scpsys_hwv_data, + }, { .compatible = "mediatek,mt8365-power-controller", .data = &mt8365_scpsys_data, diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.h b/drivers/pmdomain/mediatek/mtk-pm-domains.h index 36adcfca80c6..f608e6ec4744 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.h +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.h @@ -62,6 +62,7 @@ enum scpsys_bus_prot_block { BUS_PROT_BLOCK_INFRA, BUS_PROT_BLOCK_INFRA_NAO, BUS_PROT_BLOCK_SMI, + BUS_PROT_BLOCK_SPM, BUS_PROT_BLOCK_COUNT, }; @@ -143,6 +144,7 @@ enum scpsys_mtcmos_type { * struct scpsys_domain_data - scp domain data for power on/off flow * @name: The name of the power domain. * @sta_mask: The mask for power on/off status bit. + * @sta2nd_mask: The mask for second power on/off status bit. * @ctl_offs: The offset for main power control register. * @sram_pdn_bits: The mask for sram power control bits. * @sram_pdn_ack_bits: The mask for sram power control acked bits. @@ -155,6 +157,7 @@ enum scpsys_mtcmos_type { struct scpsys_domain_data { const char *name; u32 sta_mask; + u32 sta2nd_mask; int ctl_offs; u32 sram_pdn_bits; u32 sram_pdn_ack_bits; -- cgit v1.2.3 From 56b0d23017ec127163f7851f8ee6c88cec7f1599 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 25 Sep 2025 16:31:16 +0200 Subject: pmdomain: mediatek: Add support for MT8196 HFRPSYS power domains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the HFRPSYS Multimedia power domains found in the MediaTek MT8196 Chromebook SoC. Those power domains are all managed by the Hardware Voter MCU. Reviewed-by: Nícolas F. R. A. Prado Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mt8196-pm-domains.h | 239 ++++++++++++++++++++++++++ drivers/pmdomain/mediatek/mtk-pm-domains.c | 4 + 2 files changed, 243 insertions(+) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mt8196-pm-domains.h b/drivers/pmdomain/mediatek/mt8196-pm-domains.h index ce8d594c46f8..2e4b28720659 100644 --- a/drivers/pmdomain/mediatek/mt8196-pm-domains.h +++ b/drivers/pmdomain/mediatek/mt8196-pm-domains.h @@ -369,6 +369,239 @@ static const struct scpsys_hwv_domain_data scpsys_hwv_domain_data_mt8196[] = { }, }; +static const struct scpsys_hwv_domain_data hfrpsys_hwv_domain_data_mt8196[] = { + [MT8196_POWER_DOMAIN_VDE0] = { + .name = "vde0", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 7, + }, + [MT8196_POWER_DOMAIN_VDE1] = { + .name = "vde1", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 8, + }, + [MT8196_POWER_DOMAIN_VDE_VCORE0] = { + .name = "vde-vcore0", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 9, + }, + [MT8196_POWER_DOMAIN_VEN0] = { + .name = "ven0", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 10, + }, + [MT8196_POWER_DOMAIN_VEN1] = { + .name = "ven1", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 11, + }, + [MT8196_POWER_DOMAIN_VEN2] = { + .name = "ven2", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 12, + }, + [MT8196_POWER_DOMAIN_DISP_VCORE] = { + .name = "disp-vcore", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 24, + }, + [MT8196_POWER_DOMAIN_DIS0_DORMANT] = { + .name = "dis0-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 25, + }, + [MT8196_POWER_DOMAIN_DIS1_DORMANT] = { + .name = "dis1-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 26, + }, + [MT8196_POWER_DOMAIN_OVL0_DORMANT] = { + .name = "ovl0-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 27, + }, + [MT8196_POWER_DOMAIN_OVL1_DORMANT] = { + .name = "ovl1-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 28, + }, + [MT8196_POWER_DOMAIN_DISP_EDPTX_DORMANT] = { + .name = "disp-edptx-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 29, + }, + [MT8196_POWER_DOMAIN_DISP_DPTX_DORMANT] = { + .name = "disp-dptx-dormant", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 30, + }, + [MT8196_POWER_DOMAIN_MML0_SHUTDOWN] = { + .name = "mml0-shutdown", + .set = 0x0218, + .clr = 0x021C, + .done = 0x141C, + .en = 0x1410, + .set_sta = 0x146C, + .clr_sta = 0x1470, + .setclr_bit = 31, + }, + [MT8196_POWER_DOMAIN_MML1_SHUTDOWN] = { + .name = "mml1-shutdown", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 0, + }, + [MT8196_POWER_DOMAIN_MM_INFRA0] = { + .name = "mm-infra0", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 1, + }, + [MT8196_POWER_DOMAIN_MM_INFRA1] = { + .name = "mm-infra1", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 2, + }, + [MT8196_POWER_DOMAIN_MM_INFRA_AO] = { + .name = "mm-infra-ao", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 3, + }, + [MT8196_POWER_DOMAIN_CSI_BS_RX] = { + .name = "csi-bs-rx", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 5, + }, + [MT8196_POWER_DOMAIN_CSI_LS_RX] = { + .name = "csi-ls-rx", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 6, + }, + [MT8196_POWER_DOMAIN_DSI_PHY0] = { + .name = "dsi-phy0", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 7, + }, + [MT8196_POWER_DOMAIN_DSI_PHY1] = { + .name = "dsi-phy1", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 8, + }, + [MT8196_POWER_DOMAIN_DSI_PHY2] = { + .name = "dsi-phy2", + .set = 0x0220, + .clr = 0x0224, + .done = 0x142C, + .en = 0x1420, + .set_sta = 0x1474, + .clr_sta = 0x1478, + .setclr_bit = 9, + }, +}; + static const struct scpsys_soc_data mt8196_scpsys_data = { .domains_data = scpsys_domain_data_mt8196, .num_domains = ARRAY_SIZE(scpsys_domain_data_mt8196), @@ -383,4 +616,10 @@ static const struct scpsys_soc_data mt8196_scpsys_hwv_data = { .type = SCPSYS_MTCMOS_TYPE_HW_VOTER, }; +static const struct scpsys_soc_data mt8196_hfrpsys_hwv_data = { + .hwv_domains_data = hfrpsys_hwv_domain_data_mt8196, + .num_hwv_domains = ARRAY_SIZE(hfrpsys_hwv_domain_data_mt8196), + .type = SCPSYS_MTCMOS_TYPE_HW_VOTER, +}; + #endif /* __SOC_MEDIATEK_MT8196_PM_DOMAINS_H */ diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index 18f0b9b960d9..ac144ab8fce0 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -1158,6 +1158,10 @@ static const struct of_device_id scpsys_of_match[] = { .compatible = "mediatek,mt8196-power-controller", .data = &mt8196_scpsys_data, }, + { + .compatible = "mediatek,mt8196-hwv-hfrp-power-controller", + .data = &mt8196_hfrpsys_hwv_data, + }, { .compatible = "mediatek,mt8196-hwv-scp-power-controller", .data = &mt8196_scpsys_hwv_data, -- cgit v1.2.3 From 47c7b3c24b42f27eccbb1b181c33619774e46474 Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Fri, 17 Oct 2025 17:38:34 +0800 Subject: pmdomain: rockchip: Add support for RV1126B Add configuration and power domains for RV1126 SoC. Signed-off-by: Finley Xiao Reviewed-by: Heiko Stuebner Signed-off-by: Ulf Hansson --- drivers/pmdomain/rockchip/pm-domains.c | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c index 1955c6d453e4..4f1336a0f49a 100644 --- a/drivers/pmdomain/rockchip/pm-domains.c +++ b/drivers/pmdomain/rockchip/pm-domains.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,20 @@ struct rockchip_pmu { .active_wakeup = wakeup, \ } +#define DOMAIN_M_G(_name, pwr, status, req, idle, ack, g_mask, wakeup, keepon) \ +{ \ + .name = _name, \ + .pwr_w_mask = (pwr) << 16, \ + .pwr_mask = (pwr), \ + .status_mask = (status), \ + .req_w_mask = (req) << 16, \ + .req_mask = (req), \ + .idle_mask = (idle), \ + .ack_mask = (ack), \ + .clk_ungate_mask = (g_mask), \ + .active_wakeup = wakeup, \ +} + #define DOMAIN_M_G_SD(_name, pwr, status, req, idle, ack, g_mask, mem, wakeup, keepon) \ { \ .name = _name, \ @@ -205,6 +220,9 @@ struct rockchip_pmu { #define DOMAIN_RV1126(name, pwr, req, idle, wakeup) \ DOMAIN_M(name, pwr, pwr, req, idle, idle, wakeup) +#define DOMAIN_RV1126B(name, pwr, req, wakeup) \ + DOMAIN_M_G(name, pwr, pwr, req, req, req, req, wakeup, true) + #define DOMAIN_RK3288(name, pwr, status, req, wakeup) \ DOMAIN(name, pwr, status, req, req, (req) << 16, wakeup) @@ -1104,6 +1122,13 @@ static const struct rockchip_domain_info rv1126_pm_domains[] = { [RV1126_PD_USB] = DOMAIN_RV1126("usb", BIT(9), BIT(15), BIT(15), false), }; +static const struct rockchip_domain_info rv1126b_pm_domains[] = { + /* name pwr req wakeup */ + [RV1126B_PD_NPU] = DOMAIN_RV1126B("npu", BIT(0), BIT(8), false), + [RV1126B_PD_VDO] = DOMAIN_RV1126B("vdo", BIT(1), BIT(9), false), + [RV1126B_PD_AIISP] = DOMAIN_RV1126B("aiisp", BIT(2), BIT(10), false), +}; + static const struct rockchip_domain_info rk3036_pm_domains[] = { [RK3036_PD_MSCH] = DOMAIN_RK3036("msch", BIT(14), BIT(23), BIT(30), true), [RK3036_PD_CORE] = DOMAIN_RK3036("core", BIT(13), BIT(17), BIT(24), false), @@ -1516,6 +1541,18 @@ static const struct rockchip_pmu_info rv1126_pmu = { .domain_info = rv1126_pm_domains, }; +static const struct rockchip_pmu_info rv1126b_pmu = { + .pwr_offset = 0x210, + .status_offset = 0x230, + .req_offset = 0x110, + .idle_offset = 0x128, + .ack_offset = 0x120, + .clk_ungate_offset = 0x140, + + .num_domains = ARRAY_SIZE(rv1126b_pm_domains), + .domain_info = rv1126b_pm_domains, +}; + static const struct of_device_id rockchip_pm_domain_dt_match[] = { { .compatible = "rockchip,px30-power-controller", @@ -1585,6 +1622,10 @@ static const struct of_device_id rockchip_pm_domain_dt_match[] = { .compatible = "rockchip,rv1126-power-controller", .data = (void *)&rv1126_pmu, }, + { + .compatible = "rockchip,rv1126b-power-controller", + .data = (void *)&rv1126b_pmu, + }, { /* sentinel */ }, }; -- cgit v1.2.3 From 19e668e81e273b43b28608e8e05f4fb090a32f5a Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Thu, 23 Oct 2025 13:44:36 +0200 Subject: pmdomain: mediatek: Fix build-errors Let's add the missing header to fix the reported build-errors. Fixes: df4e9ec1ed86 ("pmdomain: mediatek: Add support for secure HWCCF infra power on") Reported-by: Stephen Rothwell Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510231317.ZZxNaFG0-lkp@intel.com/ Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-pm-domains.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index ac144ab8fce0..164c6b519af3 100644 --- a/drivers/pmdomain/mediatek/mtk-pm-domains.c +++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2020 Collabora Ltd. */ +#include #include #include #include -- cgit v1.2.3 From f08e7a4e8d6ac4de677727af352ea33c6ce9f444 Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Fri, 17 Oct 2025 17:31:12 +0200 Subject: pmdomain: mediatek: Add support for MFlexGraphics Various MediaTek SoCs use GPU integration silicon named "MFlexGraphics" by MediaTek. On the MT8196 and MT6991 SoCs, interacting with this integration silicon is required to power on the GPU. This glue silicon is in the form of an embedded microcontroller running special-purpose firmware, which autonomously adjusts clocks and regulators. Implement a driver, modelled as a pmdomain driver with a set_performance_state operation, to support these SoCs. The driver also exposes the actual achieved clock rate, as read back from the MCU, as common clock framework clocks, by acting as a clock provider as well. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Nicolas Frattaroli Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/Kconfig | 16 + drivers/pmdomain/mediatek/Makefile | 1 + drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 1044 ++++++++++++++++++++++++++ 3 files changed, 1061 insertions(+) create mode 100644 drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/Kconfig b/drivers/pmdomain/mediatek/Kconfig index 0e34a517ab7d..b06aaa9690f0 100644 --- a/drivers/pmdomain/mediatek/Kconfig +++ b/drivers/pmdomain/mediatek/Kconfig @@ -26,6 +26,22 @@ config MTK_SCPSYS_PM_DOMAINS Control Processor System (SCPSYS) has several power management related tasks in the system. +config MTK_MFG_PM_DOMAIN + bool "MediaTek MFlexGraphics power domain" + default ARCH_MEDIATEK + depends on PM + depends on OF + depends on COMMON_CLK + select PM_GENERIC_DOMAINS + imply MTK_GPUEB_MBOX + help + Say y or m here to enable the power domains driver for MediaTek + MFlexGraphics. This driver allows for power and frequency control of + GPUs on MediaTek SoCs such as the MT8196 or MT6991. + + This driver is required for the Mali GPU to work at all on MT8196 and + MT6991. + config AIROHA_CPU_PM_DOMAIN tristate "Airoha CPU power domain" default ARCH_AIROHA diff --git a/drivers/pmdomain/mediatek/Makefile b/drivers/pmdomain/mediatek/Makefile index 18ba92e3c418..b424f1ed8676 100644 --- a/drivers/pmdomain/mediatek/Makefile +++ b/drivers/pmdomain/mediatek/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTK_MFG_PM_DOMAIN) += mtk-mfg-pmdomain.o obj-$(CONFIG_MTK_SCPSYS) += mtk-scpsys.o obj-$(CONFIG_MTK_SCPSYS_PM_DOMAINS) += mtk-pm-domains.o obj-$(CONFIG_AIROHA_CPU_PM_DOMAIN) += airoha-cpu-pmdomain.o diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c new file mode 100644 index 000000000000..af20111067c0 --- /dev/null +++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for MediaTek MFlexGraphics Devices + * + * Copyright (C) 2025, Collabora Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPR_LP_STATE 0x0028 +#define EB_ON_SUSPEND 0x0 +#define EB_ON_RESUME 0x1 +#define GPR_IPI_MAGIC 0x34 + +#define RPC_PWR_CON 0x0504 +#define PWR_ACK_M GENMASK(31, 30) +#define RPC_DUMMY_REG_2 0x0658 +#define RPC_GHPM_CFG0_CON 0x0800 +#define GHPM_ENABLE_M BIT(0) +#define GHPM_ON_SEQ_M BIT(2) +#define RPC_GHPM_RO0_CON 0x09A4 +#define GHPM_STATE_M GENMASK(7, 0) +#define GHPM_PWR_STATE_M BIT(16) + +#define GF_REG_MAGIC 0x0000 +#define GF_REG_GPU_OPP_IDX 0x0004 +#define GF_REG_STK_OPP_IDX 0x0008 +#define GF_REG_GPU_OPP_NUM 0x000c +#define GF_REG_STK_OPP_NUM 0x0010 +#define GF_REG_GPU_OPP_SNUM 0x0014 +#define GF_REG_STK_OPP_SNUM 0x0018 +#define GF_REG_POWER_COUNT 0x001c +#define GF_REG_BUCK_COUNT 0x0020 +#define GF_REG_MTCMOS_COUNT 0x0024 +#define GF_REG_CG_COUNT 0x0028 /* CG = Clock Gate? */ +#define GF_REG_ACTIVE_COUNT 0x002C +#define GF_REG_TEMP_RAW 0x0030 +#define GF_REG_TEMP_NORM_GPU 0x0034 +#define GF_REG_TEMP_HIGH_GPU 0x0038 +#define GF_REG_TEMP_NORM_STK 0x003C +#define GF_REG_TEMP_HIGH_STK 0x0040 +#define GF_REG_FREQ_CUR_GPU 0x0044 +#define GF_REG_FREQ_CUR_STK 0x0048 +#define GF_REG_FREQ_OUT_GPU 0x004C /* Guess: actual achieved freq */ +#define GF_REG_FREQ_OUT_STK 0x0050 /* Guess: actual achieved freq */ +#define GF_REG_FREQ_METER_GPU 0x0054 /* Seems unused, always 0 */ +#define GF_REG_FREQ_METER_STK 0x0058 /* Seems unused, always 0 */ +#define GF_REG_VOLT_CUR_GPU 0x005C /* in tens of microvolts */ +#define GF_REG_VOLT_CUR_STK 0x0060 /* in tens of microvolts */ +#define GF_REG_VOLT_CUR_GPU_SRAM 0x0064 +#define GF_REG_VOLT_CUR_STK_SRAM 0x0068 +#define GF_REG_VOLT_CUR_GPU_REG 0x006C /* Seems unused, always 0 */ +#define GF_REG_VOLT_CUR_STK_REG 0x0070 /* Seems unused, always 0 */ +#define GF_REG_VOLT_CUR_GPU_REG_SRAM 0x0074 +#define GF_REG_VOLT_CUR_STK_REG_SRAM 0x0078 +#define GF_REG_PWR_CUR_GPU 0x007C /* in milliwatts */ +#define GF_REG_PWR_CUR_STK 0x0080 /* in milliwatts */ +#define GF_REG_PWR_MAX_GPU 0x0084 /* in milliwatts */ +#define GF_REG_PWR_MAX_STK 0x0088 /* in milliwatts */ +#define GF_REG_PWR_MIN_GPU 0x008C /* in milliwatts */ +#define GF_REG_PWR_MIN_STK 0x0090 /* in milliwatts */ +#define GF_REG_LEAKAGE_RT_GPU 0x0094 /* Unknown */ +#define GF_REG_LEAKAGE_RT_STK 0x0098 /* Unknown */ +#define GF_REG_LEAKAGE_RT_SRAM 0x009C /* Unknown */ +#define GF_REG_LEAKAGE_HT_GPU 0x00A0 /* Unknown */ +#define GF_REG_LEAKAGE_HT_STK 0x00A4 /* Unknown */ +#define GF_REG_LEAKAGE_HT_SRAM 0x00A8 /* Unknown */ +#define GF_REG_VOLT_DAC_LOW_GPU 0x00AC /* Seems unused, always 0 */ +#define GF_REG_VOLT_DAC_LOW_STK 0x00B0 /* Seems unused, always 0 */ +#define GF_REG_OPP_CUR_CEIL 0x00B4 +#define GF_REG_OPP_CUR_FLOOR 0x00B8 +#define GF_REG_OPP_CUR_LIMITER_CEIL 0x00BC +#define GF_REG_OPP_CUR_LIMITER_FLOOR 0x00C0 +#define GF_REG_OPP_PRIORITY_CEIL 0x00C4 +#define GF_REG_OPP_PRIORITY_FLOOR 0x00C8 +#define GF_REG_PWR_CTL 0x00CC +#define GF_REG_ACTIVE_SLEEP_CTL 0x00D0 +#define GF_REG_DVFS_STATE 0x00D4 +#define GF_REG_SHADER_PRESENT 0x00D8 +#define GF_REG_ASENSOR_ENABLE 0x00DC +#define GF_REG_AGING_LOAD 0x00E0 +#define GF_REG_AGING_MARGIN 0x00E4 +#define GF_REG_AVS_ENABLE 0x00E8 +#define GF_REG_AVS_MARGIN 0x00EC +#define GF_REG_CHIP_TYPE 0x00F0 +#define GF_REG_SB_VERSION 0x00F4 +#define GF_REG_PTP_VERSION 0x00F8 +#define GF_REG_DBG_VERSION 0x00FC +#define GF_REG_KDBG_VERSION 0x0100 +#define GF_REG_GPM1_MODE 0x0104 +#define GF_REG_GPM3_MODE 0x0108 +#define GF_REG_DFD_MODE 0x010C +#define GF_REG_DUAL_BUCK 0x0110 +#define GF_REG_SEGMENT_ID 0x0114 +#define GF_REG_POWER_TIME_H 0x0118 +#define GF_REG_POWER_TIME_L 0x011C +#define GF_REG_PWR_STATUS 0x0120 +#define GF_REG_STRESS_TEST 0x0124 +#define GF_REG_TEST_MODE 0x0128 +#define GF_REG_IPS_MODE 0x012C +#define GF_REG_TEMP_COMP_MODE 0x0130 +#define GF_REG_HT_TEMP_COMP_MODE 0x0134 +#define GF_REG_PWR_TRACKER_MODE 0x0138 +#define GF_REG_OPP_TABLE_GPU 0x0314 +#define GF_REG_OPP_TABLE_STK 0x09A4 +#define GF_REG_OPP_TABLE_GPU_S 0x1034 +#define GF_REG_OPP_TABLE_STK_S 0x16c4 +#define GF_REG_LIMIT_TABLE 0x1d54 +#define GF_REG_GPM3_TABLE 0x223C + +#define MFG_MT8196_E2_ID 0x101 +#define GPUEB_SLEEP_MAGIC 0x55667788UL +#define GPUEB_MEM_MAGIC 0xBABADADAUL + +#define GPUEB_TIMEOUT_US 10000UL +#define GPUEB_POLL_US 50 + +#define MAX_OPP_NUM 70 + +#define GPUEB_MBOX_MAX_RX_SIZE 32 /* in bytes */ + +/* + * This enum is part of the ABI of the GPUEB firmware. Don't change the + * numbering, as you would wreak havoc. + */ +enum mtk_mfg_ipi_cmd { + CMD_INIT_SHARED_MEM = 0, + CMD_GET_FREQ_BY_IDX = 1, + CMD_GET_POWER_BY_IDX = 2, + CMD_GET_OPPIDX_BY_FREQ = 3, + CMD_GET_LEAKAGE_POWER = 4, + CMD_SET_LIMIT = 5, + CMD_POWER_CONTROL = 6, + CMD_ACTIVE_SLEEP_CONTROL = 7, + CMD_COMMIT = 8, + CMD_DUAL_COMMIT = 9, + CMD_PDCA_CONFIG = 10, + CMD_UPDATE_DEBUG_OPP_INFO = 11, + CMD_SWITCH_LIMIT = 12, + CMD_FIX_TARGET_OPPIDX = 13, + CMD_FIX_DUAL_TARGET_OPPIDX = 14, + CMD_FIX_CUSTOM_FREQ_VOLT = 15, + CMD_FIX_DUAL_CUSTOM_FREQ_VOLT = 16, + CMD_SET_MFGSYS_CONFIG = 17, + CMD_MSSV_COMMIT = 18, + CMD_NUM = 19, +}; + +/* + * This struct is part of the ABI of the GPUEB firmware. Changing it, or + * reordering fields in it, will break things, so don't do it. Thank you. + */ +struct __packed mtk_mfg_ipi_msg { + __le32 magic; + __le32 cmd; + __le32 target; + /* + * Downstream relies on the compiler to implicitly add the following + * padding, as it declares the struct as non-packed. + */ + __le32 reserved; + union { + s32 __bitwise oppidx; + s32 __bitwise return_value; + __le32 freq; + __le32 volt; + __le32 power; + __le32 power_state; + __le32 mode; + __le32 value; + struct { + __le64 base; + __le32 size; + } shared_mem; + struct { + __le32 freq; + __le32 volt; + } custom; + struct { + __le32 limiter; + s32 __bitwise ceiling_info; + s32 __bitwise floor_info; + } set_limit; + struct { + __le32 target; + __le32 val; + } mfg_cfg; + struct { + __le32 target; + __le32 val; + } mssv; + struct { + s32 __bitwise gpu_oppidx; + s32 __bitwise stack_oppidx; + } dual_commit; + struct { + __le32 fgpu; + __le32 vgpu; + __le32 fstack; + __le32 vstack; + } dual_custom; + } u; +}; + +struct __packed mtk_mfg_ipi_sleep_msg { + __le32 event; + __le32 state; + __le32 magic; +}; + +/** + * struct mtk_mfg_opp_entry - OPP table entry from firmware + * @freq_khz: The operating point's frequency in kilohertz + * @voltage_core: The operating point's core voltage in tens of microvolts + * @voltage_sram: The operating point's SRAM voltage in tens of microvolts + * @posdiv: exponent of base 2 for PLL frequency divisor used for this OPP + * @voltage_margin: Number of tens of microvolts the voltage can be undershot + * @power_mw: estimate of power usage at this operating point, in milliwatts + * + * This struct is part of the ABI with the EB firmware. Do not change it. + */ +struct __packed mtk_mfg_opp_entry { + __le32 freq_khz; + __le32 voltage_core; + __le32 voltage_sram; + __le32 posdiv; + __le32 voltage_margin; + __le32 power_mw; +}; + +struct mtk_mfg_mbox { + struct mbox_client cl; + struct completion rx_done; + struct mtk_mfg *mfg; + struct mbox_chan *ch; + void *rx_data; +}; + +struct mtk_mfg { + struct generic_pm_domain pd; + struct platform_device *pdev; + struct clk *clk_eb; + struct clk_bulk_data *gpu_clks; + struct clk_hw clk_core_hw; + struct clk_hw clk_stack_hw; + struct regulator_bulk_data *gpu_regs; + void __iomem *rpc; + void __iomem *gpr; + void __iomem *shared_mem; + phys_addr_t shared_mem_phys; + unsigned int shared_mem_size; + u16 ghpm_en_reg; + u32 ipi_magic; + unsigned short num_gpu_opps; + unsigned short num_stack_opps; + struct dev_pm_opp_data *gpu_opps; + struct dev_pm_opp_data *stack_opps; + struct mtk_mfg_mbox *gf_mbox; + struct mtk_mfg_mbox *slp_mbox; + const struct mtk_mfg_variant *variant; +}; + +struct mtk_mfg_variant { + const char *const *clk_names; + unsigned int num_clks; + const char *const *regulator_names; + unsigned int num_regulators; + /** @turbo_below: opp indices below this value are considered turbo */ + unsigned int turbo_below; + int (*init)(struct mtk_mfg *mfg); +}; + +static inline struct mtk_mfg *mtk_mfg_from_genpd(struct generic_pm_domain *pd) +{ + return container_of(pd, struct mtk_mfg, pd); +} + +static inline void mtk_mfg_update_reg_bits(void __iomem *addr, u32 mask, u32 val) +{ + writel((readl(addr) & ~mask) | (val & mask), addr); +} + +static inline bool mtk_mfg_is_powered_on(struct mtk_mfg *mfg) +{ + return (readl(mfg->rpc + RPC_PWR_CON) & PWR_ACK_M) == PWR_ACK_M; +} + +static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_core_hw); + + return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ; +} + +static long mtk_mfg_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + /* + * The round_rate callback needs to be implemented to avoid returning + * the current clock frequency, rather than something even remotely + * close to the frequency that was asked for. + * + * Instead of writing considerable amounts of possibly slow code just to + * somehow figure out which of the three PLLs to round for, or even to + * do a search through one of two OPP tables in order to find the closest + * OPP of a frequency, just return the rate as-is. This avoids devfreq + * "rounding" a request for the lowest frequency to the possibly very + * high current frequency, breaking the powersave governor in the process. + */ + + return rate; +} + +static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mtk_mfg *mfg = container_of(hw, struct mtk_mfg, clk_stack_hw); + + return readl(mfg->shared_mem + GF_REG_FREQ_OUT_STK) * HZ_PER_KHZ; +} + +static const struct clk_ops mtk_mfg_clk_gpu_ops = { + .recalc_rate = mtk_mfg_recalc_rate_gpu, + .round_rate = mtk_mfg_round_rate, +}; + +static const struct clk_ops mtk_mfg_clk_stack_ops = { + .recalc_rate = mtk_mfg_recalc_rate_stack, + .round_rate = mtk_mfg_round_rate, +}; + +static const struct clk_init_data mtk_mfg_clk_gpu_init = { + .name = "gpu-core", + .ops = &mtk_mfg_clk_gpu_ops, + .flags = CLK_GET_RATE_NOCACHE, +}; + +static const struct clk_init_data mtk_mfg_clk_stack_init = { + .name = "gpu-stack", + .ops = &mtk_mfg_clk_stack_ops, + .flags = CLK_GET_RATE_NOCACHE, +}; + +static int mtk_mfg_eb_on(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + u32 val; + int ret; + + /* + * If MFG is already on from e.g. the bootloader, skip doing the + * power-on sequence, as it wouldn't work without powering it off first. + */ + if (mtk_mfg_is_powered_on(mfg)) + return 0; + + ret = readl_poll_timeout(mfg->rpc + RPC_GHPM_RO0_CON, val, + !(val & (GHPM_PWR_STATE_M | GHPM_STATE_M)), + GPUEB_POLL_US, GPUEB_TIMEOUT_US); + if (ret) { + dev_err(dev, "timed out waiting for EB to power on\n"); + return ret; + } + + mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M, + GHPM_ENABLE_M); + + mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M, 0); + mtk_mfg_update_reg_bits(mfg->rpc + RPC_GHPM_CFG0_CON, GHPM_ON_SEQ_M, + GHPM_ON_SEQ_M); + + mtk_mfg_update_reg_bits(mfg->rpc + mfg->ghpm_en_reg, GHPM_ENABLE_M, 0); + + + ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val, + (val & PWR_ACK_M) == PWR_ACK_M, + GPUEB_POLL_US, GPUEB_TIMEOUT_US); + if (ret) { + dev_err(dev, "timed out waiting for EB power ack, val = 0x%X\n", + val); + return ret; + } + + ret = readl_poll_timeout(mfg->gpr + GPR_LP_STATE, val, + (val == EB_ON_RESUME), + GPUEB_POLL_US, GPUEB_TIMEOUT_US); + if (ret) { + dev_err(dev, "timed out waiting for EB to resume, status = 0x%X\n", val); + return ret; + } + + return 0; +} + +static int mtk_mfg_eb_off(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + struct mtk_mfg_ipi_sleep_msg msg = { + .event = 0, + .state = 0, + .magic = GPUEB_SLEEP_MAGIC + }; + u32 val; + int ret; + + ret = mbox_send_message(mfg->slp_mbox->ch, &msg); + if (ret < 0) { + dev_err(dev, "Cannot send sleep command: %pe\n", ERR_PTR(ret)); + return ret; + } + + ret = readl_poll_timeout(mfg->rpc + RPC_PWR_CON, val, + !(val & PWR_ACK_M), GPUEB_POLL_US, + GPUEB_TIMEOUT_US); + + if (ret) { + dev_err(dev, "Timed out waiting for EB to power off, val=0x%08X\n", val); + return ret; + } + + return 0; +} + +/** + * mtk_mfg_send_ipi - synchronously send an IPI message on the gpufreq channel + * @mfg: pointer to this driver instance's private &struct mtk_mfg + * @msg: pointer to a message to send; will have magic filled and response assigned + * + * Send an IPI message on the gpufreq channel, and wait for a response. Once a + * response is received, assign a pointer to the response buffer (valid until + * next response is received) to @msg. + * + * Returns 0 on success, negative errno on failure. + */ +static int mtk_mfg_send_ipi(struct mtk_mfg *mfg, struct mtk_mfg_ipi_msg *msg) +{ + struct device *dev = &mfg->pdev->dev; + unsigned long wait; + int ret; + + msg->magic = mfg->ipi_magic; + + ret = mbox_send_message(mfg->gf_mbox->ch, msg); + if (ret < 0) { + dev_err(dev, "Cannot send GPUFreq IPI command: %pe\n", ERR_PTR(ret)); + return ret; + } + + wait = wait_for_completion_timeout(&mfg->gf_mbox->rx_done, msecs_to_jiffies(500)); + if (!wait) + return -ETIMEDOUT; + + msg = mfg->gf_mbox->rx_data; + + if (msg->u.return_value < 0) { + dev_err(dev, "IPI return: %d\n", msg->u.return_value); + return -EPROTO; + } + + return 0; +} + +static int mtk_mfg_init_shared_mem(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + struct mtk_mfg_ipi_msg msg = {}; + int ret; + + dev_dbg(dev, "clearing GPUEB shared memory, 0x%X bytes\n", mfg->shared_mem_size); + memset_io(mfg->shared_mem, 0, mfg->shared_mem_size); + + msg.cmd = CMD_INIT_SHARED_MEM; + msg.u.shared_mem.base = mfg->shared_mem_phys; + msg.u.shared_mem.size = mfg->shared_mem_size; + + ret = mtk_mfg_send_ipi(mfg, &msg); + if (ret) + return ret; + + if (readl(mfg->shared_mem + GF_REG_MAGIC) != GPUEB_MEM_MAGIC) { + dev_err(dev, "EB did not initialise shared memory correctly\n"); + return -EIO; + } + + return 0; +} + +static int mtk_mfg_power_control(struct mtk_mfg *mfg, bool enabled) +{ + struct mtk_mfg_ipi_msg msg = {}; + + msg.cmd = CMD_POWER_CONTROL; + msg.u.power_state = enabled ? 1 : 0; + + return mtk_mfg_send_ipi(mfg, &msg); +} + +static int mtk_mfg_set_oppidx(struct mtk_mfg *mfg, unsigned int opp_idx) +{ + struct mtk_mfg_ipi_msg msg = {}; + int ret; + + if (opp_idx >= mfg->num_gpu_opps) + return -EINVAL; + + msg.cmd = CMD_FIX_DUAL_TARGET_OPPIDX; + msg.u.dual_commit.gpu_oppidx = opp_idx; + msg.u.dual_commit.stack_oppidx = opp_idx; + + ret = mtk_mfg_send_ipi(mfg, &msg); + if (ret) { + dev_err(&mfg->pdev->dev, "Failed to set OPP %u: %pe\n", + opp_idx, ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static int mtk_mfg_read_opp_tables(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + struct mtk_mfg_opp_entry e = {}; + unsigned int i; + + mfg->num_gpu_opps = readl(mfg->shared_mem + GF_REG_GPU_OPP_NUM); + mfg->num_stack_opps = readl(mfg->shared_mem + GF_REG_STK_OPP_NUM); + + if (mfg->num_gpu_opps > MAX_OPP_NUM || mfg->num_gpu_opps == 0) { + dev_err(dev, "GPU OPP count (%u) out of range %u >= count > 0\n", + mfg->num_gpu_opps, MAX_OPP_NUM); + return -EINVAL; + } + + if (mfg->num_stack_opps && mfg->num_stack_opps > MAX_OPP_NUM) { + dev_err(dev, "Stack OPP count (%u) out of range %u >= count >= 0\n", + mfg->num_stack_opps, MAX_OPP_NUM); + return -EINVAL; + } + + mfg->gpu_opps = devm_kcalloc(dev, mfg->num_gpu_opps, + sizeof(struct dev_pm_opp_data), GFP_KERNEL); + if (!mfg->gpu_opps) + return -ENOMEM; + + if (mfg->num_stack_opps) { + mfg->stack_opps = devm_kcalloc(dev, mfg->num_stack_opps, + sizeof(struct dev_pm_opp_data), GFP_KERNEL); + if (!mfg->stack_opps) + return -ENOMEM; + } + + for (i = 0; i < mfg->num_gpu_opps; i++) { + memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_GPU + i * sizeof(e), + sizeof(e)); + if (mem_is_zero(&e, sizeof(e))) { + dev_err(dev, "ran into an empty GPU OPP at index %u\n", + i); + return -EINVAL; + } + mfg->gpu_opps[i].freq = e.freq_khz * HZ_PER_KHZ; + mfg->gpu_opps[i].u_volt = e.voltage_core * 10; + mfg->gpu_opps[i].level = i; + if (i < mfg->variant->turbo_below) + mfg->gpu_opps[i].turbo = true; + } + + for (i = 0; i < mfg->num_stack_opps; i++) { + memcpy_fromio(&e, mfg->shared_mem + GF_REG_OPP_TABLE_STK + i * sizeof(e), + sizeof(e)); + if (mem_is_zero(&e, sizeof(e))) { + dev_err(dev, "ran into an empty Stack OPP at index %u\n", + i); + return -EINVAL; + } + mfg->stack_opps[i].freq = e.freq_khz * HZ_PER_KHZ; + mfg->stack_opps[i].u_volt = e.voltage_core * 10; + mfg->stack_opps[i].level = i; + if (i < mfg->variant->turbo_below) + mfg->stack_opps[i].turbo = true; + } + + return 0; +} + +static const char *const mtk_mfg_mt8196_clk_names[] = { + "core", + "stack0", + "stack1", +}; + +static const char *const mtk_mfg_mt8196_regulators[] = { + "core", + "stack", + "sram", +}; + +static int mtk_mfg_mt8196_init(struct mtk_mfg *mfg) +{ + void __iomem *e2_base; + + e2_base = devm_platform_ioremap_resource_byname(mfg->pdev, "hw-revision"); + if (IS_ERR(e2_base)) + return dev_err_probe(&mfg->pdev->dev, PTR_ERR(e2_base), + "Couldn't get hw-revision register\n"); + + clk_prepare_enable(mfg->clk_eb); + + if (readl(e2_base) == MFG_MT8196_E2_ID) + mfg->ghpm_en_reg = RPC_DUMMY_REG_2; + else + mfg->ghpm_en_reg = RPC_GHPM_CFG0_CON; + + clk_disable_unprepare(mfg->clk_eb); + + return 0; +} + +static const struct mtk_mfg_variant mtk_mfg_mt8196_variant = { + .clk_names = mtk_mfg_mt8196_clk_names, + .num_clks = ARRAY_SIZE(mtk_mfg_mt8196_clk_names), + .regulator_names = mtk_mfg_mt8196_regulators, + .num_regulators = ARRAY_SIZE(mtk_mfg_mt8196_regulators), + .turbo_below = 7, + .init = mtk_mfg_mt8196_init, +}; + +static void mtk_mfg_mbox_rx_callback(struct mbox_client *cl, void *mssg) +{ + struct mtk_mfg_mbox *mb = container_of(cl, struct mtk_mfg_mbox, cl); + + if (mb->rx_data) + mb->rx_data = memcpy(mb->rx_data, mssg, GPUEB_MBOX_MAX_RX_SIZE); + complete(&mb->rx_done); +} + +static int mtk_mfg_attach_dev(struct generic_pm_domain *pd, struct device *dev) +{ + struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd); + struct dev_pm_opp_data *so = mfg->stack_opps; + struct dev_pm_opp_data *go = mfg->gpu_opps; + struct dev_pm_opp_data *prev_o; + struct dev_pm_opp_data *o; + int i, ret; + + for (i = mfg->num_gpu_opps - 1; i >= 0; i--) { + /* + * Adding the lower of the two OPPs avoids gaps of indices in + * situations where the GPU OPPs are duplicated a couple of + * times when only the Stack OPP is being lowered at that index. + */ + if (i >= mfg->num_stack_opps || go[i].freq < so[i].freq) + o = &go[i]; + else + o = &so[i]; + + /* + * Skip indices where both GPU and Stack OPPs are equal. Nominally, + * OPP core shouldn't care about dupes, but not doing so will cause + * dev_pm_opp_find_freq_ceil_indexed to -ERANGE later down the line. + */ + if (prev_o && prev_o->freq == o->freq) + continue; + + ret = dev_pm_opp_add_dynamic(dev, o); + if (ret) { + dev_err(dev, "Failed to add OPP level %u from PD %s: %pe\n", + o->level, pd->name, ERR_PTR(ret)); + dev_pm_opp_remove_all_dynamic(dev); + return ret; + } + prev_o = o; + } + + return 0; +} + +static void mtk_mfg_detach_dev(struct generic_pm_domain *pd, struct device *dev) +{ + dev_pm_opp_remove_all_dynamic(dev); +} + +static int mtk_mfg_set_performance(struct generic_pm_domain *pd, + unsigned int state) +{ + struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd); + + /* + * pmdomain core intentionally sets a performance state before turning + * a domain on, and after turning it off. For the GPUEB however, it's + * only possible to act on performance requests when the GPUEB is + * powered on. To do this, return cleanly without taking action, and + * defer setting what pmdomain core set in mtk_mfg_power_on. + */ + if (mfg->pd.status != GENPD_STATE_ON) + return 0; + + return mtk_mfg_set_oppidx(mfg, state); +} + +static int mtk_mfg_power_on(struct generic_pm_domain *pd) +{ + struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd); + int ret; + + ret = regulator_bulk_enable(mfg->variant->num_regulators, + mfg->gpu_regs); + if (ret) + return ret; + + ret = clk_prepare_enable(mfg->clk_eb); + if (ret) + goto err_disable_regulators; + + ret = clk_bulk_prepare_enable(mfg->variant->num_clks, mfg->gpu_clks); + if (ret) + goto err_disable_eb_clk; + + ret = mtk_mfg_eb_on(mfg); + if (ret) + goto err_disable_clks; + + mfg->ipi_magic = readl(mfg->gpr + GPR_IPI_MAGIC); + + ret = mtk_mfg_power_control(mfg, true); + if (ret) + goto err_eb_off; + + /* Don't try to set a OPP in probe before OPPs have been read from EB */ + if (mfg->gpu_opps) { + /* The aforementioned deferred setting of pmdomain's state */ + ret = mtk_mfg_set_oppidx(mfg, pd->performance_state); + if (ret) + dev_warn(&mfg->pdev->dev, "Failed to set oppidx in %s\n", __func__); + } + + return 0; + +err_eb_off: + mtk_mfg_eb_off(mfg); +err_disable_clks: + clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks); +err_disable_eb_clk: + clk_disable_unprepare(mfg->clk_eb); +err_disable_regulators: + regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs); + + return ret; +} + +static int mtk_mfg_power_off(struct generic_pm_domain *pd) +{ + struct mtk_mfg *mfg = mtk_mfg_from_genpd(pd); + struct device *dev = &mfg->pdev->dev; + int ret; + + ret = mtk_mfg_power_control(mfg, false); + if (ret) { + dev_err(dev, "power_control failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + ret = mtk_mfg_eb_off(mfg); + if (ret) { + dev_err(dev, "eb_off failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + clk_bulk_disable_unprepare(mfg->variant->num_clks, mfg->gpu_clks); + clk_disable_unprepare(mfg->clk_eb); + ret = regulator_bulk_disable(mfg->variant->num_regulators, mfg->gpu_regs); + if (ret) { + dev_err(dev, "Disabling regulators failed: %pe\n", ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static int mtk_mfg_init_mbox(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + struct mtk_mfg_mbox *gf; + struct mtk_mfg_mbox *slp; + + gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL); + if (!gf) + return -ENOMEM; + + gf->rx_data = devm_kzalloc(dev, GPUEB_MBOX_MAX_RX_SIZE, GFP_KERNEL); + if (!gf->rx_data) + return -ENOMEM; + + gf->mfg = mfg; + init_completion(&gf->rx_done); + gf->cl.dev = dev; + gf->cl.rx_callback = mtk_mfg_mbox_rx_callback; + gf->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC; + gf->ch = mbox_request_channel_byname(&gf->cl, "gpufreq"); + if (IS_ERR(gf->ch)) + return PTR_ERR(gf->ch); + + mfg->gf_mbox = gf; + + slp = devm_kzalloc(dev, sizeof(*slp), GFP_KERNEL); + if (!slp) + return -ENOMEM; + + slp->mfg = mfg; + init_completion(&slp->rx_done); + slp->cl.dev = dev; + slp->cl.tx_tout = GPUEB_TIMEOUT_US / USEC_PER_MSEC; + slp->cl.tx_block = true; + slp->ch = mbox_request_channel_byname(&slp->cl, "sleep"); + if (IS_ERR(slp->ch)) { + mbox_free_channel(gf->ch); + return PTR_ERR(slp->ch); + } + + mfg->slp_mbox = slp; + + return 0; +} + +static int mtk_mfg_init_clk_provider(struct mtk_mfg *mfg) +{ + struct device *dev = &mfg->pdev->dev; + struct clk_hw_onecell_data *clk_data; + int ret; + + clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, 2), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->num = 2; + + mfg->clk_core_hw.init = &mtk_mfg_clk_gpu_init; + mfg->clk_stack_hw.init = &mtk_mfg_clk_stack_init; + + ret = devm_clk_hw_register(dev, &mfg->clk_core_hw); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register GPU core clock\n"); + + ret = devm_clk_hw_register(dev, &mfg->clk_stack_hw); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register GPU stack clock\n"); + + clk_data->hws[0] = &mfg->clk_core_hw; + clk_data->hws[1] = &mfg->clk_stack_hw; + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data); + if (ret) + return dev_err_probe(dev, ret, "Couldn't register clock provider\n"); + + return 0; +} + +static int mtk_mfg_probe(struct platform_device *pdev) +{ + struct mtk_mfg *mfg; + struct device *dev = &pdev->dev; + const struct mtk_mfg_variant *data = of_device_get_match_data(dev); + struct resource res; + int ret, i; + + mfg = devm_kzalloc(dev, sizeof(*mfg), GFP_KERNEL); + if (!mfg) + return -ENOMEM; + + mfg->pdev = pdev; + mfg->variant = data; + + dev_set_drvdata(dev, mfg); + + mfg->gpr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mfg->gpr)) + return dev_err_probe(dev, PTR_ERR(mfg->gpr), + "Couldn't retrieve GPR MMIO registers\n"); + + mfg->rpc = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(mfg->rpc)) + return dev_err_probe(dev, PTR_ERR(mfg->rpc), + "Couldn't retrieve RPC MMIO registers\n"); + + mfg->clk_eb = devm_clk_get(dev, "eb"); + if (IS_ERR(mfg->clk_eb)) + return dev_err_probe(dev, PTR_ERR(mfg->clk_eb), + "Couldn't get 'eb' clock\n"); + + mfg->gpu_clks = devm_kcalloc(dev, data->num_clks, sizeof(*mfg->gpu_clks), + GFP_KERNEL); + if (!mfg->gpu_clks) + return -ENOMEM; + + for (i = 0; i < data->num_clks; i++) + mfg->gpu_clks[i].id = data->clk_names[i]; + + ret = devm_clk_bulk_get(dev, data->num_clks, mfg->gpu_clks); + if (ret) + return dev_err_probe(dev, ret, "Couldn't get GPU clocks\n"); + + mfg->gpu_regs = devm_kcalloc(dev, data->num_regulators, + sizeof(*mfg->gpu_regs), GFP_KERNEL); + if (!mfg->gpu_regs) + return -ENOMEM; + + for (i = 0; i < data->num_regulators; i++) + mfg->gpu_regs[i].supply = data->regulator_names[i]; + + ret = devm_regulator_bulk_get(dev, data->num_regulators, mfg->gpu_regs); + if (ret) + return dev_err_probe(dev, ret, "Couldn't get GPU regulators\n"); + + ret = of_reserved_mem_region_to_resource(dev->of_node, 0, &res); + if (ret) + return dev_err_probe(dev, ret, "Couldn't get GPUEB shared memory\n"); + + mfg->shared_mem = devm_ioremap(dev, res.start, resource_size(&res)); + if (!mfg->shared_mem) + return dev_err_probe(dev, -ENOMEM, "Can't ioremap GPUEB shared memory\n"); + mfg->shared_mem_size = resource_size(&res); + mfg->shared_mem_phys = res.start; + + if (data->init) { + ret = data->init(mfg); + if (ret) + return dev_err_probe(dev, ret, "Variant init failed\n"); + } + + mfg->pd.name = dev_name(dev); + mfg->pd.attach_dev = mtk_mfg_attach_dev; + mfg->pd.detach_dev = mtk_mfg_detach_dev; + mfg->pd.power_off = mtk_mfg_power_off; + mfg->pd.power_on = mtk_mfg_power_on; + mfg->pd.set_performance_state = mtk_mfg_set_performance; + mfg->pd.flags = GENPD_FLAG_OPP_TABLE_FW; + + ret = pm_genpd_init(&mfg->pd, NULL, false); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialise power domain\n"); + + ret = mtk_mfg_init_mbox(mfg); + if (ret) { + dev_err_probe(dev, ret, "Couldn't initialise mailbox\n"); + goto err_remove_genpd; + } + + ret = mtk_mfg_power_on(&mfg->pd); + if (ret) { + dev_err_probe(dev, ret, "Failed to power on MFG\n"); + goto err_free_mbox; + } + + ret = mtk_mfg_init_shared_mem(mfg); + if (ret) { + dev_err_probe(dev, ret, "Couldn't initialize EB shared memory\n"); + goto err_power_off; + } + + ret = mtk_mfg_read_opp_tables(mfg); + if (ret) { + dev_err_probe(dev, ret, "Error reading OPP tables from EB\n"); + goto err_power_off; + } + + ret = mtk_mfg_init_clk_provider(mfg); + if (ret) + goto err_power_off; + + ret = of_genpd_add_provider_simple(dev->of_node, &mfg->pd); + if (ret) { + dev_err_probe(dev, ret, "Failed to add pmdomain provider\n"); + goto err_power_off; + } + + return 0; + +err_power_off: + mtk_mfg_power_off(&mfg->pd); +err_free_mbox: + mbox_free_channel(mfg->slp_mbox->ch); + mfg->slp_mbox->ch = NULL; + mbox_free_channel(mfg->gf_mbox->ch); + mfg->gf_mbox->ch = NULL; +err_remove_genpd: + pm_genpd_remove(&mfg->pd); + + return ret; +} + +static const struct of_device_id mtk_mfg_of_match[] = { + { .compatible = "mediatek,mt8196-gpufreq", .data = &mtk_mfg_mt8196_variant }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_mfg_of_match); + +static void mtk_mfg_remove(struct platform_device *pdev) +{ + struct mtk_mfg *mfg = dev_get_drvdata(&pdev->dev); + + if (mtk_mfg_is_powered_on(mfg)) + mtk_mfg_power_off(&mfg->pd); + + of_genpd_del_provider(pdev->dev.of_node); + pm_genpd_remove(&mfg->pd); + + mbox_free_channel(mfg->gf_mbox->ch); + mfg->gf_mbox->ch = NULL; + + mbox_free_channel(mfg->slp_mbox->ch); + mfg->slp_mbox->ch = NULL; +} + +static struct platform_driver mtk_mfg_driver = { + .driver = { + .name = "mtk-mfg-pmdomain", + .of_match_table = mtk_mfg_of_match, + .suppress_bind_attrs = true, + }, + .probe = mtk_mfg_probe, + .remove = mtk_mfg_remove, +}; +module_platform_driver(mtk_mfg_driver); + +MODULE_AUTHOR("Nicolas Frattaroli "); +MODULE_DESCRIPTION("MediaTek MFlexGraphics Power Domain Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From b0671a5fd3201efc5dd38254f1917cbbd805936c Mon Sep 17 00:00:00 2001 From: Nicolas Frattaroli Date: Thu, 30 Oct 2025 14:17:10 +0100 Subject: pmdomain: mediatek: mtk-mfg: select MAILBOX in Kconfig The mtk-mfg pmdomain driver calls common mailbox framework functions. If the common mailbox framework is not selected in the kernel's configuration, the build runs into a linker error, as the symbols are absent. The hardware mailbox Kconfig system, MAILBOX, has no dependencies of its own. It's therefore safe to "select" it rather than use "depend on". Declare this "select" dependency in the Kconfig for the driver. Fixes: 1ff1f0db6aec ("pmdomain: mediatek: Add support for MFlexGraphics") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510301311.TcOCnZ1s-lkp@intel.com/ Signed-off-by: Nicolas Frattaroli Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/Kconfig b/drivers/pmdomain/mediatek/Kconfig index b06aaa9690f0..8923e6516441 100644 --- a/drivers/pmdomain/mediatek/Kconfig +++ b/drivers/pmdomain/mediatek/Kconfig @@ -32,6 +32,7 @@ config MTK_MFG_PM_DOMAIN depends on PM depends on OF depends on COMMON_CLK + select MAILBOX select PM_GENERIC_DOMAINS imply MTK_GPUEB_MBOX help -- cgit v1.2.3 From d4aa5960025ab0946c96be050586cdd2346c6471 Mon Sep 17 00:00:00 2001 From: Stanimir Varbanov Date: Fri, 31 Oct 2025 20:33:06 +0200 Subject: pmdomain: bcm: bcm2835-power: Prepare to support BCM2712 BCM2712 has a PM block but lacks asb and rpivid_asb register spaces. To avoid unwanted results add a check for asb existence during probe and also add a new register offset for bcm2712 to control grafx_v3d power domain. The decision to use the new register is implicit - if asb register base is null then the driver is probed for bcm2712 (the other supported SoCs have asb register space). Signed-off-by: Stanimir Varbanov Reviewed-by: Florian Fainelli Signed-off-by: Ulf Hansson --- drivers/pmdomain/bcm/bcm2835-power.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/bcm/bcm2835-power.c b/drivers/pmdomain/bcm/bcm2835-power.c index f5289fd184d0..1d29addfe036 100644 --- a/drivers/pmdomain/bcm/bcm2835-power.c +++ b/drivers/pmdomain/bcm/bcm2835-power.c @@ -79,6 +79,7 @@ #define PM_IMAGE 0x108 #define PM_GRAFX 0x10c #define PM_PROC 0x110 +#define PM_GRAFX_2712 0x304 #define PM_ENAB BIT(12) #define PM_ISPRSTN BIT(8) #define PM_H264RSTN BIT(7) @@ -381,6 +382,9 @@ static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain) return bcm2835_power_power_on(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: + if (!power->asb) + return bcm2835_asb_power_on(pd, PM_GRAFX_2712, + 0, 0, PM_V3DRSTN); return bcm2835_asb_power_on(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); @@ -447,6 +451,9 @@ static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain) return bcm2835_power_power_off(pd, PM_GRAFX); case BCM2835_POWER_DOMAIN_GRAFX_V3D: + if (!power->asb) + return bcm2835_asb_power_off(pd, PM_GRAFX_2712, + 0, 0, PM_V3DRSTN); return bcm2835_asb_power_off(pd, PM_GRAFX, ASB_V3D_M_CTRL, ASB_V3D_S_CTRL, PM_V3DRSTN); @@ -635,10 +642,12 @@ static int bcm2835_power_probe(struct platform_device *pdev) power->asb = pm->asb; power->rpivid_asb = pm->rpivid_asb; - id = readl(power->asb + ASB_AXI_BRDG_ID); - if (id != BCM2835_BRDG_ID /* "BRDG" */) { - dev_err(dev, "ASB register ID returned 0x%08x\n", id); - return -ENODEV; + if (power->asb) { + id = readl(power->asb + ASB_AXI_BRDG_ID); + if (id != BCM2835_BRDG_ID /* "BRDG" */) { + dev_err(dev, "ASB register ID returned 0x%08x\n", id); + return -ENODEV; + } } if (power->rpivid_asb) { -- cgit v1.2.3 From 80ed617a62fe076bbfe700c8289fc2118d724821 Mon Sep 17 00:00:00 2001 From: Brian Masney Date: Thu, 6 Nov 2025 18:40:43 -0500 Subject: pmdomain: mediatek: convert from clk round_rate() to determine_rate() The round_rate() clk ops is deprecated in the clk framework in favor of the determine_rate() clk ops, so let's convert this driver so that round_rate() can be removed from the clk core. Signed-off-by: Brian Masney Reviewed-by: Nicolas Frattaroli Signed-off-by: Ulf Hansson --- drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c index af20111067c0..9bad577b3ae4 100644 --- a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c +++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c @@ -309,11 +309,11 @@ static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw, return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ; } -static long mtk_mfg_round_rate(struct clk_hw *hw, unsigned long rate, - unsigned long *parent_rate) +static int mtk_mfg_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) { /* - * The round_rate callback needs to be implemented to avoid returning + * The determine_rate callback needs to be implemented to avoid returning * the current clock frequency, rather than something even remotely * close to the frequency that was asked for. * @@ -325,7 +325,7 @@ static long mtk_mfg_round_rate(struct clk_hw *hw, unsigned long rate, * high current frequency, breaking the powersave governor in the process. */ - return rate; + return 0; } static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw, @@ -338,12 +338,12 @@ static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw, static const struct clk_ops mtk_mfg_clk_gpu_ops = { .recalc_rate = mtk_mfg_recalc_rate_gpu, - .round_rate = mtk_mfg_round_rate, + .determine_rate = mtk_mfg_determine_rate, }; static const struct clk_ops mtk_mfg_clk_stack_ops = { .recalc_rate = mtk_mfg_recalc_rate_stack, - .round_rate = mtk_mfg_round_rate, + .determine_rate = mtk_mfg_determine_rate, }; static const struct clk_init_data mtk_mfg_clk_gpu_init = { -- cgit v1.2.3 From 0346625cd7f125d056e1424e98d0037a1baa4614 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 5 Nov 2025 10:54:08 +0100 Subject: pmdomain: Extend the genpd governor for CPUs to account for IPIs When the genpd governor for CPUs, tries to select the most optimal idle state for a group of CPUs managed in a PM domain, it fails far too often. On a Dragonboard 410c, which is an arm64 based platform with 4 CPUs in one cluster that is using PSCI OS-initiated mode, we can observe that we often fail when trying to enter the selected idle state. This is certainly a suboptimal behaviour that leads to many unnecessary requests being sent to the PSCI FW. A simple dd operation that reads from the eMMC, to generate some IRQs and I/O handling helps us to understand the problem, while also monitoring the rejected counters in debugfs for the corresponding idle states of the genpd in question. Menu governor: cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 1451 437 91 149 0 S1 65194 558 149 172 0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=500 524288000 bytes (500.0MB) copied, 3.562698 seconds, 140.3MB/s cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 2694 1073 265 892 1 S1 74567 829 561 790 0 The dd completed in ~3.6 seconds and rejects increased with 586. Teo governor: cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 4976 2096 392 1721 2 S1 160661 1893 1309 1904 0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=500 524288000 bytes (500.0MB) copied, 3.543225 seconds, 141.1MB/s cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 5192 2194 433 1830 2 S1 167677 2891 3184 4729 0 The dd completed in ~3.6 seconds and rejects increased with 1916. The main reason to the above problem is pending IPIs for one of the CPUs that is affected by the idle state that the genpd governor selected. This leads to that the PSCI FW refuses to enter it. To improve the behaviour, let's start to take into account pending IPIs for CPUs in the genpd governor, hence we fallback to use the shallower per CPU idle state. Re-testing with this change shows a significant improved behaviour. - Menu governor: cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 2556 878 19 368 1 S1 69974 596 10 152 0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=500 524288000 bytes (500.0MB) copied, 3.522010 seconds, 142.0MB/s cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 3360 1320 28 819 1 S1 70168 710 11 267 0 The dd completed in ~3.5 seconds and rejects increased with 10. - Teo governor cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 5145 1861 39 938 1 S1 188887 3117 51 1975 0 dd if=/dev/mmcblk0 of=/dev/null bs=1M count=500 524288000 bytes (500.0MB) copied, 3.653100 seconds, 136.9MB/s cat /sys/kernel/debug/pm_genpd/power-domain-cluster/idle_states State Time Spent(ms) Usage Rejected Above Below S0 5260 1923 42 1002 1 S1 190849 4033 52 2892 0 The dd completed in ~3.7 seconds and rejects increased with 4. Note that, the rejected counters in genpd are also being accumulated in the rejected counters that are managed by cpuidle, yet on a per CPU idle states basis. Comparing these counters before/after this change, through cpuidle's sysfs interface shows the similar improvements. Signed-off-by: Ulf Hansson --- drivers/pmdomain/governor.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'drivers/pmdomain') diff --git a/drivers/pmdomain/governor.c b/drivers/pmdomain/governor.c index 39359811a930..a46470f2261a 100644 --- a/drivers/pmdomain/governor.c +++ b/drivers/pmdomain/governor.c @@ -404,15 +404,21 @@ static bool cpu_power_down_ok(struct dev_pm_domain *pd) if ((idle_duration_ns >= (genpd->states[i].residency_ns + genpd->states[i].power_off_latency_ns)) && (global_constraint >= (genpd->states[i].power_on_latency_ns + - genpd->states[i].power_off_latency_ns))) { - genpd->state_idx = i; - genpd->gd->last_enter = now; - genpd->gd->reflect_residency = true; - return true; - } + genpd->states[i].power_off_latency_ns))) + break; + } while (--i >= 0); - return false; + if (i < 0) + return false; + + if (cpus_peek_for_pending_ipi(genpd->cpus)) + return false; + + genpd->state_idx = i; + genpd->gd->last_enter = now; + genpd->gd->reflect_residency = true; + return true; } struct dev_power_governor pm_domain_cpu_gov = { -- cgit v1.2.3