Merge tag '5.17-rc-part1-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6
[linux-2.6-microblaze.git] / drivers / thunderbolt / tmu.c
index 039c42a..e4a07a2 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,23 +133,46 @@ 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)
 {
+       u32 val, offset, bit;
        int ret;
-       u32 val;
 
-       ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
-                        sw->tmu.cap + TMU_RTR_CS_0, 1);
+       if (tb_switch_is_usb4(sw)) {
+               offset = sw->tmu.cap + TMU_RTR_CS_0;
+               bit = TMU_RTR_CS_0_TD;
+       } else {
+               offset = sw->cap_vsec_tmu + TB_TIME_VSEC_3_CS_26;
+               bit = TB_TIME_VSEC_3_CS_26_TD;
+       }
+
+       ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, offset, 1);
        if (ret)
                return ret;
 
        if (set)
-               val |= TMU_RTR_CS_0_TD;
+               val |= bit;
        else
-               val &= ~TMU_RTR_CS_0_TD;
+               val &= ~bit;
 
-       return tb_sw_write(sw, &val, TB_CFG_SWITCH,
-                          sw->tmu.cap + TMU_RTR_CS_0, 1);
+       return tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1);
 }
 
 /**
@@ -207,7 +235,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 +276,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 +288,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,30 +334,54 @@ out:
  */
 int tb_switch_tmu_disable(struct tb_switch *sw)
 {
-       int ret;
-
-       if (!tb_switch_is_usb4(sw))
+       /*
+        * No need to disable TMU on devices that don't support CLx since
+        * on these devices e.g. Alpine Ridge and earlier, the TMU mode
+        * HiFi bi-directional is enabled by default and we don't change it.
+        */
+       if (!tb_switch_is_clx_supported(sw))
                return 0;
 
        /* Already disabled? */
        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 +390,231 @@ 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;
+}
+
+static int tb_switch_tmu_objection_mask(struct tb_switch *sw)
+{
+       u32 val;
+       int ret;
+
+       ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
+                        sw->cap_vsec_tmu + TB_TIME_VSEC_3_CS_9, 1);
        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;
+       val &= ~TB_TIME_VSEC_3_CS_9_TMU_OBJ_MASK;
 
-               up = tb_upstream_port(sw);
-               down = tb_port_at(tb_route(sw), parent);
+       return tb_sw_write(sw, &val, TB_CFG_SWITCH,
+                          sw->cap_vsec_tmu + TB_TIME_VSEC_3_CS_9, 1);
+}
 
-               ret = tb_port_tmu_unidirectional_disable(down);
-               if (ret)
-                       return ret;
+static int tb_switch_tmu_unidirectional_enable(struct tb_switch *sw)
+{
+       struct tb_port *up = tb_upstream_port(sw);
 
-               ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
+       return tb_port_tmu_write(up, TMU_ADP_CS_6,
+                                TMU_ADP_CS_6_DISABLE_TMU_OBJ_MASK,
+                                TMU_ADP_CS_6_DISABLE_TMU_OBJ_MASK);
+}
+
+/*
+ * 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;
+
+       /*
+        * No need to enable TMU on devices that don't support CLx since on
+        * these devices e.g. Alpine Ridge and earlier, the TMU mode HiFi
+        * bi-directional is enabled by default.
+        */
+       if (!tb_switch_is_clx_supported(sw))
+               return 0;
+
+       if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request))
+               return 0;
+
+       if (tb_switch_is_titan_ridge(sw) && unidirectional) {
+               /* Titan Ridge supports only CL0s */
+               if (!tb_switch_is_cl0s_enabled(sw))
+                       return -EOPNOTSUPP;
+
+               ret = tb_switch_tmu_objection_mask(sw);
                if (ret)
                        return ret;
 
-               ret = tb_port_tmu_unidirectional_disable(up);
+               ret = tb_switch_tmu_unidirectional_enable(sw);
                if (ret)
                        return ret;
+       }
+
+       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;
+}