Commit ba454a5d authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman
Browse files

Merge tag 'thunderbolt-for-v6.13-rc1' of...

Merge tag 'thunderbolt-for-v6.13-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt into usb-next

Mika writes:

thunderbolt: Changes for v6.13 merge window

This includes following USB4/Thunderbolt changes for the v6.13 merge
window:

  - Add Gen 4 receiver lane margining support.
  - Replace usage of deprecated PCI functions.

All these have been in linux-next with no reported issues.

* tag 'thunderbolt-for-v6.13-rc1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt:
  thunderbolt: Replace deprecated PCI functions
  thunderbolt: debugfs: Implement asymmetric lane margining
  thunderbolt: debugfs: Don't hardcode margining results size
  thunderbolt: debugfs: Refactor hardware margining result parsing
  thunderbolt: debugfs: Replace margining lane numbers with an enum
  thunderbolt: debugfs: Replace "both lanes" with "all lanes"
  thunderbolt: debugfs: Implement Gen 4 margining eye selection
  thunderbolt: debugfs: Add USB4 Gen 4 margining capabilities
  thunderbolt: Don't hardcode margining capabilities size
parents de9df030 40aeea50
Loading
Loading
Loading
Loading
+368 −136
Original line number Diff line number Diff line
@@ -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 @@ static ssize_t retimer_sb_regs_write(struct file *file,
 * @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 @@ static ssize_t retimer_sb_regs_write(struct file *file,
 * @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)
{
	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)
{
	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)
{
	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;
	}

	mutex_unlock(&tb->lock);

out_free:
	free_page((unsigned long)buf);
	return ret < 0 ? ret : count;

	if (lane == -1)
		return -EINVAL;

	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;
	struct tb_port *port = margining->port;
	struct tb *tb = port->sw->tb;

	if (mutex_lock_interruptible(&tb->lock))
		return -ERESTARTSYS;
	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;

	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");
			if (i != 0)
				seq_putc(s, ' ');

			if (lane_names[i].lane == margining->lanes)
				seq_printf(s, "[%s]", lane_names[i].name);
			else
			seq_puts(s, "0 [1]\n");
				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 @@ static int margining_run_sw(struct tb_margining *margining,
	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,89 +1312,107 @@ 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 int margining_results_show(struct seq_file *s, void *not_used)
static u8 margining_hw_result_val(const u32 *results,
				  enum usb4_margining_lane lane,
				  bool right_high)
{
	struct tb_margining *margining = s->private;
	struct tb *tb = margining->port->sw->tb;
	u32 val;

	if (mutex_lock_interruptible(&tb->lock))
		return -ERESTARTSYS;
	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;

	/* 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;
	return right_high ? val : val >> USB4_MARGIN_HW_RES_LL_SHIFT;
}

		seq_printf(s, "0x%08x\n", margining->results[1]);
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) {
			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: ");
		val = margining_hw_result_val(margining->results, lane, true);
		seq_printf(s, "# lane %u right time margin: ", lane);
		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: ");
		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 {
			if (!margining->lanes || margining->lanes == 7) {
				val = margining->results[1];
				seq_puts(s, "# lane 0 high voltage margin: ");
		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->results[1] >>
					USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
				seq_puts(s, "# lane 0 low voltage margin: ");
		val = margining_hw_result_val(margining->results, lane, false);
		seq_printf(s, "# lane %u low voltage margin: ", lane);
		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);
}

static int margining_results_show(struct seq_file *s, void *not_used)
{
	struct tb_margining *margining = s->private;
	struct tb *tb = margining->port->sw->tb;

	if (mutex_lock_interruptible(&tb->lock))
		return -ERESTARTSYS;

	/* 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) {
		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 {
			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;

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

+6 −6
Original line number Diff line number Diff line
@@ -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);

+21 −11

File changed.

Preview size limit exceeded, changes collapsed.

+12 −4

File changed.

Preview size limit exceeded, changes collapsed.

+11 −7

File changed.

Preview size limit exceeded, changes collapsed.