diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2010-08-17 19:14:53 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2010-08-17 19:14:53 +0000 |
commit | 5268c49d0fb645ef2cbc307c2a6d81bfa902a58b (patch) | |
tree | 1e674868071627714098692976474431f9ba29d1 /sys/dev | |
parent | 7f00f673b9f13763b9642e62566597f9551ddff7 (diff) |
Power management for PCI devices. For now just put everything in the D3
(deepest sleep) state upon suspend, and restore power upon resume.
ok deraadt@
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/pci/pci.c | 80 | ||||
-rw-r--r-- | sys/dev/pci/pcivar.h | 3 | ||||
-rw-r--r-- | sys/dev/pci/ppb.c | 26 |
3 files changed, 79 insertions, 30 deletions
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c index a6835016216..af08dd15021 100644 --- a/sys/dev/pci/pci.c +++ b/sys/dev/pci/pci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pci.c,v 1.80 2010/07/02 04:03:31 kettenis Exp $ */ +/* $OpenBSD: pci.c,v 1.81 2010/08/17 19:14:52 kettenis Exp $ */ /* $NetBSD: pci.c,v 1.31 1997/06/06 23:48:04 thorpej Exp $ */ /* @@ -56,12 +56,12 @@ int pciactivate(struct device *, int); sizeof(pcireg_t)) struct pci_dev { LIST_ENTRY(pci_dev) pd_next; - struct device *pd_dev; pcitag_t pd_tag; /* pci register tag */ pcireg_t pd_csr; pcireg_t pd_bhlc; pcireg_t pd_int; pcireg_t pd_map[NMAPREG]; + int pd_pmcsr_state; }; #ifdef APERTURE @@ -211,25 +211,52 @@ pcipower(int why, void *arg) { struct pci_softc *sc = (struct pci_softc *)arg; struct pci_dev *pd; - pcireg_t reg; + pcireg_t bhlc, csr, reg; int i; LIST_FOREACH(pd, &sc->sc_devs, pd_next) { + /* + * Only handle header type 0 here; PCI-PCI bridges and + * CardBus bridges need special handling, which will + * be done in their specific drivers. + */ + bhlc = pci_conf_read(sc->sc_pc, pd->pd_tag, PCI_BHLC_REG); + if (PCI_HDRTYPE_TYPE(bhlc) != 0) + continue; + if (why != PWR_RESUME) { for (i = 0; i < NMAPREG; i++) - pd->pd_map[i] = pci_conf_read(sc->sc_pc, - pd->pd_tag, PCI_MAPREG_START + (i * 4)); + pd->pd_map[i] = pci_conf_read(sc->sc_pc, + pd->pd_tag, PCI_MAPREG_START + (i * 4)); pd->pd_csr = pci_conf_read(sc->sc_pc, pd->pd_tag, - PCI_COMMAND_STATUS_REG); + PCI_COMMAND_STATUS_REG); pd->pd_bhlc = pci_conf_read(sc->sc_pc, pd->pd_tag, - PCI_BHLC_REG); + PCI_BHLC_REG); pd->pd_int = pci_conf_read(sc->sc_pc, pd->pd_tag, - PCI_INTERRUPT_REG); + PCI_INTERRUPT_REG); + + /* + * Place the device into D3. The PCI Power + * Management spec says we should disable I/O + * and memory space as well as bus mastering + * before we do so. + */ + csr = pd->pd_csr; + csr &= ~PCI_COMMAND_IO_ENABLE; + csr &= ~PCI_COMMAND_MEM_ENABLE; + csr &= ~PCI_COMMAND_MASTER_ENABLE; + pci_conf_write(sc->sc_pc, pd->pd_tag, + PCI_COMMAND_STATUS_REG, csr); + pd->pd_pmcsr_state = pci_get_powerstate(sc->sc_pc, + pd->pd_tag); + pci_set_powerstate(sc->sc_pc, pd->pd_tag, + PCI_PMCSR_STATE_D3); } else { + pci_set_powerstate(sc->sc_pc, pd->pd_tag, + pd->pd_pmcsr_state); for (i = 0; i < NMAPREG; i++) pci_conf_write(sc->sc_pc, pd->pd_tag, - PCI_MAPREG_START + (i * 4), - pd->pd_map[i]); + PCI_MAPREG_START + (i * 4), pd->pd_map[i]); reg = pci_conf_read(sc->sc_pc, pd->pd_tag, PCI_COMMAND_STATUS_REG); pci_conf_write(sc->sc_pc, pd->pd_tag, @@ -360,23 +387,13 @@ pci_probe_device(struct pci_softc *sc, pcitag_t tag, if (ret != 0 && pap != NULL) *pap = pa; } else { - if ((dev = config_found_sm(&sc->sc_dev, &pa, pciprint, - pcisubmatch))) { - pcireg_t reg; + pd = malloc(sizeof *pd, M_DEVBUF, M_ZERO | M_WAITOK); + pd->pd_tag = tag; + LIST_INSERT_HEAD(&sc->sc_devs, pd, pd_next); + if ((dev = config_found_sm(&sc->sc_dev, &pa, pciprint, + pcisubmatch))) pci_dev_postattach(dev, &pa); - - /* skip header type != 0 */ - reg = pci_conf_read(pc, tag, PCI_BHLC_REG); - if (PCI_HDRTYPE_TYPE(reg) != 0) - return(0); - if (!(pd = malloc(sizeof *pd, M_DEVBUF, - M_NOWAIT))) - return(0); - pd->pd_tag = tag; - pd->pd_dev = dev; - LIST_INSERT_HEAD(&sc->sc_devs, pd, pd_next); - } } return (ret); @@ -466,6 +483,19 @@ pci_find_device(struct pci_attach_args *pa, } int +pci_get_powerstate(pci_chipset_tag_t pc, pcitag_t tag) +{ + pcireg_t reg; + int offset; + + if (pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, 0)) { + reg = pci_conf_read(pc, tag, offset + PCI_PMCSR); + return (reg & PCI_PMCSR_STATE_MASK); + } + return (PCI_PMCSR_STATE_D0); +} + +int pci_set_powerstate(pci_chipset_tag_t pc, pcitag_t tag, int state) { pcireg_t reg; diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h index 6566e50d061..8a0d45e30fc 100644 --- a/sys/dev/pci/pcivar.h +++ b/sys/dev/pci/pcivar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pcivar.h,v 1.59 2009/05/05 14:16:17 kettenis Exp $ */ +/* $OpenBSD: pcivar.h,v 1.60 2010/08/17 19:14:52 kettenis Exp $ */ /* $NetBSD: pcivar.h,v 1.23 1997/06/06 23:48:05 thorpej Exp $ */ /* @@ -236,6 +236,7 @@ struct pci_matchid { }; int pci_matchbyid(struct pci_attach_args *, const struct pci_matchid *, int); +int pci_get_powerstate(pci_chipset_tag_t, pcitag_t); int pci_set_powerstate(pci_chipset_tag_t, pcitag_t, int); /* diff --git a/sys/dev/pci/ppb.c b/sys/dev/pci/ppb.c index c51aa8d8119..39e9a8c4a36 100644 --- a/sys/dev/pci/ppb.c +++ b/sys/dev/pci/ppb.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ppb.c,v 1.43 2010/08/02 03:22:26 deraadt Exp $ */ +/* $OpenBSD: ppb.c,v 1.44 2010/08/17 19:14:52 kettenis Exp $ */ /* $NetBSD: ppb.c,v 1.16 1997/06/06 23:48:05 thorpej Exp $ */ /* @@ -81,8 +81,8 @@ struct ppb_softc { pcireg_t sc_bir; pcireg_t sc_bcr; pcireg_t sc_int; - pcireg_t sc_slcsr; + int sc_pmcsr_state; }; int ppbmatch(struct device *, void *, void *); @@ -346,7 +346,7 @@ ppbactivate(struct device *self, int act) struct ppb_softc *sc = (void *)self; pci_chipset_tag_t pc = sc->sc_pc; pcitag_t tag = sc->sc_tag; - pcireg_t blr; + pcireg_t blr, csr, reg; int rv = 0; switch (act) { @@ -362,8 +362,24 @@ ppbactivate(struct device *self, int act) if (sc->sc_cap_off) sc->sc_slcsr = pci_conf_read(pc, tag, sc->sc_cap_off + PCI_PCIE_SLCSR); + + /* + * Place the bridge into D3. The PCI Power Management + * spec says we should disable I/O and memory space as + * well as bus mastering before we do so. + */ + csr = sc->sc_csr; + csr &= ~PCI_COMMAND_IO_ENABLE; + csr &= ~PCI_COMMAND_MEM_ENABLE; + csr &= ~PCI_COMMAND_MASTER_ENABLE; + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, csr); + sc->sc_pmcsr_state = pci_get_powerstate(pc, tag); + pci_set_powerstate(pc, tag, PCI_PMCSR_STATE_D3); break; case DVACT_RESUME: + /* Restore power. */ + pci_set_powerstate(pc, tag, sc->sc_pmcsr_state); + /* Restore the registers saved above. */ pci_conf_write(pc, tag, PCI_BHLC_REG, sc->sc_bhlcr); pci_conf_write(pc, tag, PPB_REG_BUSINFO, sc->sc_bir); @@ -403,7 +419,9 @@ ppbactivate(struct device *self, int act) * Restore command register last to avoid exposing * uninitialised windows. */ - pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, sc->sc_csr); + reg = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, + (reg & 0xffff0000) | (sc->sc_csr & 0x0000ffff)); rv = config_activate_children(self, act); break; |