pmdomain: mediatek: Add support for Hardware Voter power domains

New generation SoCs like MT8196/MT6991 feature a new type of power
domains, managed by a Hardware Voter (HWV) helper (through a SoC
internal fixed-function MCU): this is used to collect votes from
both the AP and the various other remote processors present in the
SoC and transparently power on/off various power domains, avoiding
unpowered access of registers in various internal IPs from all of
the integrated remote processors (or from the AP...!).

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

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

Reviewed-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
AngeloGioacchino Del Regno
2025-09-25 16:31:13 +02:00
committed by Ulf Hansson
parent 72b0a7b34b
commit 88914db077
2 changed files with 266 additions and 26 deletions

View File

@@ -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];
if (domain_data->sta_mask == 0) {
dev_err(scpsys->dev, "%pOF: undefined domain id %d\n", node, id);
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 = 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;

View File

@@ -16,7 +16,9 @@
#define MTK_SCPD_SRAM_PDN_INVERTED BIT(9)
#define MTK_SCPD_MODEM_PWRSEQ BIT(10)
#define MTK_SCPD_SKIP_RESET_B BIT(11)
#define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data->caps & (_x))
#define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data ? \
(_scpd)->data->caps & (_x) : \
(_scpd)->hwv_data->caps & (_x))
#define SPM_VDE_PWR_CON 0x0210
#define SPM_MFG_PWR_CON 0x0214
@@ -124,6 +126,18 @@ enum scpsys_rtff_type {
SCPSYS_RTFF_TYPE_MAX
};
/**
* enum scpsys_mtcmos_type - Type of power domain controller
* @SCPSYS_MTCMOS_TYPE_DIRECT_CTL: Power domains are controlled with direct access
* @SCPSYS_MTCMOS_TYPE_HW_VOTER: Hardware-assisted voted power domain control
* @SCPSYS_MTCMOS_TYPE_MAX: Number of supported power domain types
*/
enum scpsys_mtcmos_type {
SCPSYS_MTCMOS_TYPE_DIRECT_CTL = 0,
SCPSYS_MTCMOS_TYPE_HW_VOTER,
SCPSYS_MTCMOS_TYPE_MAX
};
/**
* struct scpsys_domain_data - scp domain data for power on/off flow
* @name: The name of the power domain.
@@ -152,11 +166,40 @@ struct scpsys_domain_data {
int pwr_sta2nd_offs;
};
/**
* struct scpsys_hwv_domain_data - Hardware Voter power domain data
* @name: Name of the power domain
* @set: Offset of the HWV SET register
* @clr: Offset of the HWV CLEAR register
* @done: Offset of the HWV DONE register
* @en: Offset of the HWV ENABLE register
* @set_sta: Offset of the HWV SET STATUS register
* @clr_sta: Offset of the HWV CLEAR STATUS register
* @setclr_bit: The SET/CLR bit to enable/disable the power domain
* @sta_bit: The SET/CLR STA bit to check for on/off ACK
* @caps: The flag for active wake-up action
*/
struct scpsys_hwv_domain_data {
const char *name;
u16 set;
u16 clr;
u16 done;
u16 en;
u16 set_sta;
u16 clr_sta;
u8 setclr_bit;
u8 sta_bit;
u16 caps;
};
struct scpsys_soc_data {
const struct scpsys_domain_data *domains_data;
int num_domains;
const struct scpsys_hwv_domain_data *hwv_domains_data;
int num_hwv_domains;
enum scpsys_bus_prot_block *bus_prot_blocks;
int num_bus_prot_blocks;
enum scpsys_mtcmos_type type;
};
#endif /* __SOC_MEDIATEK_MTK_PM_DOMAINS_H */