summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2004-12-31 20:43:46 +0000
committerimp <imp@FreeBSD.org>2004-12-31 20:43:46 +0000
commit941b82eb40f0eb621ada772b0ab757741b9c3581 (patch)
tree905ae423dea35bfab9a74063e3d210dbef9c7106 /sys/dev
parentae02e9bff7b93856dbfbafda6806df12f36dc4e8 (diff)
downloadFreeBSD-src-941b82eb40f0eb621ada772b0ab757741b9c3581.zip
FreeBSD-src-941b82eb40f0eb621ada772b0ab757741b9c3581.tar.gz
Implement mimimum system software delays, per PCI PM 1.1 spec, as
suggested by Peter Edwards. This seems to fix my fxp problems and likely will fix his as well. Use DELAY rather than *sleep because we can be called from any context.
Diffstat (limited to 'sys/dev')
-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