From c63587d7f5b9db84e71daf5962dc0394eb657da2 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 11 Jun 2012 05:27:19 +0000 Subject: PCI: export pci_user functions for use by other drivers VFIO PCI support will make use of these for user-initiated PCI config accesses. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.h | 7 ------- 1 file changed, 7 deletions(-) (limited to 'drivers/pci/pci.h') diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index e4943479b23..f2dcc46bdec 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -86,13 +86,6 @@ static inline bool pci_is_bridge(struct pci_dev *pci_dev) return !!(pci_dev->subordinate); } -extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val); -extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val); -extern int pci_user_read_config_dword(struct pci_dev *dev, int where, u32 *val); -extern int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val); -extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val); -extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val); - struct pci_vpd_ops { ssize_t (*read)(struct pci_dev *dev, loff_t pos, size_t count, void *buf); ssize_t (*write)(struct pci_dev *dev, loff_t pos, size_t count, const void *buf); -- cgit v1.2.3-70-g09d2 From a8e4b9c101ae58cc64cda0201229d3318701a7f0 Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Fri, 18 May 2012 13:46:34 -0600 Subject: PCI: add generic pci_hp_add_bridge() This creates a generic pci_hp_add_bridge() that can be used by several hotplug drivers. [bhelgaas: split out from pciehp patch] Signed-off-by: Yinghai Lu Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug-pci.c | 23 +++++++++++++++++++++++ drivers/pci/pci.h | 1 + 2 files changed, 24 insertions(+) (limited to 'drivers/pci/pci.h') diff --git a/drivers/pci/hotplug-pci.c b/drivers/pci/hotplug-pci.c index d3509cdeb55..44088c4fe68 100644 --- a/drivers/pci/hotplug-pci.c +++ b/drivers/pci/hotplug-pci.c @@ -4,6 +4,29 @@ #include #include "pci.h" +int __ref pci_hp_add_bridge(struct pci_dev *dev) +{ + struct pci_bus *parent = dev->bus; + int pass, busnr, start = parent->busn_res.start; + int end = parent->busn_res.end; + + for (busnr = start; busnr <= end; busnr++) { + if (!pci_find_bus(pci_domain_nr(parent), busnr)) + break; + } + if (busnr-- > end) { + printk(KERN_ERR "No bus number available for hot-added bridge %s\n", + pci_name(dev)); + return -1; + } + for (pass = 0; pass < 2; pass++) + busnr = pci_scan_bridge(parent, dev, busnr, pass); + if (!dev->subordinate) + return -1; + + return 0; +} +EXPORT_SYMBOL_GPL(pci_hp_add_bridge); unsigned int __devinit pci_do_scan_bus(struct pci_bus *bus) { diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index e4943479b23..f3e14ce8eab 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -124,6 +124,7 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; } #endif /* Functions for PCI Hotplug drivers to use */ +int pci_hp_add_bridge(struct pci_dev *dev); extern unsigned int pci_do_scan_bus(struct pci_bus *bus); #ifdef HAVE_PCI_LEGACY -- cgit v1.2.3-70-g09d2 From 06aef8cec7563c40c7d7501d13ec1ed12f5e495b Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Thu, 17 May 2012 18:58:41 -0700 Subject: PCI: hotplug: remove pci_do_scan_bus() All callers of pci_do_scan_bus() are gone, so remove it. Note that pci_do_scan_bus() was exported, so out-of-tree modules could depend on it. [bhelgaas: changelog] Signed-off-by: Yinghai Lu Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug-pci.c | 15 --------------- drivers/pci/pci.h | 1 - 2 files changed, 16 deletions(-) (limited to 'drivers/pci/pci.h') diff --git a/drivers/pci/hotplug-pci.c b/drivers/pci/hotplug-pci.c index 44088c4fe68..6258dc260d9 100644 --- a/drivers/pci/hotplug-pci.c +++ b/drivers/pci/hotplug-pci.c @@ -27,18 +27,3 @@ int __ref pci_hp_add_bridge(struct pci_dev *dev) return 0; } EXPORT_SYMBOL_GPL(pci_hp_add_bridge); - -unsigned int __devinit pci_do_scan_bus(struct pci_bus *bus) -{ - unsigned int max; - - max = pci_scan_child_bus(bus); - - /* - * Make the discovered devices available. - */ - pci_bus_add_devices(bus); - - return max; -} -EXPORT_SYMBOL(pci_do_scan_bus); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f3e14ce8eab..1c56ea8110b 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -125,7 +125,6 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; } /* Functions for PCI Hotplug drivers to use */ int pci_hp_add_bridge(struct pci_dev *dev); -extern unsigned int pci_do_scan_bus(struct pci_bus *bus); #ifdef HAVE_PCI_LEGACY extern void pci_create_legacy_files(struct pci_bus *bus); -- cgit v1.2.3-70-g09d2 From 448bd857d48e69b33ef323739dc6d8ca20d4cda7 Mon Sep 17 00:00:00 2001 From: Huang Ying Date: Sat, 23 Jun 2012 10:23:51 +0800 Subject: PCI/PM: add PCIe runtime D3cold support This patch adds runtime D3cold support and corresponding ACPI platform support. This patch only enables runtime D3cold support; it does not enable D3cold support during system suspend/hibernate. D3cold is the deepest power saving state for a PCIe device, where its main power is removed. While it is in D3cold, you can't access the device at all, not even its configuration space (which is still accessible in D3hot). Therefore the PCI PM registers can not be used to transition into/out of the D3cold state; that must be done by platform logic such as ACPI _PR3. To support wakeup from D3cold, a system may provide auxiliary power, which allows a device to request wakeup using a Beacon or the sideband WAKE# signal. WAKE# is usually connected to platform logic such as ACPI GPE. This is quite different from other power saving states, where devices request wakeup via a PME message on the PCIe link. Some devices, such as those in plug-in slots, have no direct platform logic. For example, there is usually no ACPI _PR3 for them. D3cold support for these devices can be done via the PCIe Downstream Port leading to the device. When the PCIe port is powered on/off, the device is powered on/off too. Wakeup events from the device will be notified to the corresponding PCIe port. For more information about PCIe D3cold and corresponding ACPI support, please refer to: - PCI Express Base Specification Revision 2.0 - Advanced Configuration and Power Interface Specification Revision 5.0 [bhelgaas: changelog] Reviewed-by: Rafael J. Wysocki Originally-by: Zheng Yan Signed-off-by: Huang Ying Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 23 +++++++-- drivers/pci/pci-driver.c | 10 +++- drivers/pci/pci-sysfs.c | 29 +++++++++++ drivers/pci/pci.c | 114 +++++++++++++++++++++++++++++++++++++---- drivers/pci/pci.h | 1 + drivers/pci/pcie/portdrv_pci.c | 44 ++++++++++++++-- include/linux/pci.h | 16 ++++-- 7 files changed, 215 insertions(+), 22 deletions(-) (limited to 'drivers/pci/pci.h') diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index a9efebc586b..e1658afef87 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -48,6 +48,12 @@ static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context) if (event != ACPI_NOTIFY_DEVICE_WAKE || !pci_dev) return; + if (pci_dev->current_state == PCI_D3cold) { + pci_wakeup_event(pci_dev); + pm_runtime_resume(&pci_dev->dev); + return; + } + if (!pci_dev->pm_cap || !pci_dev->pme_support || pci_check_pme_status(pci_dev)) { if (pci_dev->pme_poll) @@ -187,10 +193,13 @@ acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev) static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev) { - int acpi_state; + int acpi_state, d_max; - acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL, - ACPI_STATE_D3); + if (pdev->no_d3cold) + d_max = ACPI_STATE_D3_HOT; + else + d_max = ACPI_STATE_D3_COLD; + acpi_state = acpi_pm_device_sleep_state(&pdev->dev, NULL, d_max); if (acpi_state < 0) return PCI_POWER_ERROR; @@ -297,7 +306,13 @@ static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable) static int acpi_pci_run_wake(struct pci_dev *dev, bool enable) { - if (dev->pme_interrupt) + /* + * Per PCI Express Base Specification Revision 2.0 section + * 5.3.3.2 Link Wakeup, platform support is needed for D3cold + * waking up to power on the main link even if there is PME + * support for D3cold + */ + if (dev->pme_interrupt && !dev->runtime_d3cold) return 0; if (!acpi_pm_device_run_wake(&dev->dev, enable)) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index bf0cee629b6..ca2e4c79a58 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -1019,10 +1019,13 @@ static int pci_pm_runtime_suspend(struct device *dev) if (!pm || !pm->runtime_suspend) return -ENOSYS; + pci_dev->no_d3cold = false; error = pm->runtime_suspend(dev); suspend_report_result(pm->runtime_suspend, error); if (error) return error; + if (!pci_dev->d3cold_allowed) + pci_dev->no_d3cold = true; pci_fixup_device(pci_fixup_suspend, pci_dev); @@ -1044,6 +1047,7 @@ static int pci_pm_runtime_suspend(struct device *dev) static int pci_pm_runtime_resume(struct device *dev) { + int rc; struct pci_dev *pci_dev = to_pci_dev(dev); const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; @@ -1054,7 +1058,11 @@ static int pci_pm_runtime_resume(struct device *dev) __pci_enable_wake(pci_dev, PCI_D0, true, false); pci_fixup_device(pci_fixup_resume, pci_dev); - return pm->runtime_resume(dev); + rc = pm->runtime_resume(dev); + + pci_dev->runtime_d3cold = false; + + return rc; } static int pci_pm_runtime_idle(struct device *dev) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 86c63fe45d1..1426db0c060 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "pci.h" static int sysfs_initialized; /* = 0 */ @@ -378,6 +379,31 @@ dev_bus_rescan_store(struct device *dev, struct device_attribute *attr, #endif +#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI) +static ssize_t d3cold_allowed_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val) < 0) + return -EINVAL; + + pdev->d3cold_allowed = !!val; + pm_runtime_resume(dev); + + return count; +} + +static ssize_t d3cold_allowed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + return sprintf (buf, "%u\n", pdev->d3cold_allowed); +} +#endif + struct device_attribute pci_dev_attrs[] = { __ATTR_RO(resource), __ATTR_RO(vendor), @@ -401,6 +427,9 @@ struct device_attribute pci_dev_attrs[] = { #ifdef CONFIG_HOTPLUG __ATTR(remove, (S_IWUSR|S_IWGRP), NULL, remove_store), __ATTR(rescan, (S_IWUSR|S_IWGRP), NULL, dev_rescan_store), +#endif +#if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI) + __ATTR(d3cold_allowed, 0644, d3cold_allowed_show, d3cold_allowed_store), #endif __ATTR_NULL, }; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 9eae64b1795..8effb9b23ee 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -622,7 +622,8 @@ static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state) dev_info(&dev->dev, "Refused to change power state, " "currently in D%d\n", dev->current_state); - /* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT + /* + * According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT * INTERFACE SPECIFICATION, REV. 1.2", a device transitioning * from D3hot to D0 _may_ perform an internal reset, thereby * going to "D0 Uninitialized" rather than "D0 Initialized". @@ -654,6 +655,16 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state) if (dev->pm_cap) { u16 pmcsr; + /* + * Configuration space is not accessible for device in + * D3cold, so just keep or set D3cold for safety + */ + if (dev->current_state == PCI_D3cold) + return; + if (state == PCI_D3cold) { + dev->current_state = PCI_D3cold; + return; + } pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); } else { @@ -694,8 +705,50 @@ static int pci_platform_power_transition(struct pci_dev *dev, pci_power_t state) */ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state) { - if (state == PCI_D0) + if (state == PCI_D0) { pci_platform_power_transition(dev, PCI_D0); + /* + * Mandatory power management transition delays, see + * PCI Express Base Specification Revision 2.0 Section + * 6.6.1: Conventional Reset. Do not delay for + * devices powered on/off by corresponding bridge, + * because have already delayed for the bridge. + */ + if (dev->runtime_d3cold) { + msleep(dev->d3cold_delay); + /* + * When powering on a bridge from D3cold, the + * whole hierarchy may be powered on into + * D0uninitialized state, resume them to give + * them a chance to suspend again + */ + pci_wakeup_bus(dev->subordinate); + } + } +} + +/** + * __pci_dev_set_current_state - Set current state of a PCI device + * @dev: Device to handle + * @data: pointer to state to be set + */ +static int __pci_dev_set_current_state(struct pci_dev *dev, void *data) +{ + pci_power_t state = *(pci_power_t *)data; + + dev->current_state = state; + return 0; +} + +/** + * __pci_bus_set_current_state - Walk given bus and set current state of devices + * @bus: Top bus of the subtree to walk. + * @state: state to be set + */ +static void __pci_bus_set_current_state(struct pci_bus *bus, pci_power_t state) +{ + if (bus) + pci_walk_bus(bus, __pci_dev_set_current_state, &state); } /** @@ -707,8 +760,15 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state) */ int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state) { - return state >= PCI_D0 ? - pci_platform_power_transition(dev, state) : -EINVAL; + int ret; + + if (state < PCI_D0) + return -EINVAL; + ret = pci_platform_power_transition(dev, state); + /* Power off the bridge may power off the whole hierarchy */ + if (!ret && state == PCI_D3cold) + __pci_bus_set_current_state(dev->subordinate, PCI_D3cold); + return ret; } EXPORT_SYMBOL_GPL(__pci_complete_power_transition); @@ -732,8 +792,8 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state) int error; /* bound the state we're entering */ - if (state > PCI_D3hot) - state = PCI_D3hot; + if (state > PCI_D3cold) + state = PCI_D3cold; else if (state < PCI_D0) state = PCI_D0; else if ((state == PCI_D1 || state == PCI_D2) && pci_no_d1d2(dev)) @@ -748,10 +808,15 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t 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)) + if (state >= PCI_D3hot && (dev->dev_flags & PCI_DEV_FLAGS_NO_D3)) return 0; - error = pci_raw_set_power_state(dev, state); + /* + * To put device in D3cold, we put device into D3hot in native + * way, then put device into D3cold with platform ops + */ + error = pci_raw_set_power_state(dev, state > PCI_D3hot ? + PCI_D3hot : state); if (!__pci_complete_power_transition(dev, state)) error = 0; @@ -1497,6 +1562,28 @@ void pci_pme_wakeup_bus(struct pci_bus *bus) pci_walk_bus(bus, pci_pme_wakeup, (void *)true); } +/** + * pci_wakeup - Wake up a PCI device + * @dev: Device to handle. + * @ign: ignored parameter + */ +static int pci_wakeup(struct pci_dev *pci_dev, void *ign) +{ + pci_wakeup_event(pci_dev); + pm_request_resume(&pci_dev->dev); + return 0; +} + +/** + * pci_wakeup_bus - Walk given bus and wake up devices on it + * @bus: Top bus of the subtree to walk. + */ +void pci_wakeup_bus(struct pci_bus *bus) +{ + if (bus) + pci_walk_bus(bus, pci_wakeup, NULL); +} + /** * pci_pme_capable - check the capability of PCI device to generate PME# * @dev: PCI device to handle. @@ -1754,6 +1841,10 @@ int pci_prepare_to_sleep(struct pci_dev *dev) if (target_state == PCI_POWER_ERROR) return -EIO; + /* D3cold during system suspend/hibernate is not supported */ + if (target_state > PCI_D3hot) + target_state = PCI_D3hot; + pci_enable_wake(dev, target_state, device_may_wakeup(&dev->dev)); error = pci_set_power_state(dev, target_state); @@ -1791,12 +1882,16 @@ int pci_finish_runtime_suspend(struct pci_dev *dev) if (target_state == PCI_POWER_ERROR) return -EIO; + dev->runtime_d3cold = target_state == PCI_D3cold; + __pci_enable_wake(dev, target_state, true, pci_dev_run_wake(dev)); error = pci_set_power_state(dev, target_state); - if (error) + if (error) { __pci_enable_wake(dev, target_state, true, false); + dev->runtime_d3cold = false; + } return error; } @@ -1866,6 +1961,7 @@ void pci_pm_init(struct pci_dev *dev) dev->pm_cap = pm; dev->d3_delay = PCI_PM_D3_WAIT; + dev->d3cold_delay = PCI_PM_D3COLD_WAIT; dev->d1_support = false; dev->d2_support = false; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index e4943479b23..5cd3dce7a24 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -70,6 +70,7 @@ extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state); extern void pci_disable_enabled_device(struct pci_dev *dev); extern int pci_finish_runtime_suspend(struct pci_dev *dev); extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); +extern void pci_wakeup_bus(struct pci_bus *bus); extern void pci_pm_init(struct pci_dev *dev); extern void platform_pci_wakeup_init(struct pci_dev *dev); extern void pci_allocate_cap_save_buffers(struct pci_dev *dev); diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 7c576b9aa01..3a7eefcb270 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -101,12 +101,48 @@ static int pcie_port_resume_noirq(struct device *dev) } #ifdef CONFIG_PM_RUNTIME -static int pcie_port_runtime_pm(struct device *dev) +struct d3cold_info { + bool no_d3cold; + unsigned int d3cold_delay; +}; + +static int pci_dev_d3cold_info(struct pci_dev *pdev, void *data) +{ + struct d3cold_info *info = data; + + info->d3cold_delay = max_t(unsigned int, pdev->d3cold_delay, + info->d3cold_delay); + if (pdev->no_d3cold) + info->no_d3cold = true; + return 0; +} + +static int pcie_port_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct d3cold_info d3cold_info = { + .no_d3cold = false, + .d3cold_delay = PCI_PM_D3_WAIT, + }; + + /* + * If any subordinate device disable D3cold, we should not put + * the port into D3cold. The D3cold delay of port should be + * the max of that of all subordinate devices. + */ + pci_walk_bus(pdev->subordinate, pci_dev_d3cold_info, &d3cold_info); + pdev->no_d3cold = d3cold_info.no_d3cold; + pdev->d3cold_delay = d3cold_info.d3cold_delay; + return 0; +} + +static int pcie_port_runtime_resume(struct device *dev) { return 0; } #else -#define pcie_port_runtime_pm NULL +#define pcie_port_runtime_suspend NULL +#define pcie_port_runtime_resume NULL #endif static const struct dev_pm_ops pcie_portdrv_pm_ops = { @@ -117,8 +153,8 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { .poweroff = pcie_port_device_suspend, .restore = pcie_port_device_resume, .resume_noirq = pcie_port_resume_noirq, - .runtime_suspend = pcie_port_runtime_pm, - .runtime_resume = pcie_port_runtime_pm, + .runtime_suspend = pcie_port_runtime_suspend, + .runtime_resume = pcie_port_runtime_resume, }; #define PCIE_PORTDRV_PM_OPS (&pcie_portdrv_pm_ops) diff --git a/include/linux/pci.h b/include/linux/pci.h index d8c379dba6a..002cfd3e33c 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -132,9 +132,10 @@ static inline const char *pci_power_name(pci_power_t state) return pci_power_names[1 + (int) state]; } -#define PCI_PM_D2_DELAY 200 -#define PCI_PM_D3_WAIT 10 -#define PCI_PM_BUS_WAIT 50 +#define PCI_PM_D2_DELAY 200 +#define PCI_PM_D3_WAIT 10 +#define PCI_PM_D3COLD_WAIT 100 +#define PCI_PM_BUS_WAIT 50 /** The pci_channel state describes connectivity between the CPU and * the pci device. If some PCI bus between here and the pci device @@ -278,11 +279,18 @@ struct pci_dev { unsigned int pme_poll:1; /* Poll device's PME status bit */ unsigned int d1_support:1; /* Low power state D1 is supported */ unsigned int d2_support:1; /* Low power state D2 is supported */ - unsigned int no_d1d2:1; /* Only allow D0 and D3 */ + unsigned int no_d1d2:1; /* D1 and D2 are forbidden */ + unsigned int no_d3cold:1; /* D3cold is forbidden */ + unsigned int d3cold_allowed:1; /* D3cold is allowed by user */ unsigned int mmio_always_on:1; /* disallow turning off io/mem decoding during bar sizing */ unsigned int wakeup_prepared:1; + unsigned int runtime_d3cold:1; /* whether go through runtime + D3cold, not set for devices + powered on/off by the + corresponding bridge */ unsigned int d3_delay; /* D3->D0 transition time in ms */ + unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ #ifdef CONFIG_PCIEASPM struct pcie_link_state *link_state; /* ASPM link state. */ -- cgit v1.2.3-70-g09d2 From db288c9c5f9db45610dab3940377625132b4af41 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 5 Jul 2012 15:20:00 -0600 Subject: PCI / PM: restore the original behavior of pci_set_power_state() Commit cc2893b6 (PCI: Ensure we re-enable devices on resume) addressed the problem with USB not being powered after resume on recent Lenovo machines, but it did that in a suboptimal way. Namely, it should have changed the relevant code paths only, which are pci_pm_resume_noirq() and pci_pm_restore_noirq() supposed to restore the device's power and standard configuration registers after system resume from suspend or hibernation. Instead, however, it modified pci_set_power_state() which is executed in several other situations too. That resulted in some undesirable effects, like attempting to change a device's power state in the same way multiple times in a row (up to as many as 4 times in a row in the snd_hda_intel driver). Fix the bug addressed by commit cc2893b6 in an alternative way, by forcibly powering up all devices in pci_pm_default_resume_early(), which is called by pci_pm_resume_noirq() and pci_pm_restore_noirq() to restore the device's power and standard configuration registers, and modifying pci_pm_runtime_resume() to avoid the forcible power-up if not necessary. Then, revert the changes made by commit cc2893b6 to make the confusion introduced by it go away. Acked-by: Matthew Garrett Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-driver.c | 14 ++++++++------ drivers/pci/pci.c | 19 ++++++++++++++++++- drivers/pci/pci.h | 1 + 3 files changed, 27 insertions(+), 7 deletions(-) (limited to 'drivers/pci/pci.h') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index ca2e4c79a58..1dd1d9dfa17 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -459,16 +459,17 @@ static int pci_restore_standard_config(struct pci_dev *pci_dev) return 0; } +#endif + +#ifdef CONFIG_PM_SLEEP + static void pci_pm_default_resume_early(struct pci_dev *pci_dev) { - pci_restore_standard_config(pci_dev); + pci_power_up(pci_dev); + pci_restore_state(pci_dev); pci_fixup_device(pci_fixup_resume_early, pci_dev); } -#endif - -#ifdef CONFIG_PM_SLEEP - /* * Default "suspend" method for devices that have no driver provided suspend, * or not even a driver at all (second part). @@ -1054,7 +1055,8 @@ static int pci_pm_runtime_resume(struct device *dev) if (!pm || !pm->runtime_resume) return -ENOSYS; - pci_pm_default_resume_early(pci_dev); + pci_restore_standard_config(pci_dev); + pci_fixup_device(pci_fixup_resume_early, pci_dev); __pci_enable_wake(pci_dev, PCI_D0, true, false); pci_fixup_device(pci_fixup_resume, pci_dev); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8effb9b23ee..acae2705e7f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -672,6 +672,19 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state) } } +/** + * pci_power_up - Put the given device into D0 forcibly + * @dev: PCI device to power up + */ +void pci_power_up(struct pci_dev *dev) +{ + if (platform_pci_power_manageable(dev)) + platform_pci_set_power_state(dev, PCI_D0); + + pci_raw_set_power_state(dev, PCI_D0); + pci_update_current_state(dev, PCI_D0); +} + /** * pci_platform_power_transition - Use platform to change device power state * @dev: PCI device to handle. @@ -762,7 +775,7 @@ int __pci_complete_power_transition(struct pci_dev *dev, pci_power_t state) { int ret; - if (state < PCI_D0) + if (state <= PCI_D0) return -EINVAL; ret = pci_platform_power_transition(dev, state); /* Power off the bridge may power off the whole hierarchy */ @@ -804,6 +817,10 @@ int pci_set_power_state(struct pci_dev *dev, pci_power_t state) */ return 0; + /* 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 diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 5cd3dce7a24..331857855eb 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -67,6 +67,7 @@ struct pci_platform_pm_ops { extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops); extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state); +extern void pci_power_up(struct pci_dev *dev); extern void pci_disable_enabled_device(struct pci_dev *dev); extern int pci_finish_runtime_suspend(struct pci_dev *dev); extern int __pci_pme_wakeup(struct pci_dev *dev, void *ign); -- cgit v1.2.3-70-g09d2