summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-11-29 11:19:31 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-11-29 11:19:31 -0800
commite33a6d83e1786d5e310ae746c67f5f4e2f93ba35 (patch)
tree84b555eb09938ce4b59e918f8acfbd0d0877b762 /drivers/thunderbolt
parent93e064ce854abfacdeb0ac526a13ad29d093e813 (diff)
parent237d4e0f41130a5ff0e1c7dc1cb41ee2fe21cd2a (diff)
Merge tag 'usb-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB / Thunderbolt updates from Greg KH: "Here is the big set of USB and Thunderbolt changes for 6.13-rc1. Overall, a pretty slow development cycle, the majority of the work going into the debugfs interface for the thunderbolt (i.e. USB4) code, to help with debugging the myrad ways that hardware vendors get their interfaces messed up. Other than that, here's the highlights: - thunderbolt changes and additions to debugfs interfaces - lots of device tree updates for new and old hardware - UVC configfs gadget updates and new apis for features - xhci driver updates and fixes - dwc3 driver updates and fixes - typec driver updates and fixes - lots of other small updates and fixes, full details in the shortlog All of these have been in linux-next for a while with no reported problems" * tag 'usb-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (148 commits) usb: typec: tcpm: Add support for sink-bc12-completion-time-ms DT property dt-bindings: usb: maxim,max33359: add usage of sink bc12 time property dt-bindings: connector: Add time property for Sink BC12 detection completion usb: dwc3: gadget: Remove dwc3_request->needs_extra_trb usb: dwc3: gadget: Cleanup SG handling usb: dwc3: gadget: Fix looping of queued SG entries usb: dwc3: gadget: Fix checking for number of TRBs left usb: dwc3: ep0: Don't clear ep0 DWC3_EP_TRANSFER_STARTED Revert "usb: gadget: composite: fix OS descriptors w_value logic" usb: ehci-spear: fix call balance of sehci clk handling routines USB: make to_usb_device_driver() use container_of_const() USB: make to_usb_driver() use container_of_const() USB: properly lock dynamic id list when showing an id USB: make single lock for all usb dynamic id lists drivers/usb/storage: refactor min with min_t drivers/usb/serial: refactor min with min_t drivers/usb/musb: refactor min/max with min_t/max_t drivers/usb/mon: refactor min with min_t drivers/usb/misc: refactor min with min_t drivers/usb/host: refactor min/max with min_t/max_t ...
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r--drivers/thunderbolt/debugfs.c504
-rw-r--r--drivers/thunderbolt/nhi.c12
-rw-r--r--drivers/thunderbolt/sb_regs.h32
-rw-r--r--drivers/thunderbolt/tb.h16
-rw-r--r--drivers/thunderbolt/usb4.c18
5 files changed, 418 insertions, 164 deletions
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index 350310bd0fee..a1d0d8a33f20 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -7,6 +7,7 @@
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
+#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
@@ -43,6 +44,24 @@
#define MAX_DWELL_TIME 500 /* ms */
#define DWELL_SAMPLE_INTERVAL 10
+enum usb4_margin_cap_voltage_indp {
+ USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN,
+ USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL,
+ USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH,
+ USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN,
+ USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH,
+ USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN,
+};
+
+enum usb4_margin_cap_time_indp {
+ USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN,
+ USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR,
+ USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH,
+ USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN,
+ USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH,
+ USB4_MARGIN_CAP_TIME_INDP_UNKNOWN,
+};
+
/* Sideband registers and their sizes as defined in the USB4 spec */
struct sb_reg {
unsigned int reg;
@@ -395,6 +414,8 @@ out:
* @target: Sideband target
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @dev: Pointer to the device that is the target (USB4 port or retimer)
+ * @gen: Link generation
+ * @asym_rx: %true% if @port supports asymmetric link with 3 Rx
* @caps: Port lane margining capabilities
* @results: Last lane margining results
* @lanes: %0, %1 or %7 (all)
@@ -416,15 +437,19 @@ out:
* @time: %true if time margining is used instead of voltage
* @right_high: %false if left/low margin test is performed, %true if
* right/high
+ * @upper_eye: %false if the lower PAM3 eye is used, %true if the upper
+ * eye is used
*/
struct tb_margining {
struct tb_port *port;
enum usb4_sb_target target;
u8 index;
struct device *dev;
- u32 caps[2];
- u32 results[2];
- unsigned int lanes;
+ unsigned int gen;
+ bool asym_rx;
+ u32 caps[3];
+ u32 results[3];
+ enum usb4_margining_lane lanes;
unsigned int min_ber_level;
unsigned int max_ber_level;
unsigned int ber_level;
@@ -441,6 +466,7 @@ struct tb_margining {
bool software;
bool time;
bool right_high;
+ bool upper_eye;
};
static int margining_modify_error_counter(struct tb_margining *margining,
@@ -463,35 +489,75 @@ static int margining_modify_error_counter(struct tb_margining *margining,
static bool supports_software(const struct tb_margining *margining)
{
- return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
+ if (margining->gen < 4)
+ return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
+ return margining->caps[2] & USB4_MARGIN_CAP_2_MODES_SW;
}
static bool supports_hardware(const struct tb_margining *margining)
{
- return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
+ if (margining->gen < 4)
+ return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
+ return margining->caps[2] & USB4_MARGIN_CAP_2_MODES_HW;
}
-static bool both_lanes(const struct tb_margining *margining)
+static bool all_lanes(const struct tb_margining *margining)
{
- return margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
+ return margining->caps[0] & USB4_MARGIN_CAP_0_ALL_LANES;
}
-static unsigned int
+static enum usb4_margin_cap_voltage_indp
independent_voltage_margins(const struct tb_margining *margining)
{
- return FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK, margining->caps[0]);
+ if (margining->gen < 4) {
+ switch (FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK, margining->caps[0])) {
+ case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN;
+ case USB4_MARGIN_CAP_0_VOLTAGE_HL:
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL;
+ case USB4_MARGIN_CAP_1_TIME_BOTH:
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH;
+ }
+ } else {
+ switch (FIELD_GET(USB4_MARGIN_CAP_2_VOLTAGE_INDP_MASK, margining->caps[2])) {
+ case USB4_MARGIN_CAP_2_VOLTAGE_MIN:
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN;
+ case USB4_MARGIN_CAP_2_VOLTAGE_BOTH:
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH;
+ }
+ }
+ return USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN;
}
static bool supports_time(const struct tb_margining *margining)
{
- return margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
+ if (margining->gen < 4)
+ return margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
+ return margining->caps[2] & USB4_MARGIN_CAP_2_TIME;
}
/* Only applicable if supports_time() returns true */
-static unsigned int
+static enum usb4_margin_cap_time_indp
independent_time_margins(const struct tb_margining *margining)
{
- return FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1]);
+ if (margining->gen < 4) {
+ switch (FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1])) {
+ case USB4_MARGIN_CAP_1_TIME_MIN:
+ return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN;
+ case USB4_MARGIN_CAP_1_TIME_LR:
+ return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR;
+ case USB4_MARGIN_CAP_1_TIME_BOTH:
+ return USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH;
+ }
+ } else {
+ switch (FIELD_GET(USB4_MARGIN_CAP_2_TIME_INDP_MASK, margining->caps[2])) {
+ case USB4_MARGIN_CAP_2_TIME_MIN:
+ return USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN;
+ case USB4_MARGIN_CAP_2_TIME_BOTH:
+ return USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH;
+ }
+ }
+ return USB4_MARGIN_CAP_TIME_INDP_UNKNOWN;
}
static bool
@@ -570,16 +636,14 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
{
struct tb_margining *margining = s->private;
struct tb *tb = margining->port->sw->tb;
- u32 cap0, cap1;
+ int ret = 0;
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
/* Dump the raw caps first */
- cap0 = margining->caps[0];
- seq_printf(s, "0x%08x\n", cap0);
- cap1 = margining->caps[1];
- seq_printf(s, "0x%08x\n", cap1);
+ for (int i = 0; i < ARRAY_SIZE(margining->caps); i++)
+ seq_printf(s, "0x%08x\n", margining->caps[i]);
seq_printf(s, "# software margining: %s\n",
supports_software(margining) ? "yes" : "no");
@@ -593,8 +657,8 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
seq_puts(s, "# hardware margining: no\n");
}
- seq_printf(s, "# both lanes simultaneously: %s\n",
- both_lanes(margining) ? "yes" : "no");
+ seq_printf(s, "# all lanes simultaneously: %s\n",
+ str_yes_no(all_lanes(margining)));
seq_printf(s, "# voltage margin steps: %u\n",
margining->voltage_steps);
seq_printf(s, "# maximum voltage offset: %u mV\n",
@@ -609,32 +673,54 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
}
switch (independent_voltage_margins(margining)) {
- case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_MIN:
seq_puts(s, "# returns minimum between high and low voltage margins\n");
break;
- case USB4_MARGIN_CAP_0_VOLTAGE_HL:
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL:
seq_puts(s, "# returns high or low voltage margin\n");
break;
- case USB4_MARGIN_CAP_0_VOLTAGE_BOTH:
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_BOTH:
seq_puts(s, "# returns both high and low margins\n");
break;
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_MIN:
+ seq_puts(s, "# returns minimum between high and low voltage margins in both lower and upper eye\n");
+ break;
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_4_BOTH:
+ seq_puts(s, "# returns both high and low margins of both upper and lower eye\n");
+ break;
+ case USB4_MARGIN_CAP_VOLTAGE_INDP_UNKNOWN:
+ tb_port_warn(margining->port,
+ "failed to parse independent voltage margining capabilities\n");
+ ret = -EIO;
+ goto out;
}
if (supports_time(margining)) {
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");
+ str_yes_no(margining->caps[1] & USB4_MARGIN_CAP_1_TIME_DESTR));
switch (independent_time_margins(margining)) {
- case USB4_MARGIN_CAP_1_TIME_MIN:
+ case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_MIN:
seq_puts(s, "# returns minimum between left and right time margins\n");
break;
- case USB4_MARGIN_CAP_1_TIME_LR:
+ case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR:
seq_puts(s, "# returns left or right margin\n");
break;
- case USB4_MARGIN_CAP_1_TIME_BOTH:
+ case USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_BOTH:
seq_puts(s, "# returns both left and right margins\n");
break;
+ case USB4_MARGIN_CAP_TIME_INDP_GEN_4_MIN:
+ seq_puts(s, "# returns minimum between left and right time margins in both lower and upper eye\n");
+ break;
+ case USB4_MARGIN_CAP_TIME_INDP_GEN_4_BOTH:
+ seq_puts(s, "# returns both left and right margins of both upper and lower eye\n");
+ break;
+ case USB4_MARGIN_CAP_TIME_INDP_UNKNOWN:
+ tb_port_warn(margining->port,
+ "failed to parse independent time margining capabilities\n");
+ ret = -EIO;
+ goto out;
}
seq_printf(s, "# time margin steps: %u\n",
@@ -645,19 +731,43 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
seq_puts(s, "# time margining: no\n");
}
+out:
mutex_unlock(&tb->lock);
- return 0;
+ return ret;
}
DEBUGFS_ATTR_RO(margining_caps);
+static const struct {
+ enum usb4_margining_lane lane;
+ const char *name;
+} lane_names[] = {
+ {
+ .lane = USB4_MARGINING_LANE_RX0,
+ .name = "0",
+ },
+ {
+ .lane = USB4_MARGINING_LANE_RX1,
+ .name = "1",
+ },
+ {
+ .lane = USB4_MARGINING_LANE_RX2,
+ .name = "2",
+ },
+ {
+ .lane = USB4_MARGINING_LANE_ALL,
+ .name = "all",
+ },
+};
+
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_margining *margining = s->private;
- struct tb *tb = margining->port->sw->tb;
- int ret = 0;
+ struct tb_port *port = margining->port;
+ struct tb *tb = port->sw->tb;
+ int lane = -1;
char *buf;
buf = validate_and_copy_from_user(user_buf, &count);
@@ -666,57 +776,60 @@ margining_lanes_write(struct file *file, const char __user *user_buf,
buf[count - 1] = '\0';
- if (mutex_lock_interruptible(&tb->lock)) {
- ret = -ERESTARTSYS;
- goto out_free;
+ for (int i = 0; i < ARRAY_SIZE(lane_names); i++) {
+ if (!strcmp(buf, lane_names[i].name)) {
+ lane = lane_names[i].lane;
+ break;
+ }
}
- if (!strcmp(buf, "0")) {
- margining->lanes = 0;
- } else if (!strcmp(buf, "1")) {
- margining->lanes = 1;
- } else if (!strcmp(buf, "all")) {
- /* Needs to be supported */
- if (both_lanes(margining))
- margining->lanes = 7;
- else
- ret = -EINVAL;
- } else {
- ret = -EINVAL;
- }
+ free_page((unsigned long)buf);
- mutex_unlock(&tb->lock);
+ if (lane == -1)
+ return -EINVAL;
-out_free:
- free_page((unsigned long)buf);
- return ret < 0 ? ret : count;
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
+ if (lane == USB4_MARGINING_LANE_ALL && !all_lanes(margining))
+ return -EINVAL;
+ /*
+ * Enabling on RX2 requires that it is supported by the
+ * USB4 port.
+ */
+ if (lane == USB4_MARGINING_LANE_RX2 && !margining->asym_rx)
+ return -EINVAL;
+
+ margining->lanes = lane;
+ }
+
+ return count;
}
static int margining_lanes_show(struct seq_file *s, void *not_used)
{
struct tb_margining *margining = s->private;
- struct tb *tb = margining->port->sw->tb;
- unsigned int lanes;
-
- if (mutex_lock_interruptible(&tb->lock))
- return -ERESTARTSYS;
+ struct tb_port *port = margining->port;
+ struct tb *tb = port->sw->tb;
- lanes = margining->lanes;
- if (both_lanes(margining)) {
- 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");
+ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &tb->lock) {
+ for (int i = 0; i < ARRAY_SIZE(lane_names); i++) {
+ if (lane_names[i].lane == USB4_MARGINING_LANE_ALL &&
+ !all_lanes(margining))
+ continue;
+ if (lane_names[i].lane == USB4_MARGINING_LANE_RX2 &&
+ !margining->asym_rx)
+ continue;
+
+ if (i != 0)
+ seq_putc(s, ' ');
+
+ if (lane_names[i].lane == margining->lanes)
+ seq_printf(s, "[%s]", lane_names[i].name);
+ else
+ seq_printf(s, "%s", lane_names[i].name);
+ }
+ seq_puts(s, "\n");
}
- mutex_unlock(&tb->lock);
return 0;
}
DEBUGFS_ATTR_RW(margining_lanes);
@@ -1004,13 +1117,16 @@ static int margining_run_sw(struct tb_margining *margining,
if (ret)
break;
- if (margining->lanes == USB4_MARGIN_SW_LANE_0)
+ if (margining->lanes == USB4_MARGINING_LANE_RX0)
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
margining->results[1]);
- else if (margining->lanes == USB4_MARGIN_SW_LANE_1)
+ else if (margining->lanes == USB4_MARGINING_LANE_RX1)
errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
margining->results[1]);
- else if (margining->lanes == USB4_MARGIN_SW_ALL_LANES)
+ else if (margining->lanes == USB4_MARGINING_LANE_RX2)
+ errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
+ margining->results[1]);
+ else if (margining->lanes == USB4_MARGINING_LANE_ALL)
errors = margining->results[1];
/* Any errors stop the test */
@@ -1030,6 +1146,31 @@ out_stop:
return ret;
}
+static int validate_margining(struct tb_margining *margining)
+{
+ /*
+ * For running on RX2 the link must be asymmetric with 3
+ * receivers. Because this is can change dynamically, check it
+ * here before we start the margining and report back error if
+ * expectations are not met.
+ */
+ if (margining->lanes == USB4_MARGINING_LANE_RX2) {
+ int ret;
+
+ ret = tb_port_get_link_width(margining->port);
+ if (ret < 0)
+ return ret;
+ if (ret != TB_LINK_WIDTH_ASYM_RX) {
+ tb_port_warn(margining->port, "link is %s expected %s",
+ tb_width_name(ret),
+ tb_width_name(TB_LINK_WIDTH_ASYM_RX));
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static int margining_run_write(void *data, u64 val)
{
struct tb_margining *margining = data;
@@ -1050,6 +1191,10 @@ static int margining_run_write(void *data, u64 val)
goto out_rpm_put;
}
+ ret = validate_margining(margining);
+ if (ret)
+ goto out_unlock;
+
if (tb_is_upstream_port(port))
down_sw = sw;
else if (port->remote)
@@ -1080,6 +1225,7 @@ static int margining_run_write(void *data, u64 val)
.time = margining->time,
.voltage_time_offset = margining->voltage_time_offset,
.right_high = margining->right_high,
+ .upper_eye = margining->upper_eye,
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
};
@@ -1095,6 +1241,7 @@ static int margining_run_write(void *data, u64 val)
.lanes = margining->lanes,
.time = margining->time,
.right_high = margining->right_high,
+ .upper_eye = margining->upper_eye,
.optional_voltage_offset_range = margining->optional_voltage_offset_range,
};
@@ -1104,7 +1251,7 @@ static int margining_run_write(void *data, u64 val)
margining->lanes);
ret = usb4_port_hw_margin(port, margining->target, margining->index, &params,
- margining->results);
+ margining->results, ARRAY_SIZE(margining->results));
}
if (down_sw)
@@ -1132,13 +1279,12 @@ static ssize_t margining_results_write(struct file *file,
return -ERESTARTSYS;
/* Just clear the results */
- margining->results[0] = 0;
- margining->results[1] = 0;
+ memset(margining->results, 0, sizeof(margining->results));
if (margining->software) {
/* Clear the error counters */
margining_modify_error_counter(margining,
- USB4_MARGIN_SW_ALL_LANES,
+ USB4_MARGINING_LANE_ALL,
USB4_MARGIN_SW_ERROR_COUNTER_CLEAR);
}
@@ -1151,10 +1297,10 @@ static void voltage_margin_show(struct seq_file *s,
{
unsigned int tmp, voltage;
- tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_MARGIN_MASK, val);
+ tmp = FIELD_GET(USB4_MARGIN_HW_RES_MARGIN_MASK, val);
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)
+ if (val & USB4_MARGIN_HW_RES_EXCEEDS)
seq_puts(s, " exceeds maximum");
seq_puts(s, "\n");
if (margining->optional_voltage_offset_range)
@@ -1166,14 +1312,55 @@ static void time_margin_show(struct seq_file *s,
{
unsigned int tmp, interval;
- tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_MARGIN_MASK, val);
+ tmp = FIELD_GET(USB4_MARGIN_HW_RES_MARGIN_MASK, val);
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)
+ if (val & USB4_MARGIN_HW_RES_EXCEEDS)
seq_puts(s, " exceeds maximum");
seq_puts(s, "\n");
}
+static u8 margining_hw_result_val(const u32 *results,
+ enum usb4_margining_lane lane,
+ bool right_high)
+{
+ u32 val;
+
+ if (lane == USB4_MARGINING_LANE_RX0)
+ val = results[1];
+ else if (lane == USB4_MARGINING_LANE_RX1)
+ val = results[1] >> USB4_MARGIN_HW_RES_LANE_SHIFT;
+ else if (lane == USB4_MARGINING_LANE_RX2)
+ val = results[2];
+ else
+ val = 0;
+
+ return right_high ? val : val >> USB4_MARGIN_HW_RES_LL_SHIFT;
+}
+
+static void margining_hw_result_format(struct seq_file *s,
+ const struct tb_margining *margining,
+ enum usb4_margining_lane lane)
+{
+ u8 val;
+
+ if (margining->time) {
+ val = margining_hw_result_val(margining->results, lane, true);
+ seq_printf(s, "# lane %u right time margin: ", lane);
+ time_margin_show(s, margining, val);
+ val = margining_hw_result_val(margining->results, lane, false);
+ seq_printf(s, "# lane %u left time margin: ", lane);
+ time_margin_show(s, margining, val);
+ } else {
+ val = margining_hw_result_val(margining->results, lane, true);
+ seq_printf(s, "# lane %u high voltage margin: ", lane);
+ voltage_margin_show(s, margining, val);
+ val = margining_hw_result_val(margining->results, lane, false);
+ seq_printf(s, "# lane %u low voltage margin: ", lane);
+ voltage_margin_show(s, margining, val);
+ }
+}
+
static int margining_results_show(struct seq_file *s, void *not_used)
{
struct tb_margining *margining = s->private;
@@ -1186,69 +1373,46 @@ static int margining_results_show(struct seq_file *s, void *not_used)
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);
- }
+ for (int i = 1; i < ARRAY_SIZE(margining->results); i++)
+ seq_printf(s, "0x%08x\n", margining->results[i]);
+
+ if (margining->lanes == USB4_MARGINING_LANE_ALL) {
+ margining_hw_result_format(s, margining,
+ USB4_MARGINING_LANE_RX0);
+ margining_hw_result_format(s, margining,
+ USB4_MARGINING_LANE_RX1);
+ if (margining->asym_rx)
+ margining_hw_result_format(s, margining,
+ USB4_MARGINING_LANE_RX2);
} 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);
- }
+ margining_hw_result_format(s, margining,
+ margining->lanes);
}
} else {
u32 lane_errors, result;
seq_printf(s, "0x%08x\n", margining->results[1]);
- result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
- if (result == USB4_MARGIN_SW_LANE_0 ||
- result == USB4_MARGIN_SW_ALL_LANES) {
+ result = FIELD_GET(USB4_MARGIN_SW_LANES_MASK, margining->results[0]);
+ if (result == USB4_MARGINING_LANE_RX0 ||
+ result == USB4_MARGINING_LANE_ALL) {
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK,
margining->results[1]);
seq_printf(s, "# lane 0 errors: %u\n", lane_errors);
}
- if (result == USB4_MARGIN_SW_LANE_1 ||
- result == USB4_MARGIN_SW_ALL_LANES) {
+ if (result == USB4_MARGINING_LANE_RX1 ||
+ result == USB4_MARGINING_LANE_ALL) {
lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK,
margining->results[1]);
seq_printf(s, "# lane 1 errors: %u\n", lane_errors);
}
+ if (margining->asym_rx &&
+ (result == USB4_MARGINING_LANE_RX2 ||
+ result == USB4_MARGINING_LANE_ALL)) {
+ lane_errors = FIELD_GET(USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK,
+ margining->results[1]);
+ seq_printf(s, "# lane 2 errors: %u\n", lane_errors);
+ }
}
mutex_unlock(&tb->lock);
@@ -1382,6 +1546,55 @@ static int margining_margin_show(struct seq_file *s, void *not_used)
}
DEBUGFS_ATTR_RW(margining_margin);
+static ssize_t margining_eye_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';
+
+ scoped_cond_guard(mutex_intr, ret = -ERESTARTSYS, &tb->lock) {
+ if (!strcmp(buf, "lower"))
+ usb4->margining->upper_eye = false;
+ else if (!strcmp(buf, "upper"))
+ usb4->margining->upper_eye = true;
+ else
+ ret = -EINVAL;
+ }
+
+ free_page((unsigned long)buf);
+ return ret ? ret : count;
+}
+
+static int margining_eye_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;
+
+ scoped_guard(mutex_intr, &tb->lock) {
+ if (usb4->margining->upper_eye)
+ seq_puts(s, "lower [upper]\n");
+ else
+ seq_puts(s, "[lower] upper\n");
+
+ return 0;
+ }
+
+ return -ERESTARTSYS;
+}
+DEBUGFS_ATTR_RW(margining_eye);
+
static struct tb_margining *margining_alloc(struct tb_port *port,
struct device *dev,
enum usb4_sb_target target,
@@ -1392,6 +1605,12 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
unsigned int val;
int ret;
+ ret = tb_port_get_link_generation(port);
+ if (ret < 0) {
+ tb_port_warn(port, "failed to read link generation\n");
+ return NULL;
+ }
+
margining = kzalloc(sizeof(*margining), GFP_KERNEL);
if (!margining)
return NULL;
@@ -1400,8 +1619,11 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
margining->target = target;
margining->index = index;
margining->dev = dev;
+ margining->gen = ret;
+ margining->asym_rx = tb_port_width_supported(port, TB_LINK_WIDTH_ASYM_RX);
- ret = usb4_port_margining_caps(port, target, index, margining->caps);
+ ret = usb4_port_margining_caps(port, target, index, margining->caps,
+ ARRAY_SIZE(margining->caps));
if (ret) {
kfree(margining);
return NULL;
@@ -1411,10 +1633,17 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
if (supports_software(margining))
margining->software = true;
- val = FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK, margining->caps[0]);
- margining->voltage_steps = val;
- val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
- margining->max_voltage_offset = 74 + val * 2;
+ if (margining->gen < 4) {
+ val = FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK, margining->caps[0]);
+ margining->voltage_steps = val;
+ val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
+ margining->max_voltage_offset = 74 + val * 2;
+ } else {
+ val = FIELD_GET(USB4_MARGIN_CAP_2_VOLTAGE_STEPS_MASK, margining->caps[2]);
+ margining->voltage_steps = val;
+ val = FIELD_GET(USB4_MARGIN_CAP_2_MAX_VOLTAGE_OFFSET_MASK, margining->caps[2]);
+ 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,
@@ -1456,11 +1685,10 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
debugfs_create_file("results", 0600, dir, margining,
&margining_results_fops);
debugfs_create_file("test", 0600, dir, margining, &margining_test_fops);
- if (independent_voltage_margins(margining) == USB4_MARGIN_CAP_0_VOLTAGE_HL ||
+ if (independent_voltage_margins(margining) == USB4_MARGIN_CAP_VOLTAGE_INDP_GEN_2_3_HL ||
(supports_time(margining) &&
- independent_time_margins(margining) == USB4_MARGIN_CAP_1_TIME_LR))
- debugfs_create_file("margin", 0600, dir, margining,
- &margining_margin_fops);
+ independent_time_margins(margining) == USB4_MARGIN_CAP_TIME_INDP_GEN_2_3_LR))
+ debugfs_create_file("margin", 0600, dir, margining, &margining_margin_fops);
margining->error_counter = USB4_MARGIN_SW_ERROR_COUNTER_CLEAR;
margining->dwell_time = MIN_DWELL_TIME;
@@ -1477,6 +1705,10 @@ static struct tb_margining *margining_alloc(struct tb_port *port,
debugfs_create_file("dwell_time", DEBUGFS_MODE, dir, margining,
&margining_dwell_time_fops);
}
+
+ if (margining->gen >= 4)
+ debugfs_create_file("eye", 0600, dir, port, &margining_eye_fops);
+
return margining;
}
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 7af2642b97cb..1257dd3ce7e6 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -1340,18 +1340,18 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (res)
return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n");
- res = pcim_iomap_regions(pdev, 1 << 0, "thunderbolt");
- if (res)
- return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
-
nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL);
if (!nhi)
return -ENOMEM;
nhi->pdev = pdev;
nhi->ops = (const struct tb_nhi_ops *)id->driver_data;
- /* cannot fail - table is allocated in pcim_iomap_regions */
- nhi->iobase = pcim_iomap_table(pdev)[0];
+
+ nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt");
+ res = PTR_ERR_OR_ZERO(nhi->iobase);
+ if (res)
+ return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n");
+
nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff;
dev_dbg(dev, "total paths: %d\n", nhi->hop_count);
diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h
index dbcad25ead50..5391502a4b87 100644
--- a/drivers/thunderbolt/sb_regs.h
+++ b/drivers/thunderbolt/sb_regs.h
@@ -49,7 +49,7 @@ enum usb4_sb_opcode {
/* USB4_SB_OPCODE_READ_LANE_MARGINING_CAP */
#define USB4_MARGIN_CAP_0_MODES_HW BIT(0)
#define USB4_MARGIN_CAP_0_MODES_SW BIT(1)
-#define USB4_MARGIN_CAP_0_2_LANES BIT(2)
+#define USB4_MARGIN_CAP_0_ALL_LANES BIT(2)
#define USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK GENMASK(4, 3)
#define USB4_MARGIN_CAP_0_VOLTAGE_MIN 0x0
#define USB4_MARGIN_CAP_0_VOLTAGE_HL 0x1
@@ -69,34 +69,44 @@ enum usb4_sb_opcode {
#define USB4_MARGIN_CAP_1_TIME_OFFSET_MASK GENMASK(20, 16)
#define USB4_MARGIN_CAP_1_MIN_BER_MASK GENMASK(25, 21)
#define USB4_MARGIN_CAP_1_MAX_BER_MASK GENMASK(30, 26)
+#define USB4_MARGIN_CAP_2_MODES_HW BIT(0)
+#define USB4_MARGIN_CAP_2_MODES_SW BIT(1)
+#define USB4_MARGIN_CAP_2_TIME BIT(2)
+#define USB4_MARGIN_CAP_2_MAX_VOLTAGE_OFFSET_MASK GENMASK(8, 3)
+#define USB4_MARGIN_CAP_2_VOLTAGE_STEPS_MASK GENMASK(15, 9)
+#define USB4_MARGIN_CAP_2_VOLTAGE_INDP_MASK GENMASK(17, 16)
+#define USB4_MARGIN_CAP_2_VOLTAGE_MIN 0x0
+#define USB4_MARGIN_CAP_2_VOLTAGE_BOTH 0x1
+#define USB4_MARGIN_CAP_2_TIME_INDP_MASK GENMASK(19, 18)
+#define USB4_MARGIN_CAP_2_TIME_MIN 0x0
+#define USB4_MARGIN_CAP_2_TIME_BOTH 0x1
/* USB4_SB_OPCODE_RUN_HW_LANE_MARGINING */
#define USB4_MARGIN_HW_TIME BIT(3)
-#define USB4_MARGIN_HW_RH BIT(4)
+#define USB4_MARGIN_HW_RHU 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)
-#define USB4_MARGIN_HW_RES_1_EXCEEDS BIT(7)
-/* Different lane margin shifts */
-#define USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT 8
-#define USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT 16
-#define USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT 24
+#define USB4_MARGIN_HW_RES_MARGIN_MASK GENMASK(6, 0)
+#define USB4_MARGIN_HW_RES_EXCEEDS BIT(7)
+
+/* Shifts for parsing the lane results */
+#define USB4_MARGIN_HW_RES_LANE_SHIFT 16
+#define USB4_MARGIN_HW_RES_LL_SHIFT 8
/* USB4_SB_OPCODE_RUN_SW_LANE_MARGINING */
#define USB4_MARGIN_SW_LANES_MASK GENMASK(2, 0)
-#define USB4_MARGIN_SW_LANE_0 0x0
-#define USB4_MARGIN_SW_LANE_1 0x1
-#define USB4_MARGIN_SW_ALL_LANES 0x7
#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_VT_MASK GENMASK(12, 6)
#define USB4_MARGIN_SW_COUNTER_MASK GENMASK(14, 13)
+#define USB4_MARGIN_SW_UPPER_EYE BIT(15)
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_0_MASK GENMASK(3, 0)
#define USB4_MARGIN_SW_ERR_COUNTER_LANE_1_MASK GENMASK(7, 4)
+#define USB4_MARGIN_SW_ERR_COUNTER_LANE_2_MASK GENMASK(11, 8)
#endif
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 6737188f2581..ddbf0cd78377 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -1367,11 +1367,18 @@ enum usb4_margin_sw_error_counter {
USB4_MARGIN_SW_ERROR_COUNTER_STOP,
};
+enum usb4_margining_lane {
+ USB4_MARGINING_LANE_RX0 = 0,
+ USB4_MARGINING_LANE_RX1 = 1,
+ USB4_MARGINING_LANE_RX2 = 2,
+ USB4_MARGINING_LANE_ALL = 7,
+};
+
/**
* struct usb4_port_margining_params - USB4 margining parameters
* @error_counter: Error counter operation for software margining
* @ber_level: Current BER level contour value
- * @lanes: %0, %1 or %7 (all)
+ * @lanes: Lanes to enable for the margining operation
* @voltage_time_offset: Offset for voltage / time for software margining
* @optional_voltage_offset_range: Enable optional extended voltage range
* @right_high: %false if left/low margin test is performed, %true if right/high
@@ -1380,18 +1387,19 @@ enum usb4_margin_sw_error_counter {
struct usb4_port_margining_params {
enum usb4_margin_sw_error_counter error_counter;
u32 ber_level;
- u32 lanes;
+ enum usb4_margining_lane lanes;
u32 voltage_time_offset;
bool optional_voltage_offset_range;
bool right_high;
+ bool upper_eye;
bool time;
};
int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
- u8 index, u32 *caps);
+ u8 index, u32 *caps, size_t ncaps);
int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
u8 index, const struct usb4_port_margining_params *params,
- u32 *results);
+ u32 *results, size_t nresults);
int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
u8 index, const struct usb4_port_margining_params *params,
u32 *results);
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index 402fdf8b1cde..e51d01671d8e 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -1631,11 +1631,12 @@ int usb4_port_asym_start(struct tb_port *port)
* @target: Sideband target
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @caps: Array with at least two elements to hold the results
+ * @ncaps: Number of elements in the caps array
*
* Reads the USB4 port lane margining capabilities into @caps.
*/
int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
- u8 index, u32 *caps)
+ u8 index, u32 *caps, size_t ncaps)
{
int ret;
@@ -1645,7 +1646,7 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
return ret;
return usb4_port_sb_read(port, target, index, USB4_SB_DATA, caps,
- sizeof(*caps) * 2);
+ sizeof(*caps) * ncaps);
}
/**
@@ -1654,14 +1655,15 @@ int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
* @target: Sideband target
* @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @params: Parameters for USB4 hardware margining
- * @results: Array with at least two elements to hold the results
+ * @results: Array to hold the results
+ * @nresults: Number of elements in the results array
*
* Runs hardware lane margining on USB4 port and returns the result in
* @results.
*/
int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
u8 index, const struct usb4_port_margining_params *params,
- u32 *results)
+ u32 *results, size_t nresults)
{
u32 val;
int ret;
@@ -1672,8 +1674,8 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
val = params->lanes;
if (params->time)
val |= USB4_MARGIN_HW_TIME;
- if (params->right_high)
- val |= USB4_MARGIN_HW_RH;
+ if (params->right_high || params->upper_eye)
+ val |= USB4_MARGIN_HW_RHU;
if (params->ber_level)
val |= FIELD_PREP(USB4_MARGIN_HW_BER_MASK, params->ber_level);
if (params->optional_voltage_offset_range)
@@ -1690,7 +1692,7 @@ int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
return ret;
return usb4_port_sb_read(port, target, index, USB4_SB_DATA, results,
- sizeof(*results) * 2);
+ sizeof(*results) * nresults);
}
/**
@@ -1722,6 +1724,8 @@ int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
val |= USB4_MARGIN_SW_OPT_VOLTAGE;
if (params->right_high)
val |= USB4_MARGIN_SW_RH;
+ if (params->upper_eye)
+ val |= USB4_MARGIN_SW_UPPER_EYE;
val |= FIELD_PREP(USB4_MARGIN_SW_COUNTER_MASK, params->error_counter);
val |= FIELD_PREP(USB4_MARGIN_SW_VT_MASK, params->voltage_time_offset);