diff --git a/sys/dev/thunderbolt/nhi.c b/sys/dev/thunderbolt/nhi.c --- a/sys/dev/thunderbolt/nhi.c +++ b/sys/dev/thunderbolt/nhi.c @@ -415,6 +415,20 @@ return (0); } +int +nhi_suspend(struct nhi_softc *sc) +{ + + return (tb_router_suspend(sc->root_rsc)); +} + +int +nhi_resume(struct nhi_softc *sc) +{ + + return (0); +} + static void nhi_memaddr_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { diff --git a/sys/dev/thunderbolt/nhi_pci.c b/sys/dev/thunderbolt/nhi_pci.c --- a/sys/dev/thunderbolt/nhi_pci.c +++ b/sys/dev/thunderbolt/nhi_pci.c @@ -242,15 +242,17 @@ static int nhi_pci_suspend(device_t dev) { + struct nhi_softc *sc = device_get_softc(dev); - return (0); + return (nhi_suspend(sc)); } static int nhi_pci_resume(device_t dev) { + struct nhi_softc *sc = device_get_softc(dev); - return (0); + return (nhi_resume(sc)); } static void diff --git a/sys/dev/thunderbolt/nhi_var.h b/sys/dev/thunderbolt/nhi_var.h --- a/sys/dev/thunderbolt/nhi_var.h +++ b/sys/dev/thunderbolt/nhi_var.h @@ -229,6 +229,8 @@ void nhi_get_tunables(struct nhi_softc *); int nhi_attach(struct nhi_softc *); int nhi_detach(struct nhi_softc *); +int nhi_suspend(struct nhi_softc *); +int nhi_resume(struct nhi_softc *); struct nhi_cmd_frame * nhi_alloc_tx_frame(struct nhi_ring_pair *); void nhi_free_tx_frame(struct nhi_ring_pair *, struct nhi_cmd_frame *); diff --git a/sys/dev/thunderbolt/router.c b/sys/dev/thunderbolt/router.c --- a/sys/dev/thunderbolt/router.c +++ b/sys/dev/thunderbolt/router.c @@ -255,6 +255,7 @@ sc->ring0 = nsc->ring0; sc->route = route; sc->nsc = nsc; + sc->suspended = false; mtx_init(&sc->mtx, "tbcfg", "Thunderbolt Router Config", MTX_DEF); TAILQ_INIT(&sc->cmd_queue); @@ -346,6 +347,90 @@ return (0); } +int +tb_router_suspend(struct router_softc *sc) +{ + int err; + uint32_t reg; + + tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "%s called\n", __func__); + if (sc->suspended) { + tb_debug(sc, DBG_ROUTER|DBG_EXTRA, "Already suspended\n"); + return (0); + } + + /* + * TODO Before we do anything, we've first got to make sure that the + * USB3 hub is in the U3 state, and the PCIe endpoint is in D3. + * + * Also check for "USB4 Port is Configured" to know if we support + * sleep state. + */ + + /* First, we've got to set ROUTER_CS_5.SLP (enter sleep). */ + err = tb_config_router_read(sc, ROUTER_CS_5, 1, ®); + if (err != 0) { + tb_debug(sc, DBG_ROUTER, "Cannot read ROUTER_CS5\n"); + return (err); + } + /* + * We want to set the enter sleep bit, as well as preventing wake + * events from: + * - Wake on PCIe (WoP). + * - Wake on USB3 (WoU). + * - Wake on DisplayPort (WoD). + */ + reg |= ROUTER_SLP; + reg &= ~(ROUTER_WOP | ROUTER_WOU | ROUTER_WOD); + err = tb_config_router_write(sc, ROUTER_CS_5, 1, ®); + if (err != 0) { + tb_debug(sc, DBG_ROUTER, "Cannot write to ROUTER_CS5\n"); + return (err); + } + + /* + * The ROUTER_CS_6.SLPR (sleep ready) bit should be set tSetSR after + * we set the SLP bit. Poll for it to be set. + * + * TODO On a v2 router, we should wait for the ROP_CMPLT notification, + * but in the meantime just polling is also valid. + */ + pause_sbt("tbrouter", ustosbt(NHI_SLPR_WAIT_US), 0, C_HARDCLOCK); + err = tb_config_router_read(sc, ROUTER_CS_6, 1, ®); + if (err != 0) { + tb_debug(sc, DBG_ROUTER, "Cannot read ROUTER_CS6\n"); + return (err); + } + if ((reg & ROUTER_SLPR) != 0) + goto ready; + tb_printf(sc, "Sleep ready bit not set after 50 ms after " + "asking to enter sleep, waiting...\n"); + for (size_t i = 0; i < NHI_SLPR_WAIT_MAX; i++) { + pause_sbt("tbrouter", ustosbt(NHI_SLPR_WAIT_US), 0, + C_HARDCLOCK); + err = tb_config_router_read(sc, ROUTER_CS_6, 1, ®); + if (err != 0) { + tb_debug(sc, DBG_ROUTER, "Cannot read ROUTER_CS6\n"); + return (err); + } + if ((reg & ROUTER_SLPR) != 0) + goto ready; + } + tb_printf(sc, "Timed out waiting for the sleep ready bit to be" + "set\n"); + return (ETIMEDOUT); + +ready: + tb_printf(sc, "Ready to enter sleep\n"); + sc->suspended = true; + /* + * TODO We must tell the host router to send LT_LRoff on the sideband + * channel of each DFP. (I thought we weren't allowed to send anything + * on the sideband channel after setting the sleep entry bit?) + */ + return (0); +} + static void router_get_config_cb(struct router_softc *sc, struct router_command *cmd, void *arg) diff --git a/sys/dev/thunderbolt/router_var.h b/sys/dev/thunderbolt/router_var.h --- a/sys/dev/thunderbolt/router_var.h +++ b/sys/dev/thunderbolt/router_var.h @@ -61,6 +61,7 @@ tb_route_t route; device_t dev; struct nhi_softc *nsc; + bool suspended; struct mtx mtx; struct nhi_ring_pair *ring0; @@ -101,6 +102,7 @@ int tb_router_attach(struct router_softc *, tb_route_t); int tb_router_attach_root(struct nhi_softc *, tb_route_t); int tb_router_detach(struct router_softc *); +int tb_router_suspend(struct router_softc *); int tb_config_read(struct router_softc *, u_int, u_int, u_int, u_int, uint32_t *); int tb_config_read_polled(struct router_softc *, u_int, u_int, u_int, u_int,