aboutsummaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/usb4.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/usb4.c')
-rw-r--r--drivers/thunderbolt/usb4.c112
1 files changed, 110 insertions, 2 deletions
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index 59b8b51d1fa4..40f13579a3fe 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -196,6 +196,46 @@ static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
return 0;
}
+static void usb4_switch_check_wakes(struct tb_switch *sw)
+{
+ struct tb_port *port;
+ bool wakeup = false;
+ u32 val;
+
+ if (!device_may_wakeup(&sw->dev))
+ return;
+
+ if (tb_route(sw)) {
+ if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1))
+ return;
+
+ tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n",
+ (val & ROUTER_CS_6_WOPS) ? "yes" : "no",
+ (val & ROUTER_CS_6_WOUS) ? "yes" : "no");
+
+ wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS);
+ }
+
+ /* Check for any connected downstream ports for USB4 wake */
+ tb_switch_for_each_port(sw, port) {
+ if (!tb_port_has_remote(port))
+ continue;
+
+ if (tb_port_read(port, &val, TB_CFG_PORT,
+ port->cap_usb4 + PORT_CS_18, 1))
+ break;
+
+ tb_port_dbg(port, "USB4 wake: %s\n",
+ (val & PORT_CS_18_WOU4S) ? "yes" : "no");
+
+ if (val & PORT_CS_18_WOU4S)
+ wakeup = true;
+ }
+
+ if (wakeup)
+ pm_wakeup_event(&sw->dev, 0);
+}
+
static bool link_is_usb4(struct tb_port *port)
{
u32 val;
@@ -229,6 +269,8 @@ int usb4_switch_setup(struct tb_switch *sw)
u32 val = 0;
int ret;
+ usb4_switch_check_wakes(sw);
+
if (!tb_route(sw))
return 0;
@@ -360,11 +402,77 @@ bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
}
/**
+ * usb4_switch_set_wake() - Enabled/disable wake
+ * @sw: USB4 router
+ * @flags: Wakeup flags (%0 to disable)
+ *
+ * Enables/disables router to wake up from sleep.
+ */
+int usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags)
+{
+ struct tb_port *port;
+ u64 route = tb_route(sw);
+ u32 val;
+ int ret;
+
+ /*
+ * Enable wakes coming from all USB4 downstream ports (from
+ * child routers). For device routers do this also for the
+ * upstream USB4 port.
+ */
+ tb_switch_for_each_port(sw, port) {
+ if (!route && tb_is_upstream_port(port))
+ continue;
+
+ ret = tb_port_read(port, &val, TB_CFG_PORT,
+ port->cap_usb4 + PORT_CS_19, 1);
+ if (ret)
+ return ret;
+
+ val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4);
+
+ if (flags & TB_WAKE_ON_CONNECT)
+ val |= PORT_CS_19_WOC;
+ if (flags & TB_WAKE_ON_DISCONNECT)
+ val |= PORT_CS_19_WOD;
+ if (flags & TB_WAKE_ON_USB4)
+ val |= PORT_CS_19_WOU4;
+
+ ret = tb_port_write(port, &val, TB_CFG_PORT,
+ port->cap_usb4 + PORT_CS_19, 1);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Enable wakes from PCIe and USB 3.x on this router. Only
+ * needed for device routers.
+ */
+ if (route) {
+ ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+ if (ret)
+ return ret;
+
+ val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU);
+ if (flags & TB_WAKE_ON_USB3)
+ val |= ROUTER_CS_5_WOU;
+ if (flags & TB_WAKE_ON_PCIE)
+ val |= ROUTER_CS_5_WOP;
+
+ ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
* usb4_switch_set_sleep() - Prepare the router to enter sleep
* @sw: USB4 router
*
- * Enables wakes and sets sleep bit for the router. Returns when the
- * router sleep ready bit has been asserted.
+ * Sets sleep bit for the router. Returns when the router sleep ready
+ * bit has been asserted.
*/
int usb4_switch_set_sleep(struct tb_switch *sw)
{