From 43e70c963284972205e5ad5beb2e3fcb895b03a1 Mon Sep 17 00:00:00 2001 From: kettenis Date: Tue, 6 Aug 2024 17:30:04 +0000 Subject: [PATCH] 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@ --- sys/dev/usb/xhci.c | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file 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); @@ -577,6 +578,73 @@ xhci_reset(struct xhci_softc *sc) return (0); } +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) { -- 2.20.1