]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
thunderbolt: Add optional voltage offset range for receiver lane margining
authorRene Sapiens <rene.sapiens@intel.com>
Fri, 19 Jul 2024 18:37:19 +0000 (21:37 +0300)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Thu, 22 Aug 2024 04:32:06 +0000 (07:32 +0300)
Add optional extended voltage offset range support for software and
hardware margining as defined by the USB4 specification.

If supported, it can be enabled like below:

 # cd /sys/kernel/debug/thunderbolt/ROUTER/portX/margining/
 # echo Y > optional_voltage_offset

Signed-off-by: Rene Sapiens <rene.sapiens@intel.com>
Co-developed-by: R Kannappan <r.kannappan@intel.com>
Signed-off-by: R Kannappan <r.kannappan@intel.com>
Co-developed-by: Aapo Vienamo <aapo.vienamo@linux.intel.com>
Signed-off-by: Aapo Vienamo <aapo.vienamo@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
drivers/thunderbolt/debugfs.c
drivers/thunderbolt/sb_regs.h
drivers/thunderbolt/tb.h
drivers/thunderbolt/usb4.c

index 5d1588baea6a95f13c652f35c633f7723b970e2c..9defa69ef3a7a2f7655bd7fa1d0daea6ff3b4b86 100644 (file)
@@ -394,8 +394,12 @@ out:
  * @ber_level: Current BER level contour value
  * @voltage_steps: Number of mandatory voltage steps
  * @max_voltage_offset: Maximum mandatory voltage offset (in mV)
+ * @voltage_steps_optional_range: Number of voltage steps for optional range
+ * @max_voltage_offset_optional_range: Maximum voltage offset for the optional
+ *                                     range (in mV).
  * @time_steps: Number of time margin steps
  * @max_time_offset: Maximum time margin offset (in mUI)
+ * @optional_voltage_offset_range: Enable optional extended voltage range
  * @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
@@ -414,8 +418,11 @@ struct tb_margining {
        unsigned int ber_level;
        unsigned int voltage_steps;
        unsigned int max_voltage_offset;
+       unsigned int voltage_steps_optional_range;
+       unsigned int max_voltage_offset_optional_range;
        unsigned int time_steps;
        unsigned int max_time_offset;
+       bool optional_voltage_offset_range;
        bool software;
        bool time;
        bool right_high;
@@ -454,6 +461,12 @@ independent_time_margins(const struct tb_margining *margining)
        return FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1]);
 }
 
+static bool
+supports_optional_voltage_offset_range(const struct tb_margining *margining)
+{
+       return margining->caps[0] & USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT;
+}
+
 static ssize_t
 margining_ber_level_write(struct file *file, const char __user *user_buf,
                           size_t count, loff_t *ppos)
@@ -553,6 +566,14 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
                   margining->voltage_steps);
        seq_printf(s, "# maximum voltage offset: %u mV\n",
                   margining->max_voltage_offset);
+       seq_printf(s, "# optional voltage offset range support: %s\n",
+                  str_yes_no(supports_optional_voltage_offset_range(margining)));
+       if (supports_optional_voltage_offset_range(margining)) {
+               seq_printf(s, "# voltage margin steps, optional range: %u\n",
+                          margining->voltage_steps_optional_range);
+               seq_printf(s, "# maximum voltage offset, optional range: %u mV\n",
+                          margining->max_voltage_offset_optional_range);
+       }
 
        switch (independent_voltage_margins(margining)) {
        case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
@@ -667,6 +688,42 @@ static int margining_lanes_show(struct seq_file *s, void *not_used)
 }
 DEBUGFS_ATTR_RW(margining_lanes);
 
+static ssize_t
+margining_optional_voltage_offset_write(struct file *file,
+                                  const char __user *user_buf,
+                                  size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct tb_margining *margining = s->private;
+       struct tb *tb = margining->port->sw->tb;
+       bool val;
+       int ret;
+
+       ret = kstrtobool_from_user(user_buf, count, &val);
+       if (ret)
+               return ret;
+
+       scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
+               margining->optional_voltage_offset_range = val;
+       }
+
+       return count;
+}
+
+static int margining_optional_voltage_offset_show(struct seq_file *s,
+                                                 void *not_used)
+{
+       struct tb_margining *margining = s->private;
+       struct tb *tb = margining->port->sw->tb;
+
+       scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
+               seq_printf(s, "%u\n", margining->optional_voltage_offset_range);
+       }
+
+       return 0;
+}
+DEBUGFS_ATTR_RW(margining_optional_voltage_offset);
+
 static ssize_t margining_mode_write(struct file *file,
                                   const char __user *user_buf,
                                   size_t count, loff_t *ppos)
@@ -785,6 +842,7 @@ static int margining_run_write(void *data, u64 val)
                        .lanes = margining->lanes,
                        .time = margining->time,
                        .right_high = margining->right_high,
+                       .optional_voltage_offset_range = margining->optional_voltage_offset_range,
                };
 
                tb_port_dbg(port,
@@ -806,6 +864,7 @@ static int margining_run_write(void *data, u64 val)
                        .lanes = margining->lanes,
                        .time = margining->time,
                        .right_high = margining->right_high,
+                       .optional_voltage_offset_range = margining->optional_voltage_offset_range,
                };
 
                /* Clear the results */
