mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git
synced 2026-05-02 18:17:50 -04:00
Merge tag 'pmdomain-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm
Pull pmdomain updates from Ulf Hansson:
"Core:
- Move the generic PM domain and its governor to the pmdomain
subsystem
- Drop the unused pm_genpd_opp_to_performance_state()
Providers:
- Convert some providers to let the ->remove() callback return void
- amlogic: Add support for G12A ISP power domain
- arm: Move the SCPI power-domain driver to the pmdomain subsystem
- arm: Move Kconfig options to the pmdomain subsystem
- qcom: Update part number to X1E80100 for the rpmhpd"
* tag 'pmdomain-v6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm:
PM: domains: Move genpd and its governor to the pmdomain subsystem
PM: domains: Drop redundant header for genpd
PM: domains: Drop the unused pm_genpd_opp_to_performance_state()
PM: domains: fix domain_governor kernel-doc warnings
pmdomain: xilinx/zynqmp: Convert to platform remove callback returning void
pmdomain: qcom-cpr: Convert to platform remove callback returning void
pmdomain: imx93-pd: Convert to platform remove callback returning void
pmdomain: imx93-blk-ctrl: Convert to platform remove callback returning void
pmdomain: imx8mp-blk-ctrl: Convert to platform remove callback returning void
pmdomain: imx8m-blk-ctrl: Convert to platform remove callback returning void
pmdomain: imx-gpcv2: Convert to platform remove callback returning void
pmdomain: imx-gpc: Convert to platform remove callback returning void
pmdomain: imx-pgc: Convert to platform remove callback returning void
pmdomain: amlogic: meson-ee-pwrc: add support for G12A ISP power domain
dt-bindings: power: meson-g12a-power: document ISP power domain
firmware: arm_scpi: Move power-domain driver to the pmdomain dir
pmdomain: arm_scmi: Move Kconfig options to the pmdomain subsystem
pmdomain: qcom: rpmhpd: Update part number to X1E80100
dt-bindings: power: rpmpd: Update part number to X1E80100
This commit is contained in:
@@ -4,6 +4,7 @@ menu "PM Domains"
|
||||
source "drivers/pmdomain/actions/Kconfig"
|
||||
source "drivers/pmdomain/amlogic/Kconfig"
|
||||
source "drivers/pmdomain/apple/Kconfig"
|
||||
source "drivers/pmdomain/arm/Kconfig"
|
||||
source "drivers/pmdomain/bcm/Kconfig"
|
||||
source "drivers/pmdomain/imx/Kconfig"
|
||||
source "drivers/pmdomain/mediatek/Kconfig"
|
||||
|
||||
@@ -16,3 +16,4 @@ obj-y += sunxi/
|
||||
obj-y += tegra/
|
||||
obj-y += ti/
|
||||
obj-y += xilinx/
|
||||
obj-y += core.o governor.o
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
|
||||
#define G12A_HHI_NANOQ_MEM_PD_REG0 (0x43 << 2)
|
||||
#define G12A_HHI_NANOQ_MEM_PD_REG1 (0x44 << 2)
|
||||
#define G12A_HHI_ISP_MEM_PD_REG0 (0x45 << 2)
|
||||
#define G12A_HHI_ISP_MEM_PD_REG1 (0x46 << 2)
|
||||
|
||||
struct meson_ee_pwrc;
|
||||
struct meson_ee_pwrc_domain;
|
||||
@@ -115,6 +117,13 @@ static struct meson_ee_pwrc_top_domain g12a_pwrc_nna = {
|
||||
.iso_mask = BIT(16) | BIT(17),
|
||||
};
|
||||
|
||||
static struct meson_ee_pwrc_top_domain g12a_pwrc_isp = {
|
||||
.sleep_reg = GX_AO_RTI_GEN_PWR_SLEEP0,
|
||||
.sleep_mask = BIT(18) | BIT(19),
|
||||
.iso_reg = GX_AO_RTI_GEN_PWR_ISO0,
|
||||
.iso_mask = BIT(18) | BIT(19),
|
||||
};
|
||||
|
||||
/* Memory PD Domains */
|
||||
|
||||
#define VPU_MEMPD(__reg) \
|
||||
@@ -231,6 +240,11 @@ static struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_nna[] = {
|
||||
{ G12A_HHI_NANOQ_MEM_PD_REG1, GENMASK(31, 0) },
|
||||
};
|
||||
|
||||
static struct meson_ee_pwrc_mem_domain g12a_pwrc_mem_isp[] = {
|
||||
{ G12A_HHI_ISP_MEM_PD_REG0, GENMASK(31, 0) },
|
||||
{ G12A_HHI_ISP_MEM_PD_REG1, GENMASK(31, 0) },
|
||||
};
|
||||
|
||||
#define VPU_PD(__name, __top_pd, __mem, __is_pwr_off, __resets, __clks) \
|
||||
{ \
|
||||
.name = __name, \
|
||||
@@ -269,6 +283,8 @@ static struct meson_ee_pwrc_domain_desc g12a_pwrc_domains[] = {
|
||||
[PWRC_G12A_ETH_ID] = MEM_PD("ETH", meson_pwrc_mem_eth),
|
||||
[PWRC_G12A_NNA_ID] = TOP_PD("NNA", &g12a_pwrc_nna, g12a_pwrc_mem_nna,
|
||||
pwrc_ee_is_powered_off),
|
||||
[PWRC_G12A_ISP_ID] = TOP_PD("ISP", &g12a_pwrc_isp, g12a_pwrc_mem_isp,
|
||||
pwrc_ee_is_powered_off),
|
||||
};
|
||||
|
||||
static struct meson_ee_pwrc_domain_desc gxbb_pwrc_domains[] = {
|
||||
|
||||
37
drivers/pmdomain/arm/Kconfig
Normal file
37
drivers/pmdomain/arm/Kconfig
Normal file
@@ -0,0 +1,37 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config ARM_SCMI_PERF_DOMAIN
|
||||
tristate "SCMI performance domain driver"
|
||||
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
|
||||
default y
|
||||
select PM_GENERIC_DOMAINS if PM
|
||||
help
|
||||
This enables support for the SCMI performance domains which can be
|
||||
enabled or disabled via the SCP firmware.
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called scmi_perf_domain.
|
||||
|
||||
config ARM_SCMI_POWER_DOMAIN
|
||||
tristate "SCMI power domain driver"
|
||||
depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
|
||||
default y
|
||||
select PM_GENERIC_DOMAINS if PM
|
||||
help
|
||||
This enables support for the SCMI power domains which can be
|
||||
enabled or disabled via the SCP firmware
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called scmi_pm_domain. Note this may needed early in boot
|
||||
before rootfs may be available.
|
||||
|
||||
config ARM_SCPI_POWER_DOMAIN
|
||||
tristate "SCPI power domain driver"
|
||||
depends on ARM_SCPI_PROTOCOL || (COMPILE_TEST && OF)
|
||||
default y
|
||||
select PM_GENERIC_DOMAINS if PM
|
||||
help
|
||||
This enables support for the SCPI power domains which can be
|
||||
enabled or disabled via the SCP firmware
|
||||
|
||||
This driver can also be built as a module. If so, the module will be
|
||||
called scpi_pm_domain.
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
obj-$(CONFIG_ARM_SCMI_PERF_DOMAIN) += scmi_perf_domain.o
|
||||
obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
|
||||
obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o
|
||||
|
||||
157
drivers/pmdomain/arm/scpi_pm_domain.c
Normal file
157
drivers/pmdomain/arm/scpi_pm_domain.c
Normal file
@@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* SCPI Generic power domain support.
|
||||
*
|
||||
* Copyright (C) 2016 ARM Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/scpi_protocol.h>
|
||||
|
||||
struct scpi_pm_domain {
|
||||
struct generic_pm_domain genpd;
|
||||
struct scpi_ops *ops;
|
||||
u32 domain;
|
||||
};
|
||||
|
||||
/*
|
||||
* These device power state values are not well-defined in the specification.
|
||||
* In case, different implementations use different values, we can make these
|
||||
* specific to compatibles rather than getting these values from device tree.
|
||||
*/
|
||||
enum scpi_power_domain_state {
|
||||
SCPI_PD_STATE_ON = 0,
|
||||
SCPI_PD_STATE_OFF = 3,
|
||||
};
|
||||
|
||||
#define to_scpi_pd(gpd) container_of(gpd, struct scpi_pm_domain, genpd)
|
||||
|
||||
static int scpi_pd_power(struct scpi_pm_domain *pd, bool power_on)
|
||||
{
|
||||
int ret;
|
||||
enum scpi_power_domain_state state;
|
||||
|
||||
if (power_on)
|
||||
state = SCPI_PD_STATE_ON;
|
||||
else
|
||||
state = SCPI_PD_STATE_OFF;
|
||||
|
||||
ret = pd->ops->device_set_power_state(pd->domain, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return !(state == pd->ops->device_get_power_state(pd->domain));
|
||||
}
|
||||
|
||||
static int scpi_pd_power_on(struct generic_pm_domain *domain)
|
||||
{
|
||||
struct scpi_pm_domain *pd = to_scpi_pd(domain);
|
||||
|
||||
return scpi_pd_power(pd, true);
|
||||
}
|
||||
|
||||
static int scpi_pd_power_off(struct generic_pm_domain *domain)
|
||||
{
|
||||
struct scpi_pm_domain *pd = to_scpi_pd(domain);
|
||||
|
||||
return scpi_pd_power(pd, false);
|
||||
}
|
||||
|
||||
static int scpi_pm_domain_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct scpi_pm_domain *scpi_pd;
|
||||
struct genpd_onecell_data *scpi_pd_data;
|
||||
struct generic_pm_domain **domains;
|
||||
struct scpi_ops *scpi_ops;
|
||||
int ret, num_domains, i;
|
||||
|
||||
scpi_ops = get_scpi_ops();
|
||||
if (!scpi_ops)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
if (!np) {
|
||||
dev_err(dev, "device tree node not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!scpi_ops->device_set_power_state ||
|
||||
!scpi_ops->device_get_power_state) {
|
||||
dev_err(dev, "power domains not supported in the firmware\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "num-domains", &num_domains);
|
||||
if (ret) {
|
||||
dev_err(dev, "number of domains not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
scpi_pd = devm_kcalloc(dev, num_domains, sizeof(*scpi_pd), GFP_KERNEL);
|
||||
if (!scpi_pd)
|
||||
return -ENOMEM;
|
||||
|
||||
scpi_pd_data = devm_kzalloc(dev, sizeof(*scpi_pd_data), GFP_KERNEL);
|
||||
if (!scpi_pd_data)
|
||||
return -ENOMEM;
|
||||
|
||||
domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
|
||||
if (!domains)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < num_domains; i++, scpi_pd++) {
|
||||
domains[i] = &scpi_pd->genpd;
|
||||
|
||||
scpi_pd->domain = i;
|
||||
scpi_pd->ops = scpi_ops;
|
||||
scpi_pd->genpd.name = devm_kasprintf(dev, GFP_KERNEL,
|
||||
"%pOFn.%d", np, i);
|
||||
if (!scpi_pd->genpd.name) {
|
||||
dev_err(dev, "Failed to allocate genpd name:%pOFn.%d\n",
|
||||
np, i);
|
||||
continue;
|
||||
}
|
||||
scpi_pd->genpd.power_off = scpi_pd_power_off;
|
||||
scpi_pd->genpd.power_on = scpi_pd_power_on;
|
||||
|
||||
/*
|
||||
* Treat all power domains as off at boot.
|
||||
*
|
||||
* The SCP firmware itself may have switched on some domains,
|
||||
* but for reference counting purpose, keep it this way.
|
||||
*/
|
||||
pm_genpd_init(&scpi_pd->genpd, NULL, true);
|
||||
}
|
||||
|
||||
scpi_pd_data->domains = domains;
|
||||
scpi_pd_data->num_domains = num_domains;
|
||||
|
||||
of_genpd_add_provider_onecell(np, scpi_pd_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id scpi_power_domain_ids[] = {
|
||||
{ .compatible = "arm,scpi-power-domains", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, scpi_power_domain_ids);
|
||||
|
||||
static struct platform_driver scpi_power_domain_driver = {
|
||||
.driver = {
|
||||
.name = "scpi_power_domain",
|
||||
.of_match_table = scpi_power_domain_ids,
|
||||
},
|
||||
.probe = scpi_pm_domain_probe,
|
||||
};
|
||||
module_platform_driver(scpi_power_domain_driver);
|
||||
|
||||
MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
|
||||
MODULE_DESCRIPTION("ARM SCPI power domain driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
3399
drivers/pmdomain/core.c
Normal file
3399
drivers/pmdomain/core.c
Normal file
File diff suppressed because it is too large
Load Diff
418
drivers/pmdomain/governor.c
Normal file
418
drivers/pmdomain/governor.c
Normal file
@@ -0,0 +1,418 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* drivers/base/power/domain_governor.c - Governors for device PM domains.
|
||||
*
|
||||
* Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/hrtimer.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/ktime.h>
|
||||
|
||||
static int dev_update_qos_constraint(struct device *dev, void *data)
|
||||
{
|
||||
s64 *constraint_ns_p = data;
|
||||
s64 constraint_ns;
|
||||
|
||||
if (dev->power.subsys_data && dev->power.subsys_data->domain_data) {
|
||||
struct gpd_timing_data *td = dev_gpd_data(dev)->td;
|
||||
|
||||
/*
|
||||
* Only take suspend-time QoS constraints of devices into
|
||||
* account, because constraints updated after the device has
|
||||
* been suspended are not guaranteed to be taken into account
|
||||
* anyway. In order for them to take effect, the device has to
|
||||
* be resumed and suspended again.
|
||||
*/
|
||||
constraint_ns = td ? td->effective_constraint_ns :
|
||||
PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS;
|
||||
} else {
|
||||
/*
|
||||
* The child is not in a domain and there's no info on its
|
||||
* suspend/resume latencies, so assume them to be negligible and
|
||||
* take its current PM QoS constraint (that's the only thing
|
||||
* known at this point anyway).
|
||||
*/
|
||||
constraint_ns = dev_pm_qos_read_value(dev, DEV_PM_QOS_RESUME_LATENCY);
|
||||
constraint_ns *= NSEC_PER_USEC;
|
||||
}
|
||||
|
||||
if (constraint_ns < *constraint_ns_p)
|
||||
*constraint_ns_p = constraint_ns;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* default_suspend_ok - Default PM domain governor routine to suspend devices.
|
||||
* @dev: Device to check.
|
||||
*
|
||||
* Returns: true if OK to suspend, false if not OK to suspend
|
||||
*/
|
||||
static bool default_suspend_ok(struct device *dev)
|
||||
{
|
||||
struct gpd_timing_data *td = dev_gpd_data(dev)->td;
|
||||
unsigned long flags;
|
||||
s64 constraint_ns;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
spin_lock_irqsave(&dev->power.lock, flags);
|
||||
|
||||
if (!td->constraint_changed) {
|
||||
bool ret = td->cached_suspend_ok;
|
||||
|
||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||
return ret;
|
||||
}
|
||||
td->constraint_changed = false;
|
||||
td->cached_suspend_ok = false;
|
||||
td->effective_constraint_ns = 0;
|
||||
constraint_ns = __dev_pm_qos_resume_latency(dev);
|
||||
|
||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||
|
||||
if (constraint_ns == 0)
|
||||
return false;
|
||||
|
||||
constraint_ns *= NSEC_PER_USEC;
|
||||
/*
|
||||
* We can walk the children without any additional locking, because
|
||||
* they all have been suspended at this point and their
|
||||
* effective_constraint_ns fields won't be modified in parallel with us.
|
||||
*/
|
||||
if (!dev->power.ignore_children)
|
||||
device_for_each_child(dev, &constraint_ns,
|
||||
dev_update_qos_constraint);
|
||||
|
||||
if (constraint_ns == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS) {
|
||||
/* "No restriction", so the device is allowed to suspend. */
|
||||
td->effective_constraint_ns = PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS;
|
||||
td->cached_suspend_ok = true;
|
||||
} else if (constraint_ns == 0) {
|
||||
/*
|
||||
* This triggers if one of the children that don't belong to a
|
||||
* domain has a zero PM QoS constraint and it's better not to
|
||||
* suspend then. effective_constraint_ns is zero already and
|
||||
* cached_suspend_ok is false, so bail out.
|
||||
*/
|
||||
return false;
|
||||
} else {
|
||||
constraint_ns -= td->suspend_latency_ns +
|
||||
td->resume_latency_ns;
|
||||
/*
|
||||
* effective_constraint_ns is zero already and cached_suspend_ok
|
||||
* is false, so if the computed value is not positive, return
|
||||
* right away.
|
||||
*/
|
||||
if (constraint_ns <= 0)
|
||||
return false;
|
||||
|
||||
td->effective_constraint_ns = constraint_ns;
|
||||
td->cached_suspend_ok = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The children have been suspended already, so we don't need to take
|
||||
* their suspend latencies into account here.
|
||||
*/
|
||||
return td->cached_suspend_ok;
|
||||
}
|
||||
|
||||
static void update_domain_next_wakeup(struct generic_pm_domain *genpd, ktime_t now)
|
||||
{
|
||||
ktime_t domain_wakeup = KTIME_MAX;
|
||||
ktime_t next_wakeup;
|
||||
struct pm_domain_data *pdd;
|
||||
struct gpd_link *link;
|
||||
|
||||
if (!(genpd->flags & GENPD_FLAG_MIN_RESIDENCY))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Devices that have a predictable wakeup pattern, may specify
|
||||
* their next wakeup. Let's find the next wakeup from all the
|
||||
* devices attached to this domain and from all the sub-domains.
|
||||
* It is possible that component's a next wakeup may have become
|
||||
* stale when we read that here. We will ignore to ensure the domain
|
||||
* is able to enter its optimal idle state.
|
||||
*/
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
next_wakeup = to_gpd_data(pdd)->td->next_wakeup;
|
||||
if (next_wakeup != KTIME_MAX && !ktime_before(next_wakeup, now))
|
||||
if (ktime_before(next_wakeup, domain_wakeup))
|
||||
domain_wakeup = next_wakeup;
|
||||
}
|
||||
|
||||
list_for_each_entry(link, &genpd->parent_links, parent_node) {
|
||||
struct genpd_governor_data *cgd = link->child->gd;
|
||||
|
||||
next_wakeup = cgd ? cgd->next_wakeup : KTIME_MAX;
|
||||
if (next_wakeup != KTIME_MAX && !ktime_before(next_wakeup, now))
|
||||
if (ktime_before(next_wakeup, domain_wakeup))
|
||||
domain_wakeup = next_wakeup;
|
||||
}
|
||||
|
||||
genpd->gd->next_wakeup = domain_wakeup;
|
||||
}
|
||||
|
||||
static bool next_wakeup_allows_state(struct generic_pm_domain *genpd,
|
||||
unsigned int state, ktime_t now)
|
||||
{
|
||||
ktime_t domain_wakeup = genpd->gd->next_wakeup;
|
||||
s64 idle_time_ns, min_sleep_ns;
|
||||
|
||||
min_sleep_ns = genpd->states[state].power_off_latency_ns +
|
||||
genpd->states[state].residency_ns;
|
||||
|
||||
idle_time_ns = ktime_to_ns(ktime_sub(domain_wakeup, now));
|
||||
|
||||
return idle_time_ns >= min_sleep_ns;
|
||||
}
|
||||
|
||||
static bool __default_power_down_ok(struct dev_pm_domain *pd,
|
||||
unsigned int state)
|
||||
{
|
||||
struct generic_pm_domain *genpd = pd_to_genpd(pd);
|
||||
struct gpd_link *link;
|
||||
struct pm_domain_data *pdd;
|
||||
s64 min_off_time_ns;
|
||||
s64 off_on_time_ns;
|
||||
|
||||
off_on_time_ns = genpd->states[state].power_off_latency_ns +
|
||||
genpd->states[state].power_on_latency_ns;
|
||||
|
||||
min_off_time_ns = -1;
|
||||
/*
|
||||
* Check if subdomains can be off for enough time.
|
||||
*
|
||||
* All subdomains have been powered off already at this point.
|
||||
*/
|
||||
list_for_each_entry(link, &genpd->parent_links, parent_node) {
|
||||
struct genpd_governor_data *cgd = link->child->gd;
|
||||
|
||||
s64 sd_max_off_ns = cgd ? cgd->max_off_time_ns : -1;
|
||||
|
||||
if (sd_max_off_ns < 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Check if the subdomain is allowed to be off long enough for
|
||||
* the current domain to turn off and on (that's how much time
|
||||
* it will have to wait worst case).
|
||||
*/
|
||||
if (sd_max_off_ns <= off_on_time_ns)
|
||||
return false;
|
||||
|
||||
if (min_off_time_ns > sd_max_off_ns || min_off_time_ns < 0)
|
||||
min_off_time_ns = sd_max_off_ns;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the devices in the domain can be off enough time.
|
||||
*/
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node) {
|
||||
struct gpd_timing_data *td;
|
||||
s64 constraint_ns;
|
||||
|
||||
/*
|
||||
* Check if the device is allowed to be off long enough for the
|
||||
* domain to turn off and on (that's how much time it will
|
||||
* have to wait worst case).
|
||||
*/
|
||||
td = to_gpd_data(pdd)->td;
|
||||
constraint_ns = td->effective_constraint_ns;
|
||||
/*
|
||||
* Zero means "no suspend at all" and this runs only when all
|
||||
* devices in the domain are suspended, so it must be positive.
|
||||
*/
|
||||
if (constraint_ns == PM_QOS_RESUME_LATENCY_NO_CONSTRAINT_NS)
|
||||
continue;
|
||||
|
||||
if (constraint_ns <= off_on_time_ns)
|
||||
return false;
|
||||
|
||||
if (min_off_time_ns > constraint_ns || min_off_time_ns < 0)
|
||||
min_off_time_ns = constraint_ns;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the computed minimum device off time is negative, there are no
|
||||
* latency constraints, so the domain can spend arbitrary time in the
|
||||
* "off" state.
|
||||
*/
|
||||
if (min_off_time_ns < 0)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* The difference between the computed minimum subdomain or device off
|
||||
* time and the time needed to turn the domain on is the maximum
|
||||
* theoretical time this domain can spend in the "off" state.
|
||||
*/
|
||||
genpd->gd->max_off_time_ns = min_off_time_ns -
|
||||
genpd->states[state].power_on_latency_ns;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* _default_power_down_ok - Default generic PM domain power off governor routine.
|
||||
* @pd: PM domain to check.
|
||||
* @now: current ktime.
|
||||
*
|
||||
* This routine must be executed under the PM domain's lock.
|
||||
*
|
||||
* Returns: true if OK to power down, false if not OK to power down
|
||||
*/
|
||||
static bool _default_power_down_ok(struct dev_pm_domain *pd, ktime_t now)
|
||||
{
|
||||
struct generic_pm_domain *genpd = pd_to_genpd(pd);
|
||||
struct genpd_governor_data *gd = genpd->gd;
|
||||
int state_idx = genpd->state_count - 1;
|
||||
struct gpd_link *link;
|
||||
|
||||
/*
|
||||
* Find the next wakeup from devices that can determine their own wakeup
|
||||
* to find when the domain would wakeup and do it for every device down
|
||||
* the hierarchy. It is not worth while to sleep if the state's residency
|
||||
* cannot be met.
|
||||
*/
|
||||
update_domain_next_wakeup(genpd, now);
|
||||
if ((genpd->flags & GENPD_FLAG_MIN_RESIDENCY) && (gd->next_wakeup != KTIME_MAX)) {
|
||||
/* Let's find out the deepest domain idle state, the devices prefer */
|
||||
while (state_idx >= 0) {
|
||||
if (next_wakeup_allows_state(genpd, state_idx, now)) {
|
||||
gd->max_off_time_changed = true;
|
||||
break;
|
||||
}
|
||||
state_idx--;
|
||||
}
|
||||
|
||||
if (state_idx < 0) {
|
||||
state_idx = 0;
|
||||
gd->cached_power_down_ok = false;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gd->max_off_time_changed) {
|
||||
genpd->state_idx = gd->cached_power_down_state_idx;
|
||||
return gd->cached_power_down_ok;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to invalidate the cached results for the parents, so
|
||||
* use the observation that default_power_down_ok() is not
|
||||
* going to be called for any parent until this instance
|
||||
* returns.
|
||||
*/
|
||||
list_for_each_entry(link, &genpd->child_links, child_node) {
|
||||
struct genpd_governor_data *pgd = link->parent->gd;
|
||||
|
||||
if (pgd)
|
||||
pgd->max_off_time_changed = true;
|
||||
}
|
||||
|
||||
gd->max_off_time_ns = -1;
|
||||
gd->max_off_time_changed = false;
|
||||
gd->cached_power_down_ok = true;
|
||||
|
||||
/*
|
||||
* Find a state to power down to, starting from the state
|
||||
* determined by the next wakeup.
|
||||
*/
|
||||
while (!__default_power_down_ok(pd, state_idx)) {
|
||||
if (state_idx == 0) {
|
||||
gd->cached_power_down_ok = false;
|
||||
break;
|
||||
}
|
||||
state_idx--;
|
||||
}
|
||||
|
||||
done:
|
||||
genpd->state_idx = state_idx;
|
||||
gd->cached_power_down_state_idx = genpd->state_idx;
|
||||
return gd->cached_power_down_ok;
|
||||
}
|
||||
|
||||
static bool default_power_down_ok(struct dev_pm_domain *pd)
|
||||
{
|
||||
return _default_power_down_ok(pd, ktime_get());
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_IDLE
|
||||
static bool cpu_power_down_ok(struct dev_pm_domain *pd)
|
||||
{
|
||||
struct generic_pm_domain *genpd = pd_to_genpd(pd);
|
||||
struct cpuidle_device *dev;
|
||||
ktime_t domain_wakeup, next_hrtimer;
|
||||
ktime_t now = ktime_get();
|
||||
s64 idle_duration_ns;
|
||||
int cpu, i;
|
||||
|
||||
/* Validate dev PM QoS constraints. */
|
||||
if (!_default_power_down_ok(pd, now))
|
||||
return false;
|
||||
|
||||
if (!(genpd->flags & GENPD_FLAG_CPU_DOMAIN))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Find the next wakeup for any of the online CPUs within the PM domain
|
||||
* and its subdomains. Note, we only need the genpd->cpus, as it already
|
||||
* contains a mask of all CPUs from subdomains.
|
||||
*/
|
||||
domain_wakeup = ktime_set(KTIME_SEC_MAX, 0);
|
||||
for_each_cpu_and(cpu, genpd->cpus, cpu_online_mask) {
|
||||
dev = per_cpu(cpuidle_devices, cpu);
|
||||
if (dev) {
|
||||
next_hrtimer = READ_ONCE(dev->next_hrtimer);
|
||||
if (ktime_before(next_hrtimer, domain_wakeup))
|
||||
domain_wakeup = next_hrtimer;
|
||||
}
|
||||
}
|
||||
|
||||
/* The minimum idle duration is from now - until the next wakeup. */
|
||||
idle_duration_ns = ktime_to_ns(ktime_sub(domain_wakeup, now));
|
||||
if (idle_duration_ns <= 0)
|
||||
return false;
|
||||
|
||||
/* Store the next domain_wakeup to allow consumers to use it. */
|
||||
genpd->gd->next_hrtimer = domain_wakeup;
|
||||
|
||||
/*
|
||||
* Find the deepest idle state that has its residency value satisfied
|
||||
* and by also taking into account the power off latency for the state.
|
||||
* Start at the state picked by the dev PM QoS constraint validation.
|
||||
*/
|
||||
i = genpd->state_idx;
|
||||
do {
|
||||
if (idle_duration_ns >= (genpd->states[i].residency_ns +
|
||||
genpd->states[i].power_off_latency_ns)) {
|
||||
genpd->state_idx = i;
|
||||
return true;
|
||||
}
|
||||
} while (--i >= 0);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dev_power_governor pm_domain_cpu_gov = {
|
||||
.suspend_ok = default_suspend_ok,
|
||||
.power_down_ok = cpu_power_down_ok,
|
||||
};
|
||||
#endif
|
||||
|
||||
struct dev_power_governor simple_qos_governor = {
|
||||
.suspend_ok = default_suspend_ok,
|
||||
.power_down_ok = default_power_down_ok,
|
||||
};
|
||||
|
||||
/*
|
||||
* pm_domain_always_on_gov - A governor implementing an always-on policy
|
||||
*/
|
||||
struct dev_power_governor pm_domain_always_on_gov = {
|
||||
.suspend_ok = default_suspend_ok,
|
||||
};
|
||||
@@ -212,7 +212,7 @@ genpd_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_pgc_power_domain_remove(struct platform_device *pdev)
|
||||
static void imx_pgc_power_domain_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_pm_domain *domain = pdev->dev.platform_data;
|
||||
|
||||
@@ -221,8 +221,6 @@ static int imx_pgc_power_domain_remove(struct platform_device *pdev)
|
||||
pm_genpd_remove(&domain->base);
|
||||
imx_pgc_put_clocks(domain);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id imx_pgc_power_domain_id[] = {
|
||||
@@ -235,7 +233,7 @@ static struct platform_driver imx_pgc_power_domain_driver = {
|
||||
.name = "imx-pgc-pd",
|
||||
},
|
||||
.probe = imx_pgc_power_domain_probe,
|
||||
.remove = imx_pgc_power_domain_remove,
|
||||
.remove_new = imx_pgc_power_domain_remove,
|
||||
.id_table = imx_pgc_power_domain_id,
|
||||
};
|
||||
builtin_platform_driver(imx_pgc_power_domain_driver)
|
||||
@@ -511,7 +509,7 @@ static int imx_gpc_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_gpc_remove(struct platform_device *pdev)
|
||||
static void imx_gpc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *pgc_node;
|
||||
int ret;
|
||||
@@ -521,7 +519,7 @@ static int imx_gpc_remove(struct platform_device *pdev)
|
||||
/* bail out if DT too old and doesn't provide the necessary info */
|
||||
if (!of_property_read_bool(pdev->dev.of_node, "#power-domain-cells") &&
|
||||
!pgc_node)
|
||||
return 0;
|
||||
return;
|
||||
|
||||
/*
|
||||
* If the old DT binding is used the toplevel driver needs to
|
||||
@@ -531,16 +529,20 @@ static int imx_gpc_remove(struct platform_device *pdev)
|
||||
of_genpd_del_provider(pdev->dev.of_node);
|
||||
|
||||
ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_PU].base);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to remove PU power domain (%pe)\n",
|
||||
ERR_PTR(ret));
|
||||
return;
|
||||
}
|
||||
imx_pgc_put_clocks(&imx_gpc_domains[GPC_PGC_DOMAIN_PU]);
|
||||
|
||||
ret = pm_genpd_remove(&imx_gpc_domains[GPC_PGC_DOMAIN_ARM].base);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to remove ARM power domain (%pe)\n",
|
||||
ERR_PTR(ret));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_gpc_driver = {
|
||||
@@ -549,6 +551,6 @@ static struct platform_driver imx_gpc_driver = {
|
||||
.of_match_table = imx_gpc_dt_ids,
|
||||
},
|
||||
.probe = imx_gpc_probe,
|
||||
.remove = imx_gpc_remove,
|
||||
.remove_new = imx_gpc_remove,
|
||||
};
|
||||
builtin_platform_driver(imx_gpc_driver)
|
||||
|
||||
@@ -1373,7 +1373,7 @@ out_domain_unmap:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_pgc_domain_remove(struct platform_device *pdev)
|
||||
static void imx_pgc_domain_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_pgc_domain *domain = pdev->dev.platform_data;
|
||||
|
||||
@@ -1385,8 +1385,6 @@ static int imx_pgc_domain_remove(struct platform_device *pdev)
|
||||
domain->bits.map, 0);
|
||||
|
||||
pm_runtime_disable(domain->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
@@ -1430,7 +1428,7 @@ static struct platform_driver imx_pgc_domain_driver = {
|
||||
.pm = &imx_pgc_domain_pm_ops,
|
||||
},
|
||||
.probe = imx_pgc_domain_probe,
|
||||
.remove = imx_pgc_domain_remove,
|
||||
.remove_new = imx_pgc_domain_remove,
|
||||
.id_table = imx_pgc_domain_id,
|
||||
};
|
||||
builtin_platform_driver(imx_pgc_domain_driver)
|
||||
|
||||
@@ -330,7 +330,7 @@ cleanup_pds:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx8m_blk_ctrl_remove(struct platform_device *pdev)
|
||||
static void imx8m_blk_ctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx8m_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
|
||||
int i;
|
||||
@@ -347,8 +347,6 @@ static int imx8m_blk_ctrl_remove(struct platform_device *pdev)
|
||||
dev_pm_genpd_remove_notifier(bc->bus_power_dev);
|
||||
|
||||
dev_pm_domain_detach(bc->bus_power_dev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
@@ -888,7 +886,7 @@ MODULE_DEVICE_TABLE(of, imx8m_blk_ctrl_of_match);
|
||||
|
||||
static struct platform_driver imx8m_blk_ctrl_driver = {
|
||||
.probe = imx8m_blk_ctrl_probe,
|
||||
.remove = imx8m_blk_ctrl_remove,
|
||||
.remove_new = imx8m_blk_ctrl_remove,
|
||||
.driver = {
|
||||
.name = "imx8m-blk-ctrl",
|
||||
.pm = &imx8m_blk_ctrl_pm_ops,
|
||||
|
||||
@@ -760,7 +760,7 @@ cleanup_pds:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx8mp_blk_ctrl_remove(struct platform_device *pdev)
|
||||
static void imx8mp_blk_ctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx8mp_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
|
||||
int i;
|
||||
@@ -777,8 +777,6 @@ static int imx8mp_blk_ctrl_remove(struct platform_device *pdev)
|
||||
dev_pm_genpd_remove_notifier(bc->bus_power_dev);
|
||||
|
||||
dev_pm_domain_detach(bc->bus_power_dev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
@@ -856,7 +854,7 @@ MODULE_DEVICE_TABLE(of, imx8mp_blk_ctrl_of_match);
|
||||
|
||||
static struct platform_driver imx8mp_blk_ctrl_driver = {
|
||||
.probe = imx8mp_blk_ctrl_probe,
|
||||
.remove = imx8mp_blk_ctrl_remove,
|
||||
.remove_new = imx8mp_blk_ctrl_remove,
|
||||
.driver = {
|
||||
.name = "imx8mp-blk-ctrl",
|
||||
.pm = &imx8mp_blk_ctrl_pm_ops,
|
||||
|
||||
@@ -306,7 +306,7 @@ cleanup_pds:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx93_blk_ctrl_remove(struct platform_device *pdev)
|
||||
static void imx93_blk_ctrl_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx93_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
|
||||
int i;
|
||||
@@ -318,8 +318,6 @@ static int imx93_blk_ctrl_remove(struct platform_device *pdev)
|
||||
|
||||
pm_genpd_remove(&domain->genpd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] = {
|
||||
@@ -438,7 +436,7 @@ MODULE_DEVICE_TABLE(of, imx93_blk_ctrl_of_match);
|
||||
|
||||
static struct platform_driver imx93_blk_ctrl_driver = {
|
||||
.probe = imx93_blk_ctrl_probe,
|
||||
.remove = imx93_blk_ctrl_remove,
|
||||
.remove_new = imx93_blk_ctrl_remove,
|
||||
.driver = {
|
||||
.name = "imx93-blk-ctrl",
|
||||
.of_match_table = imx93_blk_ctrl_of_match,
|
||||
|
||||
@@ -83,7 +83,7 @@ static int imx93_pd_off(struct generic_pm_domain *genpd)
|
||||
return 0;
|
||||
};
|
||||
|
||||
static int imx93_pd_remove(struct platform_device *pdev)
|
||||
static void imx93_pd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx93_power_domain *domain = platform_get_drvdata(pdev);
|
||||
struct device *dev = &pdev->dev;
|
||||
@@ -94,8 +94,6 @@ static int imx93_pd_remove(struct platform_device *pdev)
|
||||
|
||||
of_genpd_del_provider(np);
|
||||
pm_genpd_remove(&domain->genpd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx93_pd_probe(struct platform_device *pdev)
|
||||
@@ -167,7 +165,7 @@ static struct platform_driver imx93_power_domain_driver = {
|
||||
.of_match_table = imx93_pd_ids,
|
||||
},
|
||||
.probe = imx93_pd_probe,
|
||||
.remove = imx93_pd_remove,
|
||||
.remove_new = imx93_pd_remove,
|
||||
};
|
||||
module_platform_driver(imx93_power_domain_driver);
|
||||
|
||||
|
||||
@@ -1712,7 +1712,7 @@ err_remove_genpd:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cpr_remove(struct platform_device *pdev)
|
||||
static void cpr_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cpr_drv *drv = platform_get_drvdata(pdev);
|
||||
|
||||
@@ -1725,8 +1725,6 @@ static int cpr_remove(struct platform_device *pdev)
|
||||
pm_genpd_remove(&drv->pd);
|
||||
|
||||
debugfs_remove_recursive(drv->debugfs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id cpr_match_table[] = {
|
||||
@@ -1737,7 +1735,7 @@ MODULE_DEVICE_TABLE(of, cpr_match_table);
|
||||
|
||||
static struct platform_driver cpr_driver = {
|
||||
.probe = cpr_probe,
|
||||
.remove = cpr_remove,
|
||||
.remove_new = cpr_remove,
|
||||
.driver = {
|
||||
.name = "qcom-cpr",
|
||||
.of_match_table = cpr_match_table,
|
||||
|
||||
@@ -598,8 +598,8 @@ static const struct rpmhpd_desc sc8280xp_desc = {
|
||||
.num_pds = ARRAY_SIZE(sc8280xp_rpmhpds),
|
||||
};
|
||||
|
||||
/* SC8380xp RPMH powerdomains */
|
||||
static struct rpmhpd *sc8380xp_rpmhpds[] = {
|
||||
/* X1E80100 RPMH powerdomains */
|
||||
static struct rpmhpd *x1e80100_rpmhpds[] = {
|
||||
[RPMHPD_CX] = &cx,
|
||||
[RPMHPD_CX_AO] = &cx_ao,
|
||||
[RPMHPD_EBI] = &ebi,
|
||||
@@ -615,9 +615,9 @@ static struct rpmhpd *sc8380xp_rpmhpds[] = {
|
||||
[RPMHPD_GMXC] = &gmxc,
|
||||
};
|
||||
|
||||
static const struct rpmhpd_desc sc8380xp_desc = {
|
||||
.rpmhpds = sc8380xp_rpmhpds,
|
||||
.num_pds = ARRAY_SIZE(sc8380xp_rpmhpds),
|
||||
static const struct rpmhpd_desc x1e80100_desc = {
|
||||
.rpmhpds = x1e80100_rpmhpds,
|
||||
.num_pds = ARRAY_SIZE(x1e80100_rpmhpds),
|
||||
};
|
||||
|
||||
static const struct of_device_id rpmhpd_match_table[] = {
|
||||
@@ -629,7 +629,6 @@ static const struct of_device_id rpmhpd_match_table[] = {
|
||||
{ .compatible = "qcom,sc7280-rpmhpd", .data = &sc7280_desc },
|
||||
{ .compatible = "qcom,sc8180x-rpmhpd", .data = &sc8180x_desc },
|
||||
{ .compatible = "qcom,sc8280xp-rpmhpd", .data = &sc8280xp_desc },
|
||||
{ .compatible = "qcom,sc8380xp-rpmhpd", .data = &sc8380xp_desc },
|
||||
{ .compatible = "qcom,sdm670-rpmhpd", .data = &sdm670_desc },
|
||||
{ .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc },
|
||||
{ .compatible = "qcom,sdx55-rpmhpd", .data = &sdx55_desc},
|
||||
@@ -643,6 +642,7 @@ static const struct of_device_id rpmhpd_match_table[] = {
|
||||
{ .compatible = "qcom,sm8450-rpmhpd", .data = &sm8450_desc },
|
||||
{ .compatible = "qcom,sm8550-rpmhpd", .data = &sm8550_desc },
|
||||
{ .compatible = "qcom,sm8650-rpmhpd", .data = &sm8650_desc },
|
||||
{ .compatible = "qcom,x1e80100-rpmhpd", .data = &x1e80100_desc },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, rpmhpd_match_table);
|
||||
|
||||
@@ -293,11 +293,9 @@ static int zynqmp_gpd_probe(struct platform_device *pdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int zynqmp_gpd_remove(struct platform_device *pdev)
|
||||
static void zynqmp_gpd_remove(struct platform_device *pdev)
|
||||
{
|
||||
of_genpd_del_provider(pdev->dev.parent->of_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void zynqmp_gpd_sync_state(struct device *dev)
|
||||
@@ -315,7 +313,7 @@ static struct platform_driver zynqmp_power_domain_driver = {
|
||||
.sync_state = zynqmp_gpd_sync_state,
|
||||
},
|
||||
.probe = zynqmp_gpd_probe,
|
||||
.remove = zynqmp_gpd_remove,
|
||||
.remove_new = zynqmp_gpd_remove,
|
||||
};
|
||||
module_platform_driver(zynqmp_power_domain_driver);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user