thunderbolt: Add TMU uni-directional mode
authorGil Fine <gil.fine@intel.com>
Fri, 17 Dec 2021 01:16:38 +0000 (03:16 +0200)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Wed, 22 Dec 2021 14:13:14 +0000 (17:13 +0300)
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>
drivers/thunderbolt/tb.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tb_regs.h
drivers/thunderbolt/tmu.c

index a231191..7ed02d8 100644 (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");
 
index 3fae406..b448735 100644 (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);
index 484f25b..eb58b1a 100644 (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
index 039c42a..37048da 100644 (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 @@ 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;
+}