summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/switch.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt/switch.c')
-rw-r--r--drivers/thunderbolt/switch.c109
1 files changed, 90 insertions, 19 deletions
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index ac87e8b50e52..561e1d77240e 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -693,8 +693,14 @@ static int __tb_port_enable(struct tb_port *port, bool enable)
else
phy |= LANE_ADP_CS_1_LD;
- return tb_port_write(port, &phy, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
+
+ ret = tb_port_write(port, &phy, TB_CFG_PORT,
+ port->cap_phy + LANE_ADP_CS_1, 1);
+ if (ret)
+ return ret;
+
+ tb_port_dbg(port, "lane %sabled\n", enable ? "en" : "dis");
+ return 0;
}
/**
@@ -993,7 +999,17 @@ static bool tb_port_is_width_supported(struct tb_port *port, int width)
return !!(widths & width);
}
-static int tb_port_set_link_width(struct tb_port *port, unsigned int width)
+/**
+ * tb_port_set_link_width() - Set target link width of the lane adapter
+ * @port: Lane adapter
+ * @width: Target link width (%1 or %2)
+ *
+ * Sets the target link width of the lane adapter to @width. Does not
+ * enable/disable lane bonding. For that call tb_port_set_lane_bonding().
+ *
+ * Return: %0 in case of success and negative errno in case of error
+ */
+int tb_port_set_link_width(struct tb_port *port, unsigned int width)
{
u32 val;
int ret;
@@ -1020,13 +1036,59 @@ static int tb_port_set_link_width(struct tb_port *port, unsigned int width)
return -EINVAL;
}
- val |= LANE_ADP_CS_1_LB;
-
return tb_port_write(port, &val, TB_CFG_PORT,
port->cap_phy + LANE_ADP_CS_1, 1);
}
/**
+ * tb_port_set_lane_bonding() - Enable/disable lane bonding
+ * @port: Lane adapter
+ * @bonding: enable/disable bonding
+ *
+ * Enables or disables lane bonding. This should be called after target
+ * link width has been set (tb_port_set_link_width()). Note in most
+ * cases one should use tb_port_lane_bonding_enable() instead to enable
+ * lane bonding.
+ *
+ * As a side effect sets @port->bonding accordingly (and does the same
+ * for lane 1 too).
+ *
+ * Return: %0 in case of success and negative errno in case of error
+ */
+int tb_port_set_lane_bonding(struct tb_port *port, bool bonding)
+{
+ u32 val;
+ int ret;
+
+ if (!port->cap_phy)
+ return -EINVAL;
+
+ ret = tb_port_read(port, &val, TB_CFG_PORT,
+ port->cap_phy + LANE_ADP_CS_1, 1);
+ if (ret)
+ return ret;
+
+ if (bonding)
+ val |= LANE_ADP_CS_1_LB;
+ else
+ val &= ~LANE_ADP_CS_1_LB;
+
+ ret = tb_port_write(port, &val, TB_CFG_PORT,
+ port->cap_phy + LANE_ADP_CS_1, 1);
+ if (ret)
+ return ret;
+
+ /*
+ * When lane 0 bonding is set it will affect lane 1 too so
+ * update both.
+ */
+ port->bonded = bonding;
+ port->dual_link_port->bonded = bonding;
+
+ return 0;
+}
+
+/**
* tb_port_lane_bonding_enable() - Enable bonding on port
* @port: port to enable
*
@@ -1050,22 +1112,27 @@ int tb_port_lane_bonding_enable(struct tb_port *port)
if (ret == 1) {
ret = tb_port_set_link_width(port, 2);
if (ret)
- return ret;
+ goto err_lane0;
}
ret = tb_port_get_link_width(port->dual_link_port);
if (ret == 1) {
ret = tb_port_set_link_width(port->dual_link_port, 2);
- if (ret) {
- tb_port_set_link_width(port, 1);
- return ret;
- }
+ if (ret)
+ goto err_lane0;
}
- port->bonded = true;
- port->dual_link_port->bonded = true;
+ ret = tb_port_set_lane_bonding(port, true);
+ if (ret)
+ goto err_lane1;
return 0;
+
+err_lane1:
+ tb_port_set_link_width(port->dual_link_port, 1);
+err_lane0:
+ tb_port_set_link_width(port, 1);
+ return ret;
}
/**
@@ -1074,13 +1141,10 @@ int tb_port_lane_bonding_enable(struct tb_port *port)
*
* Disable bonding by setting the link width of the port and the
* other port in case of dual link port.
- *
*/
void tb_port_lane_bonding_disable(struct tb_port *port)
{
- port->dual_link_port->bonded = false;
- port->bonded = false;
-
+ tb_port_set_lane_bonding(port, false);
tb_port_set_link_width(port->dual_link_port, 1);
tb_port_set_link_width(port, 1);
}
@@ -1104,10 +1168,17 @@ int tb_port_wait_for_link_width(struct tb_port *port, int width,
do {
ret = tb_port_get_link_width(port);
- if (ret < 0)
- return ret;
- else if (ret == width)
+ if (ret < 0) {
+ /*
+ * Sometimes we get port locked error when
+ * polling the lanes so we can ignore it and
+ * retry.
+ */
+ if (ret != -EACCES)
+ return ret;
+ } else if (ret == width) {
return 0;
+ }
usleep_range(1000, 2000);
} while (ktime_before(ktime_get(), timeout));