diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2024-08-06 17:30:05 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2024-08-06 17:30:05 +0000 |
commit | 48a3a2db8c04229f4b66eb7be5c1d5a94b0a2566 (patch) | |
tree | 9721293ccaa6df17a4a30cad5083b7c4070f39a2 /sys/dev | |
parent | b53d6b50be9c2397bb95620f3f0ca5f7db394e77 (diff) |
Some Intel xhci(4) controllers don't fully power down unless they've seen
a "save state" command. So use that command when we suspend (and don't
reset the controller at that point such that it doesn't forget about it).
Note that on resume we don't restore the state. Instead we just reset
the controller and bring it up from scratch. There isn't much state to
save anyway since we detach all USB devices when we suspend.
ok mlarkin@, deraadt@
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/usb/xhci.c | 72 |
1 files changed, 70 insertions, 2 deletions
diff --git a/sys/dev/usb/xhci.c b/sys/dev/usb/xhci.c index cf9ef254b9f..e2ca8e8056d 100644 --- a/sys/dev/usb/xhci.c +++ b/sys/dev/usb/xhci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: xhci.c,v 1.131 2024/05/23 03:21:09 jsg Exp $ */ +/* $OpenBSD: xhci.c,v 1.132 2024/08/06 17:30:04 kettenis Exp $ */ /* * Copyright (c) 2014-2015 Martin Pieuchot @@ -79,6 +79,7 @@ struct xhci_pipe { }; int xhci_reset(struct xhci_softc *); +void xhci_suspend(struct xhci_softc *); int xhci_intr1(struct xhci_softc *); void xhci_event_dequeue(struct xhci_softc *); void xhci_event_xfer(struct xhci_softc *, uint64_t, uint32_t, uint32_t); @@ -533,7 +534,7 @@ xhci_activate(struct device *self, int act) break; case DVACT_POWERDOWN: rv = config_activate_children(self, act); - xhci_reset(sc); + xhci_suspend(sc); break; default: rv = config_activate_children(self, act); @@ -578,6 +579,73 @@ xhci_reset(struct xhci_softc *sc) } void +xhci_suspend(struct xhci_softc *sc) +{ + uint32_t hcr; + int i; + + XOWRITE4(sc, XHCI_USBCMD, 0); /* Halt controller */ + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = XOREAD4(sc, XHCI_USBSTS) & XHCI_STS_HCH; + if (hcr) + break; + } + + if (!hcr) { + printf("%s: halt timeout\n", DEVNAME(sc)); + xhci_reset(sc); + return; + } + + /* + * Some Intel controllers will not power down completely + * unless they have seen a save state command. This in turn + * will prevent the SoC from reaching its lowest idle state. + * So save the state here. + * + * Note that we don't restore this saved state anywhere. + * Instead we reset the controller and reinitialize it from + * scratch when we resume. + */ + + XOWRITE4(sc, XHCI_USBCMD, XHCI_CMD_CSS); /* Save state */ + hcr = XOREAD4(sc, XHCI_USBSTS); + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = XOREAD4(sc, XHCI_USBSTS) & XHCI_STS_SSS; + if (!hcr) + break; + } + + if (hcr) { + printf("%s: save state timeout\n", DEVNAME(sc)); + xhci_reset(sc); + return; + } + + /* Disable interrupts. */ + XRWRITE4(sc, XHCI_IMOD(0), 0); + XRWRITE4(sc, XHCI_IMAN(0), 0); + + /* Clear the event ring address. */ + XRWRITE4(sc, XHCI_ERDP_LO(0), 0); + XRWRITE4(sc, XHCI_ERDP_HI(0), 0); + + XRWRITE4(sc, XHCI_ERSTBA_LO(0), 0); + XRWRITE4(sc, XHCI_ERSTBA_HI(0), 0); + + XRWRITE4(sc, XHCI_ERSTSZ(0), 0); + + /* Clear the command ring address. */ + XOWRITE4(sc, XHCI_CRCR_LO, 0); + XOWRITE4(sc, XHCI_CRCR_HI, 0); + + XOWRITE4(sc, XHCI_DCBAAP_LO, 0); + XOWRITE4(sc, XHCI_DCBAAP_HI, 0); +} + +void xhci_reinit(struct xhci_softc *sc) { xhci_reset(sc); |