From 30a4eca69b76c0ed5a2f34dd2a3e195c9bf6bed1 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 7 Jan 2022 13:00:47 +0200 Subject: [PATCH] thunderbolt: Add internal xHCI connect flows for Thunderbolt 3 devices Both Alpine Ridge and Titan Ridge require special flows in order to activate the internal xHCI controller when there is USB device connected to the downstream type-C port. This implements the missing flows for both. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/lc.c | 110 ++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 71 ++++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 11 +++++ drivers/thunderbolt/tb.h | 7 +++ drivers/thunderbolt/tb_regs.h | 8 +++ 5 files changed, 206 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/lc.c b/drivers/thunderbolt/lc.c index 53495a3..633970f 100644 --- a/drivers/thunderbolt/lc.c +++ b/drivers/thunderbolt/lc.c @@ -217,6 +217,116 @@ bool tb_lc_is_clx_supported(struct tb_port *port) return !!(val & TB_LC_LINK_ATTR_CPS); } +/** + * tb_lc_is_usb_plugged() - Is there USB device connected to port + * @port: Device router lane 0 adapter + * + * Returns true if the @port has USB type-C device connected. + */ +bool tb_lc_is_usb_plugged(struct tb_port *port) +{ + struct tb_switch *sw = port->sw; + int cap, ret; + u32 val; + + if (sw->generation != 3) + return false; + + cap = find_port_lc_cap(port); + if (cap < 0) + return false; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_CS_42, 1); + if (ret) + return false; + + return !!(val & TB_LC_CS_42_USB_PLUGGED); +} + +/** + * tb_lc_is_xhci_connected() - Is the internal xHCI connected + * @port: Device router lane 0 adapter + * + * Returns true if the internal xHCI has been connected to @port. + */ +bool tb_lc_is_xhci_connected(struct tb_port *port) +{ + struct tb_switch *sw = port->sw; + int cap, ret; + u32 val; + + if (sw->generation != 3) + return false; + + cap = find_port_lc_cap(port); + if (cap < 0) + return false; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); + if (ret) + return false; + + return !!(val & TB_LC_LINK_REQ_XHCI_CONNECT); +} + +static int __tb_lc_xhci_connect(struct tb_port *port, bool connect) +{ + struct tb_switch *sw = port->sw; + int cap, ret; + u32 val; + + if (sw->generation != 3) + return -EINVAL; + + cap = find_port_lc_cap(port); + if (cap < 0) + return cap; + + ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); + if (ret) + return ret; + + if (connect) + val |= TB_LC_LINK_REQ_XHCI_CONNECT; + else + val &= ~TB_LC_LINK_REQ_XHCI_CONNECT; + + return tb_sw_write(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); +} + +/** + * tb_lc_xhci_connect() - Connect internal xHCI + * @port: Device router lane 0 adapter + * + * Tells LC to connect the internal xHCI to @port. Returns %0 on success + * and negative errno in case of failure. Can be called for Thunderbolt 3 + * routers only. + */ +int tb_lc_xhci_connect(struct tb_port *port) +{ + int ret; + + ret = __tb_lc_xhci_connect(port, true); + if (ret) + return ret; + + tb_port_dbg(port, "xHCI connected\n"); + return 0; +} + +/** + * tb_lc_xhci_disconnect() - Disconnect internal xHCI + * @port: Device router lane 0 adapter + * + * Tells LC to disconnect the internal xHCI from @port. Can be called + * for Thunderbolt 3 routers only. + */ +void tb_lc_xhci_disconnect(struct tb_port *port) +{ + __tb_lc_xhci_connect(port, false); + tb_port_dbg(port, "xHCI disconnected\n"); +} + static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, unsigned int flags) { diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index d026e30..b5fb3e7 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -1528,7 +1528,13 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active) case PCI_DEVICE_ID_INTEL_PORT_RIDGE: break; default: - data |= 4; + /* + * Skip Alpine Ridge, it needs to have vendor + * specific USB hotplug event enabled for the + * internal xHCI to work. + */ + if (!tb_switch_is_alpine_ridge(sw)) + data |= TB_PLUG_EVENTS_USB_DISABLE; } } else { data = data | 0x7c; @@ -3689,3 +3695,66 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw) /* Write to Upstream PCIe bridge #0 aka Up0 */ return tb_switch_pcie_bridge_write(sw, 0, 0x143, 0x0c5806b1); } + +/** + * tb_switch_xhci_connect() - Connect internal xHCI + * @sw: Router whose xHCI to connect + * + * Can be called to any router. For Alpine Ridge and Titan Ridge + * performs special flows that bring the xHCI functional for any device + * connected to the type-C port. Call only after PCIe tunnel has been + * established. The function only does the connect if not done already + * so can be called several times for the same router. + */ +int tb_switch_xhci_connect(struct tb_switch *sw) +{ + bool usb_port1, usb_port3, xhci_port1, xhci_port3; + struct tb_port *port1, *port3; + int ret; + + port1 = &sw->ports[1]; + port3 = &sw->ports[3]; + + if (tb_switch_is_alpine_ridge(sw)) { + usb_port1 = tb_lc_is_usb_plugged(port1); + usb_port3 = tb_lc_is_usb_plugged(port3); + xhci_port1 = tb_lc_is_xhci_connected(port1); + xhci_port3 = tb_lc_is_xhci_connected(port3); + + /* Figure out correct USB port to connect */ + if (usb_port1 && !xhci_port1) { + ret = tb_lc_xhci_connect(port1); + if (ret) + return ret; + } + if (usb_port3 && !xhci_port3) + return tb_lc_xhci_connect(port3); + } else if (tb_switch_is_titan_ridge(sw)) { + ret = tb_lc_xhci_connect(port1); + if (ret) + return ret; + return tb_lc_xhci_connect(port3); + } + + return 0; +} + +/** + * tb_switch_xhci_disconnect() - Disconnect internal xHCI + * @sw: Router whose xHCI to disconnect + * + * The opposite of tb_switch_xhci_connect(). Disconnects xHCI on both + * ports. + */ +void tb_switch_xhci_disconnect(struct tb_switch *sw) +{ + if (sw->generation == 3) { + struct tb_port *port1 = &sw->ports[1]; + struct tb_port *port3 = &sw->ports[3]; + + tb_lc_xhci_disconnect(port1); + tb_port_dbg(port1, "disconnected xHCI\n"); + tb_lc_xhci_disconnect(port3); + tb_port_dbg(port3, "disconnected xHCI\n"); + } +} diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index cbd0ad8..9beb47b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1054,6 +1054,8 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw) if (WARN_ON(!tunnel)) return -ENODEV; + tb_switch_xhci_disconnect(sw); + tb_tunnel_deactivate(tunnel); list_del(&tunnel->list); tb_tunnel_free(tunnel); @@ -1099,6 +1101,9 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw) if (tb_switch_pcie_l1_enable(sw)) tb_sw_warn(sw, "failed to enable PCIe L1 for Titan Ridge\n"); + if (tb_switch_xhci_connect(sw)) + tb_sw_warn(sw, "failed to connect xHCI\n"); + list_add_tail(&tunnel->list, &tcm->tunnel_list); return 0; } @@ -1256,12 +1261,18 @@ static void tb_handle_hotplug(struct work_struct *work) tb_port_unconfigure_xdomain(port); } else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) { tb_dp_resource_unavailable(tb, port); + } else if (!port->port) { + tb_sw_dbg(sw, "xHCI disconnect request\n"); + tb_switch_xhci_disconnect(sw); } else { tb_port_dbg(port, "got unplug event for disconnected port, ignoring\n"); } } else if (port->remote) { tb_port_dbg(port, "got plug event for connected port, ignoring\n"); + } else if (!port->port && sw->authorized) { + tb_sw_dbg(sw, "xHCI connect request\n"); + tb_switch_xhci_connect(sw); } else { if (tb_port_is_null(port)) { tb_port_dbg(port, "hotplug: scanning\n"); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 44e3649..b6fcd8d 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -988,6 +988,9 @@ int tb_switch_mask_clx_objections(struct tb_switch *sw); int tb_switch_pcie_l1_enable(struct tb_switch *sw); +int tb_switch_xhci_connect(struct tb_switch *sw); +void tb_switch_xhci_disconnect(struct tb_switch *sw); + int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); int tb_port_add_nfc_credits(struct tb_port *port, int credits); int tb_port_clear_counter(struct tb_port *port, int counter); @@ -1082,6 +1085,10 @@ int tb_lc_configure_xdomain(struct tb_port *port); void tb_lc_unconfigure_xdomain(struct tb_port *port); int tb_lc_start_lane_initialization(struct tb_port *port); bool tb_lc_is_clx_supported(struct tb_port *port); +bool tb_lc_is_usb_plugged(struct tb_port *port); +bool tb_lc_is_xhci_connected(struct tb_port *port); +int tb_lc_xhci_connect(struct tb_port *port); +void tb_lc_xhci_disconnect(struct tb_port *port); int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags); int tb_lc_set_sleep(struct tb_switch *sw); bool tb_lc_lane_bonding_possible(struct tb_switch *sw); diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index 9693a6e..70795a2 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -463,6 +463,8 @@ struct tb_regs_hop { #define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2 BIT(3) /* Plug Events registers */ +#define TB_PLUG_EVENTS_USB_DISABLE BIT(2) + #define TB_PLUG_EVENTS_PCIE_WR_DATA 0x1b #define TB_PLUG_EVENTS_PCIE_CMD 0x1c #define TB_PLUG_EVENTS_PCIE_CMD_DW_OFFSET_MASK GENMASK(9, 0) @@ -502,6 +504,9 @@ struct tb_regs_hop { #define TB_LC_POWER 0x740 /* Link controller registers */ +#define TB_LC_CS_42 0x2a +#define TB_LC_CS_42_USB_PLUGGED BIT(31) + #define TB_LC_PORT_ATTR 0x8d #define TB_LC_PORT_ATTR_BE BIT(12) @@ -522,4 +527,7 @@ struct tb_regs_hop { #define TB_LC_LINK_ATTR 0x97 #define TB_LC_LINK_ATTR_CPS BIT(18) +#define TB_LC_LINK_REQ 0xad +#define TB_LC_LINK_REQ_XHCI_CONNECT BIT(31) + #endif -- 2.7.4