Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53d8f670 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6:
  PCI PM: Make pci_prepare_to_sleep() disable wake-up if needed
  radeonfb: Use __pci_complete_power_transition()
  PCI PM: Introduce __pci_[start|complete]_power_transition() (rev. 2)
  PCI PM: Restore config spaces of all devices during early resume
  PCI PM: Make pci_set_power_state() handle devices with no PM support
  PCI PM: Put devices into low power states during late suspend (rev. 2)
  PCI PM: Move pci_restore_standard_config to pci-driver.c
  PCI PM: Use pci_set_power_state during early resume
  PCI PM: Consistently use variable name "error" for pm call return values
  kexec: Change kexec jump code ordering
  PM: Change hibernation code ordering
  PM: Change suspend code ordering
  PM: Rework handling of interrupts during suspend-resume
  PM: Introduce functions for suspending and resuming device interrupts
parents 93c36ed8 8efb8c76
Loading
Loading
Loading
Loading
+11 −4
Original line number Diff line number Diff line
@@ -1190,8 +1190,10 @@ static int suspend(int vetoable)
	struct apm_user	*as;

	device_suspend(PMSG_SUSPEND);
	local_irq_disable();

	device_power_down(PMSG_SUSPEND);

	local_irq_disable();
	sysdev_suspend(PMSG_SUSPEND);

	local_irq_enable();
@@ -1209,9 +1211,12 @@ static int suspend(int vetoable)
	if (err != APM_SUCCESS)
		apm_error("suspend", err);
	err = (err == APM_SUCCESS) ? 0 : -EIO;

	sysdev_resume();
	device_power_up(PMSG_RESUME);
	local_irq_enable();

	device_power_up(PMSG_RESUME);

	device_resume(PMSG_RESUME);
	queue_event(APM_NORMAL_RESUME, NULL);
	spin_lock(&user_list_lock);
@@ -1228,8 +1233,9 @@ static void standby(void)
{
	int err;

	local_irq_disable();
	device_power_down(PMSG_SUSPEND);

	local_irq_disable();
	sysdev_suspend(PMSG_SUSPEND);
	local_irq_enable();

@@ -1239,8 +1245,9 @@ static void standby(void)

	local_irq_disable();
	sysdev_resume();
	device_power_up(PMSG_RESUME);
	local_irq_enable();

	device_power_up(PMSG_RESUME);
}

static apm_event_t get_event(void)
+11 −9
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#include <linux/pm.h>
#include <linux/resume-trace.h>
#include <linux/rwsem.h>
#include <linux/interrupt.h>

#include "../base.h"
#include "power.h"
@@ -349,7 +350,8 @@ static int resume_device_noirq(struct device *dev, pm_message_t state)
 *	Execute the appropriate "noirq resume" callback for all devices marked
 *	as DPM_OFF_IRQ.
 *
 *	Must be called with interrupts disabled and only one CPU running.
 *	Must be called under dpm_list_mtx.  Device drivers should not receive
 *	interrupts while it's being executed.
 */
