summaryrefslogtreecommitdiffstats
path: root/sys/dev/pci/pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/pci.c')
-rw-r--r--sys/dev/pci/pci.c93
1 files changed, 55 insertions, 38 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index 293aef8..7528a0b 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -498,49 +498,66 @@ pci_set_powerstate_method(device_t dev, device_t child, int state)
struct pci_devinfo *dinfo = device_get_ivars(child);
pcicfgregs *cfg = &dinfo->cfg;
uint16_t status;
- int result;
+ int result, oldstate, highest, delay;
+
+ if (cfg->pp.pp_cap == 0)
+ return (EOPNOTSUPP);
/*
- * Dx -> Dx is a nop always.
+ * Optimize a no state change request away. While it would be OK to
+ * write to the hardware in theory, some devices have shown odd
+ * behavior when going from D3 -> D3.
*/
- if (pci_get_powerstate(child) == state)
+ oldstate = pci_get_powerstate(child);
+ if (oldstate == state)
return (0);
- if (cfg->pp.pp_cap != 0) {
- status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2)
- & ~PCIM_PSTAT_DMASK;
- result = 0;
- switch (state) {
- case PCI_POWERSTATE_D0:
- status |= PCIM_PSTAT_D0;
- break;
- case PCI_POWERSTATE_D1:
- if (cfg->pp.pp_cap & PCIM_PCAP_D1SUPP) {
- status |= PCIM_PSTAT_D1;
- } else {
- result = EOPNOTSUPP;
- }
- break;
- case PCI_POWERSTATE_D2:
- if (cfg->pp.pp_cap & PCIM_PCAP_D2SUPP) {
- status |= PCIM_PSTAT_D2;
- } else {
- result = EOPNOTSUPP;
- }
- break;
- case PCI_POWERSTATE_D3:
- status |= PCIM_PSTAT_D3;
- break;
- default:
- result = EINVAL;
- }
- if (result == 0)
- PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status,
- 2);
- } else {
- result = ENXIO;
+ /*
+ * The PCI power management specification states that after a state
+ * transition between PCI power states, system software must
+ * guarantee a minimal delay before the function accesses the device.
+ * Compute the worst case delay that we need to guarantee before we
+ * access the device. Many devices will be responsive much more
+ * quickly than this delay, but there are some that don't respond
+ * instantly to state changes. Transitions to/from D3 state require
+ * 10ms, while D2 requires 200us, and D0/1 require none. The delay
+ * is done below with DELAY rather than a sleeper function because
+ * this function can be called from contexts where we cannot sleep.
+ */
+ highest = (oldstate > state) ? oldstate : state;
+ if (highest == PCI_POWER_STATE_D3)
+ delay = 10000;
+ else if (highest == PCI_POWER_STATE_D2)
+ delay = 200;
+ else
+ delay = 0;
+ status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2)
+ & ~PCIM_PSTAT_DMASK;
+ result = 0;
+ switch (state) {
+ case PCI_POWERSTATE_D0:
+ status |= PCIM_PSTAT_D0;
+ break;
+ case PCI_POWERSTATE_D1:
+ if ((cfg->pp.pp_cap & PCIM_PCAP_D1SUPP) == 0)
+ return (EOPNOTSUPP);
+ status |= PCIM_PSTAT_D1;
+ break;
+ case PCI_POWERSTATE_D2:
+ if ((cfg->pp.pp_cap & PCIM_PCAP_D2SUPP) == 0)
+ return (EOPNOTSUPP);
+ status |= PCIM_PSTAT_D2;
+ break;
+ case PCI_POWERSTATE_D3:
+ status |= PCIM_PSTAT_D3;
+ break;
+ default:
+ return (EINVAL);
}
- return(result);
+ PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status, 2);
+ if (delay)
+ DELAY(delay);
+ return (0);
}
int
@@ -574,7 +591,7 @@ pci_get_powerstate_method(device_t dev, device_t child)
/* No support, device is always at D0 */
result = PCI_POWERSTATE_D0;
}
- return(result);
+ return (result);
}
/*
OpenPOWER on IntegriCloud