diff options
Diffstat (limited to 'drivers/pmdomain/mediatek/mtk-pm-domains.c')
| -rw-r--r-- | drivers/pmdomain/mediatek/mtk-pm-domains.c | 306 |
1 files changed, 278 insertions, 28 deletions
diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c index 9c9323c8c93a..80561d27f2b2 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 <linux/arm-smccc.h> #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/init.h> @@ -15,6 +16,7 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> #include <linux/soc/mediatek/infracfg.h> +#include <linux/soc/mediatek/mtk_sip_svc.h> #include "mt6735-pm-domains.h" #include "mt6795-pm-domains.h" @@ -26,11 +28,18 @@ #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 #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) @@ -45,9 +54,12 @@ #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; + const struct scpsys_hwv_domain_data *hwv_data; struct scpsys *scpsys; int num_clks; struct clk_bulk_data *clks; @@ -71,18 +83,56 @@ 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; } +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_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; @@ -250,6 +300,161 @@ 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; + + 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) + goto err_infra; + + 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); + + 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_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; +}; + +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; + + 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) + 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, + 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); + + 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; +}; + static int scpsys_ctl_pwrseq_on(struct scpsys_domain *pd) { struct scpsys *scpsys = pd->scpsys; @@ -514,6 +719,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 +735,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; + + 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 = &scpsys->soc_data->domains_data[id]; - if (domain_data->sta_mask == 0) { - dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id); + domain_data = NULL; + hwv_domain_data = &scpsys->soc_data->hwv_domains_data[id]; + + break; + default: return ERR_PTR(-EINVAL); } @@ -545,6 +770,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 +830,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 +866,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 +876,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; @@ -932,6 +1168,18 @@ static const struct of_device_id scpsys_of_match[] = { .data = &mt8195_scpsys_data, }, { + .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, + }, + { .compatible = "mediatek,mt8365-power-controller", .data = &mt8365_scpsys_data, }, @@ -946,7 +1194,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) { @@ -954,7 +1202,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; |
