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:
parent
3cc1c6de45
commit
a28ec0e165
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@ out:
|
|||
*/
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue