Commit daaed104 authored by Bjorn Helgaas's avatar Bjorn Helgaas

Merge branch 'pci/pm' into next

* pci/pm:
  x86/platform/intel-mid: Constify mid_pci_platform_pm
  PCI: pciehp: Add runtime PM support for PCIe hotplug ports
  ACPI / hotplug / PCI: Make device_is_managed_by_native_pciehp() public
  ACPI / hotplug / PCI: Use cached copy of PCI_EXP_SLTCAP_HPC bit
  PCI: Unfold conditions to block runtime PM on PCIe ports
  PCI: Consolidate conditions to allow runtime PM on PCIe ports
  PCI: Activate runtime PM on a PCIe port only if it can suspend
  PCI: Speed up algorithm in pci_bridge_d3_update()
  PCI: Autosense device removal in pci_bridge_d3_update()
  PCI: Don't acquire ref on parent in pci_bridge_d3_update()
  USB: UHCI: report non-PME wakeup signalling for Intel hardware
  PCI: Check for PME in targeted sleep state
parents db5ba864 c9312254
...@@ -320,7 +320,7 @@ void pci_bus_add_device(struct pci_dev *dev) ...@@ -320,7 +320,7 @@ void pci_bus_add_device(struct pci_dev *dev)
pci_fixup_device(pci_fixup_final, dev); pci_fixup_device(pci_fixup_final, dev);
pci_create_sysfs_dev_files(dev); pci_create_sysfs_dev_files(dev);
pci_proc_attach_device(dev); pci_proc_attach_device(dev);
pci_bridge_d3_device_changed(dev); pci_bridge_d3_update(dev);
dev->match_driver = true; dev->match_driver = true;
retval = device_attach(&dev->dev); retval = device_attach(&dev->dev);
......
...@@ -222,35 +222,6 @@ static void acpiphp_post_dock_fixup(struct acpi_device *adev) ...@@ -222,35 +222,6 @@ static void acpiphp_post_dock_fixup(struct acpi_device *adev)
acpiphp_let_context_go(context); acpiphp_let_context_go(context);
} }
/* Check whether the PCI device is managed by native PCIe hotplug driver */
static bool device_is_managed_by_native_pciehp(struct pci_dev *pdev)
{
u32 reg32;
acpi_handle tmp;
struct acpi_pci_root *root;
/* Check whether the PCIe port supports native PCIe hotplug */
if (pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &reg32))
return false;
if (!(reg32 & PCI_EXP_SLTCAP_HPC))
return false;
/*
* Check whether native PCIe hotplug has been enabled for
* this PCIe hierarchy.
*/
tmp = acpi_find_root_bridge_handle(pdev);
if (!tmp)
return false;
root = acpi_pci_find_root(tmp);
if (!root)
return false;
if (!(root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL))
return false;
return true;
}
/** /**
* acpiphp_add_context - Add ACPIPHP context to an ACPI device object. * acpiphp_add_context - Add ACPIPHP context to an ACPI device object.
* @handle: ACPI handle of the object to add a context to. * @handle: ACPI handle of the object to add a context to.
...@@ -334,7 +305,7 @@ static acpi_status acpiphp_add_context(acpi_handle handle, u32 lvl, void *data, ...@@ -334,7 +305,7 @@ static acpi_status acpiphp_add_context(acpi_handle handle, u32 lvl, void *data,
* expose slots to user space in those cases. * expose slots to user space in those cases.
*/ */
if ((acpi_pci_check_ejectable(pbus, handle) || is_dock_device(adev)) if ((acpi_pci_check_ejectable(pbus, handle) || is_dock_device(adev))
&& !(pdev && device_is_managed_by_native_pciehp(pdev))) { && !(pdev && pdev->is_hotplug_bridge && pciehp_is_native(pdev))) {
unsigned long long sun; unsigned long long sun;
int retval; int retval;
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/pci.h> #include <linux/pci.h>
#include "../pci.h" #include "../pci.h"
#include "pciehp.h" #include "pciehp.h"
...@@ -98,6 +99,7 @@ static int board_added(struct slot *p_slot) ...@@ -98,6 +99,7 @@ static int board_added(struct slot *p_slot)
pciehp_green_led_blink(p_slot); pciehp_green_led_blink(p_slot);
/* Check link training status */ /* Check link training status */
pm_runtime_get_sync(&ctrl->pcie->port->dev);
retval = pciehp_check_link_status(ctrl); retval = pciehp_check_link_status(ctrl);
if (retval) { if (retval) {
ctrl_err(ctrl, "Failed to check link status\n"); ctrl_err(ctrl, "Failed to check link status\n");
...@@ -118,12 +120,14 @@ static int board_added(struct slot *p_slot) ...@@ -118,12 +120,14 @@ static int board_added(struct slot *p_slot)
if (retval != -EEXIST) if (retval != -EEXIST)
goto err_exit; goto err_exit;
} }
pm_runtime_put(&ctrl->pcie->port->dev);
pciehp_green_led_on(p_slot); pciehp_green_led_on(p_slot);
pciehp_set_attention_status(p_slot, 0); pciehp_set_attention_status(p_slot, 0);
return 0; return 0;
err_exit: err_exit:
pm_runtime_put(&ctrl->pcie->port->dev);
set_slot_off(ctrl, p_slot); set_slot_off(ctrl, p_slot);
return retval; return retval;
} }
...@@ -137,7 +141,9 @@ static int remove_board(struct slot *p_slot) ...@@ -137,7 +141,9 @@ static int remove_board(struct slot *p_slot)
int retval; int retval;
struct controller *ctrl = p_slot->ctrl; struct controller *ctrl = p_slot->ctrl;
pm_runtime_get_sync(&ctrl->pcie->port->dev);
retval = pciehp_unconfigure_device(p_slot); retval = pciehp_unconfigure_device(p_slot);
pm_runtime_put(&ctrl->pcie->port->dev);
if (retval) if (retval)
return retval; return retval;
......
...@@ -369,6 +369,30 @@ int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp) ...@@ -369,6 +369,30 @@ int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp)
} }
EXPORT_SYMBOL_GPL(pci_get_hp_params); EXPORT_SYMBOL_GPL(pci_get_hp_params);
/**
* pciehp_is_native - Check whether a hotplug port is handled by the OS
* @pdev: Hotplug port to check
*
* Walk up from @pdev to the host bridge, obtain its cached _OSC Control Field
* and return the value of the "PCI Express Native Hot Plug control" bit.
* On failure to obtain the _OSC Control Field return %false.
*/
bool pciehp_is_native(struct pci_dev *pdev)
{
struct acpi_pci_root *root;
acpi_handle handle;
handle = acpi_find_root_bridge_handle(pdev);
if (!handle)
return false;
root = acpi_pci_find_root(handle);
if (!root)
return false;
return root->osc_control_set & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL;
}
/** /**
* pci_acpi_wake_bus - Root bus wakeup notification fork function. * pci_acpi_wake_bus - Root bus wakeup notification fork function.
* @work: Work item to handle. * @work: Work item to handle.
......
...@@ -49,7 +49,7 @@ static bool mid_pci_need_resume(struct pci_dev *dev) ...@@ -49,7 +49,7 @@ static bool mid_pci_need_resume(struct pci_dev *dev)
return false; return false;
} }
static struct pci_platform_pm_ops mid_pci_platform_pm = { static const struct pci_platform_pm_ops mid_pci_platform_pm = {
.is_manageable = mid_pci_power_manageable, .is_manageable = mid_pci_power_manageable,
.set_state = mid_pci_set_power_state, .set_state = mid_pci_set_power_state,
.choose_state = mid_pci_choose_state, .choose_state = mid_pci_choose_state,
......
...@@ -2106,6 +2106,10 @@ bool pci_dev_run_wake(struct pci_dev *dev) ...@@ -2106,6 +2106,10 @@ bool pci_dev_run_wake(struct pci_dev *dev)
if (!dev->pme_support) if (!dev->pme_support)
return false; return false;
/* PME-capable in principle, but not from the intended sleep state */
if (!pci_pme_capable(dev, pci_target_state(dev)))
return false;
while (bus->parent) { while (bus->parent) {
struct pci_dev *bridge = bus->self; struct pci_dev *bridge = bus->self;
...@@ -2226,7 +2230,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) ...@@ -2226,7 +2230,7 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev)
* This function checks if it is possible to move the bridge to D3. * This function checks if it is possible to move the bridge to D3.
* Currently we only allow D3 for recent enough PCIe ports. * Currently we only allow D3 for recent enough PCIe ports.
*/ */
static bool pci_bridge_d3_possible(struct pci_dev *bridge) bool pci_bridge_d3_possible(struct pci_dev *bridge)
{ {
unsigned int year; unsigned int year;
...@@ -2239,6 +2243,14 @@ static bool pci_bridge_d3_possible(struct pci_dev *bridge) ...@@ -2239,6 +2243,14 @@ static bool pci_bridge_d3_possible(struct pci_dev *bridge)
case PCI_EXP_TYPE_DOWNSTREAM: case PCI_EXP_TYPE_DOWNSTREAM:
if (pci_bridge_d3_disable) if (pci_bridge_d3_disable)
return false; return false;
/*
* Hotplug ports handled by firmware in System Management Mode
* may not be put into D3 by the OS (Thunderbolt on non-Macs).
*/
if (bridge->is_hotplug_bridge && !pciehp_is_native(bridge))
return false;
if (pci_bridge_d3_force) if (pci_bridge_d3_force)
return true; return true;
...@@ -2259,32 +2271,36 @@ static bool pci_bridge_d3_possible(struct pci_dev *bridge) ...@@ -2259,32 +2271,36 @@ static bool pci_bridge_d3_possible(struct pci_dev *bridge)
static int pci_dev_check_d3cold(struct pci_dev *dev, void *data) static int pci_dev_check_d3cold(struct pci_dev *dev, void *data)
{ {
bool *d3cold_ok = data; bool *d3cold_ok = data;
bool no_d3cold;
/* if (/* The device needs to be allowed to go D3cold ... */
* The device needs to be allowed to go D3cold and if it is wake dev->no_d3cold || !dev->d3cold_allowed ||
* capable to do so from D3cold.
*/
no_d3cold = dev->no_d3cold || !dev->d3cold_allowed ||
(device_may_wakeup(&dev->dev) && !pci_pme_capable(dev, PCI_D3cold)) ||
!pci_power_manageable(dev);
*d3cold_ok = !no_d3cold; /* ... and if it is wakeup capable to do so from D3cold. */
(device_may_wakeup(&dev->dev) &&
!pci_pme_capable(dev, PCI_D3cold)) ||
return no_d3cold; /* If it is a bridge it must be allowed to go to D3. */
!pci_power_manageable(dev) ||
/* Hotplug interrupts cannot be delivered if the link is down. */
dev->is_hotplug_bridge)
*d3cold_ok = false;
return !*d3cold_ok;
} }
/* /*
* pci_bridge_d3_update - Update bridge D3 capabilities * pci_bridge_d3_update - Update bridge D3 capabilities
* @dev: PCI device which is changed * @dev: PCI device which is changed
* @remove: Is the device being removed
* *
* Update upstream bridge PM capabilities accordingly depending on if the * Update upstream bridge PM capabilities accordingly depending on if the
* device PM configuration was changed or the device is being removed. The * device PM configuration was changed or the device is being removed. The
* change is also propagated upstream. * change is also propagated upstream.
*/ */
static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) void pci_bridge_d3_update(struct pci_dev *dev)
{ {
bool remove = !device_is_registered(&dev->dev);
struct pci_dev *bridge; struct pci_dev *bridge;
bool d3cold_ok = true; bool d3cold_ok = true;
...@@ -2292,55 +2308,39 @@ static void pci_bridge_d3_update(struct pci_dev *dev, bool remove) ...@@ -2292,55 +2308,39 @@ static void pci_bridge_d3_update(struct pci_dev *dev, bool remove)
if (!bridge || !pci_bridge_d3_possible(bridge)) if (!bridge || !pci_bridge_d3_possible(bridge))
return; return;
pci_dev_get(bridge);
/* /*
* If the device is removed we do not care about its D3cold * If D3 is currently allowed for the bridge, removing one of its
* capabilities. * children won't change that.
*/
if (remove && bridge->bridge_d3)
return;
/*
* If D3 is currently allowed for the bridge and a child is added or
* changed, disallowance of D3 can only be caused by that child, so
* we only need to check that single device, not any of its siblings.
*
* If D3 is currently not allowed for the bridge, checking the device
* first may allow us to skip checking its siblings.
*/ */
if (!remove) if (!remove)
pci_dev_check_d3cold(dev, &d3cold_ok); pci_dev_check_d3cold(dev, &d3cold_ok);
if (d3cold_ok) { /*
/* * If D3 is currently not allowed for the bridge, this may be caused
* We need to go through all children to find out if all of * either by the device being changed/removed or any of its siblings,
* them can still go to D3cold. * so we need to go through all children to find out if one of them
*/ * continues to block D3.
*/
if (d3cold_ok && !bridge->bridge_d3)
pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold, pci_walk_bus(bridge->subordinate, pci_dev_check_d3cold,
&d3cold_ok); &d3cold_ok);
}
if (bridge->bridge_d3 != d3cold_ok) { if (bridge->bridge_d3 != d3cold_ok) {
bridge->bridge_d3 = d3cold_ok; bridge->bridge_d3 = d3cold_ok;
/* Propagate change to upstream bridges */ /* Propagate change to upstream bridges */
pci_bridge_d3_update(bridge, false); pci_bridge_d3_update(bridge);
} }
pci_dev_put(bridge);
}
/**
* pci_bridge_d3_device_changed - Update bridge D3 capabilities on change
* @dev: PCI device that was changed
*
* If a device is added or its PM configuration, such as is it allowed to
* enter D3cold, is changed this function updates upstream bridge PM
* capabilities accordingly.
*/
void pci_bridge_d3_device_changed(struct pci_dev *dev)
{
pci_bridge_d3_update(dev, false);
}
/**
* pci_bridge_d3_device_removed - Update bridge D3 capabilities on remove
* @dev: PCI device being removed
*
* Function updates upstream bridge PM capabilities based on other devices
* still left on the bus.
*/
void pci_bridge_d3_device_removed(struct pci_dev *dev)
{
pci_bridge_d3_update(dev, true);
} }
/** /**
...@@ -2355,7 +2355,7 @@ void pci_d3cold_enable(struct pci_dev *dev) ...@@ -2355,7 +2355,7 @@ void pci_d3cold_enable(struct pci_dev *dev)
{ {
if (dev->no_d3cold) { if (dev->no_d3cold) {
dev->no_d3cold = false; dev->no_d3cold = false;
pci_bridge_d3_device_changed(dev); pci_bridge_d3_update(dev);
} }
} }
EXPORT_SYMBOL_GPL(pci_d3cold_enable); EXPORT_SYMBOL_GPL(pci_d3cold_enable);
...@@ -2372,7 +2372,7 @@ void pci_d3cold_disable(struct pci_dev *dev) ...@@ -2372,7 +2372,7 @@ void pci_d3cold_disable(struct pci_dev *dev)
{ {
if (!dev->no_d3cold) { if (!dev->no_d3cold) {
dev->no_d3cold = true; dev->no_d3cold = true;
pci_bridge_d3_device_changed(dev); pci_bridge_d3_update(dev);
} }
} }
EXPORT_SYMBOL_GPL(pci_d3cold_disable); EXPORT_SYMBOL_GPL(pci_d3cold_disable);
......
...@@ -82,8 +82,8 @@ void pci_pm_init(struct pci_dev *dev); ...@@ -82,8 +82,8 @@ void pci_pm_init(struct pci_dev *dev);
void pci_ea_init(struct pci_dev *dev); void pci_ea_init(struct pci_dev *dev);
void pci_allocate_cap_save_buffers(struct pci_dev *dev); void pci_allocate_cap_save_buffers(struct pci_dev *dev);
void pci_free_cap_save_buffers(struct pci_dev *dev); void pci_free_cap_save_buffers(struct pci_dev *dev);
void pci_bridge_d3_device_changed(struct pci_dev *dev); bool pci_bridge_d3_possible(struct pci_dev *dev);
void pci_bridge_d3_device_removed(struct pci_dev *dev); void pci_bridge_d3_update(struct pci_dev *dev);
static inline void pci_wakeup_event(struct pci_dev *dev) static inline void pci_wakeup_event(struct pci_dev *dev)
{ {
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <linux/dmi.h> #include <linux/dmi.h>
#include <linux/pci-aspm.h> #include <linux/pci-aspm.h>
#include "../pci.h"
#include "portdrv.h" #include "portdrv.h"
#include "aer/aerdrv.h" #include "aer/aerdrv.h"
...@@ -149,15 +150,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, ...@@ -149,15 +150,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
pci_save_state(dev); pci_save_state(dev);
/* if (pci_bridge_d3_possible(dev)) {
* Prevent runtime PM if the port is advertising support for PCIe
* hotplug. Otherwise the BIOS hotplug SMI code might not be able
* to enumerate devices behind this port properly (the port is
* powered down preventing all config space accesses to the
* subordinate devices). We can't be sure for native PCIe hotplug
* either so prevent that as well.
*/
if (!dev->is_hotplug_bridge) {
/* /*
* Keep the port resumed 100ms to make sure things like * Keep the port resumed 100ms to make sure things like
* config space accesses from userspace (lspci) will not * config space accesses from userspace (lspci) will not
...@@ -175,7 +168,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev, ...@@ -175,7 +168,7 @@ static int pcie_portdrv_probe(struct pci_dev *dev,
static void pcie_portdrv_remove(struct pci_dev *dev) static void pcie_portdrv_remove(struct pci_dev *dev)
{ {
if (!dev->is_hotplug_bridge) { if (pci_bridge_d3_possible(dev)) {
pm_runtime_forbid(&dev->dev); pm_runtime_forbid(&dev->dev);
pm_runtime_get_noresume(&dev->dev); pm_runtime_get_noresume(&dev->dev);
pm_runtime_dont_use_autosuspend(&dev->dev); pm_runtime_dont_use_autosuspend(&dev->dev);
......
...@@ -40,7 +40,7 @@ static void pci_destroy_dev(struct pci_dev *dev) ...@@ -40,7 +40,7 @@ static void pci_destroy_dev(struct pci_dev *dev)
list_del(&dev->bus_list); list_del(&dev->bus_list);
up_write(&pci_bus_sem); up_write(&pci_bus_sem);
pci_bridge_d3_device_removed(dev); pci_bridge_d3_update(dev);
pci_free_resources(dev); pci_free_resources(dev);
put_device(&dev->dev); put_device(&dev->dev);
} }
......
...@@ -129,6 +129,10 @@ static int uhci_pci_init(struct usb_hcd *hcd) ...@@ -129,6 +129,10 @@ static int uhci_pci_init(struct usb_hcd *hcd)
if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_HP) if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_HP)
uhci->wait_for_hp = 1; uhci->wait_for_hp = 1;
/* Intel controllers use non-PME wakeup signalling */
if (to_pci_dev(uhci_dev(uhci))->vendor == PCI_VENDOR_ID_INTEL)
device_set_run_wake(uhci_dev(uhci), 1);
/* Set up pointers to PCI-specific functions */ /* Set up pointers to PCI-specific functions */
uhci->reset_hc = uhci_pci_reset_hc; uhci->reset_hc = uhci_pci_reset_hc;
uhci->check_and_reset_hc = uhci_pci_check_and_reset_hc; uhci->check_and_reset_hc = uhci_pci_check_and_reset_hc;
......
...@@ -176,6 +176,7 @@ struct hotplug_params { ...@@ -176,6 +176,7 @@ struct hotplug_params {
#ifdef CONFIG_ACPI #ifdef CONFIG_ACPI
#include <linux/acpi.h> #include <linux/acpi.h>
int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp); int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp);
bool pciehp_is_native(struct pci_dev *pdev);
int acpi_get_hp_hw_control_from_firmware(struct pci_dev *dev, u32 flags); int acpi_get_hp_hw_control_from_firmware(struct pci_dev *dev, u32 flags);
int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle); int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle);
int acpi_pci_detect_ejectable(acpi_handle handle); int acpi_pci_detect_ejectable(acpi_handle handle);
...@@ -185,5 +186,6 @@ static inline int pci_get_hp_params(struct pci_dev *dev, ...@@ -185,5 +186,6 @@ static inline int pci_get_hp_params(struct pci_dev *dev,
{ {
return -ENODEV; return -ENODEV;
} }
static inline bool pciehp_is_native(struct pci_dev *pdev) { return true; }
#endif #endif
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment