thunderbolt: Add TMU uni-directional mode

Up until Titan Ridge (Thunderbolt 3) device routers only supported
bi-directional mode. In this patch we add to TMU a uni-directional mode.
The uni-directional mode is needed for enabling of low power state of
the link (CLx).

Signed-off-by: Gil Fine <gil.fine@intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
Gil Fine 2021-12-17 03:16:38 +02:00 committed by Mika Westerberg
parent 3cc1c6de45
commit a28ec0e165
4 changed files with 264 additions and 52 deletions

View file

@ -221,7 +221,7 @@ static int tb_enable_tmu(struct tb_switch *sw)
int ret;
/* If it is already enabled in correct mode, don't touch it */
if (tb_switch_tmu_is_enabled(sw))
if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request))
return 0;
ret = tb_switch_tmu_disable(sw);
@ -669,6 +669,7 @@ static void tb_scan_port(struct tb_port *port)
tb_switch_lane_bonding_enable(sw);
/* Set the link configured */
tb_switch_configure_link(sw);
tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false);
if (tb_enable_tmu(sw))
tb_sw_warn(sw, "failed to enable TMU\n");
@ -1375,6 +1376,7 @@ static int tb_start(struct tb *tb)
return ret;
}
tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_RATE_HIFI, false);
/* Enable TMU if it is off */
tb_switch_tmu_enable(tb->root_switch);
/* Full scan to discover devices added before the driver was loaded. */
@ -1418,6 +1420,11 @@ static void tb_restore_children(struct tb_switch *sw)
if (sw->is_unplugged)
return;
/*
* tb_switch_tmu_configure() was already called when the switch was
* added before entering system sleep or runtime suspend,
* so no need to call it again before enabling TMU.
*/
if (tb_enable_tmu(sw))
tb_sw_warn(sw, "failed to restore TMU configuration\n");

View file

@ -89,15 +89,24 @@ enum tb_switch_tmu_rate {
* @cap: Offset to the TMU capability (%0 if not found)
* @has_ucap: Does the switch support uni-directional mode
* @rate: TMU refresh rate related to upstream switch. In case of root
* switch this holds the domain rate.
* switch this holds the domain rate. Reflects the HW setting.
* @unidirectional: Is the TMU in uni-directional or bi-directional mode
* related to upstream switch. Don't case for root switch.
* related to upstream switch. Don't care for root switch.
* Reflects the HW setting.
* @unidirectional_request: Is the new TMU mode: uni-directional or bi-directional
* that is requested to be set. Related to upstream switch.
* Don't care for root switch.
* @rate_request: TMU new refresh rate related to upstream switch that is
* requested to be set. In case of root switch, this holds
* the new domain rate that is requested to be set.
*/
struct tb_switch_tmu {
int cap;
bool has_ucap;
enum tb_switch_tmu_rate rate;
bool unidirectional;
bool unidirectional_request;
enum tb_switch_tmu_rate rate_request;
};
/**
@ -891,11 +900,22 @@ int tb_switch_tmu_init(struct tb_switch *sw);
int tb_switch_tmu_post_time(struct tb_switch *sw);
int tb_switch_tmu_disable(struct tb_switch *sw);
int tb_switch_tmu_enable(struct tb_switch *sw);
static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
void tb_switch_tmu_configure(struct tb_switch *sw,
enum tb_switch_tmu_rate rate,
bool unidirectional);
/**
* tb_switch_tmu_hifi_is_enabled() - Checks if the specified TMU mode is enabled
* @sw: Router whose TMU mode to check
* @unidirectional: If uni-directional (bi-directional otherwise)
*
* Return true if hardware TMU configuration matches the one passed in
* as parameter. That is HiFi and either uni-directional or bi-directional.
*/
static inline bool tb_switch_tmu_hifi_is_enabled(const struct tb_switch *sw,
bool unidirectional)
{
return sw->tmu.rate == TB_SWITCH_TMU_RATE_HIFI &&
!sw->tmu.unidirectional;
sw->tmu.unidirectional == unidirectional;
}
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);

View file

@ -246,6 +246,7 @@ enum usb4_switch_op {
#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT 16
#define TMU_RTR_CS_22 0x16
#define TMU_RTR_CS_24 0x18
#define TMU_RTR_CS_25 0x19
enum tb_port_type {
TB_TYPE_INACTIVE = 0x000000,
@ -305,6 +306,8 @@ struct tb_regs_port_header {
/* TMU adapter registers */
#define TMU_ADP_CS_3 0x03
#define TMU_ADP_CS_3_UDM BIT(29)
#define TMU_ADP_CS_6 0x06
#define TMU_ADP_CS_6_DTS BIT(1)
/* Lane adapter registers */
#define LANE_ADP_CS_0 0x00

View file

@ -115,6 +115,11 @@ static inline int tb_port_tmu_unidirectional_disable(struct tb_port *port)
return tb_port_tmu_set_unidirectional(port, false);
}
static inline int tb_port_tmu_unidirectional_enable(struct tb_port *port)
{
return tb_port_tmu_set_unidirectional(port, true);
}
static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
{
int ret;
@ -128,6 +133,23 @@ static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
return val & TMU_ADP_CS_3_UDM;
}
static int tb_port_tmu_time_sync(struct tb_port *port, bool time_sync)
{
u32 val = time_sync ? TMU_ADP_CS_6_DTS : 0;
return tb_port_tmu_write(port, TMU_ADP_CS_6, TMU_ADP_CS_6_DTS, val);
}
static int tb_port_tmu_time_sync_disable(struct tb_port *port)
{
return tb_port_tmu_time_sync(port, true);
}
static int tb_port_tmu_time_sync_enable(struct tb_port *port)
{
return tb_port_tmu_time_sync(port, false);
}
static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set)
{
int ret;
@ -207,7 +229,8 @@ int tb_switch_tmu_init(struct tb_switch *sw)
*/
int tb_switch_tmu_post_time(struct tb_switch *sw)
{
unsigned int post_local_time_offset, post_time_offset;
unsigned int post_time_high_offset, post_time_high = 0;
unsigned int post_local_time_offset, post_time_offset;
struct tb_switch *root_switch = sw->tb->root_switch;
u64 hi, mid, lo, local_time, post_time;
int i, ret, retries = 100;
@ -247,6 +270,7 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)
post_local_time_offset = sw->tmu.cap + TMU_RTR_CS_22;
post_time_offset = sw->tmu.cap + TMU_RTR_CS_24;
post_time_high_offset = sw->tmu.cap + TMU_RTR_CS_25;
/*
* Write the Grandmaster time to the Post Local Time registers
@ -258,17 +282,24 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)
goto out;
/*
* Have the new switch update its local time (by writing 1 to
* the post_time registers) and wait for the completion of the
* same (post_time register becomes 0). This means the time has
* been converged properly.
* Have the new switch update its local time by:
* 1) writing 0x1 to the Post Time Low register and 0xffffffff to
* Post Time High register.
* 2) write 0 to Post Time High register and then wait for
* the completion of the post_time register becomes 0.
* This means the time has been converged properly.
*/
post_time = 1;
post_time = 0xffffffff00000001ULL;
ret = tb_sw_write(sw, &post_time, TB_CFG_SWITCH, post_time_offset, 2);
if (ret)
goto out;
ret = tb_sw_write(sw, &post_time_high, TB_CFG_SWITCH,
post_time_high_offset, 1);
if (ret)
goto out;
do {
usleep_range(5, 10);
ret = tb_sw_read(sw, &post_time, TB_CFG_SWITCH,
@ -297,8 +328,6 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)
*/
int tb_switch_tmu_disable(struct tb_switch *sw)
{
int ret;
if (!tb_switch_is_usb4(sw))
return 0;
@ -306,21 +335,42 @@ int tb_switch_tmu_disable(struct tb_switch *sw)
if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF)
return 0;
if (sw->tmu.unidirectional) {
if (tb_route(sw)) {
bool unidirectional = tb_switch_tmu_hifi_is_enabled(sw, true);
struct tb_switch *parent = tb_switch_parent(sw);
struct tb_port *up, *down;
struct tb_port *down, *up;
int ret;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), parent);
up = tb_upstream_port(sw);
/*
* In case of uni-directional time sync, TMU handshake is
* initiated by upstream router. In case of bi-directional
* time sync, TMU handshake is initiated by downstream router.
* Therefore, we change the rate to off in the respective
* router.
*/
if (unidirectional)
tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF);
else
tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
/* The switch may be unplugged so ignore any errors */
tb_port_tmu_unidirectional_disable(up);
ret = tb_port_tmu_unidirectional_disable(down);
tb_port_tmu_time_sync_disable(up);
ret = tb_port_tmu_time_sync_disable(down);
if (ret)
return ret;
}
tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
if (unidirectional) {
/* The switch may be unplugged so ignore any errors */
tb_port_tmu_unidirectional_disable(up);
ret = tb_port_tmu_unidirectional_disable(down);
if (ret)
return ret;
}
} else {
tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
}
sw->tmu.unidirectional = false;
sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF;
@ -329,55 +379,187 @@ int tb_switch_tmu_disable(struct tb_switch *sw)
return 0;
}
/**
* tb_switch_tmu_enable() - Enable TMU on a switch
* @sw: Switch whose TMU to enable
*
* Enables TMU of a switch to be in bi-directional, HiFi mode. In this mode
* all tunneling should work.
*/
int tb_switch_tmu_enable(struct tb_switch *sw)
static void __tb_switch_tmu_off(struct tb_switch *sw, bool unidirectional)
{
struct tb_switch *parent = tb_switch_parent(sw);
struct tb_port *down, *up;
down = tb_port_at(tb_route(sw), parent);
up = tb_upstream_port(sw);
/*
* In case of any failure in one of the steps when setting
* bi-directional or uni-directional TMU mode, get back to the TMU
* configurations in off mode. In case of additional failures in
* the functions below, ignore them since the caller shall already
* report a failure.
*/
tb_port_tmu_time_sync_disable(down);
tb_port_tmu_time_sync_disable(up);
if (unidirectional)
tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF);
else
tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
tb_port_tmu_unidirectional_disable(down);
tb_port_tmu_unidirectional_disable(up);
}
/*
* This function is called when the previous TMU mode was
* TB_SWITCH_TMU_RATE_OFF.
*/
static int __tb_switch_tmu_enable_bidirectional(struct tb_switch *sw)
{
struct tb_switch *parent = tb_switch_parent(sw);
struct tb_port *up, *down;
int ret;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), parent);
ret = tb_port_tmu_unidirectional_disable(up);
if (ret)
return ret;
ret = tb_port_tmu_unidirectional_disable(down);
if (ret)
goto out;
ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
if (ret)
goto out;
ret = tb_port_tmu_time_sync_enable(up);
if (ret)
goto out;
ret = tb_port_tmu_time_sync_enable(down);
if (ret)
goto out;
return 0;
out:
__tb_switch_tmu_off(sw, false);
return ret;
}
/*
* This function is called when the previous TMU mode was
* TB_SWITCH_TMU_RATE_OFF.
*/
static int __tb_switch_tmu_enable_unidirectional(struct tb_switch *sw)
{
struct tb_switch *parent = tb_switch_parent(sw);
struct tb_port *up, *down;
int ret;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), parent);
ret = tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_HIFI);
if (ret)
return ret;
ret = tb_port_tmu_unidirectional_enable(up);
if (ret)
goto out;
ret = tb_port_tmu_time_sync_enable(up);
if (ret)
goto out;
ret = tb_port_tmu_unidirectional_enable(down);
if (ret)
goto out;
ret = tb_port_tmu_time_sync_enable(down);
if (ret)
goto out;
return 0;
out:
__tb_switch_tmu_off(sw, true);
return ret;
}
static int tb_switch_tmu_hifi_enable(struct tb_switch *sw)
{
bool unidirectional = sw->tmu.unidirectional_request;
int ret;
if (unidirectional && !sw->tmu.has_ucap)
return -EOPNOTSUPP;
if (!tb_switch_is_usb4(sw))
return 0;
if (tb_switch_tmu_is_enabled(sw))
if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request))
return 0;
ret = tb_switch_tmu_set_time_disruption(sw, true);
if (ret)
return ret;
/* Change mode to bi-directional */
if (tb_route(sw) && sw->tmu.unidirectional) {
struct tb_switch *parent = tb_switch_parent(sw);
struct tb_port *up, *down;
up = tb_upstream_port(sw);
down = tb_port_at(tb_route(sw), parent);
ret = tb_port_tmu_unidirectional_disable(down);
if (ret)
return ret;
ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
if (ret)
return ret;
ret = tb_port_tmu_unidirectional_disable(up);
if (ret)
return ret;
if (tb_route(sw)) {
/* The used mode changes are from OFF to HiFi-Uni/HiFi-BiDir */
if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) {
if (unidirectional)
ret = __tb_switch_tmu_enable_unidirectional(sw);
else
ret = __tb_switch_tmu_enable_bidirectional(sw);
if (ret)
return ret;
}
sw->tmu.unidirectional = unidirectional;
} else {
/*
* Host router port configurations are written as
* part of configurations for downstream port of the parent
* of the child node - see above.
* Here only the host router' rate configuration is written.
*/
ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
if (ret)
return ret;
}
sw->tmu.unidirectional = false;
sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI;
tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));
tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));
return tb_switch_tmu_set_time_disruption(sw, false);
}
/**
* tb_switch_tmu_enable() - Enable TMU on a router
* @sw: Router whose TMU to enable
*
* Enables TMU of a router to be in uni-directional or bi-directional HiFi mode.
* Calling tb_switch_tmu_configure() is required before calling this function,
* to select the mode HiFi and directionality (uni-directional/bi-directional).
* In both modes all tunneling should work. Uni-directional mode is required for
* CLx (Link Low-Power) to work.
*/
int tb_switch_tmu_enable(struct tb_switch *sw)
{
if (sw->tmu.rate_request == TB_SWITCH_TMU_RATE_NORMAL)
return -EOPNOTSUPP;
return tb_switch_tmu_hifi_enable(sw);
}
/**
* tb_switch_tmu_configure() - Configure the TMU rate and directionality
* @sw: Router whose mode to change
* @rate: Rate to configure Off/LowRes/HiFi
* @unidirectional: If uni-directional (bi-directional otherwise)
*
* Selects the rate of the TMU and directionality (uni-directional or
* bi-directional). Must be called before tb_switch_tmu_enable().
*/
void tb_switch_tmu_configure(struct tb_switch *sw,
enum tb_switch_tmu_rate rate, bool unidirectional)
{
sw->tmu.unidirectional_request = unidirectional;
sw->tmu.rate_request = rate;
}