summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2024-08-06 17:30:05 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2024-08-06 17:30:05 +0000
commit48a3a2db8c04229f4b66eb7be5c1d5a94b0a2566 (patch)
tree9721293ccaa6df17a4a30cad5083b7c4070f39a2
parentb53d6b50be9c2397bb95620f3f0ca5f7db394e77 (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@
-rw-r--r--sys/dev/usb/xhci.c72
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);