162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Debugfs interface
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020, Intel Corporation
662306a36Sopenharmony_ci * Authors: Gil Fine <gil.fine@intel.com>
762306a36Sopenharmony_ci *	    Mika Westerberg <mika.westerberg@linux.intel.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/debugfs.h>
1162306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1262306a36Sopenharmony_ci#include <linux/uaccess.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "tb.h"
1562306a36Sopenharmony_ci#include "sb_regs.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define PORT_CAP_V1_PCIE_LEN	1
1862306a36Sopenharmony_ci#define PORT_CAP_V2_PCIE_LEN	2
1962306a36Sopenharmony_ci#define PORT_CAP_POWER_LEN	2
2062306a36Sopenharmony_ci#define PORT_CAP_LANE_LEN	3
2162306a36Sopenharmony_ci#define PORT_CAP_USB3_LEN	5
2262306a36Sopenharmony_ci#define PORT_CAP_DP_V1_LEN	9
2362306a36Sopenharmony_ci#define PORT_CAP_DP_V2_LEN	14
2462306a36Sopenharmony_ci#define PORT_CAP_TMU_V1_LEN	8
2562306a36Sopenharmony_ci#define PORT_CAP_TMU_V2_LEN	10
2662306a36Sopenharmony_ci#define PORT_CAP_BASIC_LEN	9
2762306a36Sopenharmony_ci#define PORT_CAP_USB4_LEN	20
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define SWITCH_CAP_TMU_LEN	26
3062306a36Sopenharmony_ci#define SWITCH_CAP_BASIC_LEN	27
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define PATH_LEN		2
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define COUNTER_SET_LEN		3
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define DEBUGFS_ATTR(__space, __write)					\
3762306a36Sopenharmony_cistatic int __space ## _open(struct inode *inode, struct file *file)	\
3862306a36Sopenharmony_ci{									\
3962306a36Sopenharmony_ci	return single_open(file, __space ## _show, inode->i_private);	\
4062306a36Sopenharmony_ci}									\
4162306a36Sopenharmony_ci									\
4262306a36Sopenharmony_cistatic const struct file_operations __space ## _fops = {		\
4362306a36Sopenharmony_ci	.owner = THIS_MODULE,						\
4462306a36Sopenharmony_ci	.open = __space ## _open,					\
4562306a36Sopenharmony_ci	.release = single_release,					\
4662306a36Sopenharmony_ci	.read  = seq_read,						\
4762306a36Sopenharmony_ci	.write = __write,						\
4862306a36Sopenharmony_ci	.llseek = seq_lseek,						\
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#define DEBUGFS_ATTR_RO(__space)					\
5262306a36Sopenharmony_ci	DEBUGFS_ATTR(__space, NULL)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#define DEBUGFS_ATTR_RW(__space)					\
5562306a36Sopenharmony_ci	DEBUGFS_ATTR(__space, __space ## _write)
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct dentry *tb_debugfs_root;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic void *validate_and_copy_from_user(const void __user *user_buf,
6062306a36Sopenharmony_ci					 size_t *count)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	size_t nbytes;
6362306a36Sopenharmony_ci	void *buf;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (!*count)
6662306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (!access_ok(user_buf, *count))
6962306a36Sopenharmony_ci		return ERR_PTR(-EFAULT);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	buf = (void *)get_zeroed_page(GFP_KERNEL);
7262306a36Sopenharmony_ci	if (!buf)
7362306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	nbytes = min_t(size_t, *count, PAGE_SIZE);
7662306a36Sopenharmony_ci	if (copy_from_user(buf, user_buf, nbytes)) {
7762306a36Sopenharmony_ci		free_page((unsigned long)buf);
7862306a36Sopenharmony_ci		return ERR_PTR(-EFAULT);
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	*count = nbytes;
8262306a36Sopenharmony_ci	return buf;
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len,
8662306a36Sopenharmony_ci		       int long_fmt_len)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	char *token;
8962306a36Sopenharmony_ci	u32 v[5];
9062306a36Sopenharmony_ci	int ret;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	token = strsep(line, "\n");
9362306a36Sopenharmony_ci	if (!token)
9462306a36Sopenharmony_ci		return false;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/*
9762306a36Sopenharmony_ci	 * For Adapter/Router configuration space:
9862306a36Sopenharmony_ci	 * Short format is: offset value\n
9962306a36Sopenharmony_ci	 *		    v[0]   v[1]
10062306a36Sopenharmony_ci	 * Long format as produced from the read side:
10162306a36Sopenharmony_ci	 * offset relative_offset cap_id vs_cap_id value\n
10262306a36Sopenharmony_ci	 * v[0]   v[1]            v[2]   v[3]      v[4]
10362306a36Sopenharmony_ci	 *
10462306a36Sopenharmony_ci	 * For Counter configuration space:
10562306a36Sopenharmony_ci	 * Short format is: offset\n
10662306a36Sopenharmony_ci	 *		    v[0]
10762306a36Sopenharmony_ci	 * Long format as produced from the read side:
10862306a36Sopenharmony_ci	 * offset relative_offset counter_id value\n
10962306a36Sopenharmony_ci	 * v[0]   v[1]            v[2]       v[3]
11062306a36Sopenharmony_ci	 */
11162306a36Sopenharmony_ci	ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]);
11262306a36Sopenharmony_ci	/* In case of Counters, clear counter, "val" content is NA */
11362306a36Sopenharmony_ci	if (ret == short_fmt_len) {
11462306a36Sopenharmony_ci		*offs = v[0];
11562306a36Sopenharmony_ci		*val = v[short_fmt_len - 1];
11662306a36Sopenharmony_ci		return true;
11762306a36Sopenharmony_ci	} else if (ret == long_fmt_len) {
11862306a36Sopenharmony_ci		*offs = v[0];
11962306a36Sopenharmony_ci		*val = v[long_fmt_len - 1];
12062306a36Sopenharmony_ci		return true;
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return false;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE)
12762306a36Sopenharmony_cistatic ssize_t regs_write(struct tb_switch *sw, struct tb_port *port,
12862306a36Sopenharmony_ci			  const char __user *user_buf, size_t count,
12962306a36Sopenharmony_ci			  loff_t *ppos)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct tb *tb = sw->tb;
13262306a36Sopenharmony_ci	char *line, *buf;
13362306a36Sopenharmony_ci	u32 val, offset;
13462306a36Sopenharmony_ci	int ret = 0;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
13762306a36Sopenharmony_ci	if (IS_ERR(buf))
13862306a36Sopenharmony_ci		return PTR_ERR(buf);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
14362306a36Sopenharmony_ci		ret = -ERESTARTSYS;
14462306a36Sopenharmony_ci		goto out;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* User did hardware changes behind the driver's back */
14862306a36Sopenharmony_ci	add_taint(TAINT_USER, LOCKDEP_STILL_OK);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	line = buf;
15162306a36Sopenharmony_ci	while (parse_line(&line, &offset, &val, 2, 5)) {
15262306a36Sopenharmony_ci		if (port)
15362306a36Sopenharmony_ci			ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1);
15462306a36Sopenharmony_ci		else
15562306a36Sopenharmony_ci			ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1);
15662306a36Sopenharmony_ci		if (ret)
15762306a36Sopenharmony_ci			break;
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ciout:
16362306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
16462306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
16562306a36Sopenharmony_ci	free_page((unsigned long)buf);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return ret < 0 ? ret : count;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic ssize_t port_regs_write(struct file *file, const char __user *user_buf,
17162306a36Sopenharmony_ci			       size_t count, loff_t *ppos)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
17462306a36Sopenharmony_ci	struct tb_port *port = s->private;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return regs_write(port->sw, port, user_buf, count, ppos);
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
18062306a36Sopenharmony_ci				 size_t count, loff_t *ppos)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
18362306a36Sopenharmony_ci	struct tb_switch *sw = s->private;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return regs_write(sw, NULL, user_buf, count, ppos);
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci#define DEBUGFS_MODE		0600
18862306a36Sopenharmony_ci#else
18962306a36Sopenharmony_ci#define port_regs_write		NULL
19062306a36Sopenharmony_ci#define switch_regs_write	NULL
19162306a36Sopenharmony_ci#define DEBUGFS_MODE		0400
19262306a36Sopenharmony_ci#endif
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)
19562306a36Sopenharmony_ci/**
19662306a36Sopenharmony_ci * struct tb_margining - Lane margining support
19762306a36Sopenharmony_ci * @caps: Port lane margining capabilities
19862306a36Sopenharmony_ci * @results: Last lane margining results
19962306a36Sopenharmony_ci * @lanes: %0, %1 or %7 (all)
20062306a36Sopenharmony_ci * @min_ber_level: Minimum supported BER level contour value
20162306a36Sopenharmony_ci * @max_ber_level: Maximum supported BER level contour value
20262306a36Sopenharmony_ci * @ber_level: Current BER level contour value
20362306a36Sopenharmony_ci * @voltage_steps: Number of mandatory voltage steps
20462306a36Sopenharmony_ci * @max_voltage_offset: Maximum mandatory voltage offset (in mV)
20562306a36Sopenharmony_ci * @time_steps: Number of time margin steps
20662306a36Sopenharmony_ci * @max_time_offset: Maximum time margin offset (in mUI)
20762306a36Sopenharmony_ci * @software: %true if software margining is used instead of hardware
20862306a36Sopenharmony_ci * @time: %true if time margining is used instead of voltage
20962306a36Sopenharmony_ci * @right_high: %false if left/low margin test is performed, %true if
21062306a36Sopenharmony_ci *		right/high
21162306a36Sopenharmony_ci */
21262306a36Sopenharmony_cistruct tb_margining {
21362306a36Sopenharmony_ci	u32 caps[2];
21462306a36Sopenharmony_ci	u32 results[2];
21562306a36Sopenharmony_ci	unsigned int lanes;
21662306a36Sopenharmony_ci	unsigned int min_ber_level;
21762306a36Sopenharmony_ci	unsigned int max_ber_level;
21862306a36Sopenharmony_ci	unsigned int ber_level;
21962306a36Sopenharmony_ci	unsigned int voltage_steps;
22062306a36Sopenharmony_ci	unsigned int max_voltage_offset;
22162306a36Sopenharmony_ci	unsigned int time_steps;
22262306a36Sopenharmony_ci	unsigned int max_time_offset;
22362306a36Sopenharmony_ci	bool software;
22462306a36Sopenharmony_ci	bool time;
22562306a36Sopenharmony_ci	bool right_high;
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic bool supports_software(const struct usb4_port *usb4)
22962306a36Sopenharmony_ci{
23062306a36Sopenharmony_ci	return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cistatic bool supports_hardware(const struct usb4_port *usb4)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic bool both_lanes(const struct usb4_port *usb4)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic unsigned int independent_voltage_margins(const struct usb4_port *usb4)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >>
24662306a36Sopenharmony_ci		USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic bool supports_time(const struct usb4_port *usb4)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci/* Only applicable if supports_time() returns true */
25562306a36Sopenharmony_cistatic unsigned int independent_time_margins(const struct usb4_port *usb4)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >>
25862306a36Sopenharmony_ci		USB4_MARGIN_CAP_1_TIME_INDP_SHIFT;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic ssize_t
26262306a36Sopenharmony_cimargining_ber_level_write(struct file *file, const char __user *user_buf,
26362306a36Sopenharmony_ci			   size_t count, loff_t *ppos)
26462306a36Sopenharmony_ci{
26562306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
26662306a36Sopenharmony_ci	struct tb_port *port = s->private;
26762306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
26862306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
26962306a36Sopenharmony_ci	unsigned int val;
27062306a36Sopenharmony_ci	int ret = 0;
27162306a36Sopenharmony_ci	char *buf;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
27462306a36Sopenharmony_ci		return -ERESTARTSYS;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	if (usb4->margining->software) {
27762306a36Sopenharmony_ci		ret = -EINVAL;
27862306a36Sopenharmony_ci		goto out_unlock;
27962306a36Sopenharmony_ci	}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
28262306a36Sopenharmony_ci	if (IS_ERR(buf)) {
28362306a36Sopenharmony_ci		ret = PTR_ERR(buf);
28462306a36Sopenharmony_ci		goto out_unlock;
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	buf[count - 1] = '\0';
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	ret = kstrtouint(buf, 10, &val);
29062306a36Sopenharmony_ci	if (ret)
29162306a36Sopenharmony_ci		goto out_free;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	if (val < usb4->margining->min_ber_level ||
29462306a36Sopenharmony_ci	    val > usb4->margining->max_ber_level) {
29562306a36Sopenharmony_ci		ret = -EINVAL;
29662306a36Sopenharmony_ci		goto out_free;
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	usb4->margining->ber_level = val;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ciout_free:
30262306a36Sopenharmony_ci	free_page((unsigned long)buf);
30362306a36Sopenharmony_ciout_unlock:
30462306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	return ret < 0 ? ret : count;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic void ber_level_show(struct seq_file *s, unsigned int val)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	if (val % 2)
31262306a36Sopenharmony_ci		seq_printf(s, "3 * 1e%d (%u)\n", -12 + (val + 1) / 2, val);
31362306a36Sopenharmony_ci	else
31462306a36Sopenharmony_ci		seq_printf(s, "1e%d (%u)\n", -12 + val / 2, val);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic int margining_ber_level_show(struct seq_file *s, void *not_used)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	struct tb_port *port = s->private;
32062306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	if (usb4->margining->software)
32362306a36Sopenharmony_ci		return -EINVAL;
32462306a36Sopenharmony_ci	ber_level_show(s, usb4->margining->ber_level);
32562306a36Sopenharmony_ci	return 0;
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_ber_level);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_cistatic int margining_caps_show(struct seq_file *s, void *not_used)
33062306a36Sopenharmony_ci{
33162306a36Sopenharmony_ci	struct tb_port *port = s->private;
33262306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
33362306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
33462306a36Sopenharmony_ci	u32 cap0, cap1;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
33762306a36Sopenharmony_ci		return -ERESTARTSYS;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	/* Dump the raw caps first */
34062306a36Sopenharmony_ci	cap0 = usb4->margining->caps[0];
34162306a36Sopenharmony_ci	seq_printf(s, "0x%08x\n", cap0);
34262306a36Sopenharmony_ci	cap1 = usb4->margining->caps[1];
34362306a36Sopenharmony_ci	seq_printf(s, "0x%08x\n", cap1);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	seq_printf(s, "# software margining: %s\n",
34662306a36Sopenharmony_ci		   supports_software(usb4) ? "yes" : "no");
34762306a36Sopenharmony_ci	if (supports_hardware(usb4)) {
34862306a36Sopenharmony_ci		seq_puts(s, "# hardware margining: yes\n");
34962306a36Sopenharmony_ci		seq_puts(s, "# minimum BER level contour: ");
35062306a36Sopenharmony_ci		ber_level_show(s, usb4->margining->min_ber_level);
35162306a36Sopenharmony_ci		seq_puts(s, "# maximum BER level contour: ");
35262306a36Sopenharmony_ci		ber_level_show(s, usb4->margining->max_ber_level);
35362306a36Sopenharmony_ci	} else {
35462306a36Sopenharmony_ci		seq_puts(s, "# hardware margining: no\n");
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	seq_printf(s, "# both lanes simultaneously: %s\n",
35862306a36Sopenharmony_ci		  both_lanes(usb4) ? "yes" : "no");
35962306a36Sopenharmony_ci	seq_printf(s, "# voltage margin steps: %u\n",
36062306a36Sopenharmony_ci		   usb4->margining->voltage_steps);
36162306a36Sopenharmony_ci	seq_printf(s, "# maximum voltage offset: %u mV\n",
36262306a36Sopenharmony_ci		   usb4->margining->max_voltage_offset);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	switch (independent_voltage_margins(usb4)) {
36562306a36Sopenharmony_ci	case USB4_MARGIN_CAP_0_VOLTAGE_MIN:
36662306a36Sopenharmony_ci		seq_puts(s, "# returns minimum between high and low voltage margins\n");
36762306a36Sopenharmony_ci		break;
36862306a36Sopenharmony_ci	case USB4_MARGIN_CAP_0_VOLTAGE_HL:
36962306a36Sopenharmony_ci		seq_puts(s, "# returns high or low voltage margin\n");
37062306a36Sopenharmony_ci		break;
37162306a36Sopenharmony_ci	case USB4_MARGIN_CAP_0_VOLTAGE_BOTH:
37262306a36Sopenharmony_ci		seq_puts(s, "# returns both high and low margins\n");
37362306a36Sopenharmony_ci		break;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	if (supports_time(usb4)) {
37762306a36Sopenharmony_ci		seq_puts(s, "# time margining: yes\n");
37862306a36Sopenharmony_ci		seq_printf(s, "# time margining is destructive: %s\n",
37962306a36Sopenharmony_ci			   cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no");
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		switch (independent_time_margins(usb4)) {
38262306a36Sopenharmony_ci		case USB4_MARGIN_CAP_1_TIME_MIN:
38362306a36Sopenharmony_ci			seq_puts(s, "# returns minimum between left and right time margins\n");
38462306a36Sopenharmony_ci			break;
38562306a36Sopenharmony_ci		case USB4_MARGIN_CAP_1_TIME_LR:
38662306a36Sopenharmony_ci			seq_puts(s, "# returns left or right margin\n");
38762306a36Sopenharmony_ci			break;
38862306a36Sopenharmony_ci		case USB4_MARGIN_CAP_1_TIME_BOTH:
38962306a36Sopenharmony_ci			seq_puts(s, "# returns both left and right margins\n");
39062306a36Sopenharmony_ci			break;
39162306a36Sopenharmony_ci		}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci		seq_printf(s, "# time margin steps: %u\n",
39462306a36Sopenharmony_ci			   usb4->margining->time_steps);
39562306a36Sopenharmony_ci		seq_printf(s, "# maximum time offset: %u mUI\n",
39662306a36Sopenharmony_ci			   usb4->margining->max_time_offset);
39762306a36Sopenharmony_ci	} else {
39862306a36Sopenharmony_ci		seq_puts(s, "# time margining: no\n");
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
40262306a36Sopenharmony_ci	return 0;
40362306a36Sopenharmony_ci}
40462306a36Sopenharmony_ciDEBUGFS_ATTR_RO(margining_caps);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic ssize_t
40762306a36Sopenharmony_cimargining_lanes_write(struct file *file, const char __user *user_buf,
40862306a36Sopenharmony_ci		      size_t count, loff_t *ppos)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
41162306a36Sopenharmony_ci	struct tb_port *port = s->private;
41262306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
41362306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
41462306a36Sopenharmony_ci	int ret = 0;
41562306a36Sopenharmony_ci	char *buf;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
41862306a36Sopenharmony_ci	if (IS_ERR(buf))
41962306a36Sopenharmony_ci		return PTR_ERR(buf);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	buf[count - 1] = '\0';
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
42462306a36Sopenharmony_ci		ret = -ERESTARTSYS;
42562306a36Sopenharmony_ci		goto out_free;
42662306a36Sopenharmony_ci	}
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	if (!strcmp(buf, "0")) {
42962306a36Sopenharmony_ci		usb4->margining->lanes = 0;
43062306a36Sopenharmony_ci	} else if (!strcmp(buf, "1")) {
43162306a36Sopenharmony_ci		usb4->margining->lanes = 1;
43262306a36Sopenharmony_ci	} else if (!strcmp(buf, "all")) {
43362306a36Sopenharmony_ci		/* Needs to be supported */
43462306a36Sopenharmony_ci		if (both_lanes(usb4))
43562306a36Sopenharmony_ci			usb4->margining->lanes = 7;
43662306a36Sopenharmony_ci		else
43762306a36Sopenharmony_ci			ret = -EINVAL;
43862306a36Sopenharmony_ci	} else {
43962306a36Sopenharmony_ci		ret = -EINVAL;
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ciout_free:
44562306a36Sopenharmony_ci	free_page((unsigned long)buf);
44662306a36Sopenharmony_ci	return ret < 0 ? ret : count;
44762306a36Sopenharmony_ci}
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_cistatic int margining_lanes_show(struct seq_file *s, void *not_used)
45062306a36Sopenharmony_ci{
45162306a36Sopenharmony_ci	struct tb_port *port = s->private;
45262306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
45362306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
45462306a36Sopenharmony_ci	unsigned int lanes;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
45762306a36Sopenharmony_ci		return -ERESTARTSYS;
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	lanes = usb4->margining->lanes;
46062306a36Sopenharmony_ci	if (both_lanes(usb4)) {
46162306a36Sopenharmony_ci		if (!lanes)
46262306a36Sopenharmony_ci			seq_puts(s, "[0] 1 all\n");
46362306a36Sopenharmony_ci		else if (lanes == 1)
46462306a36Sopenharmony_ci			seq_puts(s, "0 [1] all\n");
46562306a36Sopenharmony_ci		else
46662306a36Sopenharmony_ci			seq_puts(s, "0 1 [all]\n");
46762306a36Sopenharmony_ci	} else {
46862306a36Sopenharmony_ci		if (!lanes)
46962306a36Sopenharmony_ci			seq_puts(s, "[0] 1\n");
47062306a36Sopenharmony_ci		else
47162306a36Sopenharmony_ci			seq_puts(s, "0 [1]\n");
47262306a36Sopenharmony_ci	}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
47562306a36Sopenharmony_ci	return 0;
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_lanes);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic ssize_t margining_mode_write(struct file *file,
48062306a36Sopenharmony_ci				   const char __user *user_buf,
48162306a36Sopenharmony_ci				   size_t count, loff_t *ppos)
48262306a36Sopenharmony_ci{
48362306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
48462306a36Sopenharmony_ci	struct tb_port *port = s->private;
48562306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
48662306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
48762306a36Sopenharmony_ci	int ret = 0;
48862306a36Sopenharmony_ci	char *buf;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
49162306a36Sopenharmony_ci	if (IS_ERR(buf))
49262306a36Sopenharmony_ci		return PTR_ERR(buf);
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	buf[count - 1] = '\0';
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
49762306a36Sopenharmony_ci		ret = -ERESTARTSYS;
49862306a36Sopenharmony_ci		goto out_free;
49962306a36Sopenharmony_ci	}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	if (!strcmp(buf, "software")) {
50262306a36Sopenharmony_ci		if (supports_software(usb4))
50362306a36Sopenharmony_ci			usb4->margining->software = true;
50462306a36Sopenharmony_ci		else
50562306a36Sopenharmony_ci			ret = -EINVAL;
50662306a36Sopenharmony_ci	} else if (!strcmp(buf, "hardware")) {
50762306a36Sopenharmony_ci		if (supports_hardware(usb4))
50862306a36Sopenharmony_ci			usb4->margining->software = false;
50962306a36Sopenharmony_ci		else
51062306a36Sopenharmony_ci			ret = -EINVAL;
51162306a36Sopenharmony_ci	} else {
51262306a36Sopenharmony_ci		ret = -EINVAL;
51362306a36Sopenharmony_ci	}
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ciout_free:
51862306a36Sopenharmony_ci	free_page((unsigned long)buf);
51962306a36Sopenharmony_ci	return ret ? ret : count;
52062306a36Sopenharmony_ci}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_cistatic int margining_mode_show(struct seq_file *s, void *not_used)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	const struct tb_port *port = s->private;
52562306a36Sopenharmony_ci	const struct usb4_port *usb4 = port->usb4;
52662306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
52762306a36Sopenharmony_ci	const char *space = "";
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
53062306a36Sopenharmony_ci		return -ERESTARTSYS;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	if (supports_software(usb4)) {
53362306a36Sopenharmony_ci		if (usb4->margining->software)
53462306a36Sopenharmony_ci			seq_puts(s, "[software]");
53562306a36Sopenharmony_ci		else
53662306a36Sopenharmony_ci			seq_puts(s, "software");
53762306a36Sopenharmony_ci		space = " ";
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci	if (supports_hardware(usb4)) {
54062306a36Sopenharmony_ci		if (usb4->margining->software)
54162306a36Sopenharmony_ci			seq_printf(s, "%shardware", space);
54262306a36Sopenharmony_ci		else
54362306a36Sopenharmony_ci			seq_printf(s, "%s[hardware]", space);
54462306a36Sopenharmony_ci	}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	seq_puts(s, "\n");
54962306a36Sopenharmony_ci	return 0;
55062306a36Sopenharmony_ci}
55162306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_mode);
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_cistatic int margining_run_write(void *data, u64 val)
55462306a36Sopenharmony_ci{
55562306a36Sopenharmony_ci	struct tb_port *port = data;
55662306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
55762306a36Sopenharmony_ci	struct tb_switch *sw = port->sw;
55862306a36Sopenharmony_ci	struct tb_margining *margining;
55962306a36Sopenharmony_ci	struct tb_switch *down_sw;
56062306a36Sopenharmony_ci	struct tb *tb = sw->tb;
56162306a36Sopenharmony_ci	int ret, clx;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	if (val != 1)
56462306a36Sopenharmony_ci		return -EINVAL;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
56962306a36Sopenharmony_ci		ret = -ERESTARTSYS;
57062306a36Sopenharmony_ci		goto out_rpm_put;
57162306a36Sopenharmony_ci	}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	if (tb_is_upstream_port(port))
57462306a36Sopenharmony_ci		down_sw = sw;
57562306a36Sopenharmony_ci	else if (port->remote)
57662306a36Sopenharmony_ci		down_sw = port->remote->sw;
57762306a36Sopenharmony_ci	else
57862306a36Sopenharmony_ci		down_sw = NULL;
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	if (down_sw) {
58162306a36Sopenharmony_ci		/*
58262306a36Sopenharmony_ci		 * CL states may interfere with lane margining so
58362306a36Sopenharmony_ci		 * disable them temporarily now.
58462306a36Sopenharmony_ci		 */
58562306a36Sopenharmony_ci		ret = tb_switch_clx_disable(down_sw);
58662306a36Sopenharmony_ci		if (ret < 0) {
58762306a36Sopenharmony_ci			tb_sw_warn(down_sw, "failed to disable CL states\n");
58862306a36Sopenharmony_ci			goto out_unlock;
58962306a36Sopenharmony_ci		}
59062306a36Sopenharmony_ci		clx = ret;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	margining = usb4->margining;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	if (margining->software) {
59662306a36Sopenharmony_ci		tb_port_dbg(port, "running software %s lane margining for lanes %u\n",
59762306a36Sopenharmony_ci			    margining->time ? "time" : "voltage", margining->lanes);
59862306a36Sopenharmony_ci		ret = usb4_port_sw_margin(port, margining->lanes, margining->time,
59962306a36Sopenharmony_ci					  margining->right_high,
60062306a36Sopenharmony_ci					  USB4_MARGIN_SW_COUNTER_CLEAR);
60162306a36Sopenharmony_ci		if (ret)
60262306a36Sopenharmony_ci			goto out_clx;
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci		ret = usb4_port_sw_margin_errors(port, &margining->results[0]);
60562306a36Sopenharmony_ci	} else {
60662306a36Sopenharmony_ci		tb_port_dbg(port, "running hardware %s lane margining for lanes %u\n",
60762306a36Sopenharmony_ci			    margining->time ? "time" : "voltage", margining->lanes);
60862306a36Sopenharmony_ci		/* Clear the results */
60962306a36Sopenharmony_ci		margining->results[0] = 0;
61062306a36Sopenharmony_ci		margining->results[1] = 0;
61162306a36Sopenharmony_ci		ret = usb4_port_hw_margin(port, margining->lanes,
61262306a36Sopenharmony_ci					  margining->ber_level, margining->time,
61362306a36Sopenharmony_ci					  margining->right_high, margining->results);
61462306a36Sopenharmony_ci	}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ciout_clx:
61762306a36Sopenharmony_ci	if (down_sw)
61862306a36Sopenharmony_ci		tb_switch_clx_enable(down_sw, clx);
61962306a36Sopenharmony_ciout_unlock:
62062306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
62162306a36Sopenharmony_ciout_rpm_put:
62262306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
62362306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_ci	return ret;
62662306a36Sopenharmony_ci}
62762306a36Sopenharmony_ciDEFINE_DEBUGFS_ATTRIBUTE(margining_run_fops, NULL, margining_run_write,
62862306a36Sopenharmony_ci			 "%llu\n");
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_cistatic ssize_t margining_results_write(struct file *file,
63162306a36Sopenharmony_ci				       const char __user *user_buf,
63262306a36Sopenharmony_ci				       size_t count, loff_t *ppos)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
63562306a36Sopenharmony_ci	struct tb_port *port = s->private;
63662306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
63762306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
64062306a36Sopenharmony_ci		return -ERESTARTSYS;
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	/* Just clear the results */
64362306a36Sopenharmony_ci	usb4->margining->results[0] = 0;
64462306a36Sopenharmony_ci	usb4->margining->results[1] = 0;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
64762306a36Sopenharmony_ci	return count;
64862306a36Sopenharmony_ci}
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_cistatic void voltage_margin_show(struct seq_file *s,
65162306a36Sopenharmony_ci				const struct tb_margining *margining, u8 val)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	unsigned int tmp, voltage;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
65662306a36Sopenharmony_ci	voltage = tmp * margining->max_voltage_offset / margining->voltage_steps;
65762306a36Sopenharmony_ci	seq_printf(s, "%u mV (%u)", voltage, tmp);
65862306a36Sopenharmony_ci	if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
65962306a36Sopenharmony_ci		seq_puts(s, " exceeds maximum");
66062306a36Sopenharmony_ci	seq_puts(s, "\n");
66162306a36Sopenharmony_ci}
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_cistatic void time_margin_show(struct seq_file *s,
66462306a36Sopenharmony_ci			     const struct tb_margining *margining, u8 val)
66562306a36Sopenharmony_ci{
66662306a36Sopenharmony_ci	unsigned int tmp, interval;
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK;
66962306a36Sopenharmony_ci	interval = tmp * margining->max_time_offset / margining->time_steps;
67062306a36Sopenharmony_ci	seq_printf(s, "%u mUI (%u)", interval, tmp);
67162306a36Sopenharmony_ci	if (val & USB4_MARGIN_HW_RES_1_EXCEEDS)
67262306a36Sopenharmony_ci		seq_puts(s, " exceeds maximum");
67362306a36Sopenharmony_ci	seq_puts(s, "\n");
67462306a36Sopenharmony_ci}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_cistatic int margining_results_show(struct seq_file *s, void *not_used)
67762306a36Sopenharmony_ci{
67862306a36Sopenharmony_ci	struct tb_port *port = s->private;
67962306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
68062306a36Sopenharmony_ci	struct tb_margining *margining;
68162306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
68462306a36Sopenharmony_ci		return -ERESTARTSYS;
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	margining = usb4->margining;
68762306a36Sopenharmony_ci	/* Dump the raw results first */
68862306a36Sopenharmony_ci	seq_printf(s, "0x%08x\n", margining->results[0]);
68962306a36Sopenharmony_ci	/* Only the hardware margining has two result dwords */
69062306a36Sopenharmony_ci	if (!margining->software) {
69162306a36Sopenharmony_ci		unsigned int val;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci		seq_printf(s, "0x%08x\n", margining->results[1]);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci		if (margining->time) {
69662306a36Sopenharmony_ci			if (!margining->lanes || margining->lanes == 7) {
69762306a36Sopenharmony_ci				val = margining->results[1];
69862306a36Sopenharmony_ci				seq_puts(s, "# lane 0 right time margin: ");
69962306a36Sopenharmony_ci				time_margin_show(s, margining, val);
70062306a36Sopenharmony_ci				val = margining->results[1] >>
70162306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
70262306a36Sopenharmony_ci				seq_puts(s, "# lane 0 left time margin: ");
70362306a36Sopenharmony_ci				time_margin_show(s, margining, val);
70462306a36Sopenharmony_ci			}
70562306a36Sopenharmony_ci			if (margining->lanes == 1 || margining->lanes == 7) {
70662306a36Sopenharmony_ci				val = margining->results[1] >>
70762306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
70862306a36Sopenharmony_ci				seq_puts(s, "# lane 1 right time margin: ");
70962306a36Sopenharmony_ci				time_margin_show(s, margining, val);
71062306a36Sopenharmony_ci				val = margining->results[1] >>
71162306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
71262306a36Sopenharmony_ci				seq_puts(s, "# lane 1 left time margin: ");
71362306a36Sopenharmony_ci				time_margin_show(s, margining, val);
71462306a36Sopenharmony_ci			}
71562306a36Sopenharmony_ci		} else {
71662306a36Sopenharmony_ci			if (!margining->lanes || margining->lanes == 7) {
71762306a36Sopenharmony_ci				val = margining->results[1];
71862306a36Sopenharmony_ci				seq_puts(s, "# lane 0 high voltage margin: ");
71962306a36Sopenharmony_ci				voltage_margin_show(s, margining, val);
72062306a36Sopenharmony_ci				val = margining->results[1] >>
72162306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT;
72262306a36Sopenharmony_ci				seq_puts(s, "# lane 0 low voltage margin: ");
72362306a36Sopenharmony_ci				voltage_margin_show(s, margining, val);
72462306a36Sopenharmony_ci			}
72562306a36Sopenharmony_ci			if (margining->lanes == 1 || margining->lanes == 7) {
72662306a36Sopenharmony_ci				val = margining->results[1] >>
72762306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT;
72862306a36Sopenharmony_ci				seq_puts(s, "# lane 1 high voltage margin: ");
72962306a36Sopenharmony_ci				voltage_margin_show(s, margining, val);
73062306a36Sopenharmony_ci				val = margining->results[1] >>
73162306a36Sopenharmony_ci					USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT;
73262306a36Sopenharmony_ci				seq_puts(s, "# lane 1 low voltage margin: ");
73362306a36Sopenharmony_ci				voltage_margin_show(s, margining, val);
73462306a36Sopenharmony_ci			}
73562306a36Sopenharmony_ci		}
73662306a36Sopenharmony_ci	}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
73962306a36Sopenharmony_ci	return 0;
74062306a36Sopenharmony_ci}
74162306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_results);
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_cistatic ssize_t margining_test_write(struct file *file,
74462306a36Sopenharmony_ci				    const char __user *user_buf,
74562306a36Sopenharmony_ci				    size_t count, loff_t *ppos)
74662306a36Sopenharmony_ci{
74762306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
74862306a36Sopenharmony_ci	struct tb_port *port = s->private;
74962306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
75062306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
75162306a36Sopenharmony_ci	int ret = 0;
75262306a36Sopenharmony_ci	char *buf;
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
75562306a36Sopenharmony_ci	if (IS_ERR(buf))
75662306a36Sopenharmony_ci		return PTR_ERR(buf);
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	buf[count - 1] = '\0';
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
76162306a36Sopenharmony_ci		ret = -ERESTARTSYS;
76262306a36Sopenharmony_ci		goto out_free;
76362306a36Sopenharmony_ci	}
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	if (!strcmp(buf, "time") && supports_time(usb4))
76662306a36Sopenharmony_ci		usb4->margining->time = true;
76762306a36Sopenharmony_ci	else if (!strcmp(buf, "voltage"))
76862306a36Sopenharmony_ci		usb4->margining->time = false;
76962306a36Sopenharmony_ci	else
77062306a36Sopenharmony_ci		ret = -EINVAL;
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ciout_free:
77562306a36Sopenharmony_ci	free_page((unsigned long)buf);
77662306a36Sopenharmony_ci	return ret ? ret : count;
77762306a36Sopenharmony_ci}
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_cistatic int margining_test_show(struct seq_file *s, void *not_used)
78062306a36Sopenharmony_ci{
78162306a36Sopenharmony_ci	struct tb_port *port = s->private;
78262306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
78362306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
78662306a36Sopenharmony_ci		return -ERESTARTSYS;
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	if (supports_time(usb4)) {
78962306a36Sopenharmony_ci		if (usb4->margining->time)
79062306a36Sopenharmony_ci			seq_puts(s, "voltage [time]\n");
79162306a36Sopenharmony_ci		else
79262306a36Sopenharmony_ci			seq_puts(s, "[voltage] time\n");
79362306a36Sopenharmony_ci	} else {
79462306a36Sopenharmony_ci		seq_puts(s, "[voltage]\n");
79562306a36Sopenharmony_ci	}
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
79862306a36Sopenharmony_ci	return 0;
79962306a36Sopenharmony_ci}
80062306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_test);
80162306a36Sopenharmony_ci
80262306a36Sopenharmony_cistatic ssize_t margining_margin_write(struct file *file,
80362306a36Sopenharmony_ci				    const char __user *user_buf,
80462306a36Sopenharmony_ci				    size_t count, loff_t *ppos)
80562306a36Sopenharmony_ci{
80662306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
80762306a36Sopenharmony_ci	struct tb_port *port = s->private;
80862306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
80962306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
81062306a36Sopenharmony_ci	int ret = 0;
81162306a36Sopenharmony_ci	char *buf;
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
81462306a36Sopenharmony_ci	if (IS_ERR(buf))
81562306a36Sopenharmony_ci		return PTR_ERR(buf);
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	buf[count - 1] = '\0';
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
82062306a36Sopenharmony_ci		ret = -ERESTARTSYS;
82162306a36Sopenharmony_ci		goto out_free;
82262306a36Sopenharmony_ci	}
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci	if (usb4->margining->time) {
82562306a36Sopenharmony_ci		if (!strcmp(buf, "left"))
82662306a36Sopenharmony_ci			usb4->margining->right_high = false;
82762306a36Sopenharmony_ci		else if (!strcmp(buf, "right"))
82862306a36Sopenharmony_ci			usb4->margining->right_high = true;
82962306a36Sopenharmony_ci		else
83062306a36Sopenharmony_ci			ret = -EINVAL;
83162306a36Sopenharmony_ci	} else {
83262306a36Sopenharmony_ci		if (!strcmp(buf, "low"))
83362306a36Sopenharmony_ci			usb4->margining->right_high = false;
83462306a36Sopenharmony_ci		else if (!strcmp(buf, "high"))
83562306a36Sopenharmony_ci			usb4->margining->right_high = true;
83662306a36Sopenharmony_ci		else
83762306a36Sopenharmony_ci			ret = -EINVAL;
83862306a36Sopenharmony_ci	}
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ciout_free:
84362306a36Sopenharmony_ci	free_page((unsigned long)buf);
84462306a36Sopenharmony_ci	return ret ? ret : count;
84562306a36Sopenharmony_ci}
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_cistatic int margining_margin_show(struct seq_file *s, void *not_used)
84862306a36Sopenharmony_ci{
84962306a36Sopenharmony_ci	struct tb_port *port = s->private;
85062306a36Sopenharmony_ci	struct usb4_port *usb4 = port->usb4;
85162306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
85462306a36Sopenharmony_ci		return -ERESTARTSYS;
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	if (usb4->margining->time) {
85762306a36Sopenharmony_ci		if (usb4->margining->right_high)
85862306a36Sopenharmony_ci			seq_puts(s, "left [right]\n");
85962306a36Sopenharmony_ci		else
86062306a36Sopenharmony_ci			seq_puts(s, "[left] right\n");
86162306a36Sopenharmony_ci	} else {
86262306a36Sopenharmony_ci		if (usb4->margining->right_high)
86362306a36Sopenharmony_ci			seq_puts(s, "low [high]\n");
86462306a36Sopenharmony_ci		else
86562306a36Sopenharmony_ci			seq_puts(s, "[low] high\n");
86662306a36Sopenharmony_ci	}
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
86962306a36Sopenharmony_ci	return 0;
87062306a36Sopenharmony_ci}
87162306a36Sopenharmony_ciDEBUGFS_ATTR_RW(margining_margin);
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_cistatic void margining_port_init(struct tb_port *port)
87462306a36Sopenharmony_ci{
87562306a36Sopenharmony_ci	struct tb_margining *margining;
87662306a36Sopenharmony_ci	struct dentry *dir, *parent;
87762306a36Sopenharmony_ci	struct usb4_port *usb4;
87862306a36Sopenharmony_ci	char dir_name[10];
87962306a36Sopenharmony_ci	unsigned int val;
88062306a36Sopenharmony_ci	int ret;
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	usb4 = port->usb4;
88362306a36Sopenharmony_ci	if (!usb4)
88462306a36Sopenharmony_ci		return;
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
88762306a36Sopenharmony_ci	parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_ci	margining = kzalloc(sizeof(*margining), GFP_KERNEL);
89062306a36Sopenharmony_ci	if (!margining)
89162306a36Sopenharmony_ci		return;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	ret = usb4_port_margining_caps(port, margining->caps);
89462306a36Sopenharmony_ci	if (ret) {
89562306a36Sopenharmony_ci		kfree(margining);
89662306a36Sopenharmony_ci		return;
89762306a36Sopenharmony_ci	}
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci	usb4->margining = margining;
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	/* Set the initial mode */
90262306a36Sopenharmony_ci	if (supports_software(usb4))
90362306a36Sopenharmony_ci		margining->software = true;
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci	val = (margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK) >>
90662306a36Sopenharmony_ci		USB4_MARGIN_CAP_0_VOLTAGE_STEPS_SHIFT;
90762306a36Sopenharmony_ci	margining->voltage_steps = val;
90862306a36Sopenharmony_ci	val = (margining->caps[0] & USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK) >>
90962306a36Sopenharmony_ci		USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_SHIFT;
91062306a36Sopenharmony_ci	margining->max_voltage_offset = 74 + val * 2;
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	if (supports_time(usb4)) {
91362306a36Sopenharmony_ci		val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_STEPS_MASK) >>
91462306a36Sopenharmony_ci			USB4_MARGIN_CAP_1_TIME_STEPS_SHIFT;
91562306a36Sopenharmony_ci		margining->time_steps = val;
91662306a36Sopenharmony_ci		val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_OFFSET_MASK) >>
91762306a36Sopenharmony_ci			USB4_MARGIN_CAP_1_TIME_OFFSET_SHIFT;
91862306a36Sopenharmony_ci		/*
91962306a36Sopenharmony_ci		 * Store it as mUI (milli Unit Interval) because we want
92062306a36Sopenharmony_ci		 * to keep it as integer.
92162306a36Sopenharmony_ci		 */
92262306a36Sopenharmony_ci		margining->max_time_offset = 200 + 10 * val;
92362306a36Sopenharmony_ci	}
92462306a36Sopenharmony_ci
92562306a36Sopenharmony_ci	dir = debugfs_create_dir("margining", parent);
92662306a36Sopenharmony_ci	if (supports_hardware(usb4)) {
92762306a36Sopenharmony_ci		val = (margining->caps[1] & USB4_MARGIN_CAP_1_MIN_BER_MASK) >>
92862306a36Sopenharmony_ci			USB4_MARGIN_CAP_1_MIN_BER_SHIFT;
92962306a36Sopenharmony_ci		margining->min_ber_level = val;
93062306a36Sopenharmony_ci		val = (margining->caps[1] & USB4_MARGIN_CAP_1_MAX_BER_MASK) >>
93162306a36Sopenharmony_ci			USB4_MARGIN_CAP_1_MAX_BER_SHIFT;
93262306a36Sopenharmony_ci		margining->max_ber_level = val;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci		/* Set the default to minimum */
93562306a36Sopenharmony_ci		margining->ber_level = margining->min_ber_level;
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci		debugfs_create_file("ber_level_contour", 0400, dir, port,
93862306a36Sopenharmony_ci				    &margining_ber_level_fops);
93962306a36Sopenharmony_ci	}
94062306a36Sopenharmony_ci	debugfs_create_file("caps", 0400, dir, port, &margining_caps_fops);
94162306a36Sopenharmony_ci	debugfs_create_file("lanes", 0600, dir, port, &margining_lanes_fops);
94262306a36Sopenharmony_ci	debugfs_create_file("mode", 0600, dir, port, &margining_mode_fops);
94362306a36Sopenharmony_ci	debugfs_create_file("run", 0600, dir, port, &margining_run_fops);
94462306a36Sopenharmony_ci	debugfs_create_file("results", 0600, dir, port, &margining_results_fops);
94562306a36Sopenharmony_ci	debugfs_create_file("test", 0600, dir, port, &margining_test_fops);
94662306a36Sopenharmony_ci	if (independent_voltage_margins(usb4) ||
94762306a36Sopenharmony_ci	    (supports_time(usb4) && independent_time_margins(usb4)))
94862306a36Sopenharmony_ci		debugfs_create_file("margin", 0600, dir, port, &margining_margin_fops);
94962306a36Sopenharmony_ci}
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_cistatic void margining_port_remove(struct tb_port *port)
95262306a36Sopenharmony_ci{
95362306a36Sopenharmony_ci	struct dentry *parent;
95462306a36Sopenharmony_ci	char dir_name[10];
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	if (!port->usb4)
95762306a36Sopenharmony_ci		return;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
96062306a36Sopenharmony_ci	parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
96162306a36Sopenharmony_ci	if (parent)
96262306a36Sopenharmony_ci		debugfs_lookup_and_remove("margining", parent);
96362306a36Sopenharmony_ci
96462306a36Sopenharmony_ci	kfree(port->usb4->margining);
96562306a36Sopenharmony_ci	port->usb4->margining = NULL;
96662306a36Sopenharmony_ci}
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_cistatic void margining_switch_init(struct tb_switch *sw)
96962306a36Sopenharmony_ci{
97062306a36Sopenharmony_ci	struct tb_port *upstream, *downstream;
97162306a36Sopenharmony_ci	struct tb_switch *parent_sw;
97262306a36Sopenharmony_ci	u64 route = tb_route(sw);
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_ci	if (!route)
97562306a36Sopenharmony_ci		return;
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	upstream = tb_upstream_port(sw);
97862306a36Sopenharmony_ci	parent_sw = tb_switch_parent(sw);
97962306a36Sopenharmony_ci	downstream = tb_port_at(route, parent_sw);
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	margining_port_init(downstream);
98262306a36Sopenharmony_ci	margining_port_init(upstream);
98362306a36Sopenharmony_ci}
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_cistatic void margining_switch_remove(struct tb_switch *sw)
98662306a36Sopenharmony_ci{
98762306a36Sopenharmony_ci	struct tb_port *upstream, *downstream;
98862306a36Sopenharmony_ci	struct tb_switch *parent_sw;
98962306a36Sopenharmony_ci	u64 route = tb_route(sw);
99062306a36Sopenharmony_ci
99162306a36Sopenharmony_ci	if (!route)
99262306a36Sopenharmony_ci		return;
99362306a36Sopenharmony_ci
99462306a36Sopenharmony_ci	upstream = tb_upstream_port(sw);
99562306a36Sopenharmony_ci	parent_sw = tb_switch_parent(sw);
99662306a36Sopenharmony_ci	downstream = tb_port_at(route, parent_sw);
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	margining_port_remove(upstream);
99962306a36Sopenharmony_ci	margining_port_remove(downstream);
100062306a36Sopenharmony_ci}
100162306a36Sopenharmony_ci
100262306a36Sopenharmony_cistatic void margining_xdomain_init(struct tb_xdomain *xd)
100362306a36Sopenharmony_ci{
100462306a36Sopenharmony_ci	struct tb_switch *parent_sw;
100562306a36Sopenharmony_ci	struct tb_port *downstream;
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	parent_sw = tb_xdomain_parent(xd);
100862306a36Sopenharmony_ci	downstream = tb_port_at(xd->route, parent_sw);
100962306a36Sopenharmony_ci
101062306a36Sopenharmony_ci	margining_port_init(downstream);
101162306a36Sopenharmony_ci}
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_cistatic void margining_xdomain_remove(struct tb_xdomain *xd)
101462306a36Sopenharmony_ci{
101562306a36Sopenharmony_ci	struct tb_switch *parent_sw;
101662306a36Sopenharmony_ci	struct tb_port *downstream;
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci	parent_sw = tb_xdomain_parent(xd);
101962306a36Sopenharmony_ci	downstream = tb_port_at(xd->route, parent_sw);
102062306a36Sopenharmony_ci	margining_port_remove(downstream);
102162306a36Sopenharmony_ci}
102262306a36Sopenharmony_ci#else
102362306a36Sopenharmony_cistatic inline void margining_switch_init(struct tb_switch *sw) { }
102462306a36Sopenharmony_cistatic inline void margining_switch_remove(struct tb_switch *sw) { }
102562306a36Sopenharmony_cistatic inline void margining_xdomain_init(struct tb_xdomain *xd) { }
102662306a36Sopenharmony_cistatic inline void margining_xdomain_remove(struct tb_xdomain *xd) { }
102762306a36Sopenharmony_ci#endif
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_cistatic int port_clear_all_counters(struct tb_port *port)
103062306a36Sopenharmony_ci{
103162306a36Sopenharmony_ci	u32 *buf;
103262306a36Sopenharmony_ci	int ret;
103362306a36Sopenharmony_ci
103462306a36Sopenharmony_ci	buf = kcalloc(COUNTER_SET_LEN * port->config.max_counters, sizeof(u32),
103562306a36Sopenharmony_ci		      GFP_KERNEL);
103662306a36Sopenharmony_ci	if (!buf)
103762306a36Sopenharmony_ci		return -ENOMEM;
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	ret = tb_port_write(port, buf, TB_CFG_COUNTERS, 0,
104062306a36Sopenharmony_ci			    COUNTER_SET_LEN * port->config.max_counters);
104162306a36Sopenharmony_ci	kfree(buf);
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_ci	return ret;
104462306a36Sopenharmony_ci}
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_cistatic ssize_t counters_write(struct file *file, const char __user *user_buf,
104762306a36Sopenharmony_ci			      size_t count, loff_t *ppos)
104862306a36Sopenharmony_ci{
104962306a36Sopenharmony_ci	struct seq_file *s = file->private_data;
105062306a36Sopenharmony_ci	struct tb_port *port = s->private;
105162306a36Sopenharmony_ci	struct tb_switch *sw = port->sw;
105262306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
105362306a36Sopenharmony_ci	char *buf;
105462306a36Sopenharmony_ci	int ret;
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_ci	buf = validate_and_copy_from_user(user_buf, &count);
105762306a36Sopenharmony_ci	if (IS_ERR(buf))
105862306a36Sopenharmony_ci		return PTR_ERR(buf);
105962306a36Sopenharmony_ci
106062306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
106362306a36Sopenharmony_ci		ret = -ERESTARTSYS;
106462306a36Sopenharmony_ci		goto out;
106562306a36Sopenharmony_ci	}
106662306a36Sopenharmony_ci
106762306a36Sopenharmony_ci	/* If written delimiter only, clear all counters in one shot */
106862306a36Sopenharmony_ci	if (buf[0] == '\n') {
106962306a36Sopenharmony_ci		ret = port_clear_all_counters(port);
107062306a36Sopenharmony_ci	} else  {
107162306a36Sopenharmony_ci		char *line = buf;
107262306a36Sopenharmony_ci		u32 val, offset;
107362306a36Sopenharmony_ci
107462306a36Sopenharmony_ci		ret = -EINVAL;
107562306a36Sopenharmony_ci		while (parse_line(&line, &offset, &val, 1, 4)) {
107662306a36Sopenharmony_ci			ret = tb_port_write(port, &val, TB_CFG_COUNTERS,
107762306a36Sopenharmony_ci					    offset, 1);
107862306a36Sopenharmony_ci			if (ret)
107962306a36Sopenharmony_ci				break;
108062306a36Sopenharmony_ci		}
108162306a36Sopenharmony_ci	}
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
108462306a36Sopenharmony_ci
108562306a36Sopenharmony_ciout:
108662306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
108762306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
108862306a36Sopenharmony_ci	free_page((unsigned long)buf);
108962306a36Sopenharmony_ci
109062306a36Sopenharmony_ci	return ret < 0 ? ret : count;
109162306a36Sopenharmony_ci}
109262306a36Sopenharmony_ci
109362306a36Sopenharmony_cistatic void cap_show_by_dw(struct seq_file *s, struct tb_switch *sw,
109462306a36Sopenharmony_ci			   struct tb_port *port, unsigned int cap,
109562306a36Sopenharmony_ci			   unsigned int offset, u8 cap_id, u8 vsec_id,
109662306a36Sopenharmony_ci			   int dwords)
109762306a36Sopenharmony_ci{
109862306a36Sopenharmony_ci	int i, ret;
109962306a36Sopenharmony_ci	u32 data;
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_ci	for (i = 0; i < dwords; i++) {
110262306a36Sopenharmony_ci		if (port)
110362306a36Sopenharmony_ci			ret = tb_port_read(port, &data, TB_CFG_PORT, cap + offset + i, 1);
110462306a36Sopenharmony_ci		else
110562306a36Sopenharmony_ci			ret = tb_sw_read(sw, &data, TB_CFG_SWITCH, cap + offset + i, 1);
110662306a36Sopenharmony_ci		if (ret) {
110762306a36Sopenharmony_ci			seq_printf(s, "0x%04x <not accessible>\n", cap + offset + i);
110862306a36Sopenharmony_ci			continue;
110962306a36Sopenharmony_ci		}
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci		seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n", cap + offset + i,
111262306a36Sopenharmony_ci			   offset + i, cap_id, vsec_id, data);
111362306a36Sopenharmony_ci	}
111462306a36Sopenharmony_ci}
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_cistatic void cap_show(struct seq_file *s, struct tb_switch *sw,
111762306a36Sopenharmony_ci		     struct tb_port *port, unsigned int cap, u8 cap_id,
111862306a36Sopenharmony_ci		     u8 vsec_id, int length)
111962306a36Sopenharmony_ci{
112062306a36Sopenharmony_ci	int ret, offset = 0;
112162306a36Sopenharmony_ci
112262306a36Sopenharmony_ci	while (length > 0) {
112362306a36Sopenharmony_ci		int i, dwords = min(length, TB_MAX_CONFIG_RW_LENGTH);
112462306a36Sopenharmony_ci		u32 data[TB_MAX_CONFIG_RW_LENGTH];
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci		if (port)
112762306a36Sopenharmony_ci			ret = tb_port_read(port, data, TB_CFG_PORT, cap + offset,
112862306a36Sopenharmony_ci					   dwords);
112962306a36Sopenharmony_ci		else
113062306a36Sopenharmony_ci			ret = tb_sw_read(sw, data, TB_CFG_SWITCH, cap + offset, dwords);
113162306a36Sopenharmony_ci		if (ret) {
113262306a36Sopenharmony_ci			cap_show_by_dw(s, sw, port, cap, offset, cap_id, vsec_id, length);
113362306a36Sopenharmony_ci			return;
113462306a36Sopenharmony_ci		}
113562306a36Sopenharmony_ci
113662306a36Sopenharmony_ci		for (i = 0; i < dwords; i++) {
113762306a36Sopenharmony_ci			seq_printf(s, "0x%04x %4d 0x%02x 0x%02x 0x%08x\n",
113862306a36Sopenharmony_ci				   cap + offset + i, offset + i,
113962306a36Sopenharmony_ci				   cap_id, vsec_id, data[i]);
114062306a36Sopenharmony_ci		}
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci		length -= dwords;
114362306a36Sopenharmony_ci		offset += dwords;
114462306a36Sopenharmony_ci	}
114562306a36Sopenharmony_ci}
114662306a36Sopenharmony_ci
114762306a36Sopenharmony_cistatic void port_cap_show(struct tb_port *port, struct seq_file *s,
114862306a36Sopenharmony_ci			  unsigned int cap)
114962306a36Sopenharmony_ci{
115062306a36Sopenharmony_ci	struct tb_cap_any header;
115162306a36Sopenharmony_ci	u8 vsec_id = 0;
115262306a36Sopenharmony_ci	size_t length;
115362306a36Sopenharmony_ci	int ret;
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_ci	ret = tb_port_read(port, &header, TB_CFG_PORT, cap, 1);
115662306a36Sopenharmony_ci	if (ret) {
115762306a36Sopenharmony_ci		seq_printf(s, "0x%04x <capability read failed>\n", cap);
115862306a36Sopenharmony_ci		return;
115962306a36Sopenharmony_ci	}
116062306a36Sopenharmony_ci
116162306a36Sopenharmony_ci	switch (header.basic.cap) {
116262306a36Sopenharmony_ci	case TB_PORT_CAP_PHY:
116362306a36Sopenharmony_ci		length = PORT_CAP_LANE_LEN;
116462306a36Sopenharmony_ci		break;
116562306a36Sopenharmony_ci
116662306a36Sopenharmony_ci	case TB_PORT_CAP_TIME1:
116762306a36Sopenharmony_ci		if (usb4_switch_version(port->sw) < 2)
116862306a36Sopenharmony_ci			length = PORT_CAP_TMU_V1_LEN;
116962306a36Sopenharmony_ci		else
117062306a36Sopenharmony_ci			length = PORT_CAP_TMU_V2_LEN;
117162306a36Sopenharmony_ci		break;
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_ci	case TB_PORT_CAP_POWER:
117462306a36Sopenharmony_ci		length = PORT_CAP_POWER_LEN;
117562306a36Sopenharmony_ci		break;
117662306a36Sopenharmony_ci
117762306a36Sopenharmony_ci	case TB_PORT_CAP_ADAP:
117862306a36Sopenharmony_ci		if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) {
117962306a36Sopenharmony_ci			if (usb4_switch_version(port->sw) < 2)
118062306a36Sopenharmony_ci				length = PORT_CAP_V1_PCIE_LEN;
118162306a36Sopenharmony_ci			else
118262306a36Sopenharmony_ci				length = PORT_CAP_V2_PCIE_LEN;
118362306a36Sopenharmony_ci		} else if (tb_port_is_dpin(port)) {
118462306a36Sopenharmony_ci			if (usb4_switch_version(port->sw) < 2)
118562306a36Sopenharmony_ci				length = PORT_CAP_DP_V1_LEN;
118662306a36Sopenharmony_ci			else
118762306a36Sopenharmony_ci				length = PORT_CAP_DP_V2_LEN;
118862306a36Sopenharmony_ci		} else if (tb_port_is_dpout(port)) {
118962306a36Sopenharmony_ci			length = PORT_CAP_DP_V1_LEN;
119062306a36Sopenharmony_ci		} else if (tb_port_is_usb3_down(port) ||
119162306a36Sopenharmony_ci			   tb_port_is_usb3_up(port)) {
119262306a36Sopenharmony_ci			length = PORT_CAP_USB3_LEN;
119362306a36Sopenharmony_ci		} else {
119462306a36Sopenharmony_ci			seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
119562306a36Sopenharmony_ci				   cap, header.basic.cap);
119662306a36Sopenharmony_ci			return;
119762306a36Sopenharmony_ci		}
119862306a36Sopenharmony_ci		break;
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci	case TB_PORT_CAP_VSE:
120162306a36Sopenharmony_ci		if (!header.extended_short.length) {
120262306a36Sopenharmony_ci			ret = tb_port_read(port, (u32 *)&header + 1, TB_CFG_PORT,
120362306a36Sopenharmony_ci					   cap + 1, 1);
120462306a36Sopenharmony_ci			if (ret) {
120562306a36Sopenharmony_ci				seq_printf(s, "0x%04x <capability read failed>\n",
120662306a36Sopenharmony_ci					   cap + 1);
120762306a36Sopenharmony_ci				return;
120862306a36Sopenharmony_ci			}
120962306a36Sopenharmony_ci			length = header.extended_long.length;
121062306a36Sopenharmony_ci			vsec_id = header.extended_short.vsec_id;
121162306a36Sopenharmony_ci		} else {
121262306a36Sopenharmony_ci			length = header.extended_short.length;
121362306a36Sopenharmony_ci			vsec_id = header.extended_short.vsec_id;
121462306a36Sopenharmony_ci		}
121562306a36Sopenharmony_ci		break;
121662306a36Sopenharmony_ci
121762306a36Sopenharmony_ci	case TB_PORT_CAP_USB4:
121862306a36Sopenharmony_ci		length = PORT_CAP_USB4_LEN;
121962306a36Sopenharmony_ci		break;
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci	default:
122262306a36Sopenharmony_ci		seq_printf(s, "0x%04x <unsupported capability 0x%02x>\n",
122362306a36Sopenharmony_ci			   cap, header.basic.cap);
122462306a36Sopenharmony_ci		return;
122562306a36Sopenharmony_ci	}
122662306a36Sopenharmony_ci
122762306a36Sopenharmony_ci	cap_show(s, NULL, port, cap, header.basic.cap, vsec_id, length);
122862306a36Sopenharmony_ci}
122962306a36Sopenharmony_ci
123062306a36Sopenharmony_cistatic void port_caps_show(struct tb_port *port, struct seq_file *s)
123162306a36Sopenharmony_ci{
123262306a36Sopenharmony_ci	int cap;
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	cap = tb_port_next_cap(port, 0);
123562306a36Sopenharmony_ci	while (cap > 0) {
123662306a36Sopenharmony_ci		port_cap_show(port, s, cap);
123762306a36Sopenharmony_ci		cap = tb_port_next_cap(port, cap);
123862306a36Sopenharmony_ci	}
123962306a36Sopenharmony_ci}
124062306a36Sopenharmony_ci
124162306a36Sopenharmony_cistatic int port_basic_regs_show(struct tb_port *port, struct seq_file *s)
124262306a36Sopenharmony_ci{
124362306a36Sopenharmony_ci	u32 data[PORT_CAP_BASIC_LEN];
124462306a36Sopenharmony_ci	int ret, i;
124562306a36Sopenharmony_ci
124662306a36Sopenharmony_ci	ret = tb_port_read(port, data, TB_CFG_PORT, 0, ARRAY_SIZE(data));
124762306a36Sopenharmony_ci	if (ret)
124862306a36Sopenharmony_ci		return ret;
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(data); i++)
125162306a36Sopenharmony_ci		seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	return 0;
125462306a36Sopenharmony_ci}
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_cistatic int port_regs_show(struct seq_file *s, void *not_used)
125762306a36Sopenharmony_ci{
125862306a36Sopenharmony_ci	struct tb_port *port = s->private;
125962306a36Sopenharmony_ci	struct tb_switch *sw = port->sw;
126062306a36Sopenharmony_ci	struct tb *tb = sw->tb;
126162306a36Sopenharmony_ci	int ret;
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
126662306a36Sopenharmony_ci		ret = -ERESTARTSYS;
126762306a36Sopenharmony_ci		goto out_rpm_put;
126862306a36Sopenharmony_ci	}
126962306a36Sopenharmony_ci
127062306a36Sopenharmony_ci	seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_ci	ret = port_basic_regs_show(port, s);
127362306a36Sopenharmony_ci	if (ret)
127462306a36Sopenharmony_ci		goto out_unlock;
127562306a36Sopenharmony_ci
127662306a36Sopenharmony_ci	port_caps_show(port, s);
127762306a36Sopenharmony_ci
127862306a36Sopenharmony_ciout_unlock:
127962306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
128062306a36Sopenharmony_ciout_rpm_put:
128162306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
128262306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
128362306a36Sopenharmony_ci
128462306a36Sopenharmony_ci	return ret;
128562306a36Sopenharmony_ci}
128662306a36Sopenharmony_ciDEBUGFS_ATTR_RW(port_regs);
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_cistatic void switch_cap_show(struct tb_switch *sw, struct seq_file *s,
128962306a36Sopenharmony_ci			    unsigned int cap)
129062306a36Sopenharmony_ci{
129162306a36Sopenharmony_ci	struct tb_cap_any header;
129262306a36Sopenharmony_ci	int ret, length;
129362306a36Sopenharmony_ci	u8 vsec_id = 0;
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_ci	ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, cap, 1);
129662306a36Sopenharmony_ci	if (ret) {
129762306a36Sopenharmony_ci		seq_printf(s, "0x%04x <capability read failed>\n", cap);
129862306a36Sopenharmony_ci		return;
129962306a36Sopenharmony_ci	}
130062306a36Sopenharmony_ci
130162306a36Sopenharmony_ci	if (header.basic.cap == TB_SWITCH_CAP_VSE) {
130262306a36Sopenharmony_ci		if (!header.extended_short.length) {
130362306a36Sopenharmony_ci			ret = tb_sw_read(sw, (u32 *)&header + 1, TB_CFG_SWITCH,
130462306a36Sopenharmony_ci					 cap + 1, 1);
130562306a36Sopenharmony_ci			if (ret) {
130662306a36Sopenharmony_ci				seq_printf(s, "0x%04x <capability read failed>\n",
130762306a36Sopenharmony_ci					   cap + 1);
130862306a36Sopenharmony_ci				return;
130962306a36Sopenharmony_ci			}
131062306a36Sopenharmony_ci			length = header.extended_long.length;
131162306a36Sopenharmony_ci		} else {
131262306a36Sopenharmony_ci			length = header.extended_short.length;
131362306a36Sopenharmony_ci		}
131462306a36Sopenharmony_ci		vsec_id = header.extended_short.vsec_id;
131562306a36Sopenharmony_ci	} else {
131662306a36Sopenharmony_ci		if (header.basic.cap == TB_SWITCH_CAP_TMU) {
131762306a36Sopenharmony_ci			length = SWITCH_CAP_TMU_LEN;
131862306a36Sopenharmony_ci		} else  {
131962306a36Sopenharmony_ci			seq_printf(s, "0x%04x <unknown capability 0x%02x>\n",
132062306a36Sopenharmony_ci				   cap, header.basic.cap);
132162306a36Sopenharmony_ci			return;
132262306a36Sopenharmony_ci		}
132362306a36Sopenharmony_ci	}
132462306a36Sopenharmony_ci
132562306a36Sopenharmony_ci	cap_show(s, sw, NULL, cap, header.basic.cap, vsec_id, length);
132662306a36Sopenharmony_ci}
132762306a36Sopenharmony_ci
132862306a36Sopenharmony_cistatic void switch_caps_show(struct tb_switch *sw, struct seq_file *s)
132962306a36Sopenharmony_ci{
133062306a36Sopenharmony_ci	int cap;
133162306a36Sopenharmony_ci
133262306a36Sopenharmony_ci	cap = tb_switch_next_cap(sw, 0);
133362306a36Sopenharmony_ci	while (cap > 0) {
133462306a36Sopenharmony_ci		switch_cap_show(sw, s, cap);
133562306a36Sopenharmony_ci		cap = tb_switch_next_cap(sw, cap);
133662306a36Sopenharmony_ci	}
133762306a36Sopenharmony_ci}
133862306a36Sopenharmony_ci
133962306a36Sopenharmony_cistatic int switch_basic_regs_show(struct tb_switch *sw, struct seq_file *s)
134062306a36Sopenharmony_ci{
134162306a36Sopenharmony_ci	u32 data[SWITCH_CAP_BASIC_LEN];
134262306a36Sopenharmony_ci	size_t dwords;
134362306a36Sopenharmony_ci	int ret, i;
134462306a36Sopenharmony_ci
134562306a36Sopenharmony_ci	/* Only USB4 has the additional registers */
134662306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
134762306a36Sopenharmony_ci		dwords = ARRAY_SIZE(data);
134862306a36Sopenharmony_ci	else
134962306a36Sopenharmony_ci		dwords = 7;
135062306a36Sopenharmony_ci
135162306a36Sopenharmony_ci	ret = tb_sw_read(sw, data, TB_CFG_SWITCH, 0, dwords);
135262306a36Sopenharmony_ci	if (ret)
135362306a36Sopenharmony_ci		return ret;
135462306a36Sopenharmony_ci
135562306a36Sopenharmony_ci	for (i = 0; i < dwords; i++)
135662306a36Sopenharmony_ci		seq_printf(s, "0x%04x %4d 0x00 0x00 0x%08x\n", i, i, data[i]);
135762306a36Sopenharmony_ci
135862306a36Sopenharmony_ci	return 0;
135962306a36Sopenharmony_ci}
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_cistatic int switch_regs_show(struct seq_file *s, void *not_used)
136262306a36Sopenharmony_ci{
136362306a36Sopenharmony_ci	struct tb_switch *sw = s->private;
136462306a36Sopenharmony_ci	struct tb *tb = sw->tb;
136562306a36Sopenharmony_ci	int ret;
136662306a36Sopenharmony_ci
136762306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
136862306a36Sopenharmony_ci
136962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
137062306a36Sopenharmony_ci		ret = -ERESTARTSYS;
137162306a36Sopenharmony_ci		goto out_rpm_put;
137262306a36Sopenharmony_ci	}
137362306a36Sopenharmony_ci
137462306a36Sopenharmony_ci	seq_puts(s, "# offset relative_offset cap_id vs_cap_id value\n");
137562306a36Sopenharmony_ci
137662306a36Sopenharmony_ci	ret = switch_basic_regs_show(sw, s);
137762306a36Sopenharmony_ci	if (ret)
137862306a36Sopenharmony_ci		goto out_unlock;
137962306a36Sopenharmony_ci
138062306a36Sopenharmony_ci	switch_caps_show(sw, s);
138162306a36Sopenharmony_ci
138262306a36Sopenharmony_ciout_unlock:
138362306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
138462306a36Sopenharmony_ciout_rpm_put:
138562306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
138662306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
138762306a36Sopenharmony_ci
138862306a36Sopenharmony_ci	return ret;
138962306a36Sopenharmony_ci}
139062306a36Sopenharmony_ciDEBUGFS_ATTR_RW(switch_regs);
139162306a36Sopenharmony_ci
139262306a36Sopenharmony_cistatic int path_show_one(struct tb_port *port, struct seq_file *s, int hopid)
139362306a36Sopenharmony_ci{
139462306a36Sopenharmony_ci	u32 data[PATH_LEN];
139562306a36Sopenharmony_ci	int ret, i;
139662306a36Sopenharmony_ci
139762306a36Sopenharmony_ci	ret = tb_port_read(port, data, TB_CFG_HOPS, hopid * PATH_LEN,
139862306a36Sopenharmony_ci			   ARRAY_SIZE(data));
139962306a36Sopenharmony_ci	if (ret) {
140062306a36Sopenharmony_ci		seq_printf(s, "0x%04x <not accessible>\n", hopid * PATH_LEN);
140162306a36Sopenharmony_ci		return ret;
140262306a36Sopenharmony_ci	}
140362306a36Sopenharmony_ci
140462306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(data); i++) {
140562306a36Sopenharmony_ci		seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
140662306a36Sopenharmony_ci			   hopid * PATH_LEN + i, i, hopid, data[i]);
140762306a36Sopenharmony_ci	}
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci	return 0;
141062306a36Sopenharmony_ci}
141162306a36Sopenharmony_ci
141262306a36Sopenharmony_cistatic int path_show(struct seq_file *s, void *not_used)
141362306a36Sopenharmony_ci{
141462306a36Sopenharmony_ci	struct tb_port *port = s->private;
141562306a36Sopenharmony_ci	struct tb_switch *sw = port->sw;
141662306a36Sopenharmony_ci	struct tb *tb = sw->tb;
141762306a36Sopenharmony_ci	int start, i, ret = 0;
141862306a36Sopenharmony_ci
141962306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
142062306a36Sopenharmony_ci
142162306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
142262306a36Sopenharmony_ci		ret = -ERESTARTSYS;
142362306a36Sopenharmony_ci		goto out_rpm_put;
142462306a36Sopenharmony_ci	}
142562306a36Sopenharmony_ci
142662306a36Sopenharmony_ci	seq_puts(s, "# offset relative_offset in_hop_id value\n");
142762306a36Sopenharmony_ci
142862306a36Sopenharmony_ci	/* NHI and lane adapters have entry for path 0 */
142962306a36Sopenharmony_ci	if (tb_port_is_null(port) || tb_port_is_nhi(port)) {
143062306a36Sopenharmony_ci		ret = path_show_one(port, s, 0);
143162306a36Sopenharmony_ci		if (ret)
143262306a36Sopenharmony_ci			goto out_unlock;
143362306a36Sopenharmony_ci	}
143462306a36Sopenharmony_ci
143562306a36Sopenharmony_ci	start = tb_port_is_nhi(port) ? 1 : TB_PATH_MIN_HOPID;
143662306a36Sopenharmony_ci
143762306a36Sopenharmony_ci	for (i = start; i <= port->config.max_in_hop_id; i++) {
143862306a36Sopenharmony_ci		ret = path_show_one(port, s, i);
143962306a36Sopenharmony_ci		if (ret)
144062306a36Sopenharmony_ci			break;
144162306a36Sopenharmony_ci	}
144262306a36Sopenharmony_ci
144362306a36Sopenharmony_ciout_unlock:
144462306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
144562306a36Sopenharmony_ciout_rpm_put:
144662306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
144762306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
144862306a36Sopenharmony_ci
144962306a36Sopenharmony_ci	return ret;
145062306a36Sopenharmony_ci}
145162306a36Sopenharmony_ciDEBUGFS_ATTR_RO(path);
145262306a36Sopenharmony_ci
145362306a36Sopenharmony_cistatic int counter_set_regs_show(struct tb_port *port, struct seq_file *s,
145462306a36Sopenharmony_ci				 int counter)
145562306a36Sopenharmony_ci{
145662306a36Sopenharmony_ci	u32 data[COUNTER_SET_LEN];
145762306a36Sopenharmony_ci	int ret, i;
145862306a36Sopenharmony_ci
145962306a36Sopenharmony_ci	ret = tb_port_read(port, data, TB_CFG_COUNTERS,
146062306a36Sopenharmony_ci			   counter * COUNTER_SET_LEN, ARRAY_SIZE(data));
146162306a36Sopenharmony_ci	if (ret) {
146262306a36Sopenharmony_ci		seq_printf(s, "0x%04x <not accessible>\n",
146362306a36Sopenharmony_ci			   counter * COUNTER_SET_LEN);
146462306a36Sopenharmony_ci		return ret;
146562306a36Sopenharmony_ci	}
146662306a36Sopenharmony_ci
146762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(data); i++) {
146862306a36Sopenharmony_ci		seq_printf(s, "0x%04x %4d 0x%02x 0x%08x\n",
146962306a36Sopenharmony_ci			   counter * COUNTER_SET_LEN + i, i, counter, data[i]);
147062306a36Sopenharmony_ci	}
147162306a36Sopenharmony_ci
147262306a36Sopenharmony_ci	return 0;
147362306a36Sopenharmony_ci}
147462306a36Sopenharmony_ci
147562306a36Sopenharmony_cistatic int counters_show(struct seq_file *s, void *not_used)
147662306a36Sopenharmony_ci{
147762306a36Sopenharmony_ci	struct tb_port *port = s->private;
147862306a36Sopenharmony_ci	struct tb_switch *sw = port->sw;
147962306a36Sopenharmony_ci	struct tb *tb = sw->tb;
148062306a36Sopenharmony_ci	int i, ret = 0;
148162306a36Sopenharmony_ci
148262306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
148362306a36Sopenharmony_ci
148462306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
148562306a36Sopenharmony_ci		ret = -ERESTARTSYS;
148662306a36Sopenharmony_ci		goto out;
148762306a36Sopenharmony_ci	}
148862306a36Sopenharmony_ci
148962306a36Sopenharmony_ci	seq_puts(s, "# offset relative_offset counter_id value\n");
149062306a36Sopenharmony_ci
149162306a36Sopenharmony_ci	for (i = 0; i < port->config.max_counters; i++) {
149262306a36Sopenharmony_ci		ret = counter_set_regs_show(port, s, i);
149362306a36Sopenharmony_ci		if (ret)
149462306a36Sopenharmony_ci			break;
149562306a36Sopenharmony_ci	}
149662306a36Sopenharmony_ci
149762306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
149862306a36Sopenharmony_ci
149962306a36Sopenharmony_ciout:
150062306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
150162306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
150262306a36Sopenharmony_ci
150362306a36Sopenharmony_ci	return ret;
150462306a36Sopenharmony_ci}
150562306a36Sopenharmony_ciDEBUGFS_ATTR_RW(counters);
150662306a36Sopenharmony_ci
150762306a36Sopenharmony_ci/**
150862306a36Sopenharmony_ci * tb_switch_debugfs_init() - Add debugfs entries for router
150962306a36Sopenharmony_ci * @sw: Pointer to the router
151062306a36Sopenharmony_ci *
151162306a36Sopenharmony_ci * Adds debugfs directories and files for given router.
151262306a36Sopenharmony_ci */
151362306a36Sopenharmony_civoid tb_switch_debugfs_init(struct tb_switch *sw)
151462306a36Sopenharmony_ci{
151562306a36Sopenharmony_ci	struct dentry *debugfs_dir;
151662306a36Sopenharmony_ci	struct tb_port *port;
151762306a36Sopenharmony_ci
151862306a36Sopenharmony_ci	debugfs_dir = debugfs_create_dir(dev_name(&sw->dev), tb_debugfs_root);
151962306a36Sopenharmony_ci	sw->debugfs_dir = debugfs_dir;
152062306a36Sopenharmony_ci	debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir, sw,
152162306a36Sopenharmony_ci			    &switch_regs_fops);
152262306a36Sopenharmony_ci
152362306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
152462306a36Sopenharmony_ci		struct dentry *debugfs_dir;
152562306a36Sopenharmony_ci		char dir_name[10];
152662306a36Sopenharmony_ci
152762306a36Sopenharmony_ci		if (port->disabled)
152862306a36Sopenharmony_ci			continue;
152962306a36Sopenharmony_ci		if (port->config.type == TB_TYPE_INACTIVE)
153062306a36Sopenharmony_ci			continue;
153162306a36Sopenharmony_ci
153262306a36Sopenharmony_ci		snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
153362306a36Sopenharmony_ci		debugfs_dir = debugfs_create_dir(dir_name, sw->debugfs_dir);
153462306a36Sopenharmony_ci		debugfs_create_file("regs", DEBUGFS_MODE, debugfs_dir,
153562306a36Sopenharmony_ci				    port, &port_regs_fops);
153662306a36Sopenharmony_ci		debugfs_create_file("path", 0400, debugfs_dir, port,
153762306a36Sopenharmony_ci				    &path_fops);
153862306a36Sopenharmony_ci		if (port->config.counters_support)
153962306a36Sopenharmony_ci			debugfs_create_file("counters", 0600, debugfs_dir, port,
154062306a36Sopenharmony_ci					    &counters_fops);
154162306a36Sopenharmony_ci	}
154262306a36Sopenharmony_ci
154362306a36Sopenharmony_ci	margining_switch_init(sw);
154462306a36Sopenharmony_ci}
154562306a36Sopenharmony_ci
154662306a36Sopenharmony_ci/**
154762306a36Sopenharmony_ci * tb_switch_debugfs_remove() - Remove all router debugfs entries
154862306a36Sopenharmony_ci * @sw: Pointer to the router
154962306a36Sopenharmony_ci *
155062306a36Sopenharmony_ci * Removes all previously added debugfs entries under this router.
155162306a36Sopenharmony_ci */
155262306a36Sopenharmony_civoid tb_switch_debugfs_remove(struct tb_switch *sw)
155362306a36Sopenharmony_ci{
155462306a36Sopenharmony_ci	margining_switch_remove(sw);
155562306a36Sopenharmony_ci	debugfs_remove_recursive(sw->debugfs_dir);
155662306a36Sopenharmony_ci}
155762306a36Sopenharmony_ci
155862306a36Sopenharmony_civoid tb_xdomain_debugfs_init(struct tb_xdomain *xd)
155962306a36Sopenharmony_ci{
156062306a36Sopenharmony_ci	margining_xdomain_init(xd);
156162306a36Sopenharmony_ci}
156262306a36Sopenharmony_ci
156362306a36Sopenharmony_civoid tb_xdomain_debugfs_remove(struct tb_xdomain *xd)
156462306a36Sopenharmony_ci{
156562306a36Sopenharmony_ci	margining_xdomain_remove(xd);
156662306a36Sopenharmony_ci}
156762306a36Sopenharmony_ci
156862306a36Sopenharmony_ci/**
156962306a36Sopenharmony_ci * tb_service_debugfs_init() - Add debugfs directory for service
157062306a36Sopenharmony_ci * @svc: Thunderbolt service pointer
157162306a36Sopenharmony_ci *
157262306a36Sopenharmony_ci * Adds debugfs directory for service.
157362306a36Sopenharmony_ci */
157462306a36Sopenharmony_civoid tb_service_debugfs_init(struct tb_service *svc)
157562306a36Sopenharmony_ci{
157662306a36Sopenharmony_ci	svc->debugfs_dir = debugfs_create_dir(dev_name(&svc->dev),
157762306a36Sopenharmony_ci					      tb_debugfs_root);
157862306a36Sopenharmony_ci}
157962306a36Sopenharmony_ci
158062306a36Sopenharmony_ci/**
158162306a36Sopenharmony_ci * tb_service_debugfs_remove() - Remove service debugfs directory
158262306a36Sopenharmony_ci * @svc: Thunderbolt service pointer
158362306a36Sopenharmony_ci *
158462306a36Sopenharmony_ci * Removes the previously created debugfs directory for @svc.
158562306a36Sopenharmony_ci */
158662306a36Sopenharmony_civoid tb_service_debugfs_remove(struct tb_service *svc)
158762306a36Sopenharmony_ci{
158862306a36Sopenharmony_ci	debugfs_remove_recursive(svc->debugfs_dir);
158962306a36Sopenharmony_ci	svc->debugfs_dir = NULL;
159062306a36Sopenharmony_ci}
159162306a36Sopenharmony_ci
159262306a36Sopenharmony_civoid tb_debugfs_init(void)
159362306a36Sopenharmony_ci{
159462306a36Sopenharmony_ci	tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL);
159562306a36Sopenharmony_ci}
159662306a36Sopenharmony_ci
159762306a36Sopenharmony_civoid tb_debugfs_exit(void)
159862306a36Sopenharmony_ci{
159962306a36Sopenharmony_ci	debugfs_remove_recursive(tb_debugfs_root);
160062306a36Sopenharmony_ci}
1601