summaryrefslogtreecommitdiffstats
path: root/drivers/thunderbolt/tb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/tb.c')
-rw-r--r--drivers/thunderbolt/tb.c167
1 files changed, 131 insertions, 36 deletions
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index fd49f86e0..525f515e8 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -513,8 +513,6 @@ static void tb_port_unconfigure_xdomain(struct tb_port *port)
usb4_port_unconfigure_xdomain(port);
else
tb_lc_unconfigure_xdomain(port);
-
- tb_port_enable(port->dual_link_port);
}
static void tb_scan_xdomain(struct tb_port *port)
@@ -1087,15 +1085,14 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, int requested_up,
int requested_down)
{
+ bool clx = false, clx_disabled = false, downstream;
struct tb_switch *sw;
- bool clx, downstream;
struct tb_port *up;
int ret = 0;
if (!asym_threshold)
return 0;
- /* Disable CL states before doing any transitions */
downstream = tb_port_path_direction_downstream(src_port, dst_port);
/* Pick up router deepest in the hierarchy */
if (downstream)
@@ -1103,11 +1100,10 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
else
sw = src_port->sw;
- clx = tb_disable_clx(sw);
-
tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+ struct tb_port *down = tb_switch_downstream_port(up->sw);
+ enum tb_link_width width_up, width_down;
int consumed_up, consumed_down;
- enum tb_link_width width;
ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
&consumed_up, &consumed_down);
@@ -1128,7 +1124,8 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
if (consumed_down + requested_down < asym_threshold)
continue;
- width = TB_LINK_WIDTH_ASYM_RX;
+ width_up = TB_LINK_WIDTH_ASYM_RX;
+ width_down = TB_LINK_WIDTH_ASYM_TX;
} else {
/* Upstream, the opposite of above */
if (consumed_down + requested_down >= TB_ASYM_MIN) {
@@ -1138,22 +1135,34 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
if (consumed_up + requested_up < asym_threshold)
continue;
- width = TB_LINK_WIDTH_ASYM_TX;
+ width_up = TB_LINK_WIDTH_ASYM_TX;
+ width_down = TB_LINK_WIDTH_ASYM_RX;
}
- if (up->sw->link_width == width)
+ if (up->sw->link_width == width_up)
continue;
- if (!tb_port_width_supported(up, width))
+ if (!tb_port_width_supported(up, width_up) ||
+ !tb_port_width_supported(down, width_down))
continue;
+ /*
+ * Disable CL states before doing any transitions. We
+ * delayed it until now that we know there is a real
+ * transition taking place.
+ */
+ if (!clx_disabled) {
+ clx = tb_disable_clx(sw);
+ clx_disabled = true;
+ }
+
tb_sw_dbg(up->sw, "configuring asymmetric link\n");
/*
* Here requested + consumed > threshold so we need to
* transtion the link into asymmetric now.
*/
- ret = tb_switch_set_link_width(up->sw, width);
+ ret = tb_switch_set_link_width(up->sw, width_up);
if (ret) {
tb_sw_warn(up->sw, "failed to set link width\n");
break;
@@ -1174,24 +1183,24 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
* @dst_port: Destination adapter
* @requested_up: New lower bandwidth request upstream (Mb/s)
* @requested_down: New lower bandwidth request downstream (Mb/s)
+ * @keep_asym: Keep asymmetric link if preferred
*
* Goes over each link from @src_port to @dst_port and tries to
* transition the link to symmetric if the currently consumed bandwidth
- * allows.
+ * allows and link asymmetric preference is ignored (if @keep_asym is %false).
*/
static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
struct tb_port *dst_port, int requested_up,
- int requested_down)
+ int requested_down, bool keep_asym)
{
+ bool clx = false, clx_disabled = false, downstream;
struct tb_switch *sw;
- bool clx, downstream;
struct tb_port *up;
int ret = 0;
if (!asym_threshold)
return 0;
- /* Disable CL states before doing any transitions */
downstream = tb_port_path_direction_downstream(src_port, dst_port);
/* Pick up router deepest in the hierarchy */
if (downstream)
@@ -1199,8 +1208,6 @@ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
else
sw = src_port->sw;
- clx = tb_disable_clx(sw);
-
tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
int consumed_up, consumed_down;
@@ -1233,6 +1240,25 @@ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
if (up->sw->link_width == TB_LINK_WIDTH_DUAL)
continue;
+ /*
+ * Here consumed < threshold so we can transition the
+ * link to symmetric.
+ *
+ * However, if the router prefers asymmetric link we
+ * honor that (unless @keep_asym is %false).
+ */
+ if (keep_asym &&
+ up->sw->preferred_link_width > TB_LINK_WIDTH_DUAL) {
+ tb_sw_dbg(up->sw, "keeping preferred asymmetric link\n");
+ continue;
+ }
+
+ /* Disable CL states before doing any transitions */
+ if (!clx_disabled) {
+ clx = tb_disable_clx(sw);
+ clx_disabled = true;
+ }
+
tb_sw_dbg(up->sw, "configuring symmetric link\n");
ret = tb_switch_set_link_width(up->sw, TB_LINK_WIDTH_DUAL);
@@ -1280,7 +1306,7 @@ static void tb_configure_link(struct tb_port *down, struct tb_port *up,
struct tb_port *host_port;
host_port = tb_port_at(tb_route(sw), tb->root_switch);
- tb_configure_sym(tb, host_port, up, 0, 0);
+ tb_configure_sym(tb, host_port, up, 0, 0, false);
}
/* Set the link configured */
@@ -1465,7 +1491,7 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
* If bandwidth on a link is < asym_threshold
* transition the link to symmetric.
*/
- tb_configure_sym(tb, src_port, dst_port, 0, 0);
+ tb_configure_sym(tb, src_port, dst_port, 0, 0, true);
/* Now we can allow the domain to runtime suspend again */
pm_runtime_mark_last_busy(&dst_port->sw->dev);
pm_runtime_put_autosuspend(&dst_port->sw->dev);
@@ -1691,6 +1717,12 @@ static struct tb_port *tb_find_dp_out(struct tb *tb, struct tb_port *in)
continue;
}
+ /* Needs to be on different routers */
+ if (in->sw == port->sw) {
+ tb_port_dbg(port, "skipping DP OUT on same router\n");
+ continue;
+ }
+
tb_port_dbg(port, "DP OUT available\n");
/*
@@ -1861,6 +1893,49 @@ static void tb_tunnel_dp(struct tb *tb)
;
}
+static void tb_enter_redrive(struct tb_port *port)
+{
+ struct tb_switch *sw = port->sw;
+
+ if (!(sw->quirks & QUIRK_KEEP_POWER_IN_DP_REDRIVE))
+ return;
+
+ /*
+ * If we get hot-unplug for the DP IN port of the host router
+ * and the DP resource is not available anymore it means there
+ * is a monitor connected directly to the Type-C port and we are
+ * in "redrive" mode. For this to work we cannot enter RTD3 so
+ * we bump up the runtime PM reference count here.
+ */
+ if (!tb_port_is_dpin(port))
+ return;
+ if (tb_route(sw))
+ return;
+ if (!tb_switch_query_dp_resource(sw, port)) {
+ port->redrive = true;
+ pm_runtime_get(&sw->dev);
+ tb_port_dbg(port, "enter redrive mode, keeping powered\n");
+ }
+}
+
+static void tb_exit_redrive(struct tb_port *port)
+{
+ struct tb_switch *sw = port->sw;
+
+ if (!(sw->quirks & QUIRK_KEEP_POWER_IN_DP_REDRIVE))
+ return;
+
+ if (!tb_port_is_dpin(port))
+ return;
+ if (tb_route(sw))
+ return;
+ if (port->redrive && tb_switch_query_dp_resource(sw, port)) {
+ port->redrive = false;
+ pm_runtime_put(&sw->dev);
+ tb_port_dbg(port, "exit redrive mode\n");
+ }
+}
+
static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
{
struct tb_port *in, *out;
@@ -1877,7 +1952,10 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
}
tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, out);
- tb_deactivate_and_free_tunnel(tunnel);
+ if (tunnel)
+ tb_deactivate_and_free_tunnel(tunnel);
+ else
+ tb_enter_redrive(port);
list_del_init(&port->list);
/*
@@ -1901,9 +1979,10 @@ static void tb_dp_resource_available(struct tb *tb, struct tb_port *port)
return;
}
- tb_port_dbg(port, "DP %s resource available\n",
+ tb_port_dbg(port, "DP %s resource available after hotplug\n",
tb_port_is_dpin(port) ? "IN" : "OUT");
list_add_tail(&port->list, &tcm->dp_resources);
+ tb_exit_redrive(port);
/* Look for suitable DP IN <-> DP OUT pairs now */
tb_tunnel_dp(tb);
@@ -2287,7 +2366,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
* If bandwidth on a link is < asym_threshold transition
* the link to symmetric.
*/
- tb_configure_sym(tb, in, out, *requested_up, *requested_down);
+ tb_configure_sym(tb, in, out, *requested_up, *requested_down, true);
/*
* If requested bandwidth is less or equal than what is
* currently allocated to that tunnel we simply change
@@ -2330,7 +2409,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
ret = tb_configure_asym(tb, in, out, *requested_up,
*requested_down);
if (ret) {
- tb_configure_sym(tb, in, out, 0, 0);
+ tb_configure_sym(tb, in, out, 0, 0, true);
return ret;
}
@@ -2338,7 +2417,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
requested_down);
if (ret) {
tb_tunnel_warn(tunnel, "failed to allocate bandwidth\n");
- tb_configure_sym(tb, in, out, 0, 0);
+ tb_configure_sym(tb, in, out, 0, 0, true);
}
} else {
ret = -ENOBUFS;
@@ -2555,7 +2634,7 @@ static int tb_scan_finalize_switch(struct device *dev, void *data)
return 0;
}
-static int tb_start(struct tb *tb)
+static int tb_start(struct tb *tb, bool reset)
{
struct tb_cm *tcm = tb_priv(tb);
int ret;
@@ -2596,12 +2675,24 @@ static int tb_start(struct tb *tb)
tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_MODE_LOWRES);
/* Enable TMU if it is off */
tb_switch_tmu_enable(tb->root_switch);
- /* Full scan to discover devices added before the driver was loaded. */
- tb_scan_switch(tb->root_switch);
- /* Find out tunnels created by the boot firmware */
- tb_discover_tunnels(tb);
- /* Add DP resources from the DP tunnels created by the boot firmware */
- tb_discover_dp_resources(tb);
+
+ /*
+ * Boot firmware might have created tunnels of its own. Since we
+ * cannot be sure they are usable for us, tear them down and
+ * reset the ports to handle it as new hotplug for USB4 v1
+ * routers (for USB4 v2 and beyond we already do host reset).
+ */
+ if (reset && usb4_switch_version(tb->root_switch) == 1) {
+ tb_switch_reset(tb->root_switch);
+ } else {
+ /* Full scan to discover devices added before the driver was loaded. */
+ tb_scan_switch(tb->root_switch);
+ /* Find out tunnels created by the boot firmware */
+ tb_discover_tunnels(tb);
+ /* Add DP resources from the DP tunnels created by the boot firmware */
+ tb_discover_dp_resources(tb);
+ }
+
/*
* If the boot firmware did not create USB 3.x tunnels create them
* now for the whole topology.
@@ -2672,10 +2763,14 @@ static int tb_resume_noirq(struct tb *tb)
tb_dbg(tb, "resuming...\n");
- /* remove any pci devices the firmware might have setup */
- tb_switch_reset(tb->root_switch);
+ /*
+ * For non-USB4 hosts (Apple systems) remove any PCIe devices
+ * the firmware might have setup.
+ */
+ if (!tb_switch_is_usb4(tb->root_switch))
+ tb_switch_reset(tb->root_switch);
- tb_switch_resume(tb->root_switch);
+ tb_switch_resume(tb->root_switch, false);
tb_free_invalid_tunnels(tb);
tb_free_unplugged_children(tb->root_switch);
tb_restore_children(tb->root_switch);
@@ -2801,7 +2896,7 @@ static int tb_runtime_resume(struct tb *tb)
struct tb_tunnel *tunnel, *n;
mutex_lock(&tb->lock);
- tb_switch_resume(tb->root_switch);
+ tb_switch_resume(tb->root_switch, true);
tb_free_invalid_tunnels(tb);
tb_restore_children(tb->root_switch);
list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list)