summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2010-08-17 19:14:53 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2010-08-17 19:14:53 +0000
commit5268c49d0fb645ef2cbc307c2a6d81bfa902a58b (patch)
tree1e674868071627714098692976474431f9ba29d1 /sys/dev
parent7f00f673b9f13763b9642e62566597f9551ddff7 (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.c80
-rw-r--r--sys/dev/pci/pcivar.h3
-rw-r--r--sys/dev/pci/ppb.c26
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;