static void dpm_power_up(pm_message_t state)
{
@@ -370,14 +372,13 @@ static void dpm_power_up(pm_message_t state)
 *	device_power_up - Turn on all devices that need special attention.
 *	@state: PM transition of the system being carried out.
 *
 *	Power on system devices, then devices that required we shut them down
 *	with interrupts disabled.
 *
 *	Must be called with interrupts disabled.
 *	Call the "early" resume handlers and enable device drivers to receive
 *	interrupts.
 */
void device_power_up(pm_message_t state)
{
	dpm_power_up(state);
	resume_device_irqs();
}
EXPORT_SYMBOL_GPL(device_power_up);

@@ -602,16 +603,17 @@ static int suspend_device_noirq(struct device *dev, pm_message_t state)
 *	device_power_down - Shut down special devices.
 *	@state: PM transition of the system being carried out.
 *
 *	Power down devices that require interrupts to be disabled.
 *	Then power down system devices.
 *	Prevent device drivers from receiving interrupts and call the "late"
 *	suspend handlers.
 *
 *	Must be called with interrupts disabled and only one CPU running.
 *	Must be called under dpm_list_mtx.
 */
int device_power_down(pm_message_t state)
{
	struct device *dev;
	int error = 0;

	suspend_device_irqs();
	list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
		error = suspend_device_noirq(dev, state);
		if (error) {
@@ -621,7 +623,7 @@ int device_power_down(pm_message_t state)
		dev->power.status = DPM_OFF_IRQ;
	}
	if (error)
		dpm_power_up(resume_event(state));
		device_power_up(resume_event(state));
	return error;
}
EXPORT_SYMBOL_GPL(device_power_down);
+8 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>

#include "base.h"

@@ -369,6 +370,13 @@ int sysdev_suspend(pm_message_t state)
	struct sysdev_driver *drv, *err_drv;
	int ret;

	pr_debug("Checking wake-up interrupts\n");

	/* Return error code if there are any wake-up interrupts pending */
	ret = check_wakeup_irqs();
	if (ret)
		return ret;

	pr_debug("Suspending System Devices\n");

	list_for_each_entry_reverse(cls, &system_kset->list, kset.kobj.entry) {
+112 −65
Original line number Diff line number Diff line
@@ -352,53 +352,60 @@ static int pci_legacy_suspend(struct device *dev, pm_message_t state)
{
	struct pci_dev * pci_dev = to_pci_dev(dev);
	struct pci_driver * drv = pci_dev->driver;
	int i = 0;

	if (drv && drv->suspend) {
		pci_power_t prev = pci_dev->current_state;

	pci_dev->state_saved = false;

		i = drv->suspend(pci_dev, state);
		suspend_report_result(drv->suspend, i);
		if (i)
			return i;
	if (drv && drv->suspend) {
		pci_power_t prev = pci_dev->current_state;
		int error;

		if (pci_dev->state_saved)
			goto Fixup;
		error = drv->suspend(pci_dev, state);
		suspend_report_result(drv->suspend, error);
		if (error)
			return error;

		if (pci_dev->current_state != PCI_D0
		if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
		    && pci_dev->current_state != PCI_UNKNOWN) {
			WARN_ONCE(pci_dev->current_state != prev,
				"PCI PM: Device state not saved by %pF\n",
				drv->suspend);
			goto Fixup;
		}
	}

	pci_save_state(pci_dev);
	/*
	 * This is for compatibility with existing code with legacy PM support.
	 */
	pci_pm_set_unknown_state(pci_dev);

 Fixup:
	pci_fixup_device(pci_fixup_suspend, pci_dev);

	return i;
	return 0;
}

static int pci_legacy_suspend_late(struct device *dev, pm_message_t state)
{
	struct pci_dev * pci_dev = to_pci_dev(dev);
	struct pci_driver * drv = pci_dev->driver;
	int i = 0;

	if (drv && drv->suspend_late) {
		i = drv->suspend_late(pci_dev, state);
		suspend_report_result(drv->suspend_late, i);
		pci_power_t prev = pci_dev->current_state;
		int error;

		error = drv->suspend_late(pci_dev, state);
		suspend_report_result(drv->suspend_late, error);
		if (error)
			return error;

		if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
		    && pci_dev->current_state != PCI_UNKNOWN) {
			WARN_ONCE(pci_dev->current_state != prev,
				"PCI PM: Device state not saved by %pF\n",
				drv->suspend_late);
			return 0;
		}
	}
	return i;

	if (!pci_dev->state_saved)
		pci_save_state(pci_dev);

	pci_pm_set_unknown_state(pci_dev);

	return 0;
}

static int pci_legacy_resume_early(struct device *dev)
@@ -423,6 +430,23 @@ static int pci_legacy_resume(struct device *dev)

/* Auxiliary functions used by the new power management framework */

/**
 * pci_restore_standard_config - restore standard config registers of PCI device
 * @pci_dev: PCI device to handle
 */
static int pci_restore_standard_config(struct pci_dev *pci_dev)
{
	pci_update_current_state(pci_dev, PCI_UNKNOWN);

	if (pci_dev->current_state != PCI_D0) {
		int error = pci_set_power_state(pci_dev, PCI_D0);
		if (error)
			return error;
	}

	return pci_dev->state_saved ? pci_restore_state(pci_dev) : 0;
}

static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
{
	pci_restore_standard_config(pci_dev);
@@ -443,7 +467,6 @@ static void pci_pm_default_suspend(struct pci_dev *pci_dev)
	/* Disable non-bridge devices without PM support */
	if (!pci_is_bridge(pci_dev))
		pci_disable_enabled_device(pci_dev);
	pci_save_state(pci_dev);
}

static bool pci_has_legacy_pm_support(struct pci_dev *pci_dev)
@@ -493,13 +516,13 @@ static int pci_pm_suspend(struct device *dev)
	if (pci_has_legacy_pm_support(pci_dev))
		return pci_legacy_suspend(dev, PMSG_SUSPEND);

	pci_dev->state_saved = false;

	if (!pm) {
		pci_pm_default_suspend(pci_dev);
		goto Fixup;
	}

	pci_dev->state_saved = false;

	if (pm->suspend) {
		pci_power_t prev = pci_dev->current_state;
		int error;
@@ -509,22 +532,12 @@ static int pci_pm_suspend(struct device *dev)
		if (error)
			return error;

		if (pci_dev->state_saved)
			goto Fixup;

		if (pci_dev->current_state != PCI_D0
		if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
		    && pci_dev->current_state != PCI_UNKNOWN) {
			WARN_ONCE(pci_dev->current_state != prev,
				"PCI PM: State of device not saved by %pF\n",
				pm->suspend);
			goto Fixup;
		}
		}

	if (!pci_dev->state_saved) {
		pci_save_state(pci_dev);
		if (!pci_is_bridge(pci_dev))
			pci_prepare_to_sleep(pci_dev);
	}

 Fixup:
@@ -536,21 +549,43 @@ static int pci_pm_suspend(struct device *dev)
static int pci_pm_suspend_noirq(struct device *dev)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct device_driver *drv = dev->driver;
	int error = 0;
	struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;

	if (pci_has_legacy_pm_support(pci_dev))
		return pci_legacy_suspend_late(dev, PMSG_SUSPEND);

	if (drv && drv->pm && drv->pm->suspend_noirq) {
		error = drv->pm->suspend_noirq(dev);
		suspend_report_result(drv->pm->suspend_noirq, error);
	if (!pm) {
		pci_save_state(pci_dev);
		return 0;
	}

	if (pm->suspend_noirq) {
		pci_power_t prev = pci_dev->current_state;
		int error;

		error = pm->suspend_noirq(dev);
		suspend_report_result(pm->suspend_noirq, error);
		if (error)
			return error;

		if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
		    && pci_dev->current_state != PCI_UNKNOWN) {
			WARN_ONCE(pci_dev->current_state != prev,
				"PCI PM: State of device not saved by %pF\n",
				pm->suspend_noirq);
			return 0;
		}
	}

	if (!pci_dev->state_saved) {
		pci_save_state(pci_dev);
		if (!pci_is_bridge(pci_dev))
			pci_prepare_to_sleep(pci_dev);
	}

	if (!error)
	pci_pm_set_unknown_state(pci_dev);

	return error;
	return 0;
}

static int pci_pm_resume_noirq(struct device *dev)
@@ -617,13 +652,13 @@ static int pci_pm_freeze(struct device *dev)
	if (pci_has_legacy_pm_support(pci_dev))
		return pci_legacy_suspend(dev, PMSG_FREEZE);

	pci_dev->state_saved = false;

	if (!pm) {
		pci_pm_default_suspend(pci_dev);
		return 0;
	}

	pci_dev->state_saved = false;

	if (pm->freeze) {
		int error;

@@ -633,9 +668,6 @@ static int pci_pm_freeze(struct device *dev)
			return error;
	}

	if (!pci_dev->state_saved)
		pci_save_state(pci_dev);

	return 0;
}

