/**
* 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)
*/
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;
{
struct tb_margining *margining = data;
struct tb_port *port = margining->port;
+ struct device *dev = margining->dev;
struct tb_switch *sw = port->sw;
struct tb_switch *down_sw;
struct tb *tb = sw->tb;
if (val != 1)
return -EINVAL;
- pm_runtime_get_sync(&sw->dev);
+ pm_runtime_get_sync(dev);
if (mutex_lock_interruptible(&tb->lock)) {
ret = -ERESTARTSYS;
}
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, USB4_SB_TARGET_ROUTER, 0,
+ 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, USB4_SB_TARGET_ROUTER, 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, USB4_SB_TARGET_ROUTER, 0,
+ ret = usb4_port_hw_margin(port, margining->target, margining->index,
margining->lanes, margining->ber_level,
margining->time, margining->right_high,
margining->results);
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;
}
}
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, USB4_SB_TARGET_ROUTER, 0,
- margining->caps);
+ ret = usb4_port_margining_caps(port, target, index, margining->caps);
if (ret) {
kfree(margining);
- return;
+ return NULL;
}
/* Set the initial mode */
independent_time_margins(margining) == USB4_MARGIN_CAP_1_TIME_LR))
debugfs_create_file("margin", 0600, dir, margining,
&margining_margin_fops);
+ return margining;
+}
- usb4->margining = 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)
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)
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);
}
/**
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)
#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
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);
.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;
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)
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;
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
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;
}