| From b082174e70100217f89a4af6371987e68210c55a Mon Sep 17 00:00:00 2001 |
| From: George D Sworo <george.d.sworo@intel.com> |
| Date: Thu, 3 Feb 2022 14:40:26 -0800 |
| Subject: [PATCH] FROMGIT: 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 <mika.westerberg@linux.intel.com> |
| (cherry picked from commit 30a4eca69b76c0ed5a2f34dd2a3e195c9bf6bed1 |
| git://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git next) |
| |
| BUG= b:210792654 |
| TEST= check the enumeration of USB device connected to DFP port of the Alpine ridge based docks |
| |
| Signed-off-by: George D Sworo <george.d.sworo@intel.com> |
| Change-Id: Ic674cf2538d643cb8f4192b246ac2a595a846da3 |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/3437722 |
| Reviewed-by: Sean Paul <seanpaul@chromium.org> |
| Reviewed-by: Prashant Malani <pmalani@chromium.org> |
| --- |
| 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 53495a38b4eb..633970fbe9b0 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 d026e305fe5d..b5fb3e76ed09 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 cbd0ad85ffb1..9beb47b31c75 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 44e36498b261..b6fcd8d45324 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 9693a6ec5950..d34d7d88ca3f 100644 |
| --- a/drivers/thunderbolt/tb_regs.h |
| +++ b/drivers/thunderbolt/tb_regs.h |
| @@ -447,6 +447,8 @@ struct tb_regs_hop { |
| u32 unknown3:3; /* set to zero */ |
| } __packed; |
| |
| +#define TB_PLUG_EVENTS_USB_DISABLE BIT(2) |
| + |
| /* TMU Thunderbolt 3 registers */ |
| #define TB_TIME_VSEC_3_CS_9 0x9 |
| #define TB_TIME_VSEC_3_CS_9_TMU_OBJ_MASK GENMASK(17, 16) |
| @@ -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.35.1.574.g5d30c73bfb-goog |
| |