summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thunderbolt')
-rw-r--r--drivers/thunderbolt/Kconfig17
-rw-r--r--drivers/thunderbolt/debugfs.c648
-rw-r--r--drivers/thunderbolt/retimer.c53
-rw-r--r--drivers/thunderbolt/sb_regs.h18
-rw-r--r--drivers/thunderbolt/tb.h45
-rw-r--r--drivers/thunderbolt/usb4.c183
6 files changed, 669 insertions, 295 deletions
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 448fd2ec8f6e..0abdb69ee9f4 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -22,20 +22,25 @@ config USB4_DEBUGFS_WRITE
bool "Enable write by debugfs to configuration spaces (DANGEROUS)"
help
Enables writing to device configuration registers through
- debugfs interface.
+ debugfs interface. You can use tools such as Thunderbolt/USB4
+ debugging tools to access these registers. For more
+ information see:
+
+ https://github.com/intel/tbtools
Only enable this if you know what you are doing! Never enable
this for production systems or distro kernels.
config USB4_DEBUGFS_MARGINING
- bool "Expose receiver lane margining operations under USB4 ports (DANGEROUS)"
+ bool "Expose receiver lane margining operations under USB4 ports and retimers (DANGEROUS)"
depends on DEBUG_FS
depends on USB4_DEBUGFS_WRITE
help
- Enables hardware and software based receiver lane margining support
- under each USB4 port. Used for electrical quality and robustness
- validation during manufacturing. Should not be enabled by distro
- kernels.
+ Enables hardware and software based receiver lane margining
+ support under each USB4 port and retimer, including retimers
+ on the other side of the cable. Used for electrical quality
+ and robustness validation during manufacturing. Should not be
+ enabled by distro kernels.
config USB4_KUNIT_TEST
bool "KUnit tests" if !KUNIT_ALL_TESTS
diff --git a/drivers/thunderbolt/debugfs.c b/drivers/thunderbolt/debugfs.c
index 70b52aac3d97..11185cc1db92 100644
--- a/drivers/thunderbolt/debugfs.c
+++ b/drivers/thunderbolt/debugfs.c
@@ -7,6 +7,7 @@
* Mika Westerberg <mika.westerberg@linux.intel.com>
*/
+#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/pm_runtime.h>
#include <linux/uaccess.h>
@@ -33,6 +34,44 @@
#define COUNTER_SET_LEN 3
+/* Sideband registers and their sizes as defined in the USB4 spec */
+struct sb_reg {
+ unsigned int reg;
+ unsigned int size;
+};
+
+#define SB_MAX_SIZE 64
+
+/* Sideband registers for router */
+static const struct sb_reg port_sb_regs[] = {
+ { USB4_SB_VENDOR_ID, 4 },
+ { USB4_SB_PRODUCT_ID, 4 },
+ { USB4_SB_DEBUG_CONF, 4 },
+ { USB4_SB_DEBUG, 54 },
+ { USB4_SB_LRD_TUNING, 4 },
+ { USB4_SB_OPCODE, 4 },
+ { USB4_SB_METADATA, 4 },
+ { USB4_SB_LINK_CONF, 3 },
+ { USB4_SB_GEN23_TXFFE, 4 },
+ { USB4_SB_GEN4_TXFFE, 4 },
+ { USB4_SB_VERSION, 4 },
+ { USB4_SB_DATA, 64 },
+};
+
+/* Sideband registers for retimer */
+static const struct sb_reg retimer_sb_regs[] = {
+ { USB4_SB_VENDOR_ID, 4 },
+ { USB4_SB_PRODUCT_ID, 4 },
+ { USB4_SB_FW_VERSION, 4 },
+ { USB4_SB_LRD_TUNING, 4 },
+ { USB4_SB_OPCODE, 4 },
+ { USB4_SB_METADATA, 4 },
+ { USB4_SB_GEN23_TXFFE, 4 },
+ { USB4_SB_GEN4_TXFFE, 4 },
+ { USB4_SB_VERSION, 4 },
+ { USB4_SB_DATA, 64 },
+};
+
#define DEBUGFS_ATTR(__space, __write) \
static int __space ## _open(struct inode *inode, struct file *file) \
{ \
@@ -184,16 +223,167 @@ static ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
return regs_write(sw, NULL, user_buf, count, ppos);
}
+
+static bool parse_sb_line(char **line, u8 *reg, u8 *data, size_t data_size,
+ size_t *bytes_read)
+{
+ char *field, *token;
+ int i;
+
+ token = strsep(line, "\n");
+ if (!token)
+ return false;
+
+ /* Parse the register first */
+ field = strsep(&token, " ");
+ if (!field)
+ return false;
+ if (kstrtou8(field, 0, reg))
+ return false;
+
+ /* Then the values for the register, up to data_size */
+ for (i = 0; i < data_size; i++) {
+ field = strsep(&token, " ");
+ if (!field)
+ break;
+ if (kstrtou8(field, 0, &data[i]))
+ return false;
+ }
+
+ *bytes_read = i;
+ return true;
+}
+
+static ssize_t sb_regs_write(struct tb_port *port, const struct sb_reg *sb_regs,
+ size_t size, enum usb4_sb_target target, u8 index,
+ char *buf, size_t count, loff_t *ppos)
+{
+ u8 reg, data[SB_MAX_SIZE];
+ size_t bytes_read;
+ char *line = buf;
+
+ /* User did hardware changes behind the driver's back */
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+ /*
+ * For sideband registers we accept:
+ * reg b0 b1 b2...\n
+ *
+ * Here "reg" is the byte offset of the sideband register and "b0"..
+ * are the byte values. There can be less byte values than the register
+ * size. The leftovers will not be overwritten.
+ */
+ while (parse_sb_line(&line, &reg, data, ARRAY_SIZE(data), &bytes_read)) {
+ const struct sb_reg *sb_reg;
+ int ret;
+
+ /* At least one byte must be passed */
+ if (bytes_read < 1)
+ return -EINVAL;
+
+ /* Find the register */
+ sb_reg = NULL;
+ for (int i = 0; i < size; i++) {
+ if (sb_regs[i].reg == reg) {
+ sb_reg = &sb_regs[i];
+ break;
+ }
+ }
+
+ if (!sb_reg)
+ return -EINVAL;
+
+ if (bytes_read > sb_regs->size)
+ return -E2BIG;
+
+ ret = usb4_port_sb_write(port, target, index, sb_reg->reg, data,
+ bytes_read);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static ssize_t port_sb_regs_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 tb_switch *sw = port->sw;
+ struct tb *tb = sw->tb;
+ char *buf;
+ int ret;
+
+ buf = validate_and_copy_from_user(user_buf, &count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ pm_runtime_get_sync(&sw->dev);
+
+ if (mutex_lock_interruptible(&tb->lock)) {
+ ret = -ERESTARTSYS;
+ goto out_rpm_put;
+ }
+
+ ret = sb_regs_write(port, port_sb_regs, ARRAY_SIZE(port_sb_regs),
+ USB4_SB_TARGET_ROUTER, 0, buf, count, ppos);
+
+ mutex_unlock(&tb->lock);
+out_rpm_put:
+ pm_runtime_mark_last_busy(&sw->dev);
+ pm_runtime_put_autosuspend(&sw->dev);
+
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t retimer_sb_regs_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct tb_retimer *rt = s->private;
+ struct tb *tb = rt->tb;
+ char *buf;
+ int ret;
+
+ buf = validate_and_copy_from_user(user_buf, &count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ pm_runtime_get_sync(&rt->dev);
+
+ if (mutex_lock_interruptible(&tb->lock)) {
+ ret = -ERESTARTSYS;
+ goto out_rpm_put;
+ }
+
+ ret = sb_regs_write(rt->port, retimer_sb_regs, ARRAY_SIZE(retimer_sb_regs),
+ USB4_SB_TARGET_RETIMER, rt->index, buf, count, ppos);
+
+ mutex_unlock(&tb->lock);
+out_rpm_put:
+ pm_runtime_mark_last_busy(&rt->dev);
+ pm_runtime_put_autosuspend(&rt->dev);
+
+ return ret < 0 ? ret : count;
+}
#define DEBUGFS_MODE 0600
#else
#define port_regs_write NULL
#define switch_regs_write NULL
+#define port_sb_regs_write NULL
+#define retimer_sb_regs_write NULL
#define DEBUGFS_MODE 0400
#endif
#if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)
/**
* struct tb_margining - Lane margining support
+ * @port: USB4 port through which the margining operations are run
+ * @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)
* @caps: Port lane margining capabilities
* @results: Last lane margining results
* @lanes: %0, %1 or %7 (all)
@@ -210,6 +400,10 @@ static ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
* right/high
*/
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;
@@ -225,37 +419,37 @@ struct tb_margining {
bool right_high;
};
-static bool supports_software(const struct usb4_port *usb4)
+static bool supports_software(const struct tb_margining *margining)
{
- return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
+ return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
}
-static bool supports_hardware(const struct usb4_port *usb4)
+static bool supports_hardware(const struct tb_margining *margining)
{
- return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
+ return margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
}
-static bool both_lanes(const struct usb4_port *usb4)
+static bool both_lanes(const struct tb_margining *margining)
{
- return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
+ return margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
}
-static unsigned int independent_voltage_margins(const struct usb4_port *usb4)
+static unsigned int
+independent_voltage_margins(const struct tb_margining *margining)
{
- return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >>
- USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT;
+ return FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK, margining->caps[0]);
}
-static bool supports_time(const struct usb4_port *usb4)
+static bool supports_time(const struct tb_margining *margining)
{
- return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
+ return 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)
+static unsigned int
+independent_time_margins(const struct tb_margining *margining)
{
- return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >>
- USB4_MARGIN_CAP_1_TIME_INDP_SHIFT;
+ return FIELD_GET(USB4_MARGIN_CAP_1_TIME_INDP_MASK, margining->caps[1]);
}
static ssize_t
@@ -263,9 +457,8 @@ 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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
unsigned int val;
int ret = 0;
char *buf;
@@ -273,7 +466,7 @@ margining_ber_level_write(struct file *file, const char __user *user_buf,
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
- if (usb4->margining->software) {
+ if (margining->software) {
ret = -EINVAL;
goto out_unlock;
}
@@ -290,13 +483,13 @@ margining_ber_level_write(struct file *file, const char __user *user_buf,
if (ret)
goto out_free;
- if (val < usb4->margining->min_ber_level ||
- val > usb4->margining->max_ber_level) {
+ if (val < margining->min_ber_level ||
+ val > margining->max_ber_level) {
ret = -EINVAL;
goto out_free;
}
- usb4->margining->ber_level = val;
+ margining->ber_level = val;
out_free:
free_page((unsigned long)buf);
@@ -316,52 +509,50 @@ static void ber_level_show(struct seq_file *s, unsigned int 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;
+ const struct tb_margining *margining = s->private;
- if (usb4->margining->software)
+ if (margining->software)
return -EINVAL;
- ber_level_show(s, usb4->margining->ber_level);
+ ber_level_show(s, 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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
u32 cap0, cap1;
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
/* Dump the raw caps first */
- cap0 = usb4->margining->caps[0];
+ cap0 = margining->caps[0];
seq_printf(s, "0x%08x\n", cap0);
- cap1 = usb4->margining->caps[1];
+ cap1 = 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)) {
+ supports_software(margining) ? "yes" : "no");
+ if (supports_hardware(margining)) {
seq_puts(s, "# hardware margining: yes\n");
seq_puts(s, "# minimum BER level contour: ");
- ber_level_show(s, usb4->margining->min_ber_level);
+ ber_level_show(s, margining->min_ber_level);
seq_puts(s, "# maximum BER level contour: ");
- ber_level_show(s, usb4->margining->max_ber_level);
+ ber_level_show(s, margining->max_ber_level);
} else {
seq_puts(s, "# hardware margining: no\n");
}
seq_printf(s, "# both lanes simultaneously: %s\n",
- both_lanes(usb4) ? "yes" : "no");
+ both_lanes(margining) ? "yes" : "no");
seq_printf(s, "# voltage margin steps: %u\n",
- usb4->margining->voltage_steps);
+ margining->voltage_steps);
seq_printf(s, "# maximum voltage offset: %u mV\n",
- usb4->margining->max_voltage_offset);
+ margining->max_voltage_offset);
- switch (independent_voltage_margins(usb4)) {
+ switch (independent_voltage_margins(margining)) {
case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
seq_puts(s, "# returns minimum between high and low voltage margins\n");
break;
@@ -373,12 +564,12 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
break;
}
- if (supports_time(usb4)) {
+ 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");
- switch (independent_time_margins(usb4)) {
+ switch (independent_time_margins(margining)) {
case USB4_MARGIN_CAP_1_TIME_MIN:
seq_puts(s, "# returns minimum between left and right time margins\n");
break;
@@ -391,9 +582,9 @@ static int margining_caps_show(struct seq_file *s, void *not_used)
}
seq_printf(s, "# time margin steps: %u\n",
- usb4->margining->time_steps);
+ margining->time_steps);
seq_printf(s, "# maximum time offset: %u mUI\n",
- usb4->margining->max_time_offset);
+ margining->max_time_offset);
} else {
seq_puts(s, "# time margining: no\n");
}
@@ -408,9 +599,8 @@ 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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
int ret = 0;
char *buf;
@@ -426,13 +616,13 @@ margining_lanes_write(struct file *file, const char __user *user_buf,
}
if (!strcmp(buf, "0")) {
- usb4->margining->lanes = 0;
+ margining->lanes = 0;
} else if (!strcmp(buf, "1")) {
- usb4->margining->lanes = 1;
+ margining->lanes = 1;
} else if (!strcmp(buf, "all")) {
/* Needs to be supported */
- if (both_lanes(usb4))
- usb4->margining->lanes = 7;
+ if (both_lanes(margining))
+ margining->lanes = 7;
else
ret = -EINVAL;
} else {
@@ -448,16 +638,15 @@ out_free:
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
unsigned int lanes;
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
- lanes = usb4->margining->lanes;
- if (both_lanes(usb4)) {
+ lanes = margining->lanes;
+ if (both_lanes(margining)) {
if (!lanes)
seq_puts(s, "[0] 1 all\n");
else if (lanes == 1)
@@ -481,9 +670,8 @@ static ssize_t margining_mode_write(struct file *file,
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
int ret = 0;
char *buf;
@@ -499,13 +687,13 @@ static ssize_t margining_mode_write(struct file *file,
}
if (!strcmp(buf, "software")) {
- if (supports_software(usb4))
- usb4->margining->software = true;
+ if (supports_software(margining))
+ margining->software = true;
else
ret = -EINVAL;
} else if (!strcmp(buf, "hardware")) {
- if (supports_hardware(usb4))
- usb4->margining->software = false;
+ if (supports_hardware(margining))
+ margining->software = false;
else
ret = -EINVAL;
} else {
@@ -521,23 +709,22 @@ out_free:
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
const char *space = "";
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
- if (supports_software(usb4)) {
- if (usb4->margining->software)
+ if (supports_software(margining)) {
+ if (margining->software)
seq_puts(s, "[software]");
else
seq_puts(s, "software");
space = " ";
}
- if (supports_hardware(usb4)) {
- if (usb4->margining->software)
+ if (supports_hardware(margining)) {
+ if (margining->software)
seq_printf(s, "%shardware", space);
else
seq_printf(s, "%s[hardware]", space);
@@ -552,10 +739,10 @@ 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_margining *margining = data;
+ struct tb_port *port = margining->port;
+ struct device *dev = margining->dev;
struct tb_switch *sw = port->sw;
- struct tb_margining *margining;
struct tb_switch *down_sw;
struct tb *tb = sw->tb;
int ret, clx;
@@ -563,7 +750,7 @@ static int margining_run_write(void *data, u64 val)
if (val != 1)
return -EINVAL;
- pm_runtime_get_sync(&sw->dev);
+ pm_runtime_get_sync(dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
@@ -590,27 +777,33 @@ static int margining_run_write(void *data, u64 val)
clx = ret;
}
- 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,
+ tb_port_dbg(port,
+ "running software %s lane margining for %s lanes %u\n",
+ margining->time ? "time" : "voltage", dev_name(dev),
+ margining->lanes);
+ ret = usb4_port_sw_margin(port, margining->target, margining->index,
+ margining->lanes, margining->time,
margining->right_high,
USB4_MARGIN_SW_COUNTER_CLEAR);
if (ret)
goto out_clx;
- ret = usb4_port_sw_margin_errors(port, &margining->results[0]);
+ ret = usb4_port_sw_margin_errors(port, margining->target,
+ margining->index,
+ &margining->results[0]);
} else {
- tb_port_dbg(port, "running hardware %s lane margining for lanes %u\n",
- margining->time ? "time" : "voltage", margining->lanes);
+ tb_port_dbg(port,
+ "running hardware %s lane margining for %s lanes %u\n",
+ margining->time ? "time" : "voltage", dev_name(dev),
+ 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);
+ ret = usb4_port_hw_margin(port, margining->target, margining->index,
+ margining->lanes, margining->ber_level,
+ margining->time, margining->right_high,
+ margining->results);
}
out_clx:
@@ -619,8 +812,8 @@ out_clx:
out_unlock:
mutex_unlock(&tb->lock);
out_rpm_put:
- pm_runtime_mark_last_busy(&sw->dev);
- pm_runtime_put_autosuspend(&sw->dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
return ret;
}
@@ -632,16 +825,15 @@ static ssize_t margining_results_write(struct file *file,
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->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;
+ margining->results[0] = 0;
+ margining->results[1] = 0;
mutex_unlock(&tb->lock);
return count;
@@ -652,7 +844,7 @@ static void voltage_margin_show(struct seq_file *s,
{
unsigned int tmp, voltage;
- tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
+ tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_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)
@@ -665,7 +857,7 @@ static void time_margin_show(struct seq_file *s,
{
unsigned int tmp, interval;
- tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
+ tmp = FIELD_GET(USB4_MARGIN_HW_RES_1_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)
@@ -675,15 +867,12 @@ static void time_margin_show(struct seq_file *s,
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->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 */
@@ -745,9 +934,8 @@ static ssize_t margining_test_write(struct file *file,
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
int ret = 0;
char *buf;
@@ -762,10 +950,10 @@ static ssize_t margining_test_write(struct file *file,
goto out_free;
}
- if (!strcmp(buf, "time") && supports_time(usb4))
- usb4->margining->time = true;
+ if (!strcmp(buf, "time") && supports_time(margining))
+ margining->time = true;
else if (!strcmp(buf, "voltage"))
- usb4->margining->time = false;
+ margining->time = false;
else
ret = -EINVAL;
@@ -778,15 +966,14 @@ out_free:
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
- if (supports_time(usb4)) {
- if (usb4->margining->time)
+ if (supports_time(margining)) {
+ if (margining->time)
seq_puts(s, "voltage [time]\n");
else
seq_puts(s, "[voltage] time\n");
@@ -804,9 +991,8 @@ static ssize_t margining_margin_write(struct file *file,
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
int ret = 0;
char *buf;
@@ -821,18 +1007,18 @@ static ssize_t margining_margin_write(struct file *file,
goto out_free;
}
- if (usb4->margining->time) {
+ if (margining->time) {
if (!strcmp(buf, "left"))
- usb4->margining->right_high = false;
+ margining->right_high = false;
else if (!strcmp(buf, "right"))
- usb4->margining->right_high = true;
+ margining->right_high = true;
else
ret = -EINVAL;
} else {
if (!strcmp(buf, "low"))
- usb4->margining->right_high = false;
+ margining->right_high = false;
else if (!strcmp(buf, "high"))
- usb4->margining->right_high = true;
+ margining->right_high = true;
else
ret = -EINVAL;
}
@@ -846,20 +1032,19 @@ out_free:
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;
+ struct tb_margining *margining = s->private;
+ struct tb *tb = margining->port->sw->tb;
if (mutex_lock_interruptible(&tb->lock))
return -ERESTARTSYS;
- if (usb4->margining->time) {
- if (usb4->margining->right_high)
+ if (margining->time) {
+ if (margining->right_high)
seq_puts(s, "left [right]\n");
else
seq_puts(s, "[left] right\n");
} else {
- if (usb4->margining->right_high)
+ if (margining->right_high)
seq_puts(s, "low [high]\n");
else
seq_puts(s, "[low] high\n");
@@ -870,51 +1055,44 @@ static int margining_margin_show(struct seq_file *s, void *not_used)
}
DEBUGFS_ATTR_RW(margining_margin);
-static void margining_port_init(struct tb_port *port)
+static struct tb_margining *margining_alloc(struct tb_port *port,
+ struct device *dev,
+ enum usb4_sb_target target,
+ u8 index, struct dentry *parent)
{
struct tb_margining *margining;
- struct dentry *dir, *parent;
- struct usb4_port *usb4;
- char dir_name[10];
+ struct dentry *dir;
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;
+ return NULL;
+
+ margining->port = port;
+ margining->target = target;
+ margining->index = index;
+ margining->dev = dev;
- ret = usb4_port_margining_caps(port, margining->caps);
+ ret = usb4_port_margining_caps(port, target, index, margining->caps);
if (ret) {
kfree(margining);
- return;
+ return NULL;
}
- usb4->margining = margining;
-
/* Set the initial mode */
- if (supports_software(usb4))
+ if (supports_software(margining))
margining->software = true;
- val = (margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK) >>
- USB4_MARGIN_CAP_0_VOLTAGE_STEPS_SHIFT;
+ val = FIELD_GET(USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK, margining->caps[0]);
margining->voltage_steps = val;
- val = (margining->caps[0] & USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK) >>
- USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_SHIFT;
+ val = FIELD_GET(USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK, margining->caps[0]);
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;
+ if (supports_time(margining)) {
+ val = FIELD_GET(USB4_MARGIN_CAP_1_TIME_STEPS_MASK, margining->caps[1]);
margining->time_steps = val;
- val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_OFFSET_MASK) >>
- USB4_MARGIN_CAP_1_TIME_OFFSET_SHIFT;
+ val = FIELD_GET(USB4_MARGIN_CAP_1_TIME_OFFSET_MASK, margining->caps[1]);
/*
* Store it as mUI (milli Unit Interval) because we want
* to keep it as integer.
@@ -923,30 +1101,46 @@ static void margining_port_init(struct tb_port *port)
}
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;
+ if (supports_hardware(margining)) {
+ val = FIELD_GET(USB4_MARGIN_CAP_1_MIN_BER_MASK, margining->caps[1]);
margining->min_ber_level = val;
- val = (margining->caps[1] & USB4_MARGIN_CAP_1_MAX_BER_MASK) >>
- USB4_MARGIN_CAP_1_MAX_BER_SHIFT;
+ val = FIELD_GET(USB4_MARGIN_CAP_1_MAX_BER_MASK, margining->caps[1]);
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,
+ debugfs_create_file("ber_level_contour", 0400, dir, margining,
&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) == USB4_MARGIN_CAP_0_VOLTAGE_HL ||
- (supports_time(usb4) &&
- independent_time_margins(usb4) == USB4_MARGIN_CAP_1_TIME_LR))
- debugfs_create_file("margin", 0600, dir, port, &margining_margin_fops);
+ debugfs_create_file("caps", 0400, dir, margining, &margining_caps_fops);
+ debugfs_create_file("lanes", 0600, dir, margining, &margining_lanes_fops);
+ debugfs_create_file("mode", 0600, dir, margining, &margining_mode_fops);
+ debugfs_create_file("run", 0600, dir, margining, &margining_run_fops);
+ 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 ||
+ (supports_time(margining) &&
+ independent_time_margins(margining) == USB4_MARGIN_CAP_1_TIME_LR))
+ debugfs_create_file("margin", 0600, dir, margining,
+ &margining_margin_fops);
+ return margining;
+}
+
+static void margining_port_init(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);
+ port->usb4->margining = margining_alloc(port, &port->usb4->dev,
+ USB4_SB_TARGET_ROUTER, 0,
+ parent);
}
static void margining_port_remove(struct tb_port *port)
@@ -1020,11 +1214,27 @@ static void margining_xdomain_remove(struct tb_xdomain *xd)
downstream = tb_port_at(xd->route, parent_sw);
margining_port_remove(downstream);
}
+
+static void margining_retimer_init(struct tb_retimer *rt, struct dentry *debugfs_dir)
+{
+ rt->margining = margining_alloc(rt->port, &rt->dev,
+ USB4_SB_TARGET_RETIMER, rt->index,
+ debugfs_dir);
+}
+
+static void margining_retimer_remove(struct tb_retimer *rt)
+{
+ kfree(rt->margining);
+ rt->margining = NULL;
+}
#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) { }
+static inline void margining_retimer_init(struct tb_retimer *rt,
+ struct dentry *debugfs_dir) { }
+static inline void margining_retimer_remove(struct tb_retimer *rt) { }
#endif
static int port_clear_all_counters(struct tb_port *port)
@@ -1505,6 +1715,60 @@ out:
}
DEBUGFS_ATTR_RW(counters);
+static int sb_regs_show(struct tb_port *port, const struct sb_reg *sb_regs,
+ size_t size, enum usb4_sb_target target, u8 index,
+ struct seq_file *s)
+{
+ int ret, i;
+
+ seq_puts(s, "# register value\n");
+
+ for (i = 0; i < size; i++) {
+ const struct sb_reg *regs = &sb_regs[i];
+ u8 data[64];
+ int j;
+
+ memset(data, 0, sizeof(data));
+ ret = usb4_port_sb_read(port, target, index, regs->reg, data,
+ regs->size);
+ if (ret)
+ return ret;
+
+ seq_printf(s, "0x%02x", regs->reg);
+ for (j = 0; j < regs->size; j++)
+ seq_printf(s, " 0x%02x", data[j]);
+ seq_puts(s, "\n");
+ }
+
+ return 0;
+}
+
+static int port_sb_regs_show(struct seq_file *s, void *not_used)
+{
+ struct tb_port *port = s->private;
+ struct tb_switch *sw = port->sw;
+ struct tb *tb = sw->tb;
+ int ret;
+
+ pm_runtime_get_sync(&sw->dev);
+
+ if (mutex_lock_interruptible(&tb->lock)) {
+ ret = -ERESTARTSYS;
+ goto out_rpm_put;
+ }
+
+ ret = sb_regs_show(port, port_sb_regs, ARRAY_SIZE(port_sb_regs),
+ USB4_SB_TARGET_ROUTER, 0, s);
+
+ mutex_unlock(&tb->lock);
+out_rpm_put:
+ pm_runtime_mark_last_busy(&sw->dev);
+ pm_runtime_put_autosuspend(&sw->dev);
+
+ return ret;
+}
+DEBUGFS_ATTR_RW(port_sb_regs);
+
/**
* tb_switch_debugfs_init() - Add debugfs entries for router
* @sw: Pointer to the router
@@ -1539,6 +1803,9 @@ void tb_switch_debugfs_init(struct tb_switch *sw)
if (port->config.counters_support)
debugfs_create_file("counters", 0600, debugfs_dir, port,
&counters_fops);
+ if (port->usb4)
+ debugfs_create_file("sb_regs", DEBUGFS_MODE, debugfs_dir,
+ port, &port_sb_regs_fops);
}
margining_switch_init(sw);
@@ -1590,6 +1857,59 @@ void tb_service_debugfs_remove(struct tb_service *svc)
svc->debugfs_dir = NULL;
}
+static int retimer_sb_regs_show(struct seq_file *s, void *not_used)
+{
+ struct tb_retimer *rt = s->private;
+ struct tb *tb = rt->tb;
+ int ret;
+
+ pm_runtime_get_sync(&rt->dev);
+
+ if (mutex_lock_interruptible(&tb->lock)) {
+ ret = -ERESTARTSYS;
+ goto out_rpm_put;
+ }
+
+ ret = sb_regs_show(rt->port, retimer_sb_regs, ARRAY_SIZE(retimer_sb_regs),
+ USB4_SB_TARGET_RETIMER, rt->index, s);
+
+ mutex_unlock(&tb->lock);
+out_rpm_put:
+ pm_runtime_mark_last_busy(&rt->dev);
+ pm_runtime_put_autosuspend(&rt->dev);
+
+ return ret;
+}
+DEBUGFS_ATTR_RW(retimer_sb_regs);
+
+/**
+ * tb_retimer_debugfs_init() - Add debugfs directory for retimer
+ * @rt: Pointer to retimer structure
+ *
+ * Adds and populates retimer debugfs directory.
+ */
+void tb_retimer_debugfs_init(struct tb_retimer *rt)
+{
+ struct dentry *debugfs_dir;
+
+ debugfs_dir = debugfs_create_dir(dev_name(&rt->dev), tb_debugfs_root);
+ debugfs_create_file("sb_regs", DEBUGFS_MODE, debugfs_dir, rt,
+ &retimer_sb_regs_fops);
+ margining_retimer_init(rt, debugfs_dir);
+}
+
+/**
+ * tb_retimer_debugfs_remove() - Remove retimer debugfs directory
+ * @rt: Pointer to retimer structure
+ *
+ * Removes the retimer debugfs directory along with its contents.
+ */
+void tb_retimer_debugfs_remove(struct tb_retimer *rt)
+{
+ debugfs_lookup_and_remove(dev_name(&rt->dev), tb_debugfs_root);
+ margining_retimer_remove(rt);
+}
+
void tb_debugfs_init(void)
{
tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL);
diff --git a/drivers/thunderbolt/retimer.c b/drivers/thunderbolt/retimer.c
index 6eaaa5074ce8..721319329afa 100644
--- a/drivers/thunderbolt/retimer.c
+++ b/drivers/thunderbolt/retimer.c
@@ -14,7 +14,11 @@
#include "sb_regs.h"
#include "tb.h"
+#if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)
#define TB_MAX_RETIMER_INDEX 6
+#else
+#define TB_MAX_RETIMER_INDEX 2
+#endif
/**
* tb_retimer_nvm_read() - Read contents of retimer NVM
@@ -319,6 +323,8 @@ static ssize_t nvm_version_show(struct device *dev,
if (!rt->nvm)
ret = -EAGAIN;
+ else if (rt->no_nvm_upgrade)
+ ret = -EOPNOTSUPP;
else
ret = sysfs_emit(buf, "%x.%x\n", rt->nvm->major, rt->nvm->minor);
@@ -366,35 +372,29 @@ const struct device_type tb_retimer_type = {
.release = tb_retimer_release,
};
-static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status)
+static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status,
+ bool on_board)
{
struct tb_retimer *rt;
u32 vendor, device;
int ret;
- ret = usb4_port_retimer_read(port, index, USB4_SB_VENDOR_ID, &vendor,
- sizeof(vendor));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_VENDOR_ID, &vendor, sizeof(vendor));
if (ret) {
if (ret != -ENODEV)
tb_port_warn(port, "failed read retimer VendorId: %d\n", ret);
return ret;
}
- ret = usb4_port_retimer_read(port, index, USB4_SB_PRODUCT_ID, &device,
- sizeof(device));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_PRODUCT_ID, &device, sizeof(device));
if (ret) {
if (ret != -ENODEV)
tb_port_warn(port, "failed read retimer ProductId: %d\n", ret);
return ret;
}
- /*
- * Check that it supports NVM operations. If not then don't add
- * the device at all.
- */
- ret = usb4_port_retimer_nvm_sector_size(port, index);
- if (ret < 0)
- return ret;
rt = kzalloc(sizeof(*rt), GFP_KERNEL);
if (!rt)
@@ -407,6 +407,13 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status)
rt->port = port;
rt->tb = port->sw->tb;
+ /*
+ * Only support NVM upgrade for on-board retimers. The retimers
+ * on the other side of the connection.
+ */
+ if (!on_board || usb4_port_retimer_nvm_sector_size(port, index) <= 0)
+ rt->no_nvm_upgrade = true;
+
rt->dev.parent = &port->usb4->dev;
rt->dev.bus = &tb_bus_type;
rt->dev.type = &tb_retimer_type;
@@ -437,12 +444,14 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status)
pm_runtime_mark_last_busy(&rt->dev);
pm_runtime_use_autosuspend(&rt->dev);
+ tb_retimer_debugfs_init(rt);
return 0;
}
static void tb_retimer_remove(struct tb_retimer *rt)
{
dev_info(&rt->dev, "retimer disconnected\n");
+ tb_retimer_debugfs_remove(rt);
tb_nvm_free(rt->nvm);
device_unregister(&rt->dev);
}
@@ -485,7 +494,7 @@ static struct tb_retimer *tb_port_find_retimer(struct tb_port *port, u8 index)
int tb_retimer_scan(struct tb_port *port, bool add)
{
u32 status[TB_MAX_RETIMER_INDEX + 1] = {};
- int ret, i, last_idx = 0;
+ int ret, i, max, last_idx = 0;
/*
* Send broadcast RT to make sure retimer indices facing this
@@ -520,26 +529,28 @@ int tb_retimer_scan(struct tb_port *port, bool add)
break;
}
- tb_retimer_unset_inbound_sbtx(port);
-
- if (!last_idx)
- return 0;
-
- /* Add on-board retimers if they do not exist already */
+ max = i;
ret = 0;
- for (i = 1; i <= last_idx; i++) {
+
+ /* Add retimers if they do not exist already */
+ for (i = 1; i <= max; i++) {
struct tb_retimer *rt;
+ /* Skip cable retimers */
+ if (usb4_port_retimer_is_cable(port, i))
+ continue;
+
rt = tb_port_find_retimer(port, i);
if (rt) {
put_device(&rt->dev);
} else if (add) {
- ret = tb_retimer_add(port, i, status[i]);
+ ret = tb_retimer_add(port, i, status[i], i <= last_idx);
if (ret && ret != -EOPNOTSUPP)
break;
}
}
+ tb_retimer_unset_inbound_sbtx(port);
return ret;
}
diff --git a/drivers/thunderbolt/sb_regs.h b/drivers/thunderbolt/sb_regs.h
index f37a4320f10a..2a88edfc97b2 100644
--- a/drivers/thunderbolt/sb_regs.h
+++ b/drivers/thunderbolt/sb_regs.h
@@ -12,6 +12,10 @@
#define USB4_SB_VENDOR_ID 0x00
#define USB4_SB_PRODUCT_ID 0x01
+#define USB4_SB_FW_VERSION 0x02
+#define USB4_SB_DEBUG_CONF 0x05
+#define USB4_SB_DEBUG 0x06
+#define USB4_SB_LRD_TUNING 0x07
#define USB4_SB_OPCODE 0x08
enum usb4_sb_opcode {
@@ -22,6 +26,7 @@ enum usb4_sb_opcode {
USB4_SB_OPCODE_SET_INBOUND_SBTX = 0x5055534c, /* "LSUP" */
USB4_SB_OPCODE_UNSET_INBOUND_SBTX = 0x50555355, /* "USUP" */
USB4_SB_OPCODE_QUERY_LAST_RETIMER = 0x5453414c, /* "LAST" */
+ USB4_SB_OPCODE_QUERY_CABLE_RETIMER = 0x524c4243, /* "CBLR" */
USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE = 0x53534e47, /* "GNSS" */
USB4_SB_OPCODE_NVM_SET_OFFSET = 0x53504f42, /* "BOPS" */
USB4_SB_OPCODE_NVM_BLOCK_WRITE = 0x574b4c42, /* "BLKW" */
@@ -35,6 +40,10 @@ enum usb4_sb_opcode {
#define USB4_SB_METADATA 0x09
#define USB4_SB_METADATA_NVM_AUTH_WRITE_MASK GENMASK(5, 0)
+#define USB4_SB_LINK_CONF 0x0c
+#define USB4_SB_GEN23_TXFFE 0x0d
+#define USB4_SB_GEN4_TXFFE 0x0e
+#define USB4_SB_VERSION 0x0f
#define USB4_SB_DATA 0x12
/* USB4_SB_OPCODE_READ_LANE_MARGINING_CAP */
@@ -42,30 +51,21 @@ enum usb4_sb_opcode {
#define USB4_MARGIN_CAP_0_MODES_SW BIT(1)
#define USB4_MARGIN_CAP_0_2_LANES BIT(2)
#define USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK GENMASK(4, 3)
-#define USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT 3
#define USB4_MARGIN_CAP_0_VOLTAGE_MIN 0x0
#define USB4_MARGIN_CAP_0_VOLTAGE_HL 0x1
#define USB4_MARGIN_CAP_0_VOLTAGE_BOTH 0x2
#define USB4_MARGIN_CAP_0_TIME BIT(5)
#define USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK GENMASK(12, 6)
-#define USB4_MARGIN_CAP_0_VOLTAGE_STEPS_SHIFT 6
#define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK GENMASK(18, 13)
-#define USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_SHIFT 13
#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_INDP_SHIFT 9
#define USB4_MARGIN_CAP_1_TIME_MIN 0x0
#define USB4_MARGIN_CAP_1_TIME_LR 0x1
#define USB4_MARGIN_CAP_1_TIME_BOTH 0x2
#define USB4_MARGIN_CAP_1_TIME_STEPS_MASK GENMASK(15, 11)
-#define USB4_MARGIN_CAP_1_TIME_STEPS_SHIFT 11
#define USB4_MARGIN_CAP_1_TIME_OFFSET_MASK GENMASK(20, 16)
-#define USB4_MARGIN_CAP_1_TIME_OFFSET_SHIFT 16
#define USB4_MARGIN_CAP_1_MIN_BER_MASK GENMASK(25, 21)
-#define USB4_MARGIN_CAP_1_MIN_BER_SHIFT 21
#define USB4_MARGIN_CAP_1_MAX_BER_MASK GENMASK(30, 26)
-#define USB4_MARGIN_CAP_1_MAX_BER_SHIFT 26
-#define USB4_MARGIN_CAP_1_MAX_BER_SHIFT 26
/* USB4_SB_OPCODE_RUN_HW_LANE_MARGINING */
#define USB4_MARGIN_HW_TIME BIT(3)
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 18aae4ccaed5..b47f7873c847 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -329,6 +329,7 @@ struct usb4_port {
* @nvm: Pointer to the NVM if the retimer has one (%NULL otherwise)
* @no_nvm_upgrade: Prevent NVM upgrade of this retimer
* @auth_status: Status of last NVM authentication
+ * @margining: Pointer to margining structure if enabled
*/
struct tb_retimer {
struct device dev;
@@ -340,6 +341,9 @@ struct tb_retimer {
struct tb_nvm *nvm;
bool no_nvm_upgrade;
u32 auth_status;
+#ifdef CONFIG_USB4_DEBUGFS_MARGINING
+ struct tb_margining *margining;
+#endif
};
/**
@@ -1327,26 +1331,43 @@ int usb4_port_router_offline(struct tb_port *port);
int usb4_port_router_online(struct tb_port *port);
int usb4_port_enumerate_retimers(struct tb_port *port);
bool usb4_port_clx_supported(struct tb_port *port);
-int usb4_port_margining_caps(struct tb_port *port, u32 *caps);
bool usb4_port_asym_supported(struct tb_port *port);
int usb4_port_asym_set_link_width(struct tb_port *port, enum tb_link_width width);
int usb4_port_asym_start(struct tb_port *port);
-int usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
- unsigned int ber_level, bool timing, bool right_high,
- u32 *results);
-int usb4_port_sw_margin(struct tb_port *port, unsigned int lanes, bool timing,
+/**
+ * enum tb_sb_target - Sideband transaction target
+ * @USB4_SB_TARGET_ROUTER: Target is the router itself
+ * @USB4_SB_TARGET_PARTNER: Target is partner
+ * @USB4_SB_TARGET_RETIMER: Target is retimer
+ */
+enum usb4_sb_target {
+ USB4_SB_TARGET_ROUTER,
+ USB4_SB_TARGET_PARTNER,
+ USB4_SB_TARGET_RETIMER,
+};
+
+int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index,
+ u8 reg, void *buf, u8 size);
+int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u8 reg, const void *buf, u8 size);
+
+int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u32 *caps);
+int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, unsigned int lanes, unsigned int ber_level,
+ bool timing, bool right_high, u32 *results);
+int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, unsigned int lanes, bool timing,
bool right_high, u32 counter);
-int usb4_port_sw_margin_errors(struct tb_port *port, u32 *errors);
+int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u32 *errors);
int usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index);
int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index);
-int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
- u8 size);
-int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg,
- const void *buf, u8 size);
int usb4_port_retimer_is_last(struct tb_port *port, u8 index);
+int usb4_port_retimer_is_cable(struct tb_port *port, u8 index);
int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index);
int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index,
unsigned int address);
@@ -1445,6 +1466,8 @@ void tb_xdomain_debugfs_init(struct tb_xdomain *xd);
void tb_xdomain_debugfs_remove(struct tb_xdomain *xd);
void tb_service_debugfs_init(struct tb_service *svc);
void tb_service_debugfs_remove(struct tb_service *svc);
+void tb_retimer_debugfs_init(struct tb_retimer *rt);
+void tb_retimer_debugfs_remove(struct tb_retimer *rt);
#else
static inline void tb_debugfs_init(void) { }
static inline void tb_debugfs_exit(void) { }
@@ -1454,6 +1477,8 @@ static inline void tb_xdomain_debugfs_init(struct tb_xdomain *xd) { }
static inline void tb_xdomain_debugfs_remove(struct tb_xdomain *xd) { }
static inline void tb_service_debugfs_init(struct tb_service *svc) { }
static inline void tb_service_debugfs_remove(struct tb_service *svc) { }
+static inline void tb_retimer_debugfs_init(struct tb_retimer *rt) { }
+static inline void tb_retimer_debugfs_remove(struct tb_retimer *rt) { }
#endif
#endif
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index de480bf2a53d..4d83b65afb5b 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -17,12 +17,6 @@
#define USB4_DATA_RETRIES 3
#define USB4_DATA_DWORDS 16
-enum usb4_sb_target {
- USB4_SB_TARGET_ROUTER,
- USB4_SB_TARGET_PARTNER,
- USB4_SB_TARGET_RETIMER,
-};
-
#define USB4_NVM_READ_OFFSET_MASK GENMASK(23, 2)
#define USB4_NVM_READ_OFFSET_SHIFT 2
#define USB4_NVM_READ_LENGTH_MASK GENMASK(27, 24)
@@ -1289,8 +1283,20 @@ static int usb4_port_write_data(struct tb_port *port, const void *data,
dwords);
}
-static int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target,
- u8 index, u8 reg, void *buf, u8 size)
+/**
+ * usb4_port_sb_read() - Read from sideband register
+ * @port: USB4 port to read
+ * @target: Sideband target
+ * @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
+ * @reg: Sideband register index
+ * @buf: Buffer where the sideband data is copied
+ * @size: Size of @buf
+ *
+ * Reads data from sideband register @reg and copies it into @buf.
+ * Returns %0 in case of success and negative errno in case of failure.
+ */
+int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target, u8 index,
+ u8 reg, void *buf, u8 size)
{
size_t dwords = DIV_ROUND_UP(size, 4);
int ret;
@@ -1329,8 +1335,20 @@ static int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target,
return buf ? usb4_port_read_data(port, buf, dwords) : 0;
}
-static int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target,
- u8 index, u8 reg, const void *buf, u8 size)
+/**
+ * usb4_port_sb_write() - Write to sideband register
+ * @port: USB4 port to write
+ * @target: Sideband target
+ * @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
+ * @reg: Sideband register index
+ * @buf: Data to write
+ * @size: Size of @buf
+ *
+ * Writes @buf to sideband register @reg. Returns %0 in case of success
+ * and negative errno in case of failure.
+ */
+int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u8 reg, const void *buf, u8 size)
{
size_t dwords = DIV_ROUND_UP(size, 4);
int ret;
@@ -1610,26 +1628,31 @@ int usb4_port_asym_start(struct tb_port *port)
/**
* usb4_port_margining_caps() - Read USB4 port marginig capabilities
* @port: USB4 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
*
* Reads the USB4 port lane margining capabilities into @caps.
*/
-int usb4_port_margining_caps(struct tb_port *port, u32 *caps)
+int usb4_port_margining_caps(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u32 *caps)
{
int ret;
- ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
+ ret = usb4_port_sb_op(port, target, index,
USB4_SB_OPCODE_READ_LANE_MARGINING_CAP, 500);
if (ret)
return ret;
- return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
- USB4_SB_DATA, caps, sizeof(*caps) * 2);
+ return usb4_port_sb_read(port, target, index, USB4_SB_DATA, caps,
+ sizeof(*caps) * 2);
}
/**
* usb4_port_hw_margin() - Run hardware lane margining on port
* @port: USB4 port
+ * @target: Sideband target
+ * @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @lanes: Which lanes to run (must match the port capabilities). Can be
* %0, %1 or %7.
* @ber_level: BER level contour value
@@ -1640,9 +1663,9 @@ int usb4_port_margining_caps(struct tb_port *port, u32 *caps)
* Runs hardware lane margining on USB4 port and returns the result in
* @results.
*/
-int usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
- unsigned int ber_level, bool timing, bool right_high,
- u32 *results)
+int usb4_port_hw_margin(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, unsigned int lanes, unsigned int ber_level,
+ bool timing, bool right_high, u32 *results)
{
u32 val;
int ret;
@@ -1656,23 +1679,25 @@ int usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
val |= (ber_level << USB4_MARGIN_HW_BER_SHIFT) &
USB4_MARGIN_HW_BER_MASK;
- ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
- USB4_SB_METADATA, &val, sizeof(val));
+ ret = usb4_port_sb_write(port, target, index, USB4_SB_METADATA, &val,
+ sizeof(val));
if (ret)
return ret;
- ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
+ ret = usb4_port_sb_op(port, target, index,
USB4_SB_OPCODE_RUN_HW_LANE_MARGINING, 2500);
if (ret)
return ret;
- return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
- USB4_SB_DATA, results, sizeof(*results) * 2);
+ return usb4_port_sb_read(port, target, index, USB4_SB_DATA, results,
+ sizeof(*results) * 2);
}
/**
* usb4_port_sw_margin() - Run software lane margining on port
* @port: USB4 port
+ * @target: Sideband target
+ * @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @lanes: Which lanes to run (must match the port capabilities). Can be
* %0, %1 or %7.
* @timing: Perform timing margining instead of voltage
@@ -1683,7 +1708,8 @@ int usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
* counters by calling usb4_port_sw_margin_errors(). Returns %0 in
* success and negative errno otherwise.
*/
-int usb4_port_sw_margin(struct tb_port *port, unsigned int lanes, bool timing,
+int usb4_port_sw_margin(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, unsigned int lanes, bool timing,
bool right_high, u32 counter)
{
u32 val;
@@ -1697,34 +1723,37 @@ int usb4_port_sw_margin(struct tb_port *port, unsigned int lanes, bool timing,
val |= (counter << USB4_MARGIN_SW_COUNTER_SHIFT) &
USB4_MARGIN_SW_COUNTER_MASK;
- ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
- USB4_SB_METADATA, &val, sizeof(val));
+ ret = usb4_port_sb_write(port, target, index, USB4_SB_METADATA, &val,
+ sizeof(val));
if (ret)
return ret;
- return usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
+ return usb4_port_sb_op(port, target, index,
USB4_SB_OPCODE_RUN_SW_LANE_MARGINING, 2500);
}
/**
* usb4_port_sw_margin_errors() - Read the software margining error counters
* @port: USB4 port
+ * @target: Sideband target
+ * @index: Retimer index if taget is %USB4_SB_TARGET_RETIMER
* @errors: Error metadata is copied here.
*
* This reads back the software margining error counters from the port.
* Returns %0 in success and negative errno otherwise.
*/
-int usb4_port_sw_margin_errors(struct tb_port *port, u32 *errors)
+int usb4_port_sw_margin_errors(struct tb_port *port, enum usb4_sb_target target,
+ u8 index, u32 *errors)
{
int ret;
- ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
+ ret = usb4_port_sb_op(port, target, index,
USB4_SB_OPCODE_READ_SW_MARGIN_ERR, 150);
if (ret)
return ret;
- return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
- USB4_SB_METADATA, errors, sizeof(*errors));
+ return usb4_port_sb_read(port, target, index, USB4_SB_METADATA, errors,
+ sizeof(*errors));
}
static inline int usb4_port_retimer_op(struct tb_port *port, u8 index,
@@ -1777,68 +1806,51 @@ int usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index)
}
/**
- * usb4_port_retimer_read() - Read from retimer sideband registers
+ * usb4_port_retimer_is_last() - Is the retimer last on-board retimer
* @port: USB4 port
* @index: Retimer index
- * @reg: Sideband register to read
- * @buf: Data from @reg is stored here
- * @size: Number of bytes to read
*
- * Function reads retimer sideband registers starting from @reg. The
- * retimer is connected to @port at @index. Returns %0 in case of
- * success, and read data is copied to @buf. If there is no retimer
- * present at given @index returns %-ENODEV. In any other failure
- * returns negative errno.
+ * If the retimer at @index is last one (connected directly to the
+ * Type-C port) this function returns %1. If it is not returns %0. If
+ * the retimer is not present returns %-ENODEV. Otherwise returns
+ * negative errno.
*/
-int usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
- u8 size)
+int usb4_port_retimer_is_last(struct tb_port *port, u8 index)
{
- return usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, reg, buf,
- size);
-}
+ u32 metadata;
+ int ret;
-/**
- * usb4_port_retimer_write() - Write to retimer sideband registers
- * @port: USB4 port
- * @index: Retimer index
- * @reg: Sideband register to write
- * @buf: Data that is written starting from @reg
- * @size: Number of bytes to write
- *
- * Writes retimer sideband registers starting from @reg. The retimer is
- * connected to @port at @index. Returns %0 in case of success. If there
- * is no retimer present at given @index returns %-ENODEV. In any other
- * failure returns negative errno.
- */
-int usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg,
- const void *buf, u8 size)
-{
- return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, reg, buf,
- size);
+ ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_LAST_RETIMER,
+ 500);
+ if (ret)
+ return ret;
+
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata, sizeof(metadata));
+ return ret ? ret : metadata & 1;
}
/**
- * usb4_port_retimer_is_last() - Is the retimer last on-board retimer
+ * usb4_port_retimer_is_cable() - Is the retimer cable retimer
* @port: USB4 port
* @index: Retimer index
*
- * If the retimer at @index is last one (connected directly to the
- * Type-C port) this function returns %1. If it is not returns %0. If
- * the retimer is not present returns %-ENODEV. Otherwise returns
- * negative errno.
+ * If the retimer at @index is last cable retimer this function returns
+ * %1 and %0 if it is on-board retimer. In case a retimer is not present
+ * at @index returns %-ENODEV. Otherwise returns negative errno.
*/
-int usb4_port_retimer_is_last(struct tb_port *port, u8 index)
+int usb4_port_retimer_is_cable(struct tb_port *port, u8 index)
{
u32 metadata;
int ret;
- ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_LAST_RETIMER,
+ ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_CABLE_RETIMER,
500);
if (ret)
return ret;
- ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata,
- sizeof(metadata));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata, sizeof(metadata));
return ret ? ret : metadata & 1;
}
@@ -1863,8 +1875,8 @@ int usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index)
if (ret)
return ret;
- ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata,
- sizeof(metadata));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata, sizeof(metadata));
return ret ? ret : metadata & USB4_NVM_SECTOR_SIZE_MASK;
}
@@ -1889,8 +1901,8 @@ int usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index,
metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) &
USB4_NVM_SET_OFFSET_MASK;
- ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata,
- sizeof(metadata));
+ ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata, sizeof(metadata));
if (ret)
return ret;
@@ -1912,8 +1924,8 @@ static int usb4_port_retimer_nvm_write_next_block(void *data,
u8 index = info->index;
int ret;
- ret = usb4_port_retimer_write(port, index, USB4_SB_DATA,
- buf, dwords * 4);
+ ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_DATA, buf, dwords * 4);
if (ret)
return ret;
@@ -1992,8 +2004,8 @@ int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index,
u32 metadata, val;
int ret;
- ret = usb4_port_retimer_read(port, index, USB4_SB_OPCODE, &val,
- sizeof(val));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_OPCODE, &val, sizeof(val));
if (ret)
return ret;
@@ -2004,8 +2016,9 @@ int usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index,
return 0;
case -EAGAIN:
- ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA,
- &metadata, sizeof(metadata));
+ ret = usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata,
+ sizeof(metadata));
if (ret)
return ret;
@@ -2030,8 +2043,8 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress,
if (dwords < USB4_DATA_DWORDS)
metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT;
- ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata,
- sizeof(metadata));
+ ret = usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_METADATA, &metadata, sizeof(metadata));
if (ret)
return ret;
@@ -2039,8 +2052,8 @@ static int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress,
if (ret)
return ret;
- return usb4_port_retimer_read(port, index, USB4_SB_DATA, buf,
- dwords * 4);
+ return usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index,
+ USB4_SB_DATA, buf, dwords * 4);
}
/**