thunderbolt: Add internal xHCI connect flows for Thunderbolt 3 devices
authorMika Westerberg <mika.westerberg@linux.intel.com>
Fri, 7 Jan 2022 11:00:47 +0000 (13:00 +0200)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Wed, 2 Feb 2022 10:56:51 +0000 (13:56 +0300)
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>
drivers/thunderbolt/lc.c
drivers/thunderbolt/switch.c
drivers/thunderbolt/tb.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tb_regs.h

index 53495a3..633970f 100644 (file)
@@ -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)
 {
index d026e30..b5fb3e7 100644 (file)
@@ -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");
+       }
+}
index cbd0ad8..9beb47b 100644 (file)
@@ -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");
index 44e3649..b6fcd8d 100644 (file)
@@ -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);
index 9693a6e..70795a2 100644 (file)
@@ -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