diff options
Diffstat (limited to 'drivers/thunderbolt/switch.c')
-rw-r--r-- | drivers/thunderbolt/switch.c | 594 |
1 files changed, 161 insertions, 433 deletions
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 51e86b5171c7..7ea63bb31714 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -26,10 +26,6 @@ struct nvm_auth_status { u32 status; }; -static bool clx_enabled = true; -module_param_named(clx, clx_enabled, bool, 0444); -MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)"); - /* * Hold NVM authentication failure status per switch This information * needs to stay around even when the switch gets power cycled so we @@ -727,7 +723,7 @@ static int tb_init_port(struct tb_port *port) * can be read from the path config space. Legacy * devices we use hard-coded value. */ - if (tb_switch_is_usb4(port->sw)) { + if (port->cap_usb4) { struct tb_regs_hop hop; if (!tb_port_read(port, &hop, TB_CFG_HOPS, 0, 2)) @@ -907,15 +903,23 @@ int tb_port_get_link_speed(struct tb_port *port) speed = (val & LANE_ADP_CS_1_CURRENT_SPEED_MASK) >> LANE_ADP_CS_1_CURRENT_SPEED_SHIFT; - return speed == LANE_ADP_CS_1_CURRENT_SPEED_GEN3 ? 20 : 10; + + switch (speed) { + case LANE_ADP_CS_1_CURRENT_SPEED_GEN4: + return 40; + case LANE_ADP_CS_1_CURRENT_SPEED_GEN3: + return 20; + default: + return 10; + } } /** * tb_port_get_link_width() - Get current link width * @port: Port to check (USB4 or CIO) * - * Returns link width. Return values can be 1 (Single-Lane), 2 (Dual-Lane) - * or negative errno in case of failure. + * Returns link width. Return the link width as encoded in &enum + * tb_link_width or negative errno in case of failure. */ int tb_port_get_link_width(struct tb_port *port) { @@ -930,11 +934,13 @@ int tb_port_get_link_width(struct tb_port *port) if (ret) return ret; + /* Matches the values in enum tb_link_width */ return (val & LANE_ADP_CS_1_CURRENT_WIDTH_MASK) >> LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT; } -static bool tb_port_is_width_supported(struct tb_port *port, int width) +static bool tb_port_is_width_supported(struct tb_port *port, + unsigned int width_mask) { u32 phy, widths; int ret; @@ -950,20 +956,25 @@ static bool tb_port_is_width_supported(struct tb_port *port, int width) widths = (phy & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >> LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT; - return !!(widths & width); + return widths & width_mask; +} + +static bool is_gen4_link(struct tb_port *port) +{ + return tb_port_get_link_speed(port) > 20; } /** * tb_port_set_link_width() - Set target link width of the lane adapter * @port: Lane adapter - * @width: Target link width (%1 or %2) + * @width: Target link width * * 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) +int tb_port_set_link_width(struct tb_port *port, enum tb_link_width width) { u32 val; int ret; @@ -978,11 +989,14 @@ int tb_port_set_link_width(struct tb_port *port, unsigned int width) val &= ~LANE_ADP_CS_1_TARGET_WIDTH_MASK; switch (width) { - case 1: + case TB_LINK_WIDTH_SINGLE: + /* Gen 4 link cannot be single */ + if (is_gen4_link(port)) + return -EOPNOTSUPP; val |= LANE_ADP_CS_1_TARGET_WIDTH_SINGLE << LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; break; - case 2: + case TB_LINK_WIDTH_DUAL: val |= LANE_ADP_CS_1_TARGET_WIDTH_DUAL << LANE_ADP_CS_1_TARGET_WIDTH_SHIFT; break; @@ -1004,12 +1018,9 @@ int tb_port_set_link_width(struct tb_port *port, unsigned int width) * 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) +static int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) { u32 val; int ret; @@ -1027,19 +1038,8 @@ int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) 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; + return tb_port_write(port, &val, TB_CFG_PORT, + port->cap_phy + LANE_ADP_CS_1, 1); } /** @@ -1056,36 +1056,52 @@ int tb_port_set_lane_bonding(struct tb_port *port, bool bonding) */ int tb_port_lane_bonding_enable(struct tb_port *port) { + enum tb_link_width width; int ret; /* * Enable lane bonding for both links if not already enabled by * for example the boot firmware. */ - ret = tb_port_get_link_width(port); - if (ret == 1) { - ret = tb_port_set_link_width(port, 2); + width = tb_port_get_link_width(port); + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_link_width(port, TB_LINK_WIDTH_DUAL); if (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); + width = tb_port_get_link_width(port->dual_link_port); + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_link_width(port->dual_link_port, + TB_LINK_WIDTH_DUAL); if (ret) goto err_lane0; } - ret = tb_port_set_lane_bonding(port, true); - if (ret) - goto err_lane1; + /* + * Only set bonding if the link was not already bonded. This + * avoids the lane adapter to re-enter bonding state. + */ + if (width == TB_LINK_WIDTH_SINGLE) { + ret = tb_port_set_lane_bonding(port, true); + if (ret) + goto err_lane1; + } + + /* + * When lane 0 bonding is set it will affect lane 1 too so + * update both. + */ + port->bonded = true; + port->dual_link_port->bonded = true; return 0; err_lane1: - tb_port_set_link_width(port->dual_link_port, 1); + tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE); err_lane0: - tb_port_set_link_width(port, 1); + tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE); + return ret; } @@ -1099,27 +1115,34 @@ err_lane0: void tb_port_lane_bonding_disable(struct tb_port *port) { tb_port_set_lane_bonding(port, false); - tb_port_set_link_width(port->dual_link_port, 1); - tb_port_set_link_width(port, 1); + tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE); + tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE); + port->dual_link_port->bonded = false; + port->bonded = false; } /** * tb_port_wait_for_link_width() - Wait until link reaches specific width * @port: Port to wait for - * @width: Expected link width (%1 or %2) + * @width_mask: Expected link width mask * @timeout_msec: Timeout in ms how long to wait * * Should be used after both ends of the link have been bonded (or * bonding has been disabled) to wait until the link actually reaches - * the expected state. Returns %-ETIMEDOUT if the @width was not reached - * within the given timeout, %0 if it did. + * the expected state. Returns %-ETIMEDOUT if the width was not reached + * within the given timeout, %0 if it did. Can be passed a mask of + * expected widths and succeeds if any of the widths is reached. */ -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) { ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec); int ret; + /* Gen 4 link does not support single lane */ + if ((width_mask & TB_LINK_WIDTH_SINGLE) && is_gen4_link(port)) + return -EOPNOTSUPP; + do { ret = tb_port_get_link_width(port); if (ret < 0) { @@ -1130,7 +1153,7 @@ int tb_port_wait_for_link_width(struct tb_port *port, int width, */ if (ret != -EACCES) return ret; - } else if (ret == width) { + } else if (ret & width_mask) { return 0; } @@ -1183,135 +1206,6 @@ int tb_port_update_credits(struct tb_port *port) return tb_port_do_update_credits(port->dual_link_port); } -static int __tb_port_pm_secondary_set(struct tb_port *port, bool secondary) -{ - u32 phy; - int ret; - - ret = tb_port_read(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return ret; - - if (secondary) - phy |= LANE_ADP_CS_1_PMS; - else - phy &= ~LANE_ADP_CS_1_PMS; - - return tb_port_write(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); -} - -static int tb_port_pm_secondary_enable(struct tb_port *port) -{ - return __tb_port_pm_secondary_set(port, true); -} - -static int tb_port_pm_secondary_disable(struct tb_port *port) -{ - return __tb_port_pm_secondary_set(port, false); -} - -/* Called for USB4 or Titan Ridge routers only */ -static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx_mask) -{ - u32 val, mask = 0; - bool ret; - - /* Don't enable CLx in case of two single-lane links */ - if (!port->bonded && port->dual_link_port) - return false; - - /* Don't enable CLx in case of inter-domain link */ - if (port->xdomain) - return false; - - if (tb_switch_is_usb4(port->sw)) { - if (!usb4_port_clx_supported(port)) - return false; - } else if (!tb_lc_is_clx_supported(port)) { - return false; - } - - if (clx_mask & TB_CL1) { - /* CL0s and CL1 are enabled and supported together */ - mask |= LANE_ADP_CS_0_CL0S_SUPPORT | LANE_ADP_CS_0_CL1_SUPPORT; - } - if (clx_mask & TB_CL2) - mask |= LANE_ADP_CS_0_CL2_SUPPORT; - - ret = tb_port_read(port, &val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_0, 1); - if (ret) - return false; - - return !!(val & mask); -} - -static int __tb_port_clx_set(struct tb_port *port, enum tb_clx clx, bool enable) -{ - u32 phy, mask; - int ret; - - /* CL0s and CL1 are enabled and supported together */ - if (clx == TB_CL1) - mask = LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; - else - /* For now we support only CL0s and CL1. Not CL2 */ - return -EOPNOTSUPP; - - ret = tb_port_read(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return ret; - - if (enable) - phy |= mask; - else - phy &= ~mask; - - return tb_port_write(port, &phy, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); -} - -static int tb_port_clx_disable(struct tb_port *port, enum tb_clx clx) -{ - return __tb_port_clx_set(port, clx, false); -} - -static int tb_port_clx_enable(struct tb_port *port, enum tb_clx clx) -{ - return __tb_port_clx_set(port, clx, true); -} - -/** - * tb_port_is_clx_enabled() - Is given CL state enabled - * @port: USB4 port to check - * @clx_mask: Mask of CL states to check - * - * Returns true if any of the given CL states is enabled for @port. - */ -bool tb_port_is_clx_enabled(struct tb_port *port, unsigned int clx_mask) -{ - u32 val, mask = 0; - int ret; - - if (!tb_port_clx_supported(port, clx_mask)) - return false; - - if (clx_mask & TB_CL1) - mask |= LANE_ADP_CS_1_CL0S_ENABLE | LANE_ADP_CS_1_CL1_ENABLE; - if (clx_mask & TB_CL2) - mask |= LANE_ADP_CS_1_CL2_ENABLE; - - ret = tb_port_read(port, &val, TB_CFG_PORT, - port->cap_phy + LANE_ADP_CS_1, 1); - if (ret) - return false; - - return !!(val & mask); -} - static int tb_port_start_lane_initialization(struct tb_port *port) { int ret; @@ -1911,20 +1805,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, - char *buf) +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct tb_switch *sw = tb_to_switch(dev); + unsigned int width; + + switch (sw->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", sw->link_width); + return sysfs_emit(buf, "%u\n", width); } +static DEVICE_ATTR(rx_lanes, 0444, rx_lanes_show, NULL); -/* - * Currently link has same amount of lanes both directions (1 or 2) but - * expose them separately to allow possible asymmetric links in the future. - */ -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_switch *sw = tb_to_switch(dev); + unsigned int width; + + switch (sw->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(tx_lanes, 0444, tx_lanes_show, NULL); static ssize_t nvm_authenticate_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -2189,8 +2120,9 @@ static int tb_switch_uevent(const struct device *dev, struct kobj_uevent_env *en const struct tb_switch *sw = tb_to_switch(dev); const char *type; - if (sw->config.thunderbolt_version == USB4_VERSION_1_0) { - if (add_uevent_var(env, "USB4_VERSION=1.0")) + if (tb_switch_is_usb4(sw)) { + if (add_uevent_var(env, "USB4_VERSION=%u.0", + usb4_switch_version(sw))) return -ENOMEM; } @@ -2498,9 +2430,13 @@ int tb_switch_configure(struct tb_switch *sw) /* * For USB4 devices, we need to program the CM version * accordingly so that it knows to expose all the - * additional capabilities. + * additional capabilities. Program it according to USB4 + * version to avoid changing existing (v1) routers behaviour. */ - sw->config.cmuv = USB4_VERSION_1_0; + if (usb4_switch_version(sw) < 2) + sw->config.cmuv = ROUTER_CS_4_CMUV_V1; + else + sw->config.cmuv = ROUTER_CS_4_CMUV_V2; sw->config.plug_events_delay = 0xa; /* Enumerate the switch */ @@ -2530,6 +2466,22 @@ int tb_switch_configure(struct tb_switch *sw) return tb_plug_events_active(sw, true); } +/** + * tb_switch_configuration_valid() - Set the tunneling configuration to be valid + * @sw: Router to configure + * + * Needs to be called before any tunnels can be setup through the + * router. Can be called to any router. + * + * Returns %0 in success and negative errno otherwise. + */ +int tb_switch_configuration_valid(struct tb_switch *sw) +{ + if (tb_switch_is_usb4(sw)) + return usb4_switch_configuration_valid(sw); + return 0; +} + static int tb_switch_set_uuid(struct tb_switch *sw) { bool uid = false; @@ -2754,9 +2706,9 @@ static int tb_switch_update_link_attributes(struct tb_switch *sw) */ int tb_switch_lane_bonding_enable(struct tb_switch *sw) { - struct tb_switch *parent = tb_to_switch(sw->dev.parent); struct tb_port *up, *down; u64 route = tb_route(sw); + unsigned int width_mask; int ret; if (!route) @@ -2766,10 +2718,10 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return 0; up = tb_upstream_port(sw); - down = tb_port_at(route, parent); + down = tb_switch_downstream_port(sw); - if (!tb_port_is_width_supported(up, 2) || - !tb_port_is_width_supported(down, 2)) + if (!tb_port_is_width_supported(up, TB_LINK_WIDTH_DUAL) || + !tb_port_is_width_supported(down, TB_LINK_WIDTH_DUAL)) return 0; ret = tb_port_lane_bonding_enable(up); @@ -2785,7 +2737,11 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) return ret; } - ret = tb_port_wait_for_link_width(down, 2, 100); + /* 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(down, width_mask, 100); if (ret) { tb_port_warn(down, "timeout enabling lane bonding\n"); return ret; @@ -2808,8 +2764,8 @@ int tb_switch_lane_bonding_enable(struct tb_switch *sw) */ void tb_switch_lane_bonding_disable(struct tb_switch *sw) { - struct tb_switch *parent = tb_to_switch(sw->dev.parent); struct tb_port *up, *down; + int ret; if (!tb_route(sw)) return; @@ -2818,7 +2774,7 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) if (!up->bonded) return; - down = tb_port_at(tb_route(sw), parent); + down = tb_switch_downstream_port(sw); tb_port_lane_bonding_disable(up); tb_port_lane_bonding_disable(down); @@ -2827,7 +2783,8 @@ void tb_switch_lane_bonding_disable(struct tb_switch *sw) * It is fine if we get other errors as the router might have * been unplugged. */ - if (tb_port_wait_for_link_width(down, 1, 100) == -ETIMEDOUT) + ret = tb_port_wait_for_link_width(down, TB_LINK_WIDTH_SINGLE, 100); + if (ret == -ETIMEDOUT) tb_sw_warn(sw, "timeout disabling lane bonding\n"); tb_port_update_credits(down); @@ -2994,6 +2951,10 @@ int tb_switch_add(struct tb_switch *sw) if (ret) return ret; + ret = tb_switch_clx_init(sw); + if (ret) + return ret; + ret = tb_switch_tmu_init(sw); if (ret) return ret; @@ -3246,13 +3207,8 @@ void tb_switch_suspend(struct tb_switch *sw, bool runtime) /* * Actually only needed for Titan Ridge but for simplicity can be * done for USB4 device too as CLx is re-enabled at resume. - * CL0s and CL1 are enabled and supported together. */ - if (tb_switch_is_clx_enabled(sw, TB_CL1)) { - if (tb_switch_disable_clx(sw, TB_CL1)) - tb_sw_warn(sw, "failed to disable %s on upstream port\n", - tb_switch_clx_name(TB_CL1)); - } + tb_switch_clx_disable(sw); err = tb_plug_events_active(sw, false); if (err) @@ -3474,234 +3430,6 @@ struct tb_port *tb_switch_find_port(struct tb_switch *sw, return NULL; } -static int tb_switch_pm_secondary_resolve(struct tb_switch *sw) -{ - struct tb_switch *parent = tb_switch_parent(sw); - struct tb_port *up, *down; - int ret; - - if (!tb_route(sw)) - return 0; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_port_pm_secondary_enable(up); - if (ret) - return ret; - - return tb_port_pm_secondary_disable(down); -} - -static int __tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *parent = tb_switch_parent(sw); - bool up_clx_support, down_clx_support; - struct tb_port *up, *down; - int ret; - - if (!tb_switch_is_clx_supported(sw)) - return 0; - - /* - * Enable CLx for host router's downstream port as part of the - * downstream router enabling procedure. - */ - if (!tb_route(sw)) - return 0; - - /* Enable CLx only for first hop router (depth = 1) */ - if (tb_route(parent)) - return 0; - - ret = tb_switch_pm_secondary_resolve(sw); - if (ret) - return ret; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - - up_clx_support = tb_port_clx_supported(up, clx); - down_clx_support = tb_port_clx_supported(down, clx); - - tb_port_dbg(up, "%s %ssupported\n", tb_switch_clx_name(clx), - up_clx_support ? "" : "not "); - tb_port_dbg(down, "%s %ssupported\n", tb_switch_clx_name(clx), - down_clx_support ? "" : "not "); - - if (!up_clx_support || !down_clx_support) - return -EOPNOTSUPP; - - ret = tb_port_clx_enable(up, clx); - if (ret) - return ret; - - ret = tb_port_clx_enable(down, clx); - if (ret) { - tb_port_clx_disable(up, clx); - return ret; - } - - ret = tb_switch_mask_clx_objections(sw); - if (ret) { - tb_port_clx_disable(up, clx); - tb_port_clx_disable(down, clx); - return ret; - } - - sw->clx = clx; - - tb_port_dbg(up, "%s enabled\n", tb_switch_clx_name(clx)); - return 0; -} - -/** - * tb_switch_enable_clx() - Enable CLx on upstream port of specified router - * @sw: Router to enable CLx for - * @clx: The CLx state to enable - * - * Enable CLx state only for first hop router. That is the most common - * use-case, that is intended for better thermal management, and so helps - * to improve performance. CLx is enabled only if both sides of the link - * support CLx, and if both sides of the link are not configured as two - * single lane links and only if the link is not inter-domain link. The - * complete set of conditions is described in CM Guide 1.0 section 8.1. - * - * Return: Returns 0 on success or an error code on failure. - */ -int tb_switch_enable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *root_sw = sw->tb->root_switch; - - if (!clx_enabled) - return 0; - - /* - * CLx is not enabled and validated on Intel USB4 platforms before - * Alder Lake. - */ - if (root_sw->generation < 4 || tb_switch_is_tiger_lake(root_sw)) - return 0; - - switch (clx) { - case TB_CL1: - /* CL0s and CL1 are enabled and supported together */ - return __tb_switch_enable_clx(sw, clx); - - default: - return -EOPNOTSUPP; - } -} - -static int __tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - struct tb_switch *parent = tb_switch_parent(sw); - struct tb_port *up, *down; - int ret; - - if (!tb_switch_is_clx_supported(sw)) - return 0; - - /* - * Disable CLx for host router's downstream port as part of the - * downstream router enabling procedure. - */ - if (!tb_route(sw)) - return 0; - - /* Disable CLx only for first hop router (depth = 1) */ - if (tb_route(parent)) - return 0; - - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); - ret = tb_port_clx_disable(up, clx); - if (ret) - return ret; - - ret = tb_port_clx_disable(down, clx); - if (ret) - return ret; - - sw->clx = TB_CLX_DISABLE; - - tb_port_dbg(up, "%s disabled\n", tb_switch_clx_name(clx)); - return 0; -} - -/** - * tb_switch_disable_clx() - Disable CLx on upstream port of specified router - * @sw: Router to disable CLx for - * @clx: The CLx state to disable - * - * Return: Returns 0 on success or an error code on failure. - */ -int tb_switch_disable_clx(struct tb_switch *sw, enum tb_clx clx) -{ - if (!clx_enabled) - return 0; - - switch (clx) { - case TB_CL1: - /* CL0s and CL1 are enabled and supported together */ - return __tb_switch_disable_clx(sw, clx); - - default: - return -EOPNOTSUPP; - } -} - -/** - * tb_switch_mask_clx_objections() - Mask CLx objections for a router - * @sw: Router to mask objections for - * - * Mask the objections coming from the second depth routers in order to - * stop these objections from interfering with the CLx states of the first - * depth link. - */ -int tb_switch_mask_clx_objections(struct tb_switch *sw) -{ - int up_port = sw->config.upstream_port_number; - u32 offset, val[2], mask_obj, unmask_obj; - int ret, i; - - /* Only Titan Ridge of pre-USB4 devices support CLx states */ - if (!tb_switch_is_titan_ridge(sw)) - return 0; - - if (!tb_route(sw)) - return 0; - - /* - * In Titan Ridge there are only 2 dual-lane Thunderbolt ports: - * Port A consists of lane adapters 1,2 and - * Port B consists of lane adapters 3,4 - * If upstream port is A, (lanes are 1,2), we mask objections from - * port B (lanes 3,4) and unmask objections from Port A and vice-versa. - */ - if (up_port == 1) { - mask_obj = TB_LOW_PWR_C0_PORT_B_MASK; - unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK; - offset = TB_LOW_PWR_C1_CL1; - } else { - mask_obj = TB_LOW_PWR_C1_PORT_A_MASK; - unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK; - offset = TB_LOW_PWR_C3_CL1; - } - - ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, - sw->cap_lp + offset, ARRAY_SIZE(val)); - if (ret) - return ret; - - for (i = 0; i < ARRAY_SIZE(val); i++) { - val[i] |= mask_obj; - val[i] &= ~unmask_obj; - } - - return tb_sw_write(sw, &val, TB_CFG_SWITCH, - sw->cap_lp + offset, ARRAY_SIZE(val)); -} - /* * Can be used for read/write a specified PCIe bridge for any Thunderbolt 3 * device. For now used only for Titan Ridge. |