summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-power2
-rw-r--r--Documentation/devicetree/bindings/cpufreq/imx-cpufreq-dt.txt37
-rw-r--r--arch/powerpc/kernel/suspend.c1
-rw-r--r--arch/s390/kernel/entry.h1
-rw-r--r--drivers/acpi/acpi_lpit.c7
-rw-r--r--drivers/acpi/acpi_lpss.c111
-rw-r--r--drivers/acpi/device_pm.c165
-rw-r--r--drivers/acpi/internal.h7
-rw-r--r--drivers/acpi/power.c135
-rw-r--r--drivers/acpi/sleep.c22
-rw-r--r--drivers/base/power/clock_ops.c6
-rw-r--r--drivers/base/power/main.c36
-rw-r--r--drivers/base/power/wakeup.c6
-rw-r--r--drivers/cpufreq/Kconfig.arm17
-rw-r--r--drivers/cpufreq/Makefile2
-rw-r--r--drivers/cpufreq/armada-37xx-cpufreq.c4
-rw-r--r--drivers/cpufreq/brcmstb-avs-cpufreq.c12
-rw-r--r--drivers/cpufreq/cpufreq-dt-platdev.c5
-rw-r--r--drivers/cpufreq/cpufreq.c121
-rw-r--r--drivers/cpufreq/imx-cpufreq-dt.c97
-rw-r--r--drivers/cpufreq/pcc-cpufreq.c4
-rw-r--r--drivers/cpufreq/raspberrypi-cpufreq.c97
-rw-r--r--drivers/cpufreq/s5pv210-cpufreq.c2
-rw-r--r--drivers/input/serio/i8042.c2
-rw-r--r--drivers/opp/core.c174
-rw-r--r--drivers/opp/of.c30
-rw-r--r--drivers/pci/pci-acpi.c14
-rw-r--r--drivers/pci/pci-driver.c74
-rw-r--r--drivers/pci/pci.c116
-rw-r--r--drivers/pci/pci.h8
-rw-r--r--drivers/pci/pcie/portdrv_core.c66
-rw-r--r--drivers/power/avs/smartreflex.c41
-rw-r--r--drivers/soc/imx/soc-imx8.c3
-rw-r--r--include/acpi/acpi_bus.h11
-rw-r--r--include/linux/acpi.h14
-rw-r--r--include/linux/cpufreq.h6
-rw-r--r--include/linux/pm.h1
-rw-r--r--include/linux/pm_opp.h8
-rw-r--r--include/linux/pm_wakeup.h2
-rw-r--r--include/linux/suspend.h5
-rw-r--r--kernel/power/power.h2
-rw-r--r--kernel/power/suspend.c6
-rw-r--r--kernel/power/swap.c3
-rw-r--r--tools/power/cpupower/man/cpupower-monitor.12
-rw-r--r--tools/power/cpupower/po/cs.po2
-rw-r--r--tools/power/cpupower/po/de.po2
-rw-r--r--tools/power/cpupower/po/fr.po2
-rw-r--r--tools/power/cpupower/po/it.po2
-rw-r--r--tools/power/cpupower/po/pt.po2
-rw-r--r--tools/power/cpupower/utils/cpufreq-set.c2
-rw-r--r--tools/power/pm-graph/README552
-rwxr-xr-xtools/power/pm-graph/bootgraph.py8
-rw-r--r--tools/power/pm-graph/config/example.cfg26
-rw-r--r--tools/power/pm-graph/sleepgraph.816
-rwxr-xr-xtools/power/pm-graph/sleepgraph.py857
55 files changed, 2325 insertions, 631 deletions
diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power
index 18b7dc929234..3c5130355011 100644
--- a/Documentation/ABI/testing/sysfs-power
+++ b/Documentation/ABI/testing/sysfs-power
@@ -300,4 +300,4 @@ Description:
attempt.
Using this sysfs file will override any values that were
- set using the kernel command line for disk offset. \ No newline at end of file
+ set using the kernel command line for disk offset.
diff --git a/Documentation/devicetree/bindings/cpufreq/imx-cpufreq-dt.txt b/Documentation/devicetree/bindings/cpufreq/imx-cpufreq-dt.txt
new file mode 100644
index 000000000000..87bff5add3f9
--- /dev/null
+++ b/Documentation/devicetree/bindings/cpufreq/imx-cpufreq-dt.txt
@@ -0,0 +1,37 @@
+i.MX CPUFreq-DT OPP bindings
+================================
+
+Certain i.MX SoCs support different OPPs depending on the "market segment" and
+"speed grading" value which are written in fuses. These bits are combined with
+the opp-supported-hw values for each OPP to check if the OPP is allowed.
+
+Required properties:
+--------------------
+
+For each opp entry in 'operating-points-v2' table:
+- opp-supported-hw: Two bitmaps indicating:
+ - Supported speed grade mask
+ - Supported market segment mask
+ 0: Consumer
+ 1: Extended Consumer
+ 2: Industrial
+ 3: Automotive
+
+Example:
+--------
+
+opp_table {
+ compatible = "operating-points-v2";
+ opp-1000000000 {
+ opp-hz = /bits/ 64 <1000000000>;
+ /* grade >= 0, consumer only */
+ opp-supported-hw = <0xf>, <0x3>;
+ };
+
+ opp-1300000000 {
+ opp-hz = /bits/ 64 <1300000000>;
+ opp-microvolt = <1000000>;
+ /* grade >= 1, all segments */
+ opp-supported-hw = <0xe>, <0x7>;
+ };
+}
diff --git a/arch/powerpc/kernel/suspend.c b/arch/powerpc/kernel/suspend.c
index c612d50c9d18..b84992c10854 100644
--- a/arch/powerpc/kernel/suspend.c
+++ b/arch/powerpc/kernel/suspend.c
@@ -7,6 +7,7 @@
*/
#include <linux/mm.h>
+#include <linux/suspend.h>
#include <asm/page.h>
#include <asm/sections.h>
diff --git a/arch/s390/kernel/entry.h b/arch/s390/kernel/entry.h
index 20420c2b8a14..b2956d49b6ad 100644
--- a/arch/s390/kernel/entry.h
+++ b/arch/s390/kernel/entry.h
@@ -63,7 +63,6 @@ void __init startup_init(void);
void die(struct pt_regs *regs, const char *str);
int setup_profiling_timer(unsigned int multiplier);
void __init time_init(void);
-int pfn_is_nosave(unsigned long);
void s390_early_resume(void);
unsigned long prepare_ftrace_return(unsigned long parent, unsigned long sp, unsigned long ip);
diff --git a/drivers/acpi/acpi_lpit.c b/drivers/acpi/acpi_lpit.c
index 6116b0fb86d4..433376e819bb 100644
--- a/drivers/acpi/acpi_lpit.c
+++ b/drivers/acpi/acpi_lpit.c
@@ -129,7 +129,7 @@ static void lpit_update_residency(struct lpit_residency_info *info,
static void lpit_process(u64 begin, u64 end)
{
- while (begin + sizeof(struct acpi_lpit_native) < end) {
+ while (begin + sizeof(struct acpi_lpit_native) <= end) {
struct acpi_lpit_native *lpit_native = (struct acpi_lpit_native *)begin;
if (!lpit_native->header.type && !lpit_native->header.flags) {
@@ -148,7 +148,6 @@ static void lpit_process(u64 begin, u64 end)
void acpi_init_lpit(void)
{
acpi_status status;
- u64 lpit_begin;
struct acpi_table_lpit *lpit;
status = acpi_get_table(ACPI_SIG_LPIT, 0, (struct acpi_table_header **)&lpit);
@@ -156,6 +155,6 @@ void acpi_init_lpit(void)
if (ACPI_FAILURE(status))
return;
- lpit_begin = (u64)lpit + sizeof(*lpit);
- lpit_process(lpit_begin, lpit_begin + lpit->header.length);
+ lpit_process((u64)lpit + sizeof(*lpit),
+ (u64)lpit + lpit->header.length);
}
diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c
index 23484aa877b6..398451839178 100644
--- a/drivers/acpi/acpi_lpss.c
+++ b/drivers/acpi/acpi_lpss.c
@@ -1061,6 +1061,13 @@ static int acpi_lpss_suspend_noirq(struct device *dev)
int ret;
if (pdata->dev_desc->resume_from_noirq) {
+ /*
+ * The driver's ->suspend_late callback will be invoked by
+ * acpi_lpss_do_suspend_late(), with the assumption that the
+ * driver really wanted to run that code in ->suspend_noirq, but
+ * it could not run after acpi_dev_suspend() and the driver
+ * expected the latter to be called in the "late" phase.
+ */
ret = acpi_lpss_do_suspend_late(dev);
if (ret)
return ret;
@@ -1091,16 +1098,99 @@ static int acpi_lpss_resume_noirq(struct device *dev)
struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
int ret;
- ret = acpi_subsys_resume_noirq(dev);
+ /* Follow acpi_subsys_resume_noirq(). */
+ if (dev_pm_may_skip_resume(dev))
+ return 0;
+
+ if (dev_pm_smart_suspend_and_suspended(dev))
+ pm_runtime_set_active(dev);
+
+ ret = pm_generic_resume_noirq(dev);
if (ret)
return ret;
- if (!dev_pm_may_skip_resume(dev) && pdata->dev_desc->resume_from_noirq)
- ret = acpi_lpss_do_resume_early(dev);
+ if (!pdata->dev_desc->resume_from_noirq)
+ return 0;
- return ret;
+ /*
+ * The driver's ->resume_early callback will be invoked by
+ * acpi_lpss_do_resume_early(), with the assumption that the driver
+ * really wanted to run that code in ->resume_noirq, but it could not
+ * run before acpi_dev_resume() and the driver expected the latter to be
+ * called in the "early" phase.
+ */
+ return acpi_lpss_do_resume_early(dev);
+}
+
+static int acpi_lpss_do_restore_early(struct device *dev)
+{
+ int ret = acpi_lpss_resume(dev);
+
+ return ret ? ret : pm_generic_restore_early(dev);
}
+static int acpi_lpss_restore_early(struct device *dev)
+{
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+
+ if (pdata->dev_desc->resume_from_noirq)
+ return 0;
+
+ return acpi_lpss_do_restore_early(dev);
+}
+
+static int acpi_lpss_restore_noirq(struct device *dev)
+{
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+ int ret;
+
+ ret = pm_generic_restore_noirq(dev);
+ if (ret)
+ return ret;
+
+ if (!pdata->dev_desc->resume_from_noirq)
+ return 0;
+
+ /* This is analogous to what happens in acpi_lpss_resume_noirq(). */
+ return acpi_lpss_do_restore_early(dev);
+}
+
+static int acpi_lpss_do_poweroff_late(struct device *dev)
+{
+ int ret = pm_generic_poweroff_late(dev);
+
+ return ret ? ret : acpi_lpss_suspend(dev, device_may_wakeup(dev));
+}
+
+static int acpi_lpss_poweroff_late(struct device *dev)
+{
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+
+ if (dev_pm_smart_suspend_and_suspended(dev))
+ return 0;
+
+ if (pdata->dev_desc->resume_from_noirq)
+ return 0;
+
+ return acpi_lpss_do_poweroff_late(dev);
+}
+
+static int acpi_lpss_poweroff_noirq(struct device *dev)
+{
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+
+ if (dev_pm_smart_suspend_and_suspended(dev))
+ return 0;
+
+ if (pdata->dev_desc->resume_from_noirq) {
+ /* This is analogous to the acpi_lpss_suspend_noirq() case. */
+ int ret = acpi_lpss_do_poweroff_late(dev);
+ if (ret)
+ return ret;
+ }
+
+ return pm_generic_poweroff_noirq(dev);
+}
#endif /* CONFIG_PM_SLEEP */
static int acpi_lpss_runtime_suspend(struct device *dev)
@@ -1134,14 +1224,11 @@ static struct dev_pm_domain acpi_lpss_pm_domain = {
.resume_noirq = acpi_lpss_resume_noirq,
.resume_early = acpi_lpss_resume_early,
.freeze = acpi_subsys_freeze,
- .freeze_late = acpi_subsys_freeze_late,
- .freeze_noirq = acpi_subsys_freeze_noirq,
- .thaw_noirq = acpi_subsys_thaw_noirq,
- .poweroff = acpi_subsys_suspend,
- .poweroff_late = acpi_lpss_suspend_late,
- .poweroff_noirq = acpi_lpss_suspend_noirq,
- .restore_noirq = acpi_lpss_resume_noirq,
- .restore_early = acpi_lpss_resume_early,
+ .poweroff = acpi_subsys_poweroff,
+ .poweroff_late = acpi_lpss_poweroff_late,
+ .poweroff_noirq = acpi_lpss_poweroff_noirq,
+ .restore_noirq = acpi_lpss_restore_noirq,
+ .restore_early = acpi_lpss_restore_early,
#endif
.runtime_suspend = acpi_lpss_runtime_suspend,
.runtime_resume = acpi_lpss_runtime_resume,
diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c
index e54956ae93d3..28cffaaf9d82 100644
--- a/drivers/acpi/device_pm.c
+++ b/drivers/acpi/device_pm.c
@@ -45,6 +45,19 @@ const char *acpi_power_state_string(int state)
}
}
+static int acpi_dev_pm_explicit_get(struct acpi_device *device, int *state)
+{
+ unsigned long long psc;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "_PSC", NULL, &psc);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ *state = psc;
+ return 0;
+}
+
/**
* acpi_device_get_power - Get power state of an ACPI device.
* @device: Device to get the power state of.
@@ -53,10 +66,16 @@ const char *acpi_power_state_string(int state)
* This function does not update the device's power.state field, but it may
* update its parent's power.state field (when the parent's power state is
* unknown and the device's power state turns out to be D0).
+ *
+ * Also, it does not update power resource reference counters to ensure that
+ * the power state returned by it will be persistent and it may return a power
+ * state shallower than previously set by acpi_device_set_power() for @device
+ * (if that power state depends on any power resources).
*/
int acpi_device_get_power(struct acpi_device *device, int *state)
{
int result = ACPI_STATE_UNKNOWN;
+ int error;
if (!device || !state)
return -EINVAL;
@@ -73,18 +92,16 @@ int acpi_device_get_power(struct acpi_device *device, int *state)
* if available.
*/
if (device->power.flags.power_resources) {
- int error = acpi_power_get_inferred_state(device, &result);
+ error = acpi_power_get_inferred_state(device, &result);
if (error)
return error;
}
if (device->power.flags.explicit_get) {
- acpi_handle handle = device->handle;
- unsigned long long psc;
- acpi_status status;
+ int psc;
- status = acpi_evaluate_integer(handle, "_PSC", NULL, &psc);
- if (ACPI_FAILURE(status))
- return -ENODEV;
+ error = acpi_dev_pm_explicit_get(device, &psc);
+ if (error)
+ return error;
/*
* The power resources settings may indicate a power state
@@ -118,7 +135,6 @@ int acpi_device_get_power(struct acpi_device *device, int *state)
return 0;
}
-EXPORT_SYMBOL(acpi_device_get_power);
static int acpi_dev_pm_explicit_set(struct acpi_device *adev, int state)
{
@@ -152,7 +168,8 @@ int acpi_device_set_power(struct acpi_device *device, int state)
/* Make sure this is a valid target state */
- if (state == device->power.state) {
+ /* There is a special case for D0 addressed below. */
+ if (state > ACPI_STATE_D0 && state == device->power.state) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Device [%s] already in %s\n",
device->pnp.bus_id,
acpi_power_state_string(state)));
@@ -202,9 +219,15 @@ int acpi_device_set_power(struct acpi_device *device, int state)
return -ENODEV;
}
- result = acpi_dev_pm_explicit_set(device, state);
- if (result)
- goto end;
+ /*
+ * If the device goes from D3hot to D3cold, _PS3 has been
+ * evaluated for it already, so skip it in that case.
+ */
+ if (device->power.state < ACPI_STATE_D3_HOT) {
+ result = acpi_dev_pm_explicit_set(device, state);
+ if (result)
+ goto end;
+ }
if (device->power.flags.power_resources)
result = acpi_power_transition(device, target_state);
@@ -214,6 +237,30 @@ int acpi_device_set_power(struct acpi_device *device, int state)
if (result)
goto end;
}
+
+ if (device->power.state == ACPI_STATE_D0) {
+ int psc;
+
+ /* Nothing to do here if _PSC is not present. */
+ if (!device->power.flags.explicit_get)
+ return 0;
+
+ /*
+ * The power state of the device was set to D0 last
+ * time, but that might have happened before a
+ * system-wide transition involving the platform
+ * firmware, so it may be necessary to evaluate _PS0
+ * for the device here. However, use extra care here
+ * and evaluate _PSC to check the device's current power
+ * state, and only invoke _PS0 if the evaluation of _PSC
+ * is successful and it returns a power state different
+ * from D0.
+ */
+ result = acpi_dev_pm_explicit_get(device, &psc);
+ if (result || psc == ACPI_STATE_D0)
+ return 0;
+ }
+
result = acpi_dev_pm_explicit_set(device, ACPI_STATE_D0);
}
@@ -1073,7 +1120,7 @@ EXPORT_SYMBOL_GPL(acpi_subsys_suspend_noirq);
* acpi_subsys_resume_noirq - Run the device driver's "noirq" resume callback.
* @dev: Device to handle.
*/
-int acpi_subsys_resume_noirq(struct device *dev)
+static int acpi_subsys_resume_noirq(struct device *dev)
{
if (dev_pm_may_skip_resume(dev))
return 0;
@@ -1088,7 +1135,6 @@ int acpi_subsys_resume_noirq(struct device *dev)
return pm_generic_resume_noirq(dev);
}
-EXPORT_SYMBOL_GPL(acpi_subsys_resume_noirq);
/**
* acpi_subsys_resume_early - Resume device using ACPI.
@@ -1098,12 +1144,11 @@ EXPORT_SYMBOL_GPL(acpi_subsys_resume_noirq);
* generic early resume procedure for it during system transition into the
* working state.
*/
-int acpi_subsys_resume_early(struct device *dev)
+static int acpi_subsys_resume_early(struct device *dev)
{
int ret = acpi_dev_resume(dev);
return ret ? ret : pm_generic_resume_early(dev);
}
-EXPORT_SYMBOL_GPL(acpi_subsys_resume_early);
/**
* acpi_subsys_freeze - Run the device driver's freeze callback.
@@ -1112,65 +1157,81 @@ EXPORT_SYMBOL_GPL(acpi_subsys_resume_early);
int acpi_subsys_freeze(struct device *dev)
{
/*
- * This used to be done in acpi_subsys_prepare() for all devices and
- * some drivers may depend on it, so do it here. Ideally, however,
- * runtime-suspended devices should not be touched during freeze/thaw
- * transitions.
+ * Resume all runtime-suspended devices before creating a snapshot
+ * image of system memory, because the restore kernel generally cannot
+ * be expected to always handle them consistently and they need to be
+ * put into the runtime-active metastate during system resume anyway,
+ * so it is better to ensure that the state saved in the image will be
+ * always consistent with that.
*/
- if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND))
- pm_runtime_resume(dev);
+ pm_runtime_resume(dev);
return pm_generic_freeze(dev);
}
EXPORT_SYMBOL_GPL(acpi_subsys_freeze);
/**
- * acpi_subsys_freeze_late - Run the device driver's "late" freeze callback.
- * @dev: Device to handle.
+ * acpi_subsys_restore_early - Restore device using ACPI.
+ * @dev: Device to restore.
*/
-int acpi_subsys_freeze_late(struct device *dev)
+int acpi_subsys_restore_early(struct device *dev)
{
+ int ret = acpi_dev_resume(dev);
+ return ret ? ret : pm_generic_restore_early(dev);
+}
+EXPORT_SYMBOL_GPL(acpi_subsys_restore_early);
- if (dev_pm_smart_suspend_and_suspended(dev))
- return 0;
+/**
+ * acpi_subsys_poweroff - Run the device driver's poweroff callback.
+ * @dev: Device to handle.
+ *
+ * Follow PCI and resume devices from runtime suspend before running their
+ * system poweroff callbacks, unless the driver can cope with runtime-suspended
+ * devices during system suspend and there are no ACPI-specific reasons for
+ * resuming them.
+ */
+int acpi_subsys_poweroff(struct device *dev)
+{
+ if (!dev_pm_test_driver_flags(dev, DPM_FLAG_SMART_SUSPEND) ||
+ acpi_dev_needs_resume(dev, ACPI_COMPANION(dev)))
+ pm_runtime_resume(dev);
- return pm_generic_freeze_late(dev);
+ return pm_generic_poweroff(dev);
}
-EXPORT_SYMBOL_GPL(acpi_subsys_freeze_late);
+EXPORT_SYMBOL_GPL(acpi_subsys_poweroff);
/**
- * acpi_subsys_freeze_noirq - Run the device driver's "noirq" freeze callback.
+ * acpi_subsys_poweroff_late - Run the device driver's poweroff callback.
* @dev: Device to handle.
+ *
+ * Carry out the generic late poweroff procedure for @dev and use ACPI to put
+ * it into a low-power state during system transition into a sleep state.
*/
-int acpi_subsys_freeze_noirq(struct device *dev)
+static int acpi_subsys_poweroff_late(struct device *dev)
{
+ int ret;
if (dev_pm_smart_suspend_and_suspended(dev))
return 0;
- return pm_generic_freeze_noirq(dev);
+ ret = pm_generic_poweroff_late(dev);
+ if (ret)
+ return ret;
+
+ return acpi_dev_suspend(dev, device_may_wakeup(dev));
}
-EXPORT_SYMBOL_GPL(acpi_subsys_freeze_noirq);
/**
- * acpi_subsys_thaw_noirq - Run the device driver's "noirq" thaw callback.
- * @dev: Device to handle.
+ * acpi_subsys_poweroff_noirq - Run the driver's "noirq" poweroff callback.
+ * @dev: Device to suspend.
*/
-int acpi_subsys_thaw_noirq(struct device *dev)
+static int acpi_subsys_poweroff_noirq(struct device *dev)
{
- /*
- * If the device is in runtime suspend, the "thaw" code may not work
- * correctly with it, so skip the driver callback and make the PM core
- * skip all of the subsequent "thaw" callbacks for the device.
- */
- if (dev_pm_smart_suspend_and_suspended(dev)) {
- dev_pm_skip_next_resume_phases(dev);
+ if (dev_pm_smart_suspend_and_suspended(dev))
return 0;
- }
- return pm_generic_thaw_noirq(dev);
+ return pm_generic_poweroff_noirq(dev);
}
-EXPORT_SYMBOL_GPL(acpi_subsys_thaw_noirq);
#endif /* CONFIG_PM_SLEEP */
static struct dev_pm_domain acpi_general_pm_domain = {
@@ -1186,14 +1247,10 @@ static struct dev_pm_domain acpi_general_pm_domain = {
.resume_noirq = acpi_subsys_resume_noirq,
.resume_early = acpi_subsys_resume_early,
.freeze = acpi_subsys_freeze,
- .freeze_late = acpi_subsys_freeze_late,
- .freeze_noirq = acpi_subsys_freeze_noirq,
- .thaw_noirq = acpi_subsys_thaw_noirq,
- .poweroff = acpi_subsys_suspend,
- .poweroff_late = acpi_subsys_suspend_late,
- .poweroff_noirq = acpi_subsys_suspend_noirq,
- .restore_noirq = acpi_subsys_resume_noirq,
- .restore_early = acpi_subsys_resume_early,
+ .poweroff = acpi_subsys_poweroff,
+ .poweroff_late = acpi_subsys_poweroff_late,
+ .poweroff_noirq = acpi_subsys_poweroff_noirq,
+ .restore_early = acpi_subsys_restore_early,
#endif
},
};
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index f6157d4d637a..f4c2fe6be4f2 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -139,8 +139,15 @@ int acpi_power_get_inferred_state(struct acpi_device *device, int *state);
int acpi_power_on_resources(struct acpi_device *device, int state);
int acpi_power_transition(struct acpi_device *device, int state);
+/* --------------------------------------------------------------------------
+ Device Power Management
+ -------------------------------------------------------------------------- */
+int acpi_device_get_power(struct acpi_device *device, int *state);
int acpi_wakeup_device_init(void);
+/* --------------------------------------------------------------------------
+ Processor
+ -------------------------------------------------------------------------- */
#ifdef CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC
void acpi_early_processor_set_pdc(void);
#else
diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c
index a916417b9e70..fe1e7bc91a5e 100644
--- a/drivers/acpi/power.c
+++ b/drivers/acpi/power.c
@@ -42,6 +42,11 @@ ACPI_MODULE_NAME("power");
#define ACPI_POWER_RESOURCE_STATE_ON 0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
+struct acpi_power_dependent_device {
+ struct device *dev;
+ struct list_head node;
+};
+
struct acpi_power_resource {
struct acpi_device device;
struct list_head list_node;
@@ -51,6 +56,7 @@ struct acpi_power_resource {
unsigned int ref_count;
bool wakeup_enabled;
struct mutex resource_lock;
+ struct list_head dependents;
};
struct acpi_power_resource_entry {
@@ -232,8 +238,121 @@ static int acpi_power_get_list_state(struct list_head *list, int *state)
return 0;
}
+static int
+acpi_power_resource_add_dependent(struct acpi_power_resource *resource,
+ struct device *dev)
+{
+ struct acpi_power_dependent_device *dep;
+ int ret = 0;
+
+ mutex_lock(&resource->resource_lock);
+ list_for_each_entry(dep, &resource->dependents, node) {
+ /* Only add it once */
+ if (dep->dev == dev)
+ goto unlock;
+ }
+
+ dep = kzalloc(sizeof(*dep), GFP_KERNEL);
+ if (!dep) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ dep->dev = dev;
+ list_add_tail(&dep->node, &resource->dependents);
+ dev_dbg(dev, "added power dependency to [%s]\n", resource->name);
+
+unlock:
+ mutex_unlock(&resource->resource_lock);
+ return ret;
+}
+
+static void
+acpi_power_resource_remove_dependent(struct acpi_power_resource *resource,
+ struct device *dev)
+{
+ struct acpi_power_dependent_device *dep;
+
+ mutex_lock(&resource->resource_lock);
+ list_for_each_entry(dep, &resource->dependents, node) {
+ if (dep->dev == dev) {
+ list_del(&dep->node);
+ kfree(dep);
+ dev_dbg(dev, "removed power dependency to [%s]\n",
+ resource->name);
+ break;
+ }
+ }
+ mutex_unlock(&resource->resource_lock);
+}
+
+/**
+ * acpi_device_power_add_dependent - Add dependent device of this ACPI device
+ * @adev: ACPI device pointer
+ * @dev: Dependent device
+ *
+ * If @adev has non-empty _PR0 the @dev is added as dependent device to all
+ * power resources returned by it. This means that whenever these power
+ * resources are turned _ON the dependent devices get runtime resumed. This
+ * is needed for devices such as PCI to all