Commit e111fb92 authored by Gil Fine's avatar Gil Fine Committed by Mika Westerberg

thunderbolt: Add support for USB4 v2 80 Gb/s link

USB4 v2 bumps the per-lane speed up to 40 Gb/s. Also the lanes are
always bonded which gives 80 Gb/s symmetric link (and 120/40 Gb/s
asymmetric). This updates the speed and width of routers and XDomain
connections to support the Gen 4 link. For now we keep the link as is
even if it is already asymmetric.

While there make tb_port_set_link_width() static.
Signed-off-by: default avatarGil Fine <gil.fine@intel.com>
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
parent 6e21007d
......@@ -412,6 +412,7 @@ static void speed_get(const struct dma_test *dt, u64 *val)
static int speed_validate(u64 val)
{
switch (val) {
case 40:
case 20:
case 10:
case 0:
......@@ -489,8 +490,11 @@ static void dma_test_check_errors(struct dma_test *dt, int ret)
if (!dt->error_code) {
if (dt->link_speed && dt->xd->link_speed != dt->link_speed) {
dt->error_code = DMA_TEST_SPEED_ERROR;
} else if (dt->link_width &&
dt->xd->link_width != dt->link_width) {
} else if (dt->link_width) {
const struct tb_xdomain *xd = dt->xd;
if ((dt->link_width == 1 && xd->link_width != TB_LINK_WIDTH_SINGLE) ||
(dt->link_width == 2 && xd->link_width < TB_LINK_WIDTH_DUAL))
dt->error_code = DMA_TEST_WIDTH_ERROR;
} else if (dt->packets_to_send != dt->packets_sent ||
dt->packets_to_receive != dt->packets_received ||
......
......@@ -850,7 +850,8 @@ icm_fr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr)
sw->security_level = security_level;
sw->boot = boot;
sw->link_speed = speed_gen3 ? 20 : 10;
sw->link_width = dual_lane ? 2 : 1;
sw->link_width = dual_lane ? TB_LINK_WIDTH_DUAL :
TB_LINK_WIDTH_SINGLE;
sw->rpm = intel_vss_is_rtd3(pkg->ep_name, sizeof(pkg->ep_name));
if (add_switch(parent_sw, sw))
......@@ -1272,7 +1273,8 @@ __icm_tr_device_connected(struct tb *tb, const struct icm_pkg_header *hdr,
sw->security_level = security_level;
sw->boot = boot;
sw->link_speed = speed_gen3 ? 20 : 10;
sw->link_width = dual_lane ? 2 : 1;
sw->link_width = dual_lane ? TB_LINK_WIDTH_DUAL :
TB_LINK_WIDTH_SINGLE;
sw->rpm = force_rtd3;
if (!sw->rpm)
sw->rpm = intel_vss_is_rtd3(pkg->ep_name,
......
This diff is collapsed.
......@@ -570,7 +570,8 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
usb3_consumed_down = 0;
}
*available_up = *available_down = 40000;
/* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
*available_up = *available_down = 120000;
/* Find the minimum available bandwidth over all links */
tb_for_each_port_on_path(src_port, dst_port, port) {
......@@ -581,18 +582,45 @@ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
if (tb_is_upstream_port(port)) {
link_speed = port->sw->link_speed;
/*
* sw->link_width is from upstream perspective
* so we use the opposite for downstream of the
* host router.
*/
if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
} else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
up_bw = link_speed * 1 * 1000;
down_bw = link_speed * 3 * 1000;
} else {
up_bw = link_speed * port->sw->link_width * 1000;
down_bw = up_bw;
}
} else {
link_speed = tb_port_get_link_speed(port);
if (link_speed < 0)
return link_speed;
}
link_width = port->bonded ? 2 : 1;
link_width = tb_port_get_link_width(port);
if (link_width < 0)
return link_width;
if (link_width == TB_LINK_WIDTH_ASYM_TX) {
up_bw = link_speed * 1 * 1000;
down_bw = link_speed * 3 * 1000;
} else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
up_bw = link_speed * 3 * 1000;
down_bw = link_speed * 1 * 1000;
} else {
up_bw = link_speed * link_width * 1000;
down_bw = up_bw;
}
}
up_bw = link_speed * link_width * 1000; /* Mb/s */
/* Leave 10% guard band */
up_bw -= up_bw / 10;
down_bw = up_bw;
down_bw -= down_bw / 10;
tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw,
down_bw);
......
......@@ -135,7 +135,7 @@ struct tb_switch_tmu {
* @vendor_name: Name of the vendor (or %NULL if not known)
* @device_name: Name of the device (or %NULL if not known)
* @link_speed: Speed of the link in Gb/s
* @link_width: Width of the link (1 or 2)
* @link_width: Width of the upstream facing link
* @link_usb4: Upstream link is USB4
* @generation: Switch Thunderbolt generation
* @cap_plug_events: Offset to the plug events capability (%0 if not found)
......@@ -173,6 +173,11 @@ struct tb_switch_tmu {
* switches) you need to have domain lock held.
*
* In USB4 terminology this structure represents a router.
*
* Note @link_width is not the same as whether link is bonded or not.
* For Gen 4 links the link is also bonded when it is asymmetric. The
* correct way to find out whether the link is bonded or not is to look
* @bonded field of the upstream port.
*/
struct tb_switch {
struct device dev;
......@@ -188,7 +193,7 @@ struct tb_switch {
const char *vendor_name;
const char *device_name;
unsigned int link_speed;
unsigned int link_width;
enum tb_link_width link_width;
bool link_usb4;
unsigned int generation;
int cap_plug_events;
......@@ -1050,11 +1055,10 @@ static inline bool tb_port_use_credit_allocation(const struct tb_port *port)
int tb_port_get_link_speed(struct tb_port *port);
int tb_port_get_link_width(struct tb_port *port);
int tb_port_set_link_width(struct tb_port *port, unsigned int width);
int tb_port_set_lane_bonding(struct tb_port *port, bool bonding);
int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width);
int tb_port_lane_bonding_enable(struct tb_port *port);
void tb_port_lane_bonding_disable(struct tb_port *port);
int tb_port_wait_for_link_width(struct tb_port *port, int width,
int tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask,
int timeout_msec);
int tb_port_update_credits(struct tb_port *port);
......
......@@ -346,6 +346,7 @@ struct tb_regs_port_header {
#define LANE_ADP_CS_1_CURRENT_SPEED_SHIFT 16
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN2 0x8
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN3 0x4
#define LANE_ADP_CS_1_CURRENT_SPEED_GEN4 0x2
#define LANE_ADP_CS_1_CURRENT_WIDTH_MASK GENMASK(25, 20)
#define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT 20
#define LANE_ADP_CS_1_PMS BIT(30)
......
......@@ -1290,13 +1290,16 @@ static int tb_xdomain_link_state_change(struct tb_xdomain *xd,
static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd)
{
unsigned int width, width_mask;
struct tb_port *port;
int ret, width;
int ret;
if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_SINGLE) {
width = 1;
width = TB_LINK_WIDTH_SINGLE;
width_mask = width;
} else if (xd->target_link_width == LANE_ADP_CS_1_TARGET_WIDTH_DUAL) {
width = 2;
width = TB_LINK_WIDTH_DUAL;
width_mask = width | TB_LINK_WIDTH_ASYM_TX | TB_LINK_WIDTH_ASYM_RX;
} else {
if (xd->state_retries-- > 0) {
dev_dbg(&xd->dev,
......@@ -1328,15 +1331,16 @@ static int tb_xdomain_bond_lanes_uuid_high(struct tb_xdomain *xd)
return ret;
}
ret = tb_port_wait_for_link_width(port, width, XDOMAIN_BONDING_TIMEOUT);
ret = tb_port_wait_for_link_width(port, width_mask,
XDOMAIN_BONDING_TIMEOUT);
if (ret) {
dev_warn(&xd->dev, "error waiting for link width to become %d\n",
width);
width_mask);
return ret;
}
port->bonded = width == 2;
port->dual_link_port->bonded = width == 2;
port->bonded = width > TB_LINK_WIDTH_SINGLE;
port->dual_link_port->bonded = width > TB_LINK_WIDTH_SINGLE;
tb_port_update_credits(port);
tb_xdomain_update_link_attributes(xd);
......@@ -1735,16 +1739,57 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR(rx_speed, 0444, speed_show, NULL);
static DEVICE_ATTR(tx_speed, 0444, speed_show, NULL);
static ssize_t lanes_show(struct device *dev, struct device_attribute *attr,
static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev);
unsigned int width;
return sysfs_emit(buf, "%u\n", xd->link_width);
switch (xd->link_width) {
case TB_LINK_WIDTH_SINGLE:
case TB_LINK_WIDTH_ASYM_RX:
width = 1;
break;
case TB_LINK_WIDTH_DUAL:
width = 2;
break;
case TB_LINK_WIDTH_ASYM_TX:
width = 3;
break;
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
return sysfs_emit(buf, "%u\n", width);
}
static DEVICE_ATTR(rx_lanes, 0444, rx_lanes_show, NULL);
static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL);
static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL);
static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tb_xdomain *xd = container_of(dev, struct tb_xdomain, dev);
unsigned int width;
switch (xd->link_width) {
case TB_LINK_WIDTH_SINGLE:
case TB_LINK_WIDTH_ASYM_TX:
width = 1;
break;
case TB_LINK_WIDTH_DUAL:
width = 2;
break;
case TB_LINK_WIDTH_ASYM_RX:
width = 3;
break;
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
return sysfs_emit(buf, "%u\n", width);
}
static DEVICE_ATTR(tx_lanes, 0444, tx_lanes_show, NULL);
static struct attribute *xdomain_attrs[] = {
&dev_attr_device.attr,
......@@ -1974,6 +2019,7 @@ void tb_xdomain_remove(struct tb_xdomain *xd)
*/
int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd)
{
unsigned int width_mask;
struct tb_port *port;
int ret;
......@@ -1997,7 +2043,12 @@ int tb_xdomain_lane_bonding_enable(struct tb_xdomain *xd)
return ret;
}
ret = tb_port_wait_for_link_width(port, 2, XDOMAIN_BONDING_TIMEOUT);
/* Any of the widths are all bonded */
width_mask = TB_LINK_WIDTH_DUAL | TB_LINK_WIDTH_ASYM_TX |
TB_LINK_WIDTH_ASYM_RX;
ret = tb_port_wait_for_link_width(port, width_mask,
XDOMAIN_BONDING_TIMEOUT);
if (ret) {
tb_port_warn(port, "failed to enable lane bonding\n");
return ret;
......@@ -2024,8 +2075,11 @@ void tb_xdomain_lane_bonding_disable(struct tb_xdomain *xd)
port = tb_xdomain_downstream_port(xd);
if (port->dual_link_port) {
int ret;
tb_port_lane_bonding_disable(port);
if (tb_port_wait_for_link_width(port, 1, 100) == -ETIMEDOUT)
ret = tb_port_wait_for_link_width(port, TB_LINK_WIDTH_SINGLE, 100);
if (ret == -ETIMEDOUT)
tb_port_warn(port, "timeout disabling lane bonding\n");
tb_port_disable(port->dual_link_port);
tb_port_update_credits(port);
......
......@@ -171,6 +171,20 @@ struct tb_property *tb_property_get_next(struct tb_property_dir *dir,
int tb_register_property_dir(const char *key, struct tb_property_dir *dir);
void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir);
/**
* enum tb_link_width - Thunderbolt/USB4 link width
* @TB_LINK_WIDTH_SINGLE: Single lane link
* @TB_LINK_WIDTH_DUAL: Dual lane symmetric link
* @TB_LINK_WIDTH_ASYM_TX: Dual lane asymmetric Gen 4 link with 3 trasmitters
* @TB_LINK_WIDTH_ASYM_RX: Dual lane asymmetric Gen 4 link with 3 receivers
*/
enum tb_link_width {
TB_LINK_WIDTH_SINGLE = BIT(0),
TB_LINK_WIDTH_DUAL = BIT(1),
TB_LINK_WIDTH_ASYM_TX = BIT(2),
TB_LINK_WIDTH_ASYM_RX = BIT(3),
};
/**
* struct tb_xdomain - Cross-domain (XDomain) connection
* @dev: XDomain device
......@@ -186,7 +200,7 @@ void tb_unregister_property_dir(const char *key, struct tb_property_dir *dir);
* @vendor_name: Name of the vendor (or %NULL if not known)
* @device_name: Name of the device (or %NULL if not known)
* @link_speed: Speed of the link in Gb/s
* @link_width: Width of the link (1 or 2)
* @link_width: Width of the downstream facing link
* @link_usb4: Downstream link is USB4
* @is_unplugged: The XDomain is unplugged
* @needs_uuid: If the XDomain does not have @remote_uuid it will be
......@@ -234,7 +248,7 @@ struct tb_xdomain {
const char *vendor_name;
const char *device_name;
unsigned int link_speed;
unsigned int link_width;
enum tb_link_width link_width;
bool link_usb4;
bool is_unplugged;
bool needs_uuid;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment