diff options
author | Gil Fine <gil.fine@intel.com> | 2021-12-17 04:16:38 +0300 |
---|---|---|
committer | Mika Westerberg <mika.westerberg@linux.intel.com> | 2021-12-22 17:13:14 +0300 |
commit | a28ec0e165ba2f90568828a2578aaa8540e13bc5 (patch) | |
tree | 1154cace1b1c8eb49aff7ea6affbc25dd4fd2416 /drivers/thunderbolt/tmu.c | |
parent | 3cc1c6de458e0e58c413c3c35802ca96e55bbdbe (diff) | |
download | linux-a28ec0e165ba2f90568828a2578aaa8540e13bc5.tar.xz |
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>
Diffstat (limited to 'drivers/thunderbolt/tmu.c')
-rw-r--r-- | drivers/thunderbolt/tmu.c | 274 |
1 files changed, 228 insertions, 46 deletions
diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c index 039c42a06000..37048dab5b56 100644 --- a/drivers/thunderbolt/tmu.c +++ b/drivers/thunderbolt/tmu.c @@ -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); - - /* The switch may be unplugged so ignore any errors */ - tb_port_tmu_unidirectional_disable(up); - ret = tb_port_tmu_unidirectional_disable(down); + 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); + + 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. +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. */ -int tb_switch_tmu_enable(struct tb_switch *sw) +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; - if (!tb_switch_is_usb4(sw)) - return 0; + up = tb_upstream_port(sw); + down = tb_port_at(tb_route(sw), parent); - if (tb_switch_tmu_is_enabled(sw)) - return 0; + ret = tb_port_tmu_unidirectional_disable(up); + if (ret) + return ret; - ret = tb_switch_tmu_set_time_disruption(sw, true); + 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; - /* 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; + ret = tb_port_tmu_unidirectional_enable(up); + if (ret) + goto out; - up = tb_upstream_port(sw); - down = tb_port_at(tb_route(sw), parent); + ret = tb_port_tmu_time_sync_enable(up); + if (ret) + goto out; - ret = tb_port_tmu_unidirectional_disable(down); - if (ret) - return ret; + ret = tb_port_tmu_unidirectional_enable(down); + if (ret) + goto out; - ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI); - if (ret) - return ret; + ret = tb_port_tmu_time_sync_enable(down); + if (ret) + goto out; - ret = tb_port_tmu_unidirectional_disable(up); - if (ret) - return ret; + 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_hifi_is_enabled(sw, sw->tmu.unidirectional_request)) + return 0; + + ret = tb_switch_tmu_set_time_disruption(sw, true); + 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; +} |