aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/pci
diff options
context:
space:
mode:
authorWarner Losh <imp@FreeBSD.org>2004-12-31 20:43:46 +0000
committerWarner Losh <imp@FreeBSD.org>2004-12-31 20:43:46 +0000
commit4ee5d2f181864c7b24581aa4035574a35473cb4a (patch)
tree905ae423dea35bfab9a74063e3d210dbef9c7106 /sys/dev/pci
parentfc508e8e4f996d6c9b4e889b1a155f51cdc6ae53 (diff)
downloadsrc-4ee5d2f181864c7b24581aa4035574a35473cb4a.tar.gz
src-4ee5d2f181864c7b24581aa4035574a35473cb4a.zip
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.
Notes
Notes: svn path=/head/; revision=139505
Diffstat (limited to 'sys/dev/pci')
-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 293aef88ebc1..7528a0be0fa1 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);
}
/*