@@ -865,6 +924,8 @@ static void voltage_margin_show(struct seq_file *s,
        if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
                seq_puts(s, " exceeds maximum");
        seq_puts(s, "\n");
+       if (margining->optional_voltage_offset_range)
+               seq_puts(s, " optional voltage offset range enabled\n");
 }
 
 static void time_margin_show(struct seq_file *s,
@@ -1104,6 +1165,15 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
        val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
        margining->max_voltage_offset = 74 + val * 2;
 
+       if (supports_optional_voltage_offset_range(margining)) {
+               val = FIELD_GET(USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK,
+                               margining->caps[0]);
+               margining->voltage_steps_optional_range = val;
+               val = FIELD_GET(USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK,
+                               margining->caps[1]);
+               margining->max_voltage_offset_optional_range = 74 + val * 2;
+       }
+
        if (supports_time(margining)) {
                val = FIELD_GET(USB4_MARGIN_CAP_1_TIME_STEPS_MASK, margining->caps[1]);
                margining->time_steps = val;
@@ -1140,6 +1210,10 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
             independent_time_margins(margining) == USB4_MARGIN_CAP_1_TIME_LR))
                debugfs_create_file("margin", 0600, dir, margining,
                                    &margining_margin_fops);
+
+       if (supports_optional_voltage_offset_range(margining))
+               debugfs_create_file("optional_voltage_offset", DEBUGFS_MODE, dir, margining,
+                                   &margining_optional_voltage_offset_fops);
        return margining;
 }
 
index 86e80aa297f7facff4a95d82b3ffbbbeff6b9a4f..8bff0740222c51a7f7c8730ab9f0a7b7249ae015 100644 (file)
@@ -57,6 +57,9 @@ enum usb4_sb_opcode {
 #define USB4_MARGIN_CAP_0_TIME                 BIT(5)
 #define USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK   GENMASK(12, 6)
 #define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13)
+#define USB4_MARGIN_CAP_0_OPT_VOLTAGE_SUPPORT  BIT(19)
+#define USB4_MARGIN_CAP_0_VOLT_STEPS_OPT_MASK  GENMASK(26, 20)
+#define USB4_MARGIN_CAP_1_MAX_VOLT_OFS_OPT_MASK GENMASK(7, 0)
 #define USB4_MARGIN_CAP_1_TIME_DESTR           BIT(8)
 #define USB4_MARGIN_CAP_1_TIME_INDP_MASK       GENMASK(10, 9)
 #define USB4_MARGIN_CAP_1_TIME_MIN             0x0
@@ -72,6 +75,7 @@ enum usb4_sb_opcode {
 #define USB4_MARGIN_HW_RH                      BIT(4)
 #define USB4_MARGIN_HW_BER_MASK                        GENMASK(9, 5)
 #define USB4_MARGIN_HW_BER_SHIFT               5
+#define USB4_MARGIN_HW_OPT_VOLTAGE             BIT(10)
 
 /* Applicable to all margin values */
 #define USB4_MARGIN_HW_RES_1_MARGIN_MASK       GENMASK(6, 0)
@@ -84,6 +88,7 @@ enum usb4_sb_opcode {
 /* USB4_SB_OPCODE_RUN_SW_LANE_MARGINING */
 #define USB4_MARGIN_SW_TIME                    BIT(3)
 #define USB4_MARGIN_SW_RH                      BIT(4)
+#define USB4_MARGIN_SW_OPT_VOLTAGE             BIT(5)
 #define USB4_MARGIN_SW_COUNTER_MASK            GENMASK(14, 13)
 
 #endif
index 89ea66f885a46e976b1c5a9b86e77c1da781b4c7..262c333924b8391ba6d8a3f15597c6a947a58c92 100644 (file)
@@ -1372,6 +1372,7 @@ enum usb4_margin_sw_error_counter {
  * @error_counter: Error counter operation for software margining
  * @ber_level: Current BER level contour value
  * @lanes: %0, %1 or %7 (all)
+ * @optional_voltage_offset_range: Enable optional extended voltage range
  * @right_high: %false if left/low margin test is performed, %true if right/high
  * @time: %true if time margining is used instead of voltage
  */
@@ -1379,6 +1380,7 @@ struct usb4_port_margining_params {
        enum usb4_margin_sw_error_counter error_counter;
        u32 ber_level;
        u32 lanes;
+       bool optional_voltage_offset_range;
        bool right_high;
        bool time;
 };
index cb51cafcf20c6c1e475e44ecd6224b68dd254a07..a2f81e17ad8db88a8cbaca0513f92729425c4b5d 100644 (file)
@@ -1676,6 +1676,8 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
                val |= USB4_MARGIN_HW_RH;
        if (params->ber_level)
                val |= FIELD_PREP(USB4_MARGIN_HW_BER_MASK, params->ber_level);
+       if (params->optional_voltage_offset_range)
+               val |= USB4_MARGIN_HW_OPT_VOLTAGE;
 
        ret = usb4_port_sb_write(port, target, index, USB4_SB_METADATA, &val,
                                 sizeof(val));
@@ -1716,6 +1718,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
        val = params->lanes;
        if (params->time)
                val |= USB4_MARGIN_SW_TIME;
+       if (params->optional_voltage_offset_range)
+               val |= USB4_MARGIN_SW_OPT_VOLTAGE;
        if (params->right_high)
                val |= USB4_MARGIN_SW_RH;
        val |= FIELD_PREP(USB4_MARGIN_SW_COUNTER_MASK, params->error_counter);