#include <linux/uaccess.h>
 
 #include "tb.h"
+#include "sb_regs.h"
 
 #define PORT_CAP_PCIE_LEN      1
 #define PORT_CAP_POWER_LEN     2
 #define DEBUGFS_MODE           0400
 #endif
 
+#if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)
+/**
+ * struct tb_margining - Lane margining support
+ * @caps: Port lane margining capabilities
+ * @results: Last lane margining results
+ * @lanes: %0, %1 or %7 (all)
+ * @min_ber_level: Minimum supported BER level contour value
+ * @max_ber_level: Maximum supported BER level contour value
+ * @ber_level: Current BER level contour value
+ * @voltage_steps: Number of mandatory voltage steps
+ * @max_voltage_offset: Maximum mandatory voltage offset (in mV)
+ * @time_steps: Number of time margin steps
+ * @max_time_offset: Maximum time margin offset (in mUI)
+ * @software: %true if software margining is used instead of hardware
+ * @time: %true if time margining is used instead of voltage
+ * @right_high: %false if left/low margin test is performed, %true if
+ *             right/high
+ */
+struct tb_margining {
+       u32 caps[2];
+       u32 results[2];
+       unsigned int lanes;
+       unsigned int min_ber_level;
+       unsigned int max_ber_level;
+       unsigned int ber_level;
+       unsigned int voltage_steps;
+       unsigned int max_voltage_offset;
+       unsigned int time_steps;
+       unsigned int max_time_offset;
+       bool software;
+       bool time;
+       bool right_high;
+};
+
+static bool supports_software(const struct usb4_port *usb4)
+{
+       return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
+}
+
+static bool supports_hardware(const struct usb4_port *usb4)
+{
+       return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
+}
+
+static bool both_lanes(const struct usb4_port *usb4)
+{
+       return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
+}
+
+static unsigned int independent_voltage_margins(const struct usb4_port *usb4)
+{
+       return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >>
+               USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT;
+}
+
+static bool supports_time(const struct usb4_port *usb4)
+{
+       return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
+}
+
+/* Only applicable if supports_time() returns true */
+static unsigned int independent_time_margins(const struct usb4_port *usb4)
+{
+       return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >>
+               USB4_MARGIN_CAP_1_TIME_INDP_SHIFT;
+}
+
+static ssize_t
+margining_ber_level_write(struct file *file, const char __user *user_buf,
+                          size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       unsigned int val;
+       int ret = 0;
+       char *buf;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       if (usb4->margining->software) {
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf)) {
+               ret = PTR_ERR(buf);
+               goto out_unlock;
+       }
+
+       buf[count - 1] = '\0';
+
+       ret = kstrtouint(buf, 10, &val);
+       if (ret)
+               goto out_free;
+
+       if (val < usb4->margining->min_ber_level ||
+           val > usb4->margining->max_ber_level) {
+               ret = -EINVAL;
+               goto out_free;
+       }
+
+       usb4->margining->ber_level = val;
+
+out_free:
+       free_page((unsigned long)buf);
+out_unlock:
+       mutex_unlock(&tb->lock);
+
+       return ret < 0 ? ret : count;
+}
+
+static void ber_level_show(struct seq_file *s, unsigned int val)
+{
+       if (val % 2)
+               seq_printf(s, "3 * 1e%d (%u)\n", -12 + (val + 1) / 2, val);
+       else
+               seq_printf(s, "1e%d (%u)\n", -12 + val / 2, val);
+}
+
+static int margining_ber_level_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+
+       if (usb4->margining->software)
+               return -EINVAL;
+       ber_level_show(s, usb4->margining->ber_level);
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_ber_level);
+
+static int margining_caps_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       u32 cap0, cap1;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       /* Dump the raw caps first */
+       cap0 = usb4->margining->caps[0];
+       seq_printf(s, "0x%08x\n", cap0);
+       cap1 = usb4->margining->caps[1];
+       seq_printf(s, "0x%08x\n", cap1);
+
+       seq_printf(s, "# software margining: %s\n",
+                  supports_software(usb4) ? "yes" : "no");
+       if (supports_hardware(usb4)) {
+               seq_puts(s, "# hardware margining: yes\n");
+               seq_puts(s, "# minimum BER level contour: ");
+               ber_level_show(s, usb4->margining->min_ber_level);
+               seq_puts(s, "# maximum BER level contour: ");
+               ber_level_show(s, usb4->margining->max_ber_level);
+       } else {
+               seq_puts(s, "# hardware margining: no\n");
+       }
+
+       seq_printf(s, "# both lanes simultaneusly: %s\n",
+                 both_lanes(usb4) ? "yes" : "no");
+       seq_printf(s, "# voltage margin steps: %u\n",
+                  usb4->margining->voltage_steps);
+       seq_printf(s, "# maximum voltage offset: %u mV\n",
+                  usb4->margining->max_voltage_offset);
+
+       switch (independent_voltage_margins(usb4)) {
+       case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
+               seq_puts(s, "# returns minimum between high and low voltage margins\n");
+               break;
+       case USB4_MARGIN_CAP_0_VOLTAGE_HL:
+               seq_puts(s, "# returns high or low voltage margin\n");
+               break;
+       case USB4_MARGIN_CAP_0_VOLTAGE_BOTH:
+               seq_puts(s, "# returns both hight and low margings\n");
+               break;
+       }
+
+       if (supports_time(usb4)) {
+               seq_puts(s, "# time margining: yes\n");
+               seq_printf(s, "# time margining is destructive: %s\n",
+                          cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no");
+
+               switch (independent_time_margins(usb4)) {
+               case USB4_MARGIN_CAP_1_TIME_MIN:
+                       seq_puts(s, "# returns minimum between left and right time margins\n");
+                       break;
+               case USB4_MARGIN_CAP_1_TIME_LR:
+                       seq_puts(s, "# returns left or right margin\n");
+                       break;
+               case USB4_MARGIN_CAP_1_TIME_BOTH:
+                       seq_puts(s, "# returns both left and right margins\n");
+                       break;
+               }
+
+               seq_printf(s, "# time margin steps: %u\n",
+                          usb4->margining->time_steps);
+               seq_printf(s, "# maximum time offset: %u mUI\n",
+                          usb4->margining->max_time_offset);
+       } else {
+               seq_puts(s, "# time margining: no\n");
+       }
+
+       mutex_unlock(&tb->lock);
+       return 0;
+}
+DEBUGFS_ATTR_RO(margining_caps);
+
+static ssize_t
+margining_lanes_write(struct file *file, const char __user *user_buf,
+                     size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       int ret = 0;
+       char *buf;
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       buf[count - 1] = '\0';
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_free;
+       }
+
+       if (!strcmp(buf, "0")) {
+               usb4->margining->lanes = 0;
+       } else if (!strcmp(buf, "1")) {
+               usb4->margining->lanes = 1;
+       } else if (!strcmp(buf, "all")) {
+               /* Needs to be supported */
+               if (both_lanes(usb4))
+                       usb4->margining->lanes = 7;
+               else
+                       ret = -EINVAL;
+       } else {
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&tb->lock);
+
+out_free:
+       free_page((unsigned long)buf);
+       return ret < 0 ? ret : count;
+}
+
+static int margining_lanes_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       unsigned int lanes;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       lanes = usb4->margining->lanes;
+       if (both_lanes(usb4)) {
+               if (!lanes)
+                       seq_puts(s, "[0] 1 all\n");
+               else if (lanes == 1)
+                       seq_puts(s, "0 [1] all\n");
+               else
+                       seq_puts(s, "0 1 [all]\n");
+       } else {
+               if (!lanes)
+                       seq_puts(s, "[0] 1\n");
+               else
+                       seq_puts(s, "0 [1]\n");
+       }
+
+       mutex_unlock(&tb->lock);
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_lanes);
+
+static ssize_t margining_mode_write(struct file *file,
+                                  const char __user *user_buf,
+                                  size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       int ret = 0;
+       char *buf;
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       buf[count - 1] = '\0';
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_free;
+       }
+
+       if (!strcmp(buf, "software")) {
+               if (supports_software(usb4))
+                       usb4->margining->software = true;
+               else
+                       ret = -EINVAL;
+       } else if (!strcmp(buf, "hardware")) {
+               if (supports_hardware(usb4))
+                       usb4->margining->software = false;
+               else
+                       ret = -EINVAL;
+       } else {
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&tb->lock);
+
+out_free:
+       free_page((unsigned long)buf);
+       return ret ? ret : count;
+}
+
+static int margining_mode_show(struct seq_file *s, void *not_used)
+{
+       const struct tb_port *port = s->private;
+       const struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       const char *space = "";
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       if (supports_software(usb4)) {
+               if (usb4->margining->software)
+                       seq_puts(s, "[software]");
+               else
+                       seq_puts(s, "software");
+               space = " ";
+       }
+       if (supports_hardware(usb4)) {
+               if (usb4->margining->software)
+                       seq_printf(s, "%shardware", space);
+               else
+                       seq_printf(s, "%s[hardware]", space);
+       }
+
+       mutex_unlock(&tb->lock);
+
+       seq_puts(s, "\n");
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_mode);
+
+static int margining_run_write(void *data, u64 val)
+{
+       struct tb_port *port = data;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb_switch *sw = port->sw;
+       struct tb_margining *margining;
+       struct tb *tb = sw->tb;
+       int ret;
+
+       if (val != 1)
+               return -EINVAL;
+
+       pm_runtime_get_sync(&sw->dev);
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_rpm_put;
+       }
+
+       /*
+        * CL states may interfere with lane margining so inform the user know
+        * and bail out.
+        */
+       if (tb_port_is_clx_enabled(port, TB_CL1 | TB_CL2)) {
+               tb_port_warn(port,
+                            "CL states are enabled, Disable them with clx=0 and re-connect\n");
+               ret = -EINVAL;
+               goto out_unlock;
+       }
+
+       margining = usb4->margining;
+
+       if (margining->software) {
+               tb_port_dbg(port, "running software %s lane margining for lanes %u\n",
+                           margining->time ? "time" : "voltage", margining->lanes);
+               ret = usb4_port_sw_margin(port, margining->lanes, margining->time,
+                                         margining->right_high,
+                                         USB4_MARGIN_SW_COUNTER_CLEAR);
+               if (ret)
+                       goto out_unlock;
+
+               ret = usb4_port_sw_margin_errors(port, &margining->results[0]);
+       } else {
+               tb_port_dbg(port, "running hardware %s lane margining for lanes %u\n",
+                           margining->time ? "time" : "voltage", margining->lanes);
+               /* Clear the results */
+               margining->results[0] = 0;
+               margining->results[1] = 0;
+               ret = usb4_port_hw_margin(port, margining->lanes,
+                                         margining->ber_level, margining->time,
+                                         margining->right_high, margining->results);
+       }
+
+out_unlock:
+       mutex_unlock(&tb->lock);
+out_rpm_put:
+       pm_runtime_mark_last_busy(&sw->dev);
+       pm_runtime_put_autosuspend(&sw->dev);
+
+       return ret;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(margining_run_fops, NULL, margining_run_write,
+                        "%llu\n");
+
+static ssize_t margining_results_write(struct file *file,
+                                      const char __user *user_buf,
+                                      size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       /* Just clear the results */
+       usb4->margining->results[0] = 0;
+       usb4->margining->results[1] = 0;
+
+       mutex_unlock(&tb->lock);
+       return count;
+}
+
+static void voltage_margin_show(struct seq_file *s,
+                               const struct tb_margining *margining, u8 val)
+{
+       unsigned int tmp, voltage;
+
+       tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
+       voltage = tmp * margining->max_voltage_offset / margining->voltage_steps;
+       seq_printf(s, "%u mV (%u)", voltage, tmp);
+       if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
+               seq_puts(s, " exceeds maximum");
+       seq_puts(s, "\n");
+}
+
+static void time_margin_show(struct seq_file *s,
+                            const struct tb_margining *margining, u8 val)
+{
+       unsigned int tmp, interval;
+
+       tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
+       interval = tmp * margining->max_time_offset / margining->time_steps;
+       seq_printf(s, "%u mUI (%u)", interval, tmp);
+       if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
+               seq_puts(s, " exceeds maximum");
+       seq_puts(s, "\n");
+}
+
+static int margining_results_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb_margining *margining;
+       struct tb *tb = port->sw->tb;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       margining = usb4->margining;
+       /* Dump the raw results first */
+       seq_printf(s, "0x%08x\n", margining->results[0]);
+       /* Only the hardware margining has two result dwords */
+       if (!margining->software) {
+               unsigned int val;
+
+               seq_printf(s, "0x%08x\n", margining->results[1]);
+
+               if (margining->time) {
+                       if (!margining->lanes || margining->lanes == 7) {
+                               val = margining->results[1];
+                               seq_puts(s, "# lane 0 right time margin: ");
+                               time_margin_show(s, margining, val);
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 0 left time margin: ");
+                               time_margin_show(s, margining, val);
+                       }
+                       if (margining->lanes == 1 || margining->lanes == 7) {
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 1 right time margin: ");
+                               time_margin_show(s, margining, val);
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 1 left time margin: ");
+                               time_margin_show(s, margining, val);
+                       }
+               } else {
+                       if (!margining->lanes || margining->lanes == 7) {
+                               val = margining->results[1];
+                               seq_puts(s, "# lane 0 high voltage margin: ");
+                               voltage_margin_show(s, margining, val);
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 0 low voltage margin: ");
+                               voltage_margin_show(s, margining, val);
+                       }
+                       if (margining->lanes == 1 || margining->lanes == 7) {
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 1 high voltage margin: ");
+                               voltage_margin_show(s, margining, val);
+                               val = margining->results[1] >>
+                                       USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
+                               seq_puts(s, "# lane 1 low voltage margin: ");
+                               voltage_margin_show(s, margining, val);
+                       }
+               }
+       }
+
+       mutex_unlock(&tb->lock);
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_results);
+
+static ssize_t margining_test_write(struct file *file,
+                                   const char __user *user_buf,
+                                   size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       int ret = 0;
+       char *buf;
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       buf[count - 1] = '\0';
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_free;
+       }
+
+       if (!strcmp(buf, "time") && supports_time(usb4))
+               usb4->margining->time = true;
+       else if (!strcmp(buf, "voltage"))
+               usb4->margining->time = false;
+       else
+               ret = -EINVAL;
+
+       mutex_unlock(&tb->lock);
+
+out_free:
+       free_page((unsigned long)buf);
+       return ret ? ret : count;
+}
+
+static int margining_test_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       if (supports_time(usb4)) {
+               if (usb4->margining->time)
+                       seq_puts(s, "voltage [time]\n");
+               else
+                       seq_puts(s, "[voltage] time\n");
+       } else {
+               seq_puts(s, "[voltage]\n");
+       }
+
+       mutex_unlock(&tb->lock);
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_test);
+
+static ssize_t margining_margin_write(struct file *file,
+                                   const char __user *user_buf,
+                                   size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+       int ret = 0;
+       char *buf;
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       buf[count - 1] = '\0';
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_free;
+       }
+
+       if (usb4->margining->time) {
+               if (!strcmp(buf, "left"))
+                       usb4->margining->right_high = false;
+               else if (!strcmp(buf, "right"))
+                       usb4->margining->right_high = true;
+               else
+                       ret = -EINVAL;
+       } else {
+               if (!strcmp(buf, "low"))
+                       usb4->margining->right_high = false;
+               else if (!strcmp(buf, "high"))
+                       usb4->margining->right_high = true;
+               else
+                       ret = -EINVAL;
+       }
+
+       mutex_unlock(&tb->lock);
+
+out_free:
+       free_page((unsigned long)buf);
+       return ret ? ret : count;
+}
+
+static int margining_margin_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct usb4_port *usb4 = port->usb4;
+       struct tb *tb = port->sw->tb;
+
+       if (mutex_lock_interruptible(&tb->lock))
+               return -ERESTARTSYS;
+
+       if (usb4->margining->time) {
+               if (usb4->margining->right_high)
+                       seq_puts(s, "left [right]\n");
+               else
+                       seq_puts(s, "[left] right\n");
+       } else {
+               if (usb4->margining->right_high)
+                       seq_puts(s, "low [high]\n");
+               else
+                       seq_puts(s, "[low] high\n");
+       }
+
+       mutex_unlock(&tb->lock);
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_margin);
+
+static void margining_port_init(struct tb_port *port)
+{
+       struct tb_margining *margining;
+       struct dentry *dir, *parent;
+       struct usb4_port *usb4;
+       char dir_name[10];
+       unsigned int val;
+       int ret;
+
+       usb4 = port->usb4;
+       if (!usb4)
+               return;
+
+       snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+       parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+
+       margining = kzalloc(sizeof(*margining), GFP_KERNEL);
+       if (!margining)
+               return;
+
+       ret = usb4_port_margining_caps(port, margining->caps);
+       if (ret) {
+               kfree(margining);
+               return;
+       }
+
+       usb4->margining = margining;
+
+       /* Set the initial mode */
+       if (supports_software(usb4))
+               margining->software = true;
+
+       val = (margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK) >>
+               USB4_MARGIN_CAP_0_VOLTAGE_STEPS_SHIFT;
+       margining->voltage_steps = val;
+       val = (margining->caps[0] & USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK) >>
+               USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_SHIFT;
+       margining->max_voltage_offset = 74 + val * 2;
+
+       if (supports_time(usb4)) {
+               val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_STEPS_MASK) >>
+                       USB4_MARGIN_CAP_1_TIME_STEPS_SHIFT;
+               margining->time_steps = val;
+               val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_OFFSET_MASK) >>
+                       USB4_MARGIN_CAP_1_TIME_OFFSET_SHIFT;
+               /*
+                * Store it as mUI (milli Unit Interval) because we want
+                * to keep it as integer.
+                */
+               margining->max_time_offset = 200 + 10 * val;
+       }
+
+       dir = debugfs_create_dir("margining", parent);
+       if (supports_hardware(usb4)) {
+               val = (margining->caps[1] & USB4_MARGIN_CAP_1_MIN_BER_MASK) >>
+                       USB4_MARGIN_CAP_1_MIN_BER_SHIFT;
+               margining->min_ber_level = val;
+               val = (margining->caps[1] & USB4_MARGIN_CAP_1_MAX_BER_MASK) >>
+                       USB4_MARGIN_CAP_1_MAX_BER_SHIFT;
+               margining->max_ber_level = val;
+
+               /* Set the default to minimum */
+               margining->ber_level = margining->min_ber_level;
+
+               debugfs_create_file("ber_level_contour", 0400, dir, port,
+                                   &margining_ber_level_fops);
+       }
+       debugfs_create_file("caps", 0400, dir, port, &margining_caps_fops);
+       debugfs_create_file("lanes", 0600, dir, port, &margining_lanes_fops);
+       debugfs_create_file("mode", 0600, dir, port, &margining_mode_fops);
+       debugfs_create_file("run", 0600, dir, port, &margining_run_fops);
+       debugfs_create_file("results", 0600, dir, port, &margining_results_fops);
+       debugfs_create_file("test", 0600, dir, port, &margining_test_fops);
+       if (independent_voltage_margins(usb4) ||
+           (supports_time(usb4) && independent_time_margins(usb4)))
+               debugfs_create_file("margin", 0600, dir, port, &margining_margin_fops);
+}
+
+static void margining_port_remove(struct tb_port *port)
+{
+       struct dentry *parent;
+       char dir_name[10];
+
+       if (!port->usb4)
+               return;
+
+       snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+       parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+       debugfs_remove_recursive(debugfs_lookup("margining", parent));
+
+       kfree(port->usb4->margining);
+       port->usb4->margining = NULL;
+}
+
+static void margining_switch_init(struct tb_switch *sw)
+{
+       struct tb_port *upstream, *downstream;
+       struct tb_switch *parent_sw;
+       u64 route = tb_route(sw);
+
+       if (!route)
+               return;
+
+       upstream = tb_upstream_port(sw);
+       parent_sw = tb_switch_parent(sw);
+       downstream = tb_port_at(route, parent_sw);
+
+       margining_port_init(downstream);
+       margining_port_init(upstream);
+}
+
+static void margining_switch_remove(struct tb_switch *sw)
+{
+       struct tb_switch *parent_sw;
+       struct tb_port *downstream;
+       u64 route = tb_route(sw);
+
+       if (!route)
+               return;
+
+       /*
+        * Upstream is removed with the router itself but we need to
+        * remove the downstream port margining directory.
+        */
+       parent_sw = tb_switch_parent(sw);
+       downstream = tb_port_at(route, parent_sw);
+       margining_port_remove(downstream);
+}
+
+static void margining_xdomain_init(struct tb_xdomain *xd)
+{
+       struct tb_switch *parent_sw;
+       struct tb_port *downstream;
+
+       parent_sw = tb_xdomain_parent(xd);
+       downstream = tb_port_at(xd->route, parent_sw);
+
+       margining_port_init(downstream);
+}
+
+static void margining_xdomain_remove(struct tb_xdomain *xd)
+{
+       struct tb_switch *parent_sw;
+       struct tb_port *downstream;
+
+       parent_sw = tb_xdomain_parent(xd);
+       downstream = tb_port_at(xd->route, parent_sw);
+       margining_port_remove(downstream);
+}
+#else
+static inline void margining_switch_init(struct tb_switch *sw) { }
+static inline void margining_switch_remove(struct tb_switch *sw) { }
+static inline void margining_xdomain_init(struct tb_xdomain *xd) { }
+static inline void margining_xdomain_remove(struct tb_xdomain *xd) { }
+#endif
+
 static int port_clear_all_counters(struct tb_port *port)
 {
        u32 *buf;
                        debugfs_create_file("counters", 0600, debugfs_dir, port,
                                            &counters_fops);
        }
+
+       margining_switch_init(sw);
 }
 
 /**
  */
 void tb_switch_debugfs_remove(struct tb_switch *sw)
 {
+       margining_switch_remove(sw);
        debugfs_remove_recursive(sw->debugfs_dir);
 }
 
+void tb_xdomain_debugfs_init(struct tb_xdomain *xd)
+{
+       margining_xdomain_init(xd);
+}
+
+void tb_xdomain_debugfs_remove(struct tb_xdomain *xd)
+{
+       margining_xdomain_remove(xd);
+}
+
 /**
  * tb_service_debugfs_init() - Add debugfs directory for service
  * @svc: Thunderbolt service pointer