thunderbolt: Add optional voltage offset range for receiver lane margining

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>
This commit is contained in:
Rene Sapiens 2024-07-19 21:37:19 +03:00 committed by Mika Westerberg
parent 81f848d287
commit 9fafd46b39
4 changed files with 85 additions and 0 deletions

View 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;
}

View 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

View 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;
};

View 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);