@@ -643,20 +675,25 @@ static int pci_pm_freeze_noirq(struct device *dev)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct device_driver *drv = dev->driver;
	int error = 0;

	if (pci_has_legacy_pm_support(pci_dev))
		return pci_legacy_suspend_late(dev, PMSG_FREEZE);

	if (drv && drv->pm && drv->pm->freeze_noirq) {
		int error;

		error = drv->pm->freeze_noirq(dev);
		suspend_report_result(drv->pm->freeze_noirq, error);
		if (error)
			return error;
	}

	if (!error)
	if (!pci_dev->state_saved)
		pci_save_state(pci_dev);

	pci_pm_set_unknown_state(pci_dev);

	return error;
	return 0;
}

static int pci_pm_thaw_noirq(struct device *dev)
@@ -699,46 +736,56 @@ static int pci_pm_poweroff(struct device *dev)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
	int error = 0;

	if (pci_has_legacy_pm_support(pci_dev))
		return pci_legacy_suspend(dev, PMSG_HIBERNATE);

	pci_dev->state_saved = false;

	if (!pm) {
		pci_pm_default_suspend(pci_dev);
		goto Fixup;
	}

	pci_dev->state_saved = false;

	if (pm->poweroff) {
		int error;

		error = pm->poweroff(dev);
		suspend_report_result(pm->poweroff, error);
		if (error)
			return error;
	}

	if (!pci_dev->state_saved && !pci_is_bridge(pci_dev))
		pci_prepare_to_sleep(pci_dev);

 Fixup:
	pci_fixup_device(pci_fixup_suspend, pci_dev);

	return error;
	return 0;
}

static int pci_pm_poweroff_noirq(struct device *dev)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct device_driver *drv = dev->driver;
	int error = 0;

	if (pci_has_legacy_pm_support(to_pci_dev(dev)))
		return pci_legacy_suspend_late(dev, PMSG_HIBERNATE);

	if (drv && drv->pm && drv->pm->poweroff_noirq) {
	if (!drv || !drv->pm)
		return 0;

	if (drv->pm->poweroff_noirq) {
		int error;

		error = drv->pm->poweroff_noirq(dev);
		suspend_report_result(drv->pm->poweroff_noirq, error);
		if (error)
			return error;
	}

	return error;
	if (!pci_dev->state_saved && !pci_is_bridge(pci_dev))
		pci_prepare_to_sleep(pci_dev);

	return 0;
}

static int pci_pm_restore_noirq(struct device *dev)
+65 −77
Original line number Diff line number Diff line
@@ -426,7 +426,6 @@ static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
 *                           given PCI device
 * @dev: PCI device to handle.
 * @state: PCI power state (D0, D1, D2, D3hot) to put the device into.
 * @wait: If 'true', wait for the device to change its power state
 *
 * RETURN VALUE:
 * -EINVAL if the requested state is invalid.
@@ -435,12 +434,15 @@ static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
 * 0 if device already is in the requested state.
 * 0 if device's power state has been successfully changed.
 */
static int
pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state, bool wait)
static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state)
{
	u16 pmcsr;
	bool need_restore = false;

	/* Check if we're already there */
	if (dev->current_state == state)
		return 0;

	if (!dev->pm_cap)
		return -EIO;

@@ -451,10 +453,7 @@ pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state, bool wait)
	 * Can enter D0 from any state, but if we can only go deeper 
	 * to sleep if we're already in a low power state
	 */
	if (dev->current_state == state) {
		/* we're already there */
		return 0;
	} else if (state != PCI_D0 && dev->current_state <= PCI_D3cold
	if (state != PCI_D0 && dev->current_state <= PCI_D3cold
	    && dev->current_state > state) {
		dev_err(&dev->dev, "invalid power transition "
			"(from state %d to %d)\n", dev->current_state, state);
@@ -481,10 +480,8 @@ pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state, bool wait)
		break;
	case PCI_UNKNOWN: /* Boot-up */
		if ((pmcsr & PCI_PM_CTRL_STATE_MASK) == PCI_D3hot
		 && !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET)) {
		 && !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET))
			need_restore = true;
			wait = true;
		}
		/* Fall-through: force to D0 */
	default:
		pmcsr = 0;
@@ -494,9 +491,6 @@ pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state, bool wait)
	/* enter specified state */
	pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);

	if (!wait)
		return 0;

	/* Mandatory power management transition delays */
	/* see PCI PM 1.1 5.6.1 table 18 */
	if (state == PCI_D3hot || dev->current_state == PCI_D3hot)
@@ -521,7 +515,7 @@ pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state, bool wait)
	if (need_restore)
		pci_restore_bars(dev);

	if (wait && dev->bus->self)
	if (dev->bus->self)
		pcie_aspm_pm_state_change(dev->bus->self);

	return 0;
@@ -545,6 +539,53 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
	}
}

/**
 * pci_platform_power_transition - Use platform to change device power state
 * @dev: PCI device to handle.
 * @state: State to put the device into.
 */
static int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state)
{
	int error;

	if (platform_pci_power_manageable(dev)) {
		error = platform_pci_set_power_state(dev, state);
		if (!error)
			pci_update_current_state(dev, state);
	} else {
		error = -ENODEV;
		/* Fall back to PCI_D0 if native PM is not supported */
		pci_update_current_state(dev, PCI_D0);
	}

	return error;
}

/**
 * __pci_start_power_transition - Start power transition of a PCI device
 * @dev: PCI device to handle.
 * @state: State to put the device into.
 */
static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
{
	if (state == PCI_D0)
		pci_platform_power_transition(dev, PCI_D0);
}

/**
 * __pci_complete_power_transition - Complete power transition of a PCI device
 * @dev: PCI device to handle.
 * @state: State to put the device into.
 *
 * This function should not be called directly by device drivers.
 */
int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state)
{
	return state > PCI_D0 ?
			pci_platform_power_transition(dev, state) : -EINVAL;
}
EXPORT_SYMBOL_GPL(__pci_complete_power_transition);

/**
 * pci_set_power_state - Set the power state of a PCI device
 * @dev: PCI device to handle.
@@ -577,30 +618,21 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state)
		 */
		return 0;

	if (state == PCI_D0 && platform_pci_power_manageable(dev)) {
		/*
		 * Allow the platform to change the state, for example via ACPI
		 * _PR0, _PS0 and some such, but do not trust it.
		 */
		int ret = platform_pci_set_power_state(dev, PCI_D0);
		if (!ret)
			pci_update_current_state(dev, PCI_D0);
	}
	/* Check if we're already there */
	if (dev->current_state == state)
		return 0;

	__pci_start_power_transition(dev, state);

	/* This device is quirked not to be put into D3, so
	   don't put it in D3 */
	if (state == PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3))
		return 0;

	error = pci_raw_set_power_state(dev, state, true);
	error = pci_raw_set_power_state(dev, state);

	if (state > PCI_D0 && platform_pci_power_manageable(dev)) {
		/* Allow the platform to finalize the transition */
		int ret = platform_pci_set_power_state(dev, state);
		if (!ret) {
			pci_update_current_state(dev, state);
	if (!__pci_complete_power_transition(dev, state))
		error = 0;
		}
	}

	return error;
}
@@ -1231,7 +1263,7 @@ int pci_prepare_to_sleep(struct pci_dev *dev)
	if (target_state == PCI_POWER_ERROR)
		return -EIO;

	pci_enable_wake(dev, target_state, true);
	pci_enable_wake(dev, target_state, device_may_wakeup(&dev->dev));

	error = pci_set_power_state(dev, target_state);

@@ -1380,50 +1412,6 @@ void pci_allocate_cap_save_buffers(struct pci_dev *dev)
			"unable to preallocate PCI-X save buffer\n");
}

/**
 * pci_restore_standard_config - restore standard config registers of PCI device
 * @dev: PCI device to handle
 *
 * This function assumes that the device's configuration space is accessible.
 * If the device needs to be powered up, the function will wait for it to
 * change the state.
 */
int pci_restore_standard_config(struct pci_dev *dev)
{
	pci_power_t prev_state;
	int error;

	pci_update_current_state(dev, PCI_D0);

	prev_state = dev->current_state;
	if (prev_state == PCI_D0)
		goto Restore;

	error = pci_raw_set_power_state(dev, PCI_D0, false);
	if (error)
		return error;

	/*
	 * This assumes that we won't get a bus in B2 or B3 from the BIOS, but
	 * we've made this assumption forever and it appears to be universally
	 * satisfied.
	 */
	switch(prev_state) {
	case PCI_D3cold:
	case PCI_D3hot:
		mdelay(pci_pm_d3_delay);
		break;
	case PCI_D2:
		udelay(PCI_PM_D2_DELAY);
		break;
	}

	pci_update_current_state(dev, PCI_D0);

 Restore:
	return dev->state_saved ? pci_restore_state(dev) : 0;
}

/**
 * pci_enable_ari - enable ARI forwarding if hardware support it
 * @dev: the PCI device
Loading