mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-04-04 20:32:27 -04:00
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:
committed by
Ulf Hansson
parent
72b0a7b34b
commit
88914db077
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user