diff options
author | imp <imp@FreeBSD.org> | 2004-12-31 20:43:46 +0000 |
---|---|---|
committer | imp <imp@FreeBSD.org> | 2004-12-31 20:43:46 +0000 |
commit | 941b82eb40f0eb621ada772b0ab757741b9c3581 (patch) | |
tree | 905ae423dea35bfab9a74063e3d210dbef9c7106 /sys/dev/pci | |
parent | ae02e9bff7b93856dbfbafda6806df12f36dc4e8 (diff) | |
download | FreeBSD-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/pci')
-rw-r--r-- | sys/dev/pci/pci.c | 93 |
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); } /* |