]> git.ipfire.org Git - thirdparty/linux.git/blobdiff - drivers/thunderbolt/switch.c
Merge tag 'usb-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[thirdparty/linux.git] / drivers / thunderbolt / switch.c
index fad40c4bc710341f27b7f98f6d4317aef6627ae0..6ffc4e81ffed78bedaac182b5a77742390c6cd74 100644 (file)
@@ -676,6 +676,13 @@ int tb_port_disable(struct tb_port *port)
        return __tb_port_enable(port, false);
 }
 
+static int tb_port_reset(struct tb_port *port)
+{
+       if (tb_switch_is_usb4(port->sw))
+               return port->cap_usb4 ? usb4_port_reset(port) : 0;
+       return tb_lc_reset_port(port);
+}
+
 /*
  * tb_init_port() - initialize a port
  *
@@ -771,7 +778,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
        if (max_hopid < 0 || max_hopid > port_max_hopid)
                max_hopid = port_max_hopid;
 
-       return ida_simple_get(ida, min_hopid, max_hopid + 1, GFP_KERNEL);
+       return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL);
 }
 
 /**
@@ -809,7 +816,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid)
  */
 void tb_port_release_in_hopid(struct tb_port *port, int hopid)
 {
-       ida_simple_remove(&port->in_hopids, hopid);
+       ida_free(&port->in_hopids, hopid);
 }
 
 /**
@@ -819,7 +826,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid)
  */
 void tb_port_release_out_hopid(struct tb_port *port, int hopid)
 {
-       ida_simple_remove(&port->out_hopids, hopid);
+       ida_free(&port->out_hopids, hopid);
 }
 
 static inline bool tb_switch_is_reachable(const struct tb_switch *parent,
@@ -1120,7 +1127,7 @@ int tb_port_lane_bonding_enable(struct tb_port *port)
                ret = tb_port_set_link_width(port->dual_link_port,
                                             TB_LINK_WIDTH_DUAL);
                if (ret)
-                       goto err_lane0;
+                       goto err_lane1;
        }
 
        /*
@@ -1534,29 +1541,124 @@ static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
               regs->__unknown1, regs->__unknown4);
 }
 
+static int tb_switch_reset_host(struct tb_switch *sw)
+{
+       if (sw->generation > 1) {
+               struct tb_port *port;
+
+               tb_switch_for_each_port(sw, port) {
+                       int i, ret;
+
+                       /*
+                        * For lane adapters we issue downstream port
+                        * reset and clear up path config spaces.
+                        *
+                        * For protocol adapters we disable the path and
+                        * clear path config space one by one (from 8 to
+                        * Max Input HopID of the adapter).
+                        */
+                       if (tb_port_is_null(port) && !tb_is_upstream_port(port)) {
+                               ret = tb_port_reset(port);
+                               if (ret)
+                                       return ret;
+                       } else if (tb_port_is_usb3_down(port) ||
+                                  tb_port_is_usb3_up(port)) {
+                               tb_usb3_port_enable(port, false);
+                       } else if (tb_port_is_dpin(port) ||
+                                  tb_port_is_dpout(port)) {
+                               tb_dp_port_enable(port, false);
+                       } else if (tb_port_is_pcie_down(port) ||
+                                  tb_port_is_pcie_up(port)) {
+                               tb_pci_port_enable(port, false);
+                       } else {
+                               continue;
+                       }
+
+                       /* Cleanup path config space of protocol adapter */
+                       for (i = TB_PATH_MIN_HOPID;
+                            i <= port->config.max_in_hop_id; i++) {
+                               ret = tb_path_deactivate_hop(port, i);
+                               if (ret)
+                                       return ret;
+                       }
+               }
+       } else {
+               struct tb_cfg_result res;
+
+               /* Thunderbolt 1 uses the "reset" config space packet */
+               res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
+                                     TB_CFG_SWITCH, 2, 2);
+               if (res.err)
+                       return res.err;
+               res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
+               if (res.err > 0)
+                       return -EIO;
+               else if (res.err < 0)
+                       return res.err;
+       }
+
+       return 0;
+}
+
+static int tb_switch_reset_device(struct tb_switch *sw)
+{
+       return tb_port_reset(tb_switch_downstream_port(sw));
+}
+
+static bool tb_switch_enumerated(struct tb_switch *sw)
+{
+       u32 val;
+       int ret;
+
+       /*
+        * Read directly from the hardware because we use this also
+        * during system sleep where sw->config.enabled is already set
+        * by us.
+        */
+       ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_3, 1);
+       if (ret)
+               return false;
+
+       return !!(val & ROUTER_CS_3_V);
+}
+
 /**
- * tb_switch_reset() - reconfigure route, enable and send TB_CFG_PKG_RESET
- * @sw: Switch to reset
+ * tb_switch_reset() - Perform reset to the router
+ * @sw: Router to reset
  *
- * Return: Returns 0 on success or an error code on failure.
+ * Issues reset to the router @sw. Can be used for any router. For host
+ * routers, resets all the downstream ports and cleans up path config
+ * spaces accordingly. For device routers issues downstream port reset
+ * through the parent router, so as side effect there will be unplug
+ * soon after this is finished.
+ *
+ * If the router is not enumerated does nothing.
+ *
+ * Returns %0 on success or negative errno in case of failure.
  */
 int tb_switch_reset(struct tb_switch *sw)
 {
-       struct tb_cfg_result res;
+       int ret;
 
-       if (sw->generation > 1)
+       /*
+        * We cannot access the port config spaces unless the router is
+        * already enumerated. If the router is not enumerated it is
+        * equal to being reset so we can skip that here.
+        */
+       if (!tb_switch_enumerated(sw))
                return 0;
 
-       tb_sw_dbg(sw, "resetting switch\n");
+       tb_sw_dbg(sw, "resetting\n");
 
-       res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
-                             TB_CFG_SWITCH, 2, 2);
-       if (res.err)
-               return res.err;
-       res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
-       if (res.err > 0)
-               return -EIO;
-       return res.err;
+       if (tb_route(sw))
+               ret = tb_switch_reset_device(sw);
+       else
+               ret = tb_switch_reset_host(sw);
+
+       if (ret)
+               tb_sw_warn(sw, "failed to reset\n");
+
+       return ret;
 }
 
 /**
@@ -2228,7 +2330,7 @@ static const struct dev_pm_ops tb_switch_pm_ops = {
                           NULL)
 };
 
-struct device_type tb_switch_type = {
+const struct device_type tb_switch_type = {
        .name = "thunderbolt_device",
        .release = tb_switch_release,
        .uevent = tb_switch_uevent,