mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-28 02:32:09 -04:00
Power management updates for 3.4
Assorted extensions and fixes including: * Introduction of early/late suspend/hibernation device callbacks. * Generic PM domains extensions and fixes. * devfreq updates from Axel Lin and MyungJoo Ham. * Device PM QoS updates. * Fixes of concurrency problems with wakeup sources. * System suspend and hibernation fixes. -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.18 (GNU/Linux) iQIcBAABAgAGBQJPZww5AAoJEKhOf7ml8uNsiBYQAL9YGso7KypZhLspNxvAKuZr iHyme2F7OdOiUfo40DVH5tRuEsQvLOl0S+9ukWLrzQotKBsMfym05jtbGN9m6Ygh Z793sx3eRI3mltekJ9yrOxH6BOBDMWMkwY8ztU/X5aYDNirgJ/qtAjSK4BvWXBrz APeaUReVnLdaNP8SnhHfne/KPsHk++NKZvAAva7E6RwtZn4KV6bfiBPGb8yvY8pP m4cg1S5QEduMy+zQJ8+IlEHR91bt9spUyRwbhw6ZHCNzNeu4iEZT8DVt1O1sIRbO LsNcClqsd40nr781SoF8N9GmGUxlUDr46bS3FSsDkYzn8uyxGEsv00edJZtPwIm5 7nPuYat3Ke1YsON0Kcd/wkBGXqw/Rjfp3F1bnHjpVx/0oM/6MPrFNnIwvpHspejG kN3770idYJ17dLckhcsbYsLdy8yirITILDzvHT0AAaZ9z4Lr9Pm56WwFZLyb/lhR 2cqK8Bb8W9YvcVsKV8YqkyBVrygWMe+c56KoAoUBiSNxvW6LphmXFBj5QiFMs8s8 Xh8H7xU96FKbpNMIAZ1+bpI4zgulQG4xPXI9pKbhMfjaMUgj2zQeO8/t0WlB1M0z +kEUcYHJnXrRrObQuHEFXZdIjy/E0fdUboMIrlLt0gm97OxnG6imPseQp6/leQkC t+L4Aq6TOUofUU86d4cI =IGhc -----END PGP SIGNATURE----- Merge tag 'pm-for-3.4' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm Pull power management updates for 3.4 from Rafael Wysocki: "Assorted extensions and fixes including: * Introduction of early/late suspend/hibernation device callbacks. * Generic PM domains extensions and fixes. * devfreq updates from Axel Lin and MyungJoo Ham. * Device PM QoS updates. * Fixes of concurrency problems with wakeup sources. * System suspend and hibernation fixes." * tag 'pm-for-3.4' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (43 commits) PM / Domains: Check domain status during hibernation restore of devices PM / devfreq: add relation of recommended frequency. PM / shmobile: Make MTU2 driver use pm_genpd_dev_always_on() PM / shmobile: Make CMT driver use pm_genpd_dev_always_on() PM / shmobile: Make TMU driver use pm_genpd_dev_always_on() PM / Domains: Introduce "always on" device flag PM / Domains: Fix hibernation restore of devices, v2 PM / Domains: Fix handling of wakeup devices during system resume sh_mmcif / PM: Use PM QoS latency constraint tmio_mmc / PM: Use PM QoS latency constraint PM / QoS: Make it possible to expose PM QoS latency constraints PM / Sleep: JBD and JBD2 missing set_freezable() PM / Domains: Fix include for PM_GENERIC_DOMAINS=n case PM / Freezer: Remove references to TIF_FREEZE in comments PM / Sleep: Add more wakeup source initialization routines PM / Hibernate: Enable usermodehelpers in hibernate() error path PM / Sleep: Make __pm_stay_awake() delete wakeup source timers PM / Sleep: Fix race conditions related to wakeup source timer function PM / Sleep: Fix possible infinite loop during wakeup source destruction PM / Hibernate: print physical addresses consistently with other parts of kernel ...
This commit is contained in:
commit
c7c66c0cb0
53 changed files with 1452 additions and 611 deletions
|
|
@ -366,7 +366,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd)
|
|||
not_suspended = 0;
|
||||
list_for_each_entry(pdd, &genpd->dev_list, list_node)
|
||||
if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev)
|
||||
|| pdd->dev->power.irq_safe))
|
||||
|| pdd->dev->power.irq_safe || to_gpd_data(pdd)->always_on))
|
||||
not_suspended++;
|
||||
|
||||
if (not_suspended > genpd->in_progress)
|
||||
|
|
@ -503,6 +503,9 @@ static int pm_genpd_runtime_suspend(struct device *dev)
|
|||
|
||||
might_sleep_if(!genpd->dev_irq_safe);
|
||||
|
||||
if (dev_gpd_data(dev)->always_on)
|
||||
return -EBUSY;
|
||||
|
||||
stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL;
|
||||
if (stop_ok && !stop_ok(dev))
|
||||
return -EBUSY;
|
||||
|
|
@ -764,8 +767,10 @@ static int pm_genpd_prepare(struct device *dev)
|
|||
|
||||
genpd_acquire_lock(genpd);
|
||||
|
||||
if (genpd->prepared_count++ == 0)
|
||||
if (genpd->prepared_count++ == 0) {
|
||||
genpd->suspended_count = 0;
|
||||
genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF;
|
||||
}
|
||||
|
||||
genpd_release_lock(genpd);
|
||||
|
||||
|
|
@ -820,17 +825,16 @@ static int pm_genpd_suspend(struct device *dev)
|
|||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_suspend_noirq - Late suspend of a device from an I/O PM domain.
|
||||
* pm_genpd_suspend_late - Late suspend of a device from an I/O PM domain.
|
||||
* @dev: Device to suspend.
|
||||
*
|
||||
* Carry out a late suspend of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a PM domain consisting of I/O devices.
|
||||
*/
|
||||
static int pm_genpd_suspend_noirq(struct device *dev)
|
||||
static int pm_genpd_suspend_late(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
|
|
@ -838,14 +842,28 @@ static int pm_genpd_suspend_noirq(struct device *dev)
|
|||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
return genpd->suspend_power_off ? 0 : genpd_suspend_late(genpd, dev);
|
||||
}
|
||||
|
||||
ret = genpd_suspend_late(genpd, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
/**
|
||||
* pm_genpd_suspend_noirq - Completion of suspend of device in an I/O PM domain.
|
||||
* @dev: Device to suspend.
|
||||
*
|
||||
* Stop the device and remove power from the domain if all devices in it have
|
||||
* been stopped.
|
||||
*/
|
||||
static int pm_genpd_suspend_noirq(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on
|
||||
|| (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)))
|
||||
return 0;
|
||||
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
|
@ -862,13 +880,10 @@ static int pm_genpd_suspend_noirq(struct device *dev)
|
|||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_resume_noirq - Early resume of a device from an I/O power domain.
|
||||
* pm_genpd_resume_noirq - Start of resume of device in an I/O PM domain.
|
||||
* @dev: Device to resume.
|
||||
*
|
||||
* Carry out an early resume of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
* Restore power to the device's PM domain, if necessary, and start the device.
|
||||
*/
|
||||
static int pm_genpd_resume_noirq(struct device *dev)
|
||||
{
|
||||
|
|
@ -880,7 +895,8 @@ static int pm_genpd_resume_noirq(struct device *dev)
|
|||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off)
|
||||
if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on
|
||||
|| (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
|
|
@ -890,13 +906,34 @@ static int pm_genpd_resume_noirq(struct device *dev)
|
|||
*/
|
||||
pm_genpd_poweron(genpd);
|
||||
genpd->suspended_count--;
|
||||
genpd_start_dev(genpd, dev);
|
||||
|
||||
return genpd_resume_early(genpd, dev);
|
||||
return genpd_start_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_resume - Resume a device belonging to an I/O power domain.
|
||||
* pm_genpd_resume_early - Early resume of a device in an I/O PM domain.
|
||||
* @dev: Device to resume.
|
||||
*
|
||||
* Carry out an early resume of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
*/
|
||||
static int pm_genpd_resume_early(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : genpd_resume_early(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_resume - Resume of device in an I/O PM domain.
|
||||
* @dev: Device to resume.
|
||||
*
|
||||
* Resume a device under the assumption that its pm_domain field points to the
|
||||
|
|
@ -917,7 +954,7 @@ static int pm_genpd_resume(struct device *dev)
|
|||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_freeze - Freeze a device belonging to an I/O power domain.
|
||||
* pm_genpd_freeze - Freezing a device in an I/O PM domain.
|
||||
* @dev: Device to freeze.
|
||||
*
|
||||
* Freeze a device under the assumption that its pm_domain field points to the
|
||||
|
|
@ -938,7 +975,29 @@ static int pm_genpd_freeze(struct device *dev)
|
|||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_freeze_noirq - Late freeze of a device from an I/O power domain.
|
||||
* pm_genpd_freeze_late - Late freeze of a device in an I/O PM domain.
|
||||
* @dev: Device to freeze.
|
||||
*
|
||||
* Carry out a late freeze of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
*/
|
||||
static int pm_genpd_freeze_late(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : genpd_freeze_late(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_freeze_noirq - Completion of freezing a device in an I/O PM domain.
|
||||
* @dev: Device to freeze.
|
||||
*
|
||||
* Carry out a late freeze of a device under the assumption that its
|
||||
|
|
@ -949,7 +1008,6 @@ static int pm_genpd_freeze(struct device *dev)
|
|||
static int pm_genpd_freeze_noirq(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
|
|
@ -957,26 +1015,16 @@ static int pm_genpd_freeze_noirq(struct device *dev)
|
|||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
ret = genpd_freeze_late(genpd, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
genpd_stop_dev(genpd, dev);
|
||||
|
||||
return 0;
|
||||
return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ?
|
||||
0 : genpd_stop_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_thaw_noirq - Early thaw of a device from an I/O power domain.
|
||||
* pm_genpd_thaw_noirq - Early thaw of device in an I/O PM domain.
|
||||
* @dev: Device to thaw.
|
||||
*
|
||||
* Carry out an early thaw of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
* Start the device, unless power has been removed from the domain already
|
||||
* before the system transition.
|
||||
*/
|
||||
static int pm_genpd_thaw_noirq(struct device *dev)
|
||||
{
|
||||
|
|
@ -988,12 +1036,30 @@ static int pm_genpd_thaw_noirq(struct device *dev)
|
|||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ?
|
||||
0 : genpd_start_dev(genpd, dev);
|
||||
}
|
||||
|
||||
genpd_start_dev(genpd, dev);
|
||||
/**
|
||||
* pm_genpd_thaw_early - Early thaw of device in an I/O PM domain.
|
||||
* @dev: Device to thaw.
|
||||
*
|
||||
* Carry out an early thaw of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
*/
|
||||
static int pm_genpd_thaw_early(struct device *dev)
|
||||
{
|
||||
struct generic_pm_domain *genpd;
|
||||
|
||||
return genpd_thaw_early(genpd, dev);
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
genpd = dev_to_genpd(dev);
|
||||
if (IS_ERR(genpd))
|
||||
return -EINVAL;
|
||||
|
||||
return genpd->suspend_power_off ? 0 : genpd_thaw_early(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1018,13 +1084,11 @@ static int pm_genpd_thaw(struct device *dev)
|
|||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_restore_noirq - Early restore of a device from an I/O power domain.
|
||||
* pm_genpd_restore_noirq - Start of restore of device in an I/O PM domain.
|
||||
* @dev: Device to resume.
|
||||
*
|
||||
* Carry out an early restore of a device under the assumption that its
|
||||
* pm_domain field points to the domain member of an object of type
|
||||
* struct generic_pm_domain representing a power domain consisting of I/O
|
||||
* devices.
|
||||
* Make sure the domain will be in the same power state as before the
|
||||
* hibernation the system is resuming from and start the device if necessary.
|
||||
*/
|
||||
static int pm_genpd_restore_noirq(struct device *dev)
|
||||
{
|
||||
|
|
@ -1040,23 +1104,35 @@ static int pm_genpd_restore_noirq(struct device *dev)
|
|||
* Since all of the "noirq" callbacks are executed sequentially, it is
|
||||
* guaranteed that this function will never run twice in parallel for
|
||||
* the same PM domain, so it is not necessary to use locking here.
|
||||
*
|
||||
* At this point suspended_count == 0 means we are being run for the
|
||||
* first time for the given domain in the present cycle.
|
||||
*/
|
||||
genpd->status = GPD_STATE_POWER_OFF;
|
||||
if (genpd->suspend_power_off) {
|
||||
if (genpd->suspended_count++ == 0) {
|
||||
/*
|
||||
* The boot kernel might put the domain into the power on state,
|
||||
* so make sure it really is powered off.
|
||||
* The boot kernel might put the domain into arbitrary state,
|
||||
* so make it appear as powered off to pm_genpd_poweron(), so
|
||||
* that it tries to power it on in case it was really off.
|
||||
*/
|
||||
if (genpd->power_off)
|
||||
genpd->power_off(genpd);
|
||||
return 0;
|
||||
genpd->status = GPD_STATE_POWER_OFF;
|
||||
if (genpd->suspend_power_off) {
|
||||
/*
|
||||
* If the domain was off before the hibernation, make
|
||||
* sure it will be off going forward.
|
||||
*/
|
||||
if (genpd->power_off)
|
||||
genpd->power_off(genpd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
pm_genpd_poweron(genpd);
|
||||
genpd->suspended_count--;
|
||||
genpd_start_dev(genpd, dev);
|
||||
if (genpd->suspend_power_off)
|
||||
return 0;
|
||||
|
||||
return genpd_resume_early(genpd, dev);
|
||||
pm_genpd_poweron(genpd);
|
||||
|
||||
return dev_gpd_data(dev)->always_on ? 0 : genpd_start_dev(genpd, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1099,11 +1175,15 @@ static void pm_genpd_complete(struct device *dev)
|
|||
|
||||
#define pm_genpd_prepare NULL
|
||||
#define pm_genpd_suspend NULL
|
||||
#define pm_genpd_suspend_late NULL
|
||||
#define pm_genpd_suspend_noirq NULL
|
||||
#define pm_genpd_resume_early NULL
|
||||
#define pm_genpd_resume_noirq NULL
|
||||
#define pm_genpd_resume NULL
|
||||
#define pm_genpd_freeze NULL
|
||||
#define pm_genpd_freeze_late NULL
|
||||
#define pm_genpd_freeze_noirq NULL
|
||||
#define pm_genpd_thaw_early NULL
|
||||
#define pm_genpd_thaw_noirq NULL
|
||||
#define pm_genpd_thaw NULL
|
||||
#define pm_genpd_restore_noirq NULL
|
||||
|
|
@ -1170,6 +1250,38 @@ int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* __pm_genpd_of_add_device - Add a device to an I/O PM domain.
|
||||
* @genpd_node: Device tree node pointer representing a PM domain to which the
|
||||
* the device is added to.
|
||||
* @dev: Device to be added.
|
||||
* @td: Set of PM QoS timing parameters to attach to the device.
|
||||
*/
|
||||
int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev,
|
||||
struct gpd_timing_data *td)
|
||||
{
|
||||
struct generic_pm_domain *genpd = NULL, *gpd;
|
||||
|
||||
dev_dbg(dev, "%s()\n", __func__);
|
||||
|
||||
if (IS_ERR_OR_NULL(genpd_node) || IS_ERR_OR_NULL(dev))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&gpd_list_lock);
|
||||
list_for_each_entry(gpd, &gpd_list, gpd_list_node) {
|
||||
if (gpd->of_node == genpd_node) {
|
||||
genpd = gpd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&gpd_list_lock);
|
||||
|
||||
if (!genpd)
|
||||
return -EINVAL;
|
||||
|
||||
return __pm_genpd_add_device(genpd, dev, td);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_remove_device - Remove a device from an I/O PM domain.
|
||||
* @genpd: PM domain to remove the device from.
|
||||
|
|
@ -1215,6 +1327,26 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_genpd_dev_always_on - Set/unset the "always on" flag for a given device.
|
||||
* @dev: Device to set/unset the flag for.
|
||||
* @val: The new value of the device's "always on" flag.
|
||||
*/
|
||||
void pm_genpd_dev_always_on(struct device *dev, bool val)
|
||||
{
|
||||
struct pm_subsys_data *psd;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->power.lock, flags);
|
||||
|
||||
psd = dev_to_psd(dev);
|
||||
if (psd && psd->domain_data)
|
||||
to_gpd_data(psd->domain_data)->always_on = val;
|
||||
|
||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_genpd_dev_always_on);
|
||||
|
||||
/**
|
||||
* pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain.
|
||||
* @genpd: Master PM domain to add the subdomain to.
|
||||
|
|
@ -1450,7 +1582,7 @@ static int pm_genpd_default_suspend_late(struct device *dev)
|
|||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend_late;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_suspend_noirq(dev);
|
||||
return cb ? cb(dev) : pm_generic_suspend_late(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1461,7 +1593,7 @@ static int pm_genpd_default_resume_early(struct device *dev)
|
|||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume_early;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_resume_noirq(dev);
|
||||
return cb ? cb(dev) : pm_generic_resume_early(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1494,7 +1626,7 @@ static int pm_genpd_default_freeze_late(struct device *dev)
|
|||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_freeze_noirq(dev);
|
||||
return cb ? cb(dev) : pm_generic_freeze_late(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1505,7 +1637,7 @@ static int pm_genpd_default_thaw_early(struct device *dev)
|
|||
{
|
||||
int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early;
|
||||
|
||||
return cb ? cb(dev) : pm_generic_thaw_noirq(dev);
|
||||
return cb ? cb(dev) : pm_generic_thaw_early(dev);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1557,23 +1689,28 @@ void pm_genpd_init(struct generic_pm_domain *genpd,
|
|||
genpd->poweroff_task = NULL;
|
||||
genpd->resume_count = 0;
|
||||
genpd->device_count = 0;
|
||||
genpd->suspended_count = 0;
|
||||
genpd->max_off_time_ns = -1;
|
||||
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
|
||||
genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume;
|
||||
genpd->domain.ops.runtime_idle = pm_generic_runtime_idle;
|
||||
genpd->domain.ops.prepare = pm_genpd_prepare;
|
||||
genpd->domain.ops.suspend = pm_genpd_suspend;
|
||||
genpd->domain.ops.suspend_late = pm_genpd_suspend_late;
|
||||
genpd->domain.ops.suspend_noirq = pm_genpd_suspend_noirq;
|
||||
genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq;
|
||||
genpd->domain.ops.resume_early = pm_genpd_resume_early;
|
||||
genpd->domain.ops.resume = pm_genpd_resume;
|
||||
genpd->domain.ops.freeze = pm_genpd_freeze;
|
||||
genpd->domain.ops.freeze_late = pm_genpd_freeze_late;
|
||||
genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq;
|
||||
genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq;
|
||||
genpd->domain.ops.thaw_early = pm_genpd_thaw_early;
|
||||
genpd->domain.ops.thaw = pm_genpd_thaw;
|
||||
genpd->domain.ops.poweroff = pm_genpd_suspend;
|
||||
genpd->domain.ops.poweroff_late = pm_genpd_suspend_late;
|
||||
genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
|
||||
genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq;
|
||||
genpd->domain.ops.restore_early = pm_genpd_resume_early;
|
||||
genpd->domain.ops.restore = pm_genpd_resume;
|
||||
genpd->domain.ops.complete = pm_genpd_complete;
|
||||
genpd->dev_ops.save_state = pm_genpd_default_save_state;
|
||||
|
|
|
|||
|
|
@ -91,68 +91,39 @@ int pm_generic_prepare(struct device *dev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback.
|
||||
* @dev: Device to handle.
|
||||
* @event: PM transition of the system under way.
|
||||
* @bool: Whether or not this is the "noirq" stage.
|
||||
*
|
||||
* Execute the PM callback corresponding to @event provided by the driver of
|
||||
* @dev, if defined, and return its error code. Return 0 if the callback is
|
||||
* not present.
|
||||
*/
|
||||
static int __pm_generic_call(struct device *dev, int event, bool noirq)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
int (*callback)(struct device *);
|
||||
|
||||
if (!pm)
|
||||
return 0;
|
||||
|
||||
switch (event) {
|
||||
case PM_EVENT_SUSPEND:
|
||||
callback = noirq ? pm->suspend_noirq : pm->suspend;
|
||||
break;
|
||||
case PM_EVENT_FREEZE:
|
||||
callback = noirq ? pm->freeze_noirq : pm->freeze;
|
||||
break;
|
||||
case PM_EVENT_HIBERNATE:
|
||||
callback = noirq ? pm->poweroff_noirq : pm->poweroff;
|
||||
break;
|
||||
case PM_EVENT_RESUME:
|
||||
callback = noirq ? pm->resume_noirq : pm->resume;
|
||||
break;
|
||||
case PM_EVENT_THAW:
|
||||
callback = noirq ? pm->thaw_noirq : pm->thaw;
|
||||
break;
|
||||
case PM_EVENT_RESTORE:
|
||||
callback = noirq ? pm->restore_noirq : pm->restore;
|
||||
break;
|
||||
default:
|
||||
callback = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
return callback ? callback(dev) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems.
|
||||
* @dev: Device to suspend.
|
||||
*/
|
||||
int pm_generic_suspend_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_SUSPEND, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->suspend_noirq ? pm->suspend_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_suspend_late - Generic suspend_late callback for subsystems.
|
||||
* @dev: Device to suspend.
|
||||
*/
|
||||
int pm_generic_suspend_late(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->suspend_late ? pm->suspend_late(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_suspend_late);
|
||||
|
||||
/**
|
||||
* pm_generic_suspend - Generic suspend callback for subsystems.
|
||||
* @dev: Device to suspend.
|
||||
*/
|
||||
int pm_generic_suspend(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_SUSPEND, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->suspend ? pm->suspend(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_suspend);
|
||||
|
||||
|
|
@ -162,17 +133,33 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend);
|
|||
*/
|
||||
int pm_generic_freeze_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_FREEZE, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->freeze_noirq ? pm->freeze_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_freeze_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_freeze_late - Generic freeze_late callback for subsystems.
|
||||
* @dev: Device to freeze.
|
||||
*/
|
||||
int pm_generic_freeze_late(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->freeze_late ? pm->freeze_late(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_freeze_late);
|
||||
|
||||
/**
|
||||
* pm_generic_freeze - Generic freeze callback for subsystems.
|
||||
* @dev: Device to freeze.
|
||||
*/
|
||||
int pm_generic_freeze(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_FREEZE, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->freeze ? pm->freeze(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_freeze);
|
||||
|
||||
|
|
@ -182,17 +169,33 @@ EXPORT_SYMBOL_GPL(pm_generic_freeze);
|
|||
*/
|
||||
int pm_generic_poweroff_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_HIBERNATE, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->poweroff_noirq ? pm->poweroff_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_poweroff_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_poweroff_late - Generic poweroff_late callback for subsystems.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
int pm_generic_poweroff_late(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->poweroff_late ? pm->poweroff_late(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_poweroff_late);
|
||||
|
||||
/**
|
||||
* pm_generic_poweroff - Generic poweroff callback for subsystems.
|
||||
* @dev: Device to handle.
|
||||
*/
|
||||
int pm_generic_poweroff(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_HIBERNATE, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->poweroff ? pm->poweroff(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_poweroff);
|
||||
|
||||
|
|
@ -202,17 +205,33 @@ EXPORT_SYMBOL_GPL(pm_generic_poweroff);
|
|||
*/
|
||||
int pm_generic_thaw_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_THAW, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->thaw_noirq ? pm->thaw_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_thaw_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_thaw_early - Generic thaw_early callback for subsystems.
|
||||
* @dev: Device to thaw.
|
||||
*/
|
||||
int pm_generic_thaw_early(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->thaw_early ? pm->thaw_early(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_thaw_early);
|
||||
|
||||
/**
|
||||
* pm_generic_thaw - Generic thaw callback for subsystems.
|
||||
* @dev: Device to thaw.
|
||||
*/
|
||||
int pm_generic_thaw(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_THAW, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->thaw ? pm->thaw(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_thaw);
|
||||
|
||||
|
|
@ -222,17 +241,33 @@ EXPORT_SYMBOL_GPL(pm_generic_thaw);
|
|||
*/
|
||||
int pm_generic_resume_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_RESUME, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->resume_noirq ? pm->resume_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_resume_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_resume_early - Generic resume_early callback for subsystems.
|
||||
* @dev: Device to resume.
|
||||
*/
|
||||
int pm_generic_resume_early(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->resume_early ? pm->resume_early(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_resume_early);
|
||||
|
||||
/**
|
||||
* pm_generic_resume - Generic resume callback for subsystems.
|
||||
* @dev: Device to resume.
|
||||
*/
|
||||
int pm_generic_resume(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_RESUME, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->resume ? pm->resume(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_resume);
|
||||
|
||||
|
|
@ -242,17 +277,33 @@ EXPORT_SYMBOL_GPL(pm_generic_resume);
|
|||
*/
|
||||
int pm_generic_restore_noirq(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_RESTORE, true);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->restore_noirq ? pm->restore_noirq(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_restore_noirq);
|
||||
|
||||
/**
|
||||
* pm_generic_restore_early - Generic restore_early callback for subsystems.
|
||||
* @dev: Device to resume.
|
||||
*/
|
||||
int pm_generic_restore_early(struct device *dev)
|
||||
{
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->restore_early ? pm->restore_early(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_restore_early);
|
||||
|
||||
/**
|
||||
* pm_generic_restore - Generic restore callback for subsystems.
|
||||
* @dev: Device to restore.
|
||||
*/
|
||||
int pm_generic_restore(struct device *dev)
|
||||
{
|
||||
return __pm_generic_call(dev, PM_EVENT_RESTORE, false);
|
||||
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
|
||||
|
||||
return pm && pm->restore ? pm->restore(dev) : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_generic_restore);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct device *);
|
|||
LIST_HEAD(dpm_list);
|
||||
LIST_HEAD(dpm_prepared_list);
|
||||
LIST_HEAD(dpm_suspended_list);
|
||||
LIST_HEAD(dpm_late_early_list);
|
||||
LIST_HEAD(dpm_noirq_list);
|
||||
|
||||
struct suspend_stats suspend_stats;
|
||||
|
|
@ -245,6 +246,40 @@ static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_late_early_op - Return the PM operation appropriate for given PM event.
|
||||
* @ops: PM operations to choose from.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*
|
||||
* Runtime PM is disabled for @dev while this function is being executed.
|
||||
*/
|
||||
static pm_callback_t pm_late_early_op(const struct dev_pm_ops *ops,
|
||||
pm_message_t state)
|
||||
{
|
||||
switch (state.event) {
|
||||
#ifdef CONFIG_SUSPEND
|
||||
case PM_EVENT_SUSPEND:
|
||||
return ops->suspend_late;
|
||||
case PM_EVENT_RESUME:
|
||||
return ops->resume_early;
|
||||
#endif /* CONFIG_SUSPEND */
|
||||
#ifdef CONFIG_HIBERNATE_CALLBACKS
|
||||
case PM_EVENT_FREEZE:
|
||||
case PM_EVENT_QUIESCE:
|
||||
return ops->freeze_late;
|
||||
case PM_EVENT_HIBERNATE:
|
||||
return ops->poweroff_late;
|
||||
case PM_EVENT_THAW:
|
||||
case PM_EVENT_RECOVER:
|
||||
return ops->thaw_early;
|
||||
case PM_EVENT_RESTORE:
|
||||
return ops->restore_early;
|
||||
#endif /* CONFIG_HIBERNATE_CALLBACKS */
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_noirq_op - Return the PM operation appropriate for given PM event.
|
||||
* @ops: PM operations to choose from.
|
||||
|
|
@ -374,21 +409,21 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
|
|||
TRACE_RESUME(0);
|
||||
|
||||
if (dev->pm_domain) {
|
||||
info = "EARLY power domain ";
|
||||
info = "noirq power domain ";
|
||||
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
||||
} else if (dev->type && dev->type->pm) {
|
||||
info = "EARLY type ";
|
||||
info = "noirq type ";
|
||||
callback = pm_noirq_op(dev->type->pm, state);
|
||||
} else if (dev->class && dev->class->pm) {
|
||||
info = "EARLY class ";
|
||||
info = "noirq class ";
|
||||
callback = pm_noirq_op(dev->class->pm, state);
|
||||
} else if (dev->bus && dev->bus->pm) {
|
||||
info = "EARLY bus ";
|
||||
info = "noirq bus ";
|
||||
callback = pm_noirq_op(dev->bus->pm, state);
|
||||
}
|
||||
|
||||
if (!callback && dev->driver && dev->driver->pm) {
|
||||
info = "EARLY driver ";
|
||||
info = "noirq driver ";
|
||||
callback = pm_noirq_op(dev->driver->pm, state);
|
||||
}
|
||||
|
||||
|
|
@ -399,13 +434,13 @@ static int device_resume_noirq(struct device *dev, pm_message_t state)
|
|||
}
|
||||
|
||||
/**
|
||||
* dpm_resume_noirq - Execute "early resume" callbacks for non-sysdev devices.
|
||||
* dpm_resume_noirq - Execute "noirq resume" callbacks for all devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*
|
||||
* Call the "noirq" resume handlers for all devices marked as DPM_OFF_IRQ and
|
||||
* Call the "noirq" resume handlers for all devices in dpm_noirq_list and
|
||||
* enable device drivers to receive interrupts.
|
||||
*/
|
||||
void dpm_resume_noirq(pm_message_t state)
|
||||
static void dpm_resume_noirq(pm_message_t state)
|
||||
{
|
||||
ktime_t starttime = ktime_get();
|
||||
|
||||
|
|
@ -415,7 +450,7 @@ void dpm_resume_noirq(pm_message_t state)
|
|||
int error;
|
||||
|
||||
get_device(dev);
|
||||
list_move_tail(&dev->power.entry, &dpm_suspended_list);
|
||||
list_move_tail(&dev->power.entry, &dpm_late_early_list);
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
|
||||
error = device_resume_noirq(dev, state);
|
||||
|
|
@ -423,6 +458,80 @@ void dpm_resume_noirq(pm_message_t state)
|
|||
suspend_stats.failed_resume_noirq++;
|
||||
dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
|
||||
dpm_save_failed_dev(dev_name(dev));
|
||||
pm_dev_err(dev, state, " noirq", error);
|
||||
}
|
||||
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
put_device(dev);
|
||||
}
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
dpm_show_time(starttime, state, "noirq");
|
||||
resume_device_irqs();
|
||||
}
|
||||
|
||||
/**
|
||||
* device_resume_early - Execute an "early resume" callback for given device.
|
||||
* @dev: Device to handle.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*
|
||||
* Runtime PM is disabled for @dev while this function is being executed.
|
||||
*/
|
||||
static int device_resume_early(struct device *dev, pm_message_t state)
|
||||
{
|
||||
pm_callback_t callback = NULL;
|
||||
char *info = NULL;
|
||||
int error = 0;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
|
||||
if (dev->pm_domain) {
|
||||
info = "early power domain ";
|
||||
callback = pm_late_early_op(&dev->pm_domain->ops, state);
|
||||
} else if (dev->type && dev->type->pm) {
|
||||
info = "early type ";
|
||||
callback = pm_late_early_op(dev->type->pm, state);
|
||||
} else if (dev->class && dev->class->pm) {
|
||||
info = "early class ";
|
||||
callback = pm_late_early_op(dev->class->pm, state);
|
||||
} else if (dev->bus && dev->bus->pm) {
|
||||
info = "early bus ";
|
||||
callback = pm_late_early_op(dev->bus->pm, state);
|
||||
}
|
||||
|
||||
if (!callback && dev->driver && dev->driver->pm) {
|
||||
info = "early driver ";
|
||||
callback = pm_late_early_op(dev->driver->pm, state);
|
||||
}
|
||||
|
||||
error = dpm_run_callback(callback, dev, state, info);
|
||||
|
||||
TRACE_RESUME(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_resume_early - Execute "early resume" callbacks for all devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*/
|
||||
static void dpm_resume_early(pm_message_t state)
|
||||
{
|
||||
ktime_t starttime = ktime_get();
|
||||
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
while (!list_empty(&dpm_late_early_list)) {
|
||||
struct device *dev = to_device(dpm_late_early_list.next);
|
||||
int error;
|
||||
|
||||
get_device(dev);
|
||||
list_move_tail(&dev->power.entry, &dpm_suspended_list);
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
|
||||
error = device_resume_early(dev, state);
|
||||
if (error) {
|
||||
suspend_stats.failed_resume_early++;
|
||||
dpm_save_failed_step(SUSPEND_RESUME_EARLY);
|
||||
dpm_save_failed_dev(dev_name(dev));
|
||||
pm_dev_err(dev, state, " early", error);
|
||||
}
|
||||
|
||||
|
|
@ -431,9 +540,18 @@ void dpm_resume_noirq(pm_message_t state)
|
|||
}
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
dpm_show_time(starttime, state, "early");
|
||||
resume_device_irqs();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_resume_noirq);
|
||||
|
||||
/**
|
||||
* dpm_resume_start - Execute "noirq" and "early" device callbacks.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*/
|
||||
void dpm_resume_start(pm_message_t state)
|
||||
{
|
||||
dpm_resume_noirq(state);
|
||||
dpm_resume_early(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_resume_start);
|
||||
|
||||
/**
|
||||
* device_resume - Execute "resume" callbacks for given device.
|
||||
|
|
@ -716,21 +834,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
|
|||
char *info = NULL;
|
||||
|
||||
if (dev->pm_domain) {
|
||||
info = "LATE power domain ";
|
||||
info = "noirq power domain ";
|
||||
callback = pm_noirq_op(&dev->pm_domain->ops, state);
|
||||
} else if (dev->type && dev->type->pm) {
|
||||
info = "LATE type ";
|
||||
info = "noirq type ";
|
||||
callback = pm_noirq_op(dev->type->pm, state);
|
||||
} else if (dev->class && dev->class->pm) {
|
||||
info = "LATE class ";
|
||||
info = "noirq class ";
|
||||
callback = pm_noirq_op(dev->class->pm, state);
|
||||
} else if (dev->bus && dev->bus->pm) {
|
||||
info = "LATE bus ";
|
||||
info = "noirq bus ";
|
||||
callback = pm_noirq_op(dev->bus->pm, state);
|
||||
}
|
||||
|
||||
if (!callback && dev->driver && dev->driver->pm) {
|
||||
info = "LATE driver ";
|
||||
info = "noirq driver ";
|
||||
callback = pm_noirq_op(dev->driver->pm, state);
|
||||
}
|
||||
|
||||
|
|
@ -738,21 +856,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state)
|
|||
}
|
||||
|
||||
/**
|
||||
* dpm_suspend_noirq - Execute "late suspend" callbacks for non-sysdev devices.
|
||||
* dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*
|
||||
* Prevent device drivers from receiving interrupts and call the "noirq" suspend
|
||||
* handlers for all non-sysdev devices.
|
||||
*/
|
||||
int dpm_suspend_noirq(pm_message_t state)
|
||||
static int dpm_suspend_noirq(pm_message_t state)
|
||||
{
|
||||
ktime_t starttime = ktime_get();
|
||||
int error = 0;
|
||||
|
||||
suspend_device_irqs();
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
while (!list_empty(&dpm_suspended_list)) {
|
||||
struct device *dev = to_device(dpm_suspended_list.prev);
|
||||
while (!list_empty(&dpm_late_early_list)) {
|
||||
struct device *dev = to_device(dpm_late_early_list.prev);
|
||||
|
||||
get_device(dev);
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
|
|
@ -761,7 +879,7 @@ int dpm_suspend_noirq(pm_message_t state)
|
|||
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
if (error) {
|
||||
pm_dev_err(dev, state, " late", error);
|
||||
pm_dev_err(dev, state, " noirq", error);
|
||||
suspend_stats.failed_suspend_noirq++;
|
||||
dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
|
||||
dpm_save_failed_dev(dev_name(dev));
|
||||
|
|
@ -776,10 +894,95 @@ int dpm_suspend_noirq(pm_message_t state)
|
|||
if (error)
|
||||
dpm_resume_noirq(resume_event(state));
|
||||
else
|
||||
dpm_show_time(starttime, state, "late");
|
||||
dpm_show_time(starttime, state, "noirq");
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_suspend_noirq);
|
||||
|
||||
/**
|
||||
* device_suspend_late - Execute a "late suspend" callback for given device.
|
||||
* @dev: Device to handle.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*
|
||||
* Runtime PM is disabled for @dev while this function is being executed.
|
||||
*/
|
||||
static int device_suspend_late(struct device *dev, pm_message_t state)
|
||||
{
|
||||
pm_callback_t callback = NULL;
|
||||
char *info = NULL;
|
||||
|
||||
if (dev->pm_domain) {
|
||||
info = "late power domain ";
|
||||
callback = pm_late_early_op(&dev->pm_domain->ops, state);
|
||||
} else if (dev->type && dev->type->pm) {
|
||||
info = "late type ";
|
||||
callback = pm_late_early_op(dev->type->pm, state);
|
||||
} else if (dev->class && dev->class->pm) {
|
||||
info = "late class ";
|
||||
callback = pm_late_early_op(dev->class->pm, state);
|
||||
} else if (dev->bus && dev->bus->pm) {
|
||||
info = "late bus ";
|
||||
callback = pm_late_early_op(dev->bus->pm, state);
|
||||
}
|
||||
|
||||
if (!callback && dev->driver && dev->driver->pm) {
|
||||
info = "late driver ";
|
||||
callback = pm_late_early_op(dev->driver->pm, state);
|
||||
}
|
||||
|
||||
return dpm_run_callback(callback, dev, state, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_suspend_late - Execute "late suspend" callbacks for all devices.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*/
|
||||
static int dpm_suspend_late(pm_message_t state)
|
||||
{
|
||||
ktime_t starttime = ktime_get();
|
||||
int error = 0;
|
||||
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
while (!list_empty(&dpm_suspended_list)) {
|
||||
struct device *dev = to_device(dpm_suspended_list.prev);
|
||||
|
||||
get_device(dev);
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
|
||||
error = device_suspend_late(dev, state);
|
||||
|
||||
mutex_lock(&dpm_list_mtx);
|
||||
if (error) {
|
||||
pm_dev_err(dev, state, " late", error);
|
||||
suspend_stats.failed_suspend_late++;
|
||||
dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
|
||||
dpm_save_failed_dev(dev_name(dev));
|
||||
put_device(dev);
|
||||
break;
|
||||
}
|
||||
if (!list_empty(&dev->power.entry))
|
||||
list_move(&dev->power.entry, &dpm_late_early_list);
|
||||
put_device(dev);
|
||||
}
|
||||
mutex_unlock(&dpm_list_mtx);
|
||||
if (error)
|
||||
dpm_resume_early(resume_event(state));
|
||||
else
|
||||
dpm_show_time(starttime, state, "late");
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* dpm_suspend_end - Execute "late" and "noirq" device suspend callbacks.
|
||||
* @state: PM transition of the system being carried out.
|
||||
*/
|
||||
int dpm_suspend_end(pm_message_t state)
|
||||
{
|
||||
int error = dpm_suspend_late(state);
|
||||
|
||||
return error ? : dpm_suspend_noirq(state);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dpm_suspend_end);
|
||||
|
||||
/**
|
||||
* legacy_suspend - Execute a legacy (bus or class) suspend callback for device.
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ extern void dpm_sysfs_remove(struct device *dev);
|
|||
extern void rpm_sysfs_remove(struct device *dev);
|
||||
extern int wakeup_sysfs_add(struct device *dev);
|
||||
extern void wakeup_sysfs_remove(struct device *dev);
|
||||
extern int pm_qos_sysfs_add(struct device *dev);
|
||||
extern void pm_qos_sysfs_remove(struct device *dev);
|
||||
|
||||
#else /* CONFIG_PM */
|
||||
|
||||
|
|
@ -79,5 +81,7 @@ static inline void dpm_sysfs_remove(struct device *dev) {}
|
|||
static inline void rpm_sysfs_remove(struct device *dev) {}
|
||||
static inline int wakeup_sysfs_add(struct device *dev) { return 0; }
|
||||
static inline void wakeup_sysfs_remove(struct device *dev) {}
|
||||
static inline int pm_qos_sysfs_add(struct device *dev) { return 0; }
|
||||
static inline void pm_qos_sysfs_remove(struct device *dev) {}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include <linux/mutex.h>
|
||||
#include <linux/export.h>
|
||||
|
||||
#include "power.h"
|
||||
|
||||
static DEFINE_MUTEX(dev_pm_qos_mtx);
|
||||
|
||||
|
|
@ -166,6 +167,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev)
|
|||
struct dev_pm_qos_request *req, *tmp;
|
||||
struct pm_qos_constraints *c;
|
||||
|
||||
/*
|
||||
* If the device's PM QoS resume latency limit has been exposed to user
|
||||
* space, it has to be hidden at this point.
|
||||
*/
|
||||
dev_pm_qos_hide_latency_limit(dev);
|
||||
|
||||
mutex_lock(&dev_pm_qos_mtx);
|
||||
|
||||
dev->power.power_state = PMSG_INVALID;
|
||||
|
|
@ -445,3 +452,57 @@ int dev_pm_qos_add_ancestor_request(struct device *dev,
|
|||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static void __dev_pm_qos_drop_user_request(struct device *dev)
|
||||
{
|
||||
dev_pm_qos_remove_request(dev->power.pq_req);
|
||||
dev->power.pq_req = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space.
|
||||
* @dev: Device whose PM QoS latency limit is to be exposed to user space.
|
||||
* @value: Initial value of the latency limit.
|
||||
*/
|
||||
int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)
|
||||
{
|
||||
struct dev_pm_qos_request *req;
|
||||
int ret;
|
||||
|
||||
if (!device_is_registered(dev) || value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (dev->power.pq_req)
|
||||
return -EEXIST;
|
||||
|
||||
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = dev_pm_qos_add_request(dev, req, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev->power.pq_req = req;
|
||||
ret = pm_qos_sysfs_add(dev);
|
||||
if (ret)
|
||||
__dev_pm_qos_drop_user_request(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);
|
||||
|
||||
/**
|
||||
* dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space.
|
||||
* @dev: Device whose PM QoS latency limit is to be hidden from user space.
|
||||
*/
|
||||
void dev_pm_qos_hide_latency_limit(struct device *dev)
|
||||
{
|
||||
if (dev->power.pq_req) {
|
||||
pm_qos_sysfs_remove(dev);
|
||||
__dev_pm_qos_drop_user_request(dev);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit);
|
||||
#endif /* CONFIG_PM_RUNTIME */
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
|
@ -217,6 +218,31 @@ static ssize_t autosuspend_delay_ms_store(struct device *dev,
|
|||
static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show,
|
||||
autosuspend_delay_ms_store);
|
||||
|
||||
static ssize_t pm_qos_latency_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", dev->power.pq_req->node.prio);
|
||||
}
|
||||
|
||||
static ssize_t pm_qos_latency_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
s32 value;
|
||||
int ret;
|
||||
|
||||
if (kstrtos32(buf, 0, &value))
|
||||
return -EINVAL;
|
||||
|
||||
if (value < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = dev_pm_qos_update_request(dev->power.pq_req, value);
|
||||
return ret < 0 ? ret : n;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(pm_qos_resume_latency_us, 0644,
|
||||
pm_qos_latency_show, pm_qos_latency_store);
|
||||
#endif /* CONFIG_PM_RUNTIME */
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
|
@ -490,6 +516,17 @@ static struct attribute_group pm_runtime_attr_group = {
|
|||
.attrs = runtime_attrs,
|
||||
};
|
||||
|
||||
static struct attribute *pm_qos_attrs[] = {
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
&dev_attr_pm_qos_resume_latency_us.attr,
|
||||
#endif /* CONFIG_PM_RUNTIME */
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group pm_qos_attr_group = {
|
||||
.name = power_group_name,
|
||||
.attrs = pm_qos_attrs,
|
||||
};
|
||||
|
||||
int dpm_sysfs_add(struct device *dev)
|
||||
{
|
||||
int rc;
|
||||
|
|
@ -530,6 +567,16 @@ void wakeup_sysfs_remove(struct device *dev)
|
|||
sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);
|
||||
}
|
||||
|
||||
int pm_qos_sysfs_add(struct device *dev)
|
||||
{
|
||||
return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group);
|
||||
}
|
||||
|
||||
void pm_qos_sysfs_remove(struct device *dev)
|
||||
{
|
||||
sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group);
|
||||
}
|
||||
|
||||
void rpm_sysfs_remove(struct device *dev)
|
||||
{
|
||||
sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,23 @@ static void pm_wakeup_timer_fn(unsigned long data);
|
|||
|
||||
static LIST_HEAD(wakeup_sources);
|
||||
|
||||
/**
|
||||
* wakeup_source_prepare - Prepare a new wakeup source for initialization.
|
||||
* @ws: Wakeup source to prepare.
|
||||
* @name: Pointer to the name of the new wakeup source.
|
||||
*
|
||||
* Callers must ensure that the @name string won't be freed when @ws is still in
|
||||
* use.
|
||||
*/
|
||||
void wakeup_source_prepare(struct wakeup_source *ws, const char *name)
|
||||
{
|
||||
if (ws) {
|
||||
memset(ws, 0, sizeof(*ws));
|
||||
ws->name = name;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_prepare);
|
||||
|
||||
/**
|
||||
* wakeup_source_create - Create a struct wakeup_source object.
|
||||
* @name: Name of the new wakeup source.
|
||||
|
|
@ -60,37 +77,44 @@ struct wakeup_source *wakeup_source_create(const char *name)
|
|||
{
|
||||
struct wakeup_source *ws;
|
||||
|
||||
ws = kzalloc(sizeof(*ws), GFP_KERNEL);
|
||||
ws = kmalloc(sizeof(*ws), GFP_KERNEL);
|
||||
if (!ws)
|
||||
return NULL;
|
||||
|
||||
spin_lock_init(&ws->lock);
|
||||
if (name)
|
||||
ws->name = kstrdup(name, GFP_KERNEL);
|
||||
|
||||
wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL);
|
||||
return ws;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_create);
|
||||
|
||||
/**
|
||||
* wakeup_source_drop - Prepare a struct wakeup_source object for destruction.
|
||||
* @ws: Wakeup source to prepare for destruction.
|
||||
*
|
||||
* Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never
|
||||
* be run in parallel with this function for the same wakeup source object.
|
||||
*/
|
||||
void wakeup_source_drop(struct wakeup_source *ws)
|
||||
{
|
||||
if (!ws)
|
||||
return;
|
||||
|
||||
del_timer_sync(&ws->timer);
|
||||
__pm_relax(ws);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_drop);
|
||||
|
||||
/**
|
||||
* wakeup_source_destroy - Destroy a struct wakeup_source object.
|
||||
* @ws: Wakeup source to destroy.
|
||||
*
|
||||
* Use only for wakeup source objects created with wakeup_source_create().
|
||||
*/
|
||||
void wakeup_source_destroy(struct wakeup_source *ws)
|
||||
{
|
||||
if (!ws)
|
||||
return;
|
||||
|
||||
spin_lock_irq(&ws->lock);
|
||||
while (ws->active) {
|
||||
spin_unlock_irq(&ws->lock);
|
||||
|
||||
schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT));
|
||||
|
||||
spin_lock_irq(&ws->lock);
|
||||
}
|
||||
spin_unlock_irq(&ws->lock);
|
||||
|
||||
wakeup_source_drop(ws);
|
||||
kfree(ws->name);
|
||||
kfree(ws);
|
||||
}
|
||||
|
|
@ -105,6 +129,7 @@ void wakeup_source_add(struct wakeup_source *ws)
|
|||
if (WARN_ON(!ws))
|
||||
return;
|
||||
|
||||
spin_lock_init(&ws->lock);
|
||||
setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
|
||||
ws->active = false;
|
||||
|
||||
|
|
@ -152,8 +177,10 @@ EXPORT_SYMBOL_GPL(wakeup_source_register);
|
|||
*/
|
||||
void wakeup_source_unregister(struct wakeup_source *ws)
|
||||
{
|
||||
wakeup_source_remove(ws);
|
||||
wakeup_source_destroy(ws);
|
||||
if (ws) {
|
||||
wakeup_source_remove(ws);
|
||||
wakeup_source_destroy(ws);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wakeup_source_unregister);
|
||||
|
||||
|
|
@ -349,7 +376,6 @@ static void wakeup_source_activate(struct wakeup_source *ws)
|
|||
{
|
||||
ws->active = true;
|
||||
ws->active_count++;
|
||||
ws->timer_expires = jiffies;
|
||||
ws->last_time = ktime_get();
|
||||
|
||||
/* Increment the counter of events in progress. */
|
||||
|
|
@ -370,9 +396,14 @@ void __pm_stay_awake(struct wakeup_source *ws)
|
|||
return;
|
||||
|
||||
spin_lock_irqsave(&ws->lock, flags);
|
||||
|
||||
ws->event_count++;
|
||||
if (!ws->active)
|
||||
wakeup_source_activate(ws);
|
||||
|
||||
del_timer(&ws->timer);
|
||||
ws->timer_expires = 0;
|
||||
|
||||
spin_unlock_irqrestore(&ws->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__pm_stay_awake);
|
||||
|
|
@ -438,6 +469,7 @@ static void wakeup_source_deactivate(struct wakeup_source *ws)
|
|||
ws->max_time = duration;
|
||||
|
||||
del_timer(&ws->timer);
|
||||
ws->timer_expires = 0;
|
||||
|
||||
/*
|
||||
* Increment the counter of registered wakeup events and decrement the
|
||||
|
|
@ -492,11 +524,22 @@ EXPORT_SYMBOL_GPL(pm_relax);
|
|||
* pm_wakeup_timer_fn - Delayed finalization of a wakeup event.
|
||||
* @data: Address of the wakeup source object associated with the event source.
|
||||
*
|
||||
* Call __pm_relax() for the wakeup source whose address is stored in @data.
|
||||
* Call wakeup_source_deactivate() for the wakeup source whose address is stored
|
||||
* in @data if it is currently active and its timer has not been canceled and
|
||||
* the expiration time of the timer is not in future.
|
||||
*/
|
||||
static void pm_wakeup_timer_fn(unsigned long data)
|
||||
{
|
||||
__pm_relax((struct wakeup_source *)data);
|
||||
struct wakeup_source *ws = (struct wakeup_source *)data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ws->lock, flags);
|
||||
|
||||
if (ws->active && ws->timer_expires
|
||||
&& time_after_eq(jiffies, ws->timer_expires))
|
||||
wakeup_source_deactivate(ws);
|
||||
|
||||
spin_unlock_irqrestore(&ws->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -534,7 +577,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec)
|
|||
if (!expires)
|
||||
expires = 1;
|
||||
|
||||
if (time_after(expires, ws->timer_expires)) {
|
||||
if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {
|
||||
mod_timer(&ws->timer, expires);
|
||||
ws->timer_expires = expires;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue