162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * USB4 specific functionality
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2019, Intel Corporation
662306a36Sopenharmony_ci * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
762306a36Sopenharmony_ci *	    Rajmohan Mani <rajmohan.mani@intel.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/delay.h>
1162306a36Sopenharmony_ci#include <linux/ktime.h>
1262306a36Sopenharmony_ci#include <linux/units.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "sb_regs.h"
1562306a36Sopenharmony_ci#include "tb.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define USB4_DATA_RETRIES		3
1862306a36Sopenharmony_ci#define USB4_DATA_DWORDS		16
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cienum usb4_sb_target {
2162306a36Sopenharmony_ci	USB4_SB_TARGET_ROUTER,
2262306a36Sopenharmony_ci	USB4_SB_TARGET_PARTNER,
2362306a36Sopenharmony_ci	USB4_SB_TARGET_RETIMER,
2462306a36Sopenharmony_ci};
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define USB4_NVM_READ_OFFSET_MASK	GENMASK(23, 2)
2762306a36Sopenharmony_ci#define USB4_NVM_READ_OFFSET_SHIFT	2
2862306a36Sopenharmony_ci#define USB4_NVM_READ_LENGTH_MASK	GENMASK(27, 24)
2962306a36Sopenharmony_ci#define USB4_NVM_READ_LENGTH_SHIFT	24
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define USB4_NVM_SET_OFFSET_MASK	USB4_NVM_READ_OFFSET_MASK
3262306a36Sopenharmony_ci#define USB4_NVM_SET_OFFSET_SHIFT	USB4_NVM_READ_OFFSET_SHIFT
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define USB4_DROM_ADDRESS_MASK		GENMASK(14, 2)
3562306a36Sopenharmony_ci#define USB4_DROM_ADDRESS_SHIFT		2
3662306a36Sopenharmony_ci#define USB4_DROM_SIZE_MASK		GENMASK(19, 15)
3762306a36Sopenharmony_ci#define USB4_DROM_SIZE_SHIFT		15
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#define USB4_NVM_SECTOR_SIZE_MASK	GENMASK(23, 0)
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define USB4_BA_LENGTH_MASK		GENMASK(7, 0)
4262306a36Sopenharmony_ci#define USB4_BA_INDEX_MASK		GENMASK(15, 0)
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cienum usb4_ba_index {
4562306a36Sopenharmony_ci	USB4_BA_MAX_USB3 = 0x1,
4662306a36Sopenharmony_ci	USB4_BA_MIN_DP_AUX = 0x2,
4762306a36Sopenharmony_ci	USB4_BA_MIN_DP_MAIN = 0x3,
4862306a36Sopenharmony_ci	USB4_BA_MAX_PCIE = 0x4,
4962306a36Sopenharmony_ci	USB4_BA_MAX_HI = 0x5,
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define USB4_BA_VALUE_MASK		GENMASK(31, 16)
5362306a36Sopenharmony_ci#define USB4_BA_VALUE_SHIFT		16
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int usb4_native_switch_op(struct tb_switch *sw, u16 opcode,
5662306a36Sopenharmony_ci				 u32 *metadata, u8 *status,
5762306a36Sopenharmony_ci				 const void *tx_data, size_t tx_dwords,
5862306a36Sopenharmony_ci				 void *rx_data, size_t rx_dwords)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	u32 val;
6162306a36Sopenharmony_ci	int ret;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (metadata) {
6462306a36Sopenharmony_ci		ret = tb_sw_write(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
6562306a36Sopenharmony_ci		if (ret)
6662306a36Sopenharmony_ci			return ret;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci	if (tx_dwords) {
6962306a36Sopenharmony_ci		ret = tb_sw_write(sw, tx_data, TB_CFG_SWITCH, ROUTER_CS_9,
7062306a36Sopenharmony_ci				  tx_dwords);
7162306a36Sopenharmony_ci		if (ret)
7262306a36Sopenharmony_ci			return ret;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	val = opcode | ROUTER_CS_26_OV;
7662306a36Sopenharmony_ci	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
7762306a36Sopenharmony_ci	if (ret)
7862306a36Sopenharmony_ci		return ret;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	ret = tb_switch_wait_for_bit(sw, ROUTER_CS_26, ROUTER_CS_26_OV, 0, 500);
8162306a36Sopenharmony_ci	if (ret)
8262306a36Sopenharmony_ci		return ret;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
8562306a36Sopenharmony_ci	if (ret)
8662306a36Sopenharmony_ci		return ret;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (val & ROUTER_CS_26_ONS)
8962306a36Sopenharmony_ci		return -EOPNOTSUPP;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (status)
9262306a36Sopenharmony_ci		*status = (val & ROUTER_CS_26_STATUS_MASK) >>
9362306a36Sopenharmony_ci			ROUTER_CS_26_STATUS_SHIFT;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (metadata) {
9662306a36Sopenharmony_ci		ret = tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
9762306a36Sopenharmony_ci		if (ret)
9862306a36Sopenharmony_ci			return ret;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	if (rx_dwords) {
10162306a36Sopenharmony_ci		ret = tb_sw_read(sw, rx_data, TB_CFG_SWITCH, ROUTER_CS_9,
10262306a36Sopenharmony_ci				 rx_dwords);
10362306a36Sopenharmony_ci		if (ret)
10462306a36Sopenharmony_ci			return ret;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int __usb4_switch_op(struct tb_switch *sw, u16 opcode, u32 *metadata,
11162306a36Sopenharmony_ci			    u8 *status, const void *tx_data, size_t tx_dwords,
11262306a36Sopenharmony_ci			    void *rx_data, size_t rx_dwords)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	const struct tb_cm_ops *cm_ops = sw->tb->cm_ops;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (tx_dwords > USB4_DATA_DWORDS || rx_dwords > USB4_DATA_DWORDS)
11762306a36Sopenharmony_ci		return -EINVAL;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/*
12062306a36Sopenharmony_ci	 * If the connection manager implementation provides USB4 router
12162306a36Sopenharmony_ci	 * operation proxy callback, call it here instead of running the
12262306a36Sopenharmony_ci	 * operation natively.
12362306a36Sopenharmony_ci	 */
12462306a36Sopenharmony_ci	if (cm_ops->usb4_switch_op) {
12562306a36Sopenharmony_ci		int ret;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		ret = cm_ops->usb4_switch_op(sw, opcode, metadata, status,
12862306a36Sopenharmony_ci					     tx_data, tx_dwords, rx_data,
12962306a36Sopenharmony_ci					     rx_dwords);
13062306a36Sopenharmony_ci		if (ret != -EOPNOTSUPP)
13162306a36Sopenharmony_ci			return ret;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci		/*
13462306a36Sopenharmony_ci		 * If the proxy was not supported then run the native
13562306a36Sopenharmony_ci		 * router operation instead.
13662306a36Sopenharmony_ci		 */
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return usb4_native_switch_op(sw, opcode, metadata, status, tx_data,
14062306a36Sopenharmony_ci				     tx_dwords, rx_data, rx_dwords);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic inline int usb4_switch_op(struct tb_switch *sw, u16 opcode,
14462306a36Sopenharmony_ci				 u32 *metadata, u8 *status)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	return __usb4_switch_op(sw, opcode, metadata, status, NULL, 0, NULL, 0);
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic inline int usb4_switch_op_data(struct tb_switch *sw, u16 opcode,
15062306a36Sopenharmony_ci				      u32 *metadata, u8 *status,
15162306a36Sopenharmony_ci				      const void *tx_data, size_t tx_dwords,
15262306a36Sopenharmony_ci				      void *rx_data, size_t rx_dwords)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	return __usb4_switch_op(sw, opcode, metadata, status, tx_data,
15562306a36Sopenharmony_ci				tx_dwords, rx_data, rx_dwords);
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void usb4_switch_check_wakes(struct tb_switch *sw)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	bool wakeup_usb4 = false;
16162306a36Sopenharmony_ci	struct usb4_port *usb4;
16262306a36Sopenharmony_ci	struct tb_port *port;
16362306a36Sopenharmony_ci	bool wakeup = false;
16462306a36Sopenharmony_ci	u32 val;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (!device_may_wakeup(&sw->dev))
16762306a36Sopenharmony_ci		return;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (tb_route(sw)) {
17062306a36Sopenharmony_ci		if (tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1))
17162306a36Sopenharmony_ci			return;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		tb_sw_dbg(sw, "PCIe wake: %s, USB3 wake: %s\n",
17462306a36Sopenharmony_ci			  (val & ROUTER_CS_6_WOPS) ? "yes" : "no",
17562306a36Sopenharmony_ci			  (val & ROUTER_CS_6_WOUS) ? "yes" : "no");
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci		wakeup = val & (ROUTER_CS_6_WOPS | ROUTER_CS_6_WOUS);
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/*
18162306a36Sopenharmony_ci	 * Check for any downstream ports for USB4 wake,
18262306a36Sopenharmony_ci	 * connection wake and disconnection wake.
18362306a36Sopenharmony_ci	 */
18462306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
18562306a36Sopenharmony_ci		if (!port->cap_usb4)
18662306a36Sopenharmony_ci			continue;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci		if (tb_port_read(port, &val, TB_CFG_PORT,
18962306a36Sopenharmony_ci				 port->cap_usb4 + PORT_CS_18, 1))
19062306a36Sopenharmony_ci			break;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		tb_port_dbg(port, "USB4 wake: %s, connection wake: %s, disconnection wake: %s\n",
19362306a36Sopenharmony_ci			    (val & PORT_CS_18_WOU4S) ? "yes" : "no",
19462306a36Sopenharmony_ci			    (val & PORT_CS_18_WOCS) ? "yes" : "no",
19562306a36Sopenharmony_ci			    (val & PORT_CS_18_WODS) ? "yes" : "no");
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		wakeup_usb4 = val & (PORT_CS_18_WOU4S | PORT_CS_18_WOCS |
19862306a36Sopenharmony_ci				     PORT_CS_18_WODS);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		usb4 = port->usb4;
20162306a36Sopenharmony_ci		if (device_may_wakeup(&usb4->dev) && wakeup_usb4)
20262306a36Sopenharmony_ci			pm_wakeup_event(&usb4->dev, 0);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		wakeup |= wakeup_usb4;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (wakeup)
20862306a36Sopenharmony_ci		pm_wakeup_event(&sw->dev, 0);
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic bool link_is_usb4(struct tb_port *port)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	u32 val;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	if (!port->cap_usb4)
21662306a36Sopenharmony_ci		return false;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (tb_port_read(port, &val, TB_CFG_PORT,
21962306a36Sopenharmony_ci			 port->cap_usb4 + PORT_CS_18, 1))
22062306a36Sopenharmony_ci		return false;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return !(val & PORT_CS_18_TCM);
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci/**
22662306a36Sopenharmony_ci * usb4_switch_setup() - Additional setup for USB4 device
22762306a36Sopenharmony_ci * @sw: USB4 router to setup
22862306a36Sopenharmony_ci *
22962306a36Sopenharmony_ci * USB4 routers need additional settings in order to enable all the
23062306a36Sopenharmony_ci * tunneling. This function enables USB and PCIe tunneling if it can be
23162306a36Sopenharmony_ci * enabled (e.g the parent switch also supports them). If USB tunneling
23262306a36Sopenharmony_ci * is not available for some reason (like that there is Thunderbolt 3
23362306a36Sopenharmony_ci * switch upstream) then the internal xHCI controller is enabled
23462306a36Sopenharmony_ci * instead.
23562306a36Sopenharmony_ci *
23662306a36Sopenharmony_ci * This does not set the configuration valid bit of the router. To do
23762306a36Sopenharmony_ci * that call usb4_switch_configuration_valid().
23862306a36Sopenharmony_ci */
23962306a36Sopenharmony_ciint usb4_switch_setup(struct tb_switch *sw)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct tb_switch *parent = tb_switch_parent(sw);
24262306a36Sopenharmony_ci	struct tb_port *down;
24362306a36Sopenharmony_ci	bool tbt3, xhci;
24462306a36Sopenharmony_ci	u32 val = 0;
24562306a36Sopenharmony_ci	int ret;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	usb4_switch_check_wakes(sw);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	if (!tb_route(sw))
25062306a36Sopenharmony_ci		return 0;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1);
25362306a36Sopenharmony_ci	if (ret)
25462306a36Sopenharmony_ci		return ret;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
25762306a36Sopenharmony_ci	sw->link_usb4 = link_is_usb4(down);
25862306a36Sopenharmony_ci	tb_sw_dbg(sw, "link: %s\n", sw->link_usb4 ? "USB4" : "TBT");
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	xhci = val & ROUTER_CS_6_HCI;
26162306a36Sopenharmony_ci	tbt3 = !(val & ROUTER_CS_6_TNS);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n",
26462306a36Sopenharmony_ci		  tbt3 ? "yes" : "no", xhci ? "yes" : "no");
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
26762306a36Sopenharmony_ci	if (ret)
26862306a36Sopenharmony_ci		return ret;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (tb_acpi_may_tunnel_usb3() && sw->link_usb4 &&
27162306a36Sopenharmony_ci	    tb_switch_find_port(parent, TB_TYPE_USB3_DOWN)) {
27262306a36Sopenharmony_ci		val |= ROUTER_CS_5_UTO;
27362306a36Sopenharmony_ci		xhci = false;
27462306a36Sopenharmony_ci	}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	/*
27762306a36Sopenharmony_ci	 * Only enable PCIe tunneling if the parent router supports it
27862306a36Sopenharmony_ci	 * and it is not disabled.
27962306a36Sopenharmony_ci	 */
28062306a36Sopenharmony_ci	if (tb_acpi_may_tunnel_pcie() &&
28162306a36Sopenharmony_ci	    tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) {
28262306a36Sopenharmony_ci		val |= ROUTER_CS_5_PTO;
28362306a36Sopenharmony_ci		/*
28462306a36Sopenharmony_ci		 * xHCI can be enabled if PCIe tunneling is supported
28562306a36Sopenharmony_ci		 * and the parent does not have any USB3 dowstream
28662306a36Sopenharmony_ci		 * adapters (so we cannot do USB 3.x tunneling).
28762306a36Sopenharmony_ci		 */
28862306a36Sopenharmony_ci		if (xhci)
28962306a36Sopenharmony_ci			val |= ROUTER_CS_5_HCO;
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	/* TBT3 supported by the CM */
29362306a36Sopenharmony_ci	val &= ~ROUTER_CS_5_CNS;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci/**
29962306a36Sopenharmony_ci * usb4_switch_configuration_valid() - Set tunneling configuration to be valid
30062306a36Sopenharmony_ci * @sw: USB4 router
30162306a36Sopenharmony_ci *
30262306a36Sopenharmony_ci * Sets configuration valid bit for the router. Must be called before
30362306a36Sopenharmony_ci * any tunnels can be set through the router and after
30462306a36Sopenharmony_ci * usb4_switch_setup() has been called. Can be called to host and device
30562306a36Sopenharmony_ci * routers (does nothing for the latter).
30662306a36Sopenharmony_ci *
30762306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
30862306a36Sopenharmony_ci */
30962306a36Sopenharmony_ciint usb4_switch_configuration_valid(struct tb_switch *sw)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	u32 val;
31262306a36Sopenharmony_ci	int ret;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	if (!tb_route(sw))
31562306a36Sopenharmony_ci		return 0;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
31862306a36Sopenharmony_ci	if (ret)
31962306a36Sopenharmony_ci		return ret;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	val |= ROUTER_CS_5_CV;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
32462306a36Sopenharmony_ci	if (ret)
32562306a36Sopenharmony_ci		return ret;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR,
32862306a36Sopenharmony_ci				      ROUTER_CS_6_CR, 50);
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci/**
33262306a36Sopenharmony_ci * usb4_switch_read_uid() - Read UID from USB4 router
33362306a36Sopenharmony_ci * @sw: USB4 router
33462306a36Sopenharmony_ci * @uid: UID is stored here
33562306a36Sopenharmony_ci *
33662306a36Sopenharmony_ci * Reads 64-bit UID from USB4 router config space.
33762306a36Sopenharmony_ci */
33862306a36Sopenharmony_ciint usb4_switch_read_uid(struct tb_switch *sw, u64 *uid)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	return tb_sw_read(sw, uid, TB_CFG_SWITCH, ROUTER_CS_7, 2);
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cistatic int usb4_switch_drom_read_block(void *data,
34462306a36Sopenharmony_ci				       unsigned int dwaddress, void *buf,
34562306a36Sopenharmony_ci				       size_t dwords)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	struct tb_switch *sw = data;
34862306a36Sopenharmony_ci	u8 status = 0;
34962306a36Sopenharmony_ci	u32 metadata;
35062306a36Sopenharmony_ci	int ret;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	metadata = (dwords << USB4_DROM_SIZE_SHIFT) & USB4_DROM_SIZE_MASK;
35362306a36Sopenharmony_ci	metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) &
35462306a36Sopenharmony_ci		USB4_DROM_ADDRESS_MASK;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_DROM_READ, &metadata,
35762306a36Sopenharmony_ci				  &status, NULL, 0, buf, dwords);
35862306a36Sopenharmony_ci	if (ret)
35962306a36Sopenharmony_ci		return ret;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	return status ? -EIO : 0;
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci/**
36562306a36Sopenharmony_ci * usb4_switch_drom_read() - Read arbitrary bytes from USB4 router DROM
36662306a36Sopenharmony_ci * @sw: USB4 router
36762306a36Sopenharmony_ci * @address: Byte address inside DROM to start reading
36862306a36Sopenharmony_ci * @buf: Buffer where the DROM content is stored
36962306a36Sopenharmony_ci * @size: Number of bytes to read from DROM
37062306a36Sopenharmony_ci *
37162306a36Sopenharmony_ci * Uses USB4 router operations to read router DROM. For devices this
37262306a36Sopenharmony_ci * should always work but for hosts it may return %-EOPNOTSUPP in which
37362306a36Sopenharmony_ci * case the host router does not have DROM.
37462306a36Sopenharmony_ci */
37562306a36Sopenharmony_ciint usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
37662306a36Sopenharmony_ci			  size_t size)
37762306a36Sopenharmony_ci{
37862306a36Sopenharmony_ci	return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES,
37962306a36Sopenharmony_ci				usb4_switch_drom_read_block, sw);
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci/**
38362306a36Sopenharmony_ci * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding
38462306a36Sopenharmony_ci * @sw: USB4 router
38562306a36Sopenharmony_ci *
38662306a36Sopenharmony_ci * Checks whether conditions are met so that lane bonding can be
38762306a36Sopenharmony_ci * established with the upstream router. Call only for device routers.
38862306a36Sopenharmony_ci */
38962306a36Sopenharmony_cibool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
39062306a36Sopenharmony_ci{
39162306a36Sopenharmony_ci	struct tb_port *up;
39262306a36Sopenharmony_ci	int ret;
39362306a36Sopenharmony_ci	u32 val;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	up = tb_upstream_port(sw);
39662306a36Sopenharmony_ci	ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1);
39762306a36Sopenharmony_ci	if (ret)
39862306a36Sopenharmony_ci		return false;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	return !!(val & PORT_CS_18_BE);
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci/**
40462306a36Sopenharmony_ci * usb4_switch_set_wake() - Enabled/disable wake
40562306a36Sopenharmony_ci * @sw: USB4 router
40662306a36Sopenharmony_ci * @flags: Wakeup flags (%0 to disable)
40762306a36Sopenharmony_ci *
40862306a36Sopenharmony_ci * Enables/disables router to wake up from sleep.
40962306a36Sopenharmony_ci */
41062306a36Sopenharmony_ciint usb4_switch_set_wake(struct tb_switch *sw, unsigned int flags)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct usb4_port *usb4;
41362306a36Sopenharmony_ci	struct tb_port *port;
41462306a36Sopenharmony_ci	u64 route = tb_route(sw);
41562306a36Sopenharmony_ci	u32 val;
41662306a36Sopenharmony_ci	int ret;
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_ci	/*
41962306a36Sopenharmony_ci	 * Enable wakes coming from all USB4 downstream ports (from
42062306a36Sopenharmony_ci	 * child routers). For device routers do this also for the
42162306a36Sopenharmony_ci	 * upstream USB4 port.
42262306a36Sopenharmony_ci	 */
42362306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
42462306a36Sopenharmony_ci		if (!tb_port_is_null(port))
42562306a36Sopenharmony_ci			continue;
42662306a36Sopenharmony_ci		if (!route && tb_is_upstream_port(port))
42762306a36Sopenharmony_ci			continue;
42862306a36Sopenharmony_ci		if (!port->cap_usb4)
42962306a36Sopenharmony_ci			continue;
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci		ret = tb_port_read(port, &val, TB_CFG_PORT,
43262306a36Sopenharmony_ci				   port->cap_usb4 + PORT_CS_19, 1);
43362306a36Sopenharmony_ci		if (ret)
43462306a36Sopenharmony_ci			return ret;
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci		val &= ~(PORT_CS_19_WOC | PORT_CS_19_WOD | PORT_CS_19_WOU4);
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		if (tb_is_upstream_port(port)) {
43962306a36Sopenharmony_ci			val |= PORT_CS_19_WOU4;
44062306a36Sopenharmony_ci		} else {
44162306a36Sopenharmony_ci			bool configured = val & PORT_CS_19_PC;
44262306a36Sopenharmony_ci			usb4 = port->usb4;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci			if (((flags & TB_WAKE_ON_CONNECT) |
44562306a36Sopenharmony_ci			      device_may_wakeup(&usb4->dev)) && !configured)
44662306a36Sopenharmony_ci				val |= PORT_CS_19_WOC;
44762306a36Sopenharmony_ci			if (((flags & TB_WAKE_ON_DISCONNECT) |
44862306a36Sopenharmony_ci			      device_may_wakeup(&usb4->dev)) && configured)
44962306a36Sopenharmony_ci				val |= PORT_CS_19_WOD;
45062306a36Sopenharmony_ci			if ((flags & TB_WAKE_ON_USB4) && configured)
45162306a36Sopenharmony_ci				val |= PORT_CS_19_WOU4;
45262306a36Sopenharmony_ci		}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci		ret = tb_port_write(port, &val, TB_CFG_PORT,
45562306a36Sopenharmony_ci				    port->cap_usb4 + PORT_CS_19, 1);
45662306a36Sopenharmony_ci		if (ret)
45762306a36Sopenharmony_ci			return ret;
45862306a36Sopenharmony_ci	}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	/*
46162306a36Sopenharmony_ci	 * Enable wakes from PCIe, USB 3.x and DP on this router. Only
46262306a36Sopenharmony_ci	 * needed for device routers.
46362306a36Sopenharmony_ci	 */
46462306a36Sopenharmony_ci	if (route) {
46562306a36Sopenharmony_ci		ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
46662306a36Sopenharmony_ci		if (ret)
46762306a36Sopenharmony_ci			return ret;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci		val &= ~(ROUTER_CS_5_WOP | ROUTER_CS_5_WOU | ROUTER_CS_5_WOD);
47062306a36Sopenharmony_ci		if (flags & TB_WAKE_ON_USB3)
47162306a36Sopenharmony_ci			val |= ROUTER_CS_5_WOU;
47262306a36Sopenharmony_ci		if (flags & TB_WAKE_ON_PCIE)
47362306a36Sopenharmony_ci			val |= ROUTER_CS_5_WOP;
47462306a36Sopenharmony_ci		if (flags & TB_WAKE_ON_DP)
47562306a36Sopenharmony_ci			val |= ROUTER_CS_5_WOD;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci		ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
47862306a36Sopenharmony_ci		if (ret)
47962306a36Sopenharmony_ci			return ret;
48062306a36Sopenharmony_ci	}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	return 0;
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci/**
48662306a36Sopenharmony_ci * usb4_switch_set_sleep() - Prepare the router to enter sleep
48762306a36Sopenharmony_ci * @sw: USB4 router
48862306a36Sopenharmony_ci *
48962306a36Sopenharmony_ci * Sets sleep bit for the router. Returns when the router sleep ready
49062306a36Sopenharmony_ci * bit has been asserted.
49162306a36Sopenharmony_ci */
49262306a36Sopenharmony_ciint usb4_switch_set_sleep(struct tb_switch *sw)
49362306a36Sopenharmony_ci{
49462306a36Sopenharmony_ci	int ret;
49562306a36Sopenharmony_ci	u32 val;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	/* Set sleep bit and wait for sleep ready to be asserted */
49862306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
49962306a36Sopenharmony_ci	if (ret)
50062306a36Sopenharmony_ci		return ret;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	val |= ROUTER_CS_5_SLP;
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
50562306a36Sopenharmony_ci	if (ret)
50662306a36Sopenharmony_ci		return ret;
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	return tb_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_SLPR,
50962306a36Sopenharmony_ci				      ROUTER_CS_6_SLPR, 500);
51062306a36Sopenharmony_ci}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci/**
51362306a36Sopenharmony_ci * usb4_switch_nvm_sector_size() - Return router NVM sector size
51462306a36Sopenharmony_ci * @sw: USB4 router
51562306a36Sopenharmony_ci *
51662306a36Sopenharmony_ci * If the router supports NVM operations this function returns the NVM
51762306a36Sopenharmony_ci * sector size in bytes. If NVM operations are not supported returns
51862306a36Sopenharmony_ci * %-EOPNOTSUPP.
51962306a36Sopenharmony_ci */
52062306a36Sopenharmony_ciint usb4_switch_nvm_sector_size(struct tb_switch *sw)
52162306a36Sopenharmony_ci{
52262306a36Sopenharmony_ci	u32 metadata;
52362306a36Sopenharmony_ci	u8 status;
52462306a36Sopenharmony_ci	int ret;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &metadata,
52762306a36Sopenharmony_ci			     &status);
52862306a36Sopenharmony_ci	if (ret)
52962306a36Sopenharmony_ci		return ret;
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (status)
53262306a36Sopenharmony_ci		return status == 0x2 ? -EOPNOTSUPP : -EIO;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	return metadata & USB4_NVM_SECTOR_SIZE_MASK;
53562306a36Sopenharmony_ci}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_cistatic int usb4_switch_nvm_read_block(void *data,
53862306a36Sopenharmony_ci	unsigned int dwaddress, void *buf, size_t dwords)
53962306a36Sopenharmony_ci{
54062306a36Sopenharmony_ci	struct tb_switch *sw = data;
54162306a36Sopenharmony_ci	u8 status = 0;
54262306a36Sopenharmony_ci	u32 metadata;
54362306a36Sopenharmony_ci	int ret;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	metadata = (dwords << USB4_NVM_READ_LENGTH_SHIFT) &
54662306a36Sopenharmony_ci		   USB4_NVM_READ_LENGTH_MASK;
54762306a36Sopenharmony_ci	metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) &
54862306a36Sopenharmony_ci		   USB4_NVM_READ_OFFSET_MASK;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_READ, &metadata,
55162306a36Sopenharmony_ci				  &status, NULL, 0, buf, dwords);
55262306a36Sopenharmony_ci	if (ret)
55362306a36Sopenharmony_ci		return ret;
55462306a36Sopenharmony_ci
55562306a36Sopenharmony_ci	return status ? -EIO : 0;
55662306a36Sopenharmony_ci}
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci/**
55962306a36Sopenharmony_ci * usb4_switch_nvm_read() - Read arbitrary bytes from router NVM
56062306a36Sopenharmony_ci * @sw: USB4 router
56162306a36Sopenharmony_ci * @address: Starting address in bytes
56262306a36Sopenharmony_ci * @buf: Read data is placed here
56362306a36Sopenharmony_ci * @size: How many bytes to read
56462306a36Sopenharmony_ci *
56562306a36Sopenharmony_ci * Reads NVM contents of the router. If NVM is not supported returns
56662306a36Sopenharmony_ci * %-EOPNOTSUPP.
56762306a36Sopenharmony_ci */
56862306a36Sopenharmony_ciint usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
56962306a36Sopenharmony_ci			 size_t size)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES,
57262306a36Sopenharmony_ci				usb4_switch_nvm_read_block, sw);
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci/**
57662306a36Sopenharmony_ci * usb4_switch_nvm_set_offset() - Set NVM write offset
57762306a36Sopenharmony_ci * @sw: USB4 router
57862306a36Sopenharmony_ci * @address: Start offset
57962306a36Sopenharmony_ci *
58062306a36Sopenharmony_ci * Explicitly sets NVM write offset. Normally when writing to NVM this
58162306a36Sopenharmony_ci * is done automatically by usb4_switch_nvm_write().
58262306a36Sopenharmony_ci *
58362306a36Sopenharmony_ci * Returns %0 in success and negative errno if there was a failure.
58462306a36Sopenharmony_ci */
58562306a36Sopenharmony_ciint usb4_switch_nvm_set_offset(struct tb_switch *sw, unsigned int address)
58662306a36Sopenharmony_ci{
58762306a36Sopenharmony_ci	u32 metadata, dwaddress;
58862306a36Sopenharmony_ci	u8 status = 0;
58962306a36Sopenharmony_ci	int ret;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci	dwaddress = address / 4;
59262306a36Sopenharmony_ci	metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) &
59362306a36Sopenharmony_ci		   USB4_NVM_SET_OFFSET_MASK;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &metadata,
59662306a36Sopenharmony_ci			     &status);
59762306a36Sopenharmony_ci	if (ret)
59862306a36Sopenharmony_ci		return ret;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	return status ? -EIO : 0;
60162306a36Sopenharmony_ci}
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_cistatic int usb4_switch_nvm_write_next_block(void *data, unsigned int dwaddress,
60462306a36Sopenharmony_ci					    const void *buf, size_t dwords)
60562306a36Sopenharmony_ci{
60662306a36Sopenharmony_ci	struct tb_switch *sw = data;
60762306a36Sopenharmony_ci	u8 status;
60862306a36Sopenharmony_ci	int ret;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_NVM_WRITE, NULL, &status,
61162306a36Sopenharmony_ci				  buf, dwords, NULL, 0);
61262306a36Sopenharmony_ci	if (ret)
61362306a36Sopenharmony_ci		return ret;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	return status ? -EIO : 0;
61662306a36Sopenharmony_ci}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci/**
61962306a36Sopenharmony_ci * usb4_switch_nvm_write() - Write to the router NVM
62062306a36Sopenharmony_ci * @sw: USB4 router
62162306a36Sopenharmony_ci * @address: Start address where to write in bytes
62262306a36Sopenharmony_ci * @buf: Pointer to the data to write
62362306a36Sopenharmony_ci * @size: Size of @buf in bytes
62462306a36Sopenharmony_ci *
62562306a36Sopenharmony_ci * Writes @buf to the router NVM using USB4 router operations. If NVM
62662306a36Sopenharmony_ci * write is not supported returns %-EOPNOTSUPP.
62762306a36Sopenharmony_ci */
62862306a36Sopenharmony_ciint usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
62962306a36Sopenharmony_ci			  const void *buf, size_t size)
63062306a36Sopenharmony_ci{
63162306a36Sopenharmony_ci	int ret;
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	ret = usb4_switch_nvm_set_offset(sw, address);
63462306a36Sopenharmony_ci	if (ret)
63562306a36Sopenharmony_ci		return ret;
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES,
63862306a36Sopenharmony_ci				 usb4_switch_nvm_write_next_block, sw);
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci/**
64262306a36Sopenharmony_ci * usb4_switch_nvm_authenticate() - Authenticate new NVM
64362306a36Sopenharmony_ci * @sw: USB4 router
64462306a36Sopenharmony_ci *
64562306a36Sopenharmony_ci * After the new NVM has been written via usb4_switch_nvm_write(), this
64662306a36Sopenharmony_ci * function triggers NVM authentication process. The router gets power
64762306a36Sopenharmony_ci * cycled and if the authentication is successful the new NVM starts
64862306a36Sopenharmony_ci * running. In case of failure returns negative errno.
64962306a36Sopenharmony_ci *
65062306a36Sopenharmony_ci * The caller should call usb4_switch_nvm_authenticate_status() to read
65162306a36Sopenharmony_ci * the status of the authentication after power cycle. It should be the
65262306a36Sopenharmony_ci * first router operation to avoid the status being lost.
65362306a36Sopenharmony_ci */
65462306a36Sopenharmony_ciint usb4_switch_nvm_authenticate(struct tb_switch *sw)
65562306a36Sopenharmony_ci{
65662306a36Sopenharmony_ci	int ret;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, NULL, NULL);
65962306a36Sopenharmony_ci	switch (ret) {
66062306a36Sopenharmony_ci	/*
66162306a36Sopenharmony_ci	 * The router is power cycled once NVM_AUTH is started so it is
66262306a36Sopenharmony_ci	 * expected to get any of the following errors back.
66362306a36Sopenharmony_ci	 */
66462306a36Sopenharmony_ci	case -EACCES:
66562306a36Sopenharmony_ci	case -ENOTCONN:
66662306a36Sopenharmony_ci	case -ETIMEDOUT:
66762306a36Sopenharmony_ci		return 0;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	default:
67062306a36Sopenharmony_ci		return ret;
67162306a36Sopenharmony_ci	}
67262306a36Sopenharmony_ci}
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci/**
67562306a36Sopenharmony_ci * usb4_switch_nvm_authenticate_status() - Read status of last NVM authenticate
67662306a36Sopenharmony_ci * @sw: USB4 router
67762306a36Sopenharmony_ci * @status: Status code of the operation
67862306a36Sopenharmony_ci *
67962306a36Sopenharmony_ci * The function checks if there is status available from the last NVM
68062306a36Sopenharmony_ci * authenticate router operation. If there is status then %0 is returned
68162306a36Sopenharmony_ci * and the status code is placed in @status. Returns negative errno in case
68262306a36Sopenharmony_ci * of failure.
68362306a36Sopenharmony_ci *
68462306a36Sopenharmony_ci * Must be called before any other router operation.
68562306a36Sopenharmony_ci */
68662306a36Sopenharmony_ciint usb4_switch_nvm_authenticate_status(struct tb_switch *sw, u32 *status)
68762306a36Sopenharmony_ci{
68862306a36Sopenharmony_ci	const struct tb_cm_ops *cm_ops = sw->tb->cm_ops;
68962306a36Sopenharmony_ci	u16 opcode;
69062306a36Sopenharmony_ci	u32 val;
69162306a36Sopenharmony_ci	int ret;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	if (cm_ops->usb4_switch_nvm_authenticate_status) {
69462306a36Sopenharmony_ci		ret = cm_ops->usb4_switch_nvm_authenticate_status(sw, status);
69562306a36Sopenharmony_ci		if (ret != -EOPNOTSUPP)
69662306a36Sopenharmony_ci			return ret;
69762306a36Sopenharmony_ci	}
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
70062306a36Sopenharmony_ci	if (ret)
70162306a36Sopenharmony_ci		return ret;
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	/* Check that the opcode is correct */
70462306a36Sopenharmony_ci	opcode = val & ROUTER_CS_26_OPCODE_MASK;
70562306a36Sopenharmony_ci	if (opcode == USB4_SWITCH_OP_NVM_AUTH) {
70662306a36Sopenharmony_ci		if (val & ROUTER_CS_26_OV)
70762306a36Sopenharmony_ci			return -EBUSY;
70862306a36Sopenharmony_ci		if (val & ROUTER_CS_26_ONS)
70962306a36Sopenharmony_ci			return -EOPNOTSUPP;
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci		*status = (val & ROUTER_CS_26_STATUS_MASK) >>
71262306a36Sopenharmony_ci			ROUTER_CS_26_STATUS_SHIFT;
71362306a36Sopenharmony_ci	} else {
71462306a36Sopenharmony_ci		*status = 0;
71562306a36Sopenharmony_ci	}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	return 0;
71862306a36Sopenharmony_ci}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci/**
72162306a36Sopenharmony_ci * usb4_switch_credits_init() - Read buffer allocation parameters
72262306a36Sopenharmony_ci * @sw: USB4 router
72362306a36Sopenharmony_ci *
72462306a36Sopenharmony_ci * Reads @sw buffer allocation parameters and initializes @sw buffer
72562306a36Sopenharmony_ci * allocation fields accordingly. Specifically @sw->credits_allocation
72662306a36Sopenharmony_ci * is set to %true if these parameters can be used in tunneling.
72762306a36Sopenharmony_ci *
72862306a36Sopenharmony_ci * Returns %0 on success and negative errno otherwise.
72962306a36Sopenharmony_ci */
73062306a36Sopenharmony_ciint usb4_switch_credits_init(struct tb_switch *sw)
73162306a36Sopenharmony_ci{
73262306a36Sopenharmony_ci	int max_usb3, min_dp_aux, min_dp_main, max_pcie, max_dma;
73362306a36Sopenharmony_ci	int ret, length, i, nports;
73462306a36Sopenharmony_ci	const struct tb_port *port;
73562306a36Sopenharmony_ci	u32 data[USB4_DATA_DWORDS];
73662306a36Sopenharmony_ci	u32 metadata = 0;
73762306a36Sopenharmony_ci	u8 status = 0;
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	memset(data, 0, sizeof(data));
74062306a36Sopenharmony_ci	ret = usb4_switch_op_data(sw, USB4_SWITCH_OP_BUFFER_ALLOC, &metadata,
74162306a36Sopenharmony_ci				  &status, NULL, 0, data, ARRAY_SIZE(data));
74262306a36Sopenharmony_ci	if (ret)
74362306a36Sopenharmony_ci		return ret;
74462306a36Sopenharmony_ci	if (status)
74562306a36Sopenharmony_ci		return -EIO;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	length = metadata & USB4_BA_LENGTH_MASK;
74862306a36Sopenharmony_ci	if (WARN_ON(length > ARRAY_SIZE(data)))
74962306a36Sopenharmony_ci		return -EMSGSIZE;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	max_usb3 = -1;
75262306a36Sopenharmony_ci	min_dp_aux = -1;
75362306a36Sopenharmony_ci	min_dp_main = -1;
75462306a36Sopenharmony_ci	max_pcie = -1;
75562306a36Sopenharmony_ci	max_dma = -1;
75662306a36Sopenharmony_ci
75762306a36Sopenharmony_ci	tb_sw_dbg(sw, "credit allocation parameters:\n");
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	for (i = 0; i < length; i++) {
76062306a36Sopenharmony_ci		u16 index, value;
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci		index = data[i] & USB4_BA_INDEX_MASK;
76362306a36Sopenharmony_ci		value = (data[i] & USB4_BA_VALUE_MASK) >> USB4_BA_VALUE_SHIFT;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci		switch (index) {
76662306a36Sopenharmony_ci		case USB4_BA_MAX_USB3:
76762306a36Sopenharmony_ci			tb_sw_dbg(sw, " USB3: %u\n", value);
76862306a36Sopenharmony_ci			max_usb3 = value;
76962306a36Sopenharmony_ci			break;
77062306a36Sopenharmony_ci		case USB4_BA_MIN_DP_AUX:
77162306a36Sopenharmony_ci			tb_sw_dbg(sw, " DP AUX: %u\n", value);
77262306a36Sopenharmony_ci			min_dp_aux = value;
77362306a36Sopenharmony_ci			break;
77462306a36Sopenharmony_ci		case USB4_BA_MIN_DP_MAIN:
77562306a36Sopenharmony_ci			tb_sw_dbg(sw, " DP main: %u\n", value);
77662306a36Sopenharmony_ci			min_dp_main = value;
77762306a36Sopenharmony_ci			break;
77862306a36Sopenharmony_ci		case USB4_BA_MAX_PCIE:
77962306a36Sopenharmony_ci			tb_sw_dbg(sw, " PCIe: %u\n", value);
78062306a36Sopenharmony_ci			max_pcie = value;
78162306a36Sopenharmony_ci			break;
78262306a36Sopenharmony_ci		case USB4_BA_MAX_HI:
78362306a36Sopenharmony_ci			tb_sw_dbg(sw, " DMA: %u\n", value);
78462306a36Sopenharmony_ci			max_dma = value;
78562306a36Sopenharmony_ci			break;
78662306a36Sopenharmony_ci		default:
78762306a36Sopenharmony_ci			tb_sw_dbg(sw, " unknown credit allocation index %#x, skipping\n",
78862306a36Sopenharmony_ci				  index);
78962306a36Sopenharmony_ci			break;
79062306a36Sopenharmony_ci		}
79162306a36Sopenharmony_ci	}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	/*
79462306a36Sopenharmony_ci	 * Validate the buffer allocation preferences. If we find
79562306a36Sopenharmony_ci	 * issues, log a warning and fall back using the hard-coded
79662306a36Sopenharmony_ci	 * values.
79762306a36Sopenharmony_ci	 */
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ci	/* Host router must report baMaxHI */
80062306a36Sopenharmony_ci	if (!tb_route(sw) && max_dma < 0) {
80162306a36Sopenharmony_ci		tb_sw_warn(sw, "host router is missing baMaxHI\n");
80262306a36Sopenharmony_ci		goto err_invalid;
80362306a36Sopenharmony_ci	}
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	nports = 0;
80662306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
80762306a36Sopenharmony_ci		if (tb_port_is_null(port))
80862306a36Sopenharmony_ci			nports++;
80962306a36Sopenharmony_ci	}
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci	/* Must have DP buffer allocation (multiple USB4 ports) */
81262306a36Sopenharmony_ci	if (nports > 2 && (min_dp_aux < 0 || min_dp_main < 0)) {
81362306a36Sopenharmony_ci		tb_sw_warn(sw, "multiple USB4 ports require baMinDPaux/baMinDPmain\n");
81462306a36Sopenharmony_ci		goto err_invalid;
81562306a36Sopenharmony_ci	}
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
81862306a36Sopenharmony_ci		if (tb_port_is_dpout(port) && min_dp_main < 0) {
81962306a36Sopenharmony_ci			tb_sw_warn(sw, "missing baMinDPmain");
82062306a36Sopenharmony_ci			goto err_invalid;
82162306a36Sopenharmony_ci		}
82262306a36Sopenharmony_ci		if ((tb_port_is_dpin(port) || tb_port_is_dpout(port)) &&
82362306a36Sopenharmony_ci		    min_dp_aux < 0) {
82462306a36Sopenharmony_ci			tb_sw_warn(sw, "missing baMinDPaux");
82562306a36Sopenharmony_ci			goto err_invalid;
82662306a36Sopenharmony_ci		}
82762306a36Sopenharmony_ci		if ((tb_port_is_usb3_down(port) || tb_port_is_usb3_up(port)) &&
82862306a36Sopenharmony_ci		    max_usb3 < 0) {
82962306a36Sopenharmony_ci			tb_sw_warn(sw, "missing baMaxUSB3");
83062306a36Sopenharmony_ci			goto err_invalid;
83162306a36Sopenharmony_ci		}
83262306a36Sopenharmony_ci		if ((tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) &&
83362306a36Sopenharmony_ci		    max_pcie < 0) {
83462306a36Sopenharmony_ci			tb_sw_warn(sw, "missing baMaxPCIe");
83562306a36Sopenharmony_ci			goto err_invalid;
83662306a36Sopenharmony_ci		}
83762306a36Sopenharmony_ci	}
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci	/*
84062306a36Sopenharmony_ci	 * Buffer allocation passed the validation so we can use it in
84162306a36Sopenharmony_ci	 * path creation.
84262306a36Sopenharmony_ci	 */
84362306a36Sopenharmony_ci	sw->credit_allocation = true;
84462306a36Sopenharmony_ci	if (max_usb3 > 0)
84562306a36Sopenharmony_ci		sw->max_usb3_credits = max_usb3;
84662306a36Sopenharmony_ci	if (min_dp_aux > 0)
84762306a36Sopenharmony_ci		sw->min_dp_aux_credits = min_dp_aux;
84862306a36Sopenharmony_ci	if (min_dp_main > 0)
84962306a36Sopenharmony_ci		sw->min_dp_main_credits = min_dp_main;
85062306a36Sopenharmony_ci	if (max_pcie > 0)
85162306a36Sopenharmony_ci		sw->max_pcie_credits = max_pcie;
85262306a36Sopenharmony_ci	if (max_dma > 0)
85362306a36Sopenharmony_ci		sw->max_dma_credits = max_dma;
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	return 0;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cierr_invalid:
85862306a36Sopenharmony_ci	return -EINVAL;
85962306a36Sopenharmony_ci}
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci/**
86262306a36Sopenharmony_ci * usb4_switch_query_dp_resource() - Query availability of DP IN resource
86362306a36Sopenharmony_ci * @sw: USB4 router
86462306a36Sopenharmony_ci * @in: DP IN adapter
86562306a36Sopenharmony_ci *
86662306a36Sopenharmony_ci * For DP tunneling this function can be used to query availability of
86762306a36Sopenharmony_ci * DP IN resource. Returns true if the resource is available for DP
86862306a36Sopenharmony_ci * tunneling, false otherwise.
86962306a36Sopenharmony_ci */
87062306a36Sopenharmony_cibool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
87162306a36Sopenharmony_ci{
87262306a36Sopenharmony_ci	u32 metadata = in->port;
87362306a36Sopenharmony_ci	u8 status;
87462306a36Sopenharmony_ci	int ret;
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &metadata,
87762306a36Sopenharmony_ci			     &status);
87862306a36Sopenharmony_ci	/*
87962306a36Sopenharmony_ci	 * If DP resource allocation is not supported assume it is
88062306a36Sopenharmony_ci	 * always available.
88162306a36Sopenharmony_ci	 */
88262306a36Sopenharmony_ci	if (ret == -EOPNOTSUPP)
88362306a36Sopenharmony_ci		return true;
88462306a36Sopenharmony_ci	if (ret)
88562306a36Sopenharmony_ci		return false;
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	return !status;
88862306a36Sopenharmony_ci}
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci/**
89162306a36Sopenharmony_ci * usb4_switch_alloc_dp_resource() - Allocate DP IN resource
89262306a36Sopenharmony_ci * @sw: USB4 router
89362306a36Sopenharmony_ci * @in: DP IN adapter
89462306a36Sopenharmony_ci *
89562306a36Sopenharmony_ci * Allocates DP IN resource for DP tunneling using USB4 router
89662306a36Sopenharmony_ci * operations. If the resource was allocated returns %0. Otherwise
89762306a36Sopenharmony_ci * returns negative errno, in particular %-EBUSY if the resource is
89862306a36Sopenharmony_ci * already allocated.
89962306a36Sopenharmony_ci */
90062306a36Sopenharmony_ciint usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
90162306a36Sopenharmony_ci{
90262306a36Sopenharmony_ci	u32 metadata = in->port;
90362306a36Sopenharmony_ci	u8 status;
90462306a36Sopenharmony_ci	int ret;
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &metadata,
90762306a36Sopenharmony_ci			     &status);
90862306a36Sopenharmony_ci	if (ret == -EOPNOTSUPP)
90962306a36Sopenharmony_ci		return 0;
91062306a36Sopenharmony_ci	if (ret)
91162306a36Sopenharmony_ci		return ret;
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_ci	return status ? -EBUSY : 0;
91462306a36Sopenharmony_ci}
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci/**
91762306a36Sopenharmony_ci * usb4_switch_dealloc_dp_resource() - Releases allocated DP IN resource
91862306a36Sopenharmony_ci * @sw: USB4 router
91962306a36Sopenharmony_ci * @in: DP IN adapter
92062306a36Sopenharmony_ci *
92162306a36Sopenharmony_ci * Releases the previously allocated DP IN resource.
92262306a36Sopenharmony_ci */
92362306a36Sopenharmony_ciint usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
92462306a36Sopenharmony_ci{
92562306a36Sopenharmony_ci	u32 metadata = in->port;
92662306a36Sopenharmony_ci	u8 status;
92762306a36Sopenharmony_ci	int ret;
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &metadata,
93062306a36Sopenharmony_ci			     &status);
93162306a36Sopenharmony_ci	if (ret == -EOPNOTSUPP)
93262306a36Sopenharmony_ci		return 0;
93362306a36Sopenharmony_ci	if (ret)
93462306a36Sopenharmony_ci		return ret;
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	return status ? -EIO : 0;
93762306a36Sopenharmony_ci}
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_cistatic int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port)
94062306a36Sopenharmony_ci{
94162306a36Sopenharmony_ci	struct tb_port *p;
94262306a36Sopenharmony_ci	int usb4_idx = 0;
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_ci	/* Assume port is primary */
94562306a36Sopenharmony_ci	tb_switch_for_each_port(sw, p) {
94662306a36Sopenharmony_ci		if (!tb_port_is_null(p))
94762306a36Sopenharmony_ci			continue;
94862306a36Sopenharmony_ci		if (tb_is_upstream_port(p))
94962306a36Sopenharmony_ci			continue;
95062306a36Sopenharmony_ci		if (!p->link_nr) {
95162306a36Sopenharmony_ci			if (p == port)
95262306a36Sopenharmony_ci				break;
95362306a36Sopenharmony_ci			usb4_idx++;
95462306a36Sopenharmony_ci		}
95562306a36Sopenharmony_ci	}
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci	return usb4_idx;
95862306a36Sopenharmony_ci}
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ci/**
96162306a36Sopenharmony_ci * usb4_switch_map_pcie_down() - Map USB4 port to a PCIe downstream adapter
96262306a36Sopenharmony_ci * @sw: USB4 router
96362306a36Sopenharmony_ci * @port: USB4 port
96462306a36Sopenharmony_ci *
96562306a36Sopenharmony_ci * USB4 routers have direct mapping between USB4 ports and PCIe
96662306a36Sopenharmony_ci * downstream adapters where the PCIe topology is extended. This
96762306a36Sopenharmony_ci * function returns the corresponding downstream PCIe adapter or %NULL
96862306a36Sopenharmony_ci * if no such mapping was possible.
96962306a36Sopenharmony_ci */
97062306a36Sopenharmony_cistruct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
97162306a36Sopenharmony_ci					  const struct tb_port *port)
97262306a36Sopenharmony_ci{
97362306a36Sopenharmony_ci	int usb4_idx = usb4_port_idx(sw, port);
97462306a36Sopenharmony_ci	struct tb_port *p;
97562306a36Sopenharmony_ci	int pcie_idx = 0;
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	/* Find PCIe down port matching usb4_port */
97862306a36Sopenharmony_ci	tb_switch_for_each_port(sw, p) {
97962306a36Sopenharmony_ci		if (!tb_port_is_pcie_down(p))
98062306a36Sopenharmony_ci			continue;
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci		if (pcie_idx == usb4_idx)
98362306a36Sopenharmony_ci			return p;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci		pcie_idx++;
98662306a36Sopenharmony_ci	}
98762306a36Sopenharmony_ci
98862306a36Sopenharmony_ci	return NULL;
98962306a36Sopenharmony_ci}
99062306a36Sopenharmony_ci
99162306a36Sopenharmony_ci/**
99262306a36Sopenharmony_ci * usb4_switch_map_usb3_down() - Map USB4 port to a USB3 downstream adapter
99362306a36Sopenharmony_ci * @sw: USB4 router
99462306a36Sopenharmony_ci * @port: USB4 port
99562306a36Sopenharmony_ci *
99662306a36Sopenharmony_ci * USB4 routers have direct mapping between USB4 ports and USB 3.x
99762306a36Sopenharmony_ci * downstream adapters where the USB 3.x topology is extended. This
99862306a36Sopenharmony_ci * function returns the corresponding downstream USB 3.x adapter or
99962306a36Sopenharmony_ci * %NULL if no such mapping was possible.
100062306a36Sopenharmony_ci */
100162306a36Sopenharmony_cistruct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
100262306a36Sopenharmony_ci					  const struct tb_port *port)
100362306a36Sopenharmony_ci{
100462306a36Sopenharmony_ci	int usb4_idx = usb4_port_idx(sw, port);
100562306a36Sopenharmony_ci	struct tb_port *p;
100662306a36Sopenharmony_ci	int usb_idx = 0;
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_ci	/* Find USB3 down port matching usb4_port */
100962306a36Sopenharmony_ci	tb_switch_for_each_port(sw, p) {
101062306a36Sopenharmony_ci		if (!tb_port_is_usb3_down(p))
101162306a36Sopenharmony_ci			continue;
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ci		if (usb_idx == usb4_idx)
101462306a36Sopenharmony_ci			return p;
101562306a36Sopenharmony_ci
101662306a36Sopenharmony_ci		usb_idx++;
101762306a36Sopenharmony_ci	}
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	return NULL;
102062306a36Sopenharmony_ci}
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_ci/**
102362306a36Sopenharmony_ci * usb4_switch_add_ports() - Add USB4 ports for this router
102462306a36Sopenharmony_ci * @sw: USB4 router
102562306a36Sopenharmony_ci *
102662306a36Sopenharmony_ci * For USB4 router finds all USB4 ports and registers devices for each.
102762306a36Sopenharmony_ci * Can be called to any router.
102862306a36Sopenharmony_ci *
102962306a36Sopenharmony_ci * Return %0 in case of success and negative errno in case of failure.
103062306a36Sopenharmony_ci */
103162306a36Sopenharmony_ciint usb4_switch_add_ports(struct tb_switch *sw)
103262306a36Sopenharmony_ci{
103362306a36Sopenharmony_ci	struct tb_port *port;
103462306a36Sopenharmony_ci
103562306a36Sopenharmony_ci	if (tb_switch_is_icm(sw) || !tb_switch_is_usb4(sw))
103662306a36Sopenharmony_ci		return 0;
103762306a36Sopenharmony_ci
103862306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
103962306a36Sopenharmony_ci		struct usb4_port *usb4;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci		if (!tb_port_is_null(port))
104262306a36Sopenharmony_ci			continue;
104362306a36Sopenharmony_ci		if (!port->cap_usb4)
104462306a36Sopenharmony_ci			continue;
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci		usb4 = usb4_port_device_add(port);
104762306a36Sopenharmony_ci		if (IS_ERR(usb4)) {
104862306a36Sopenharmony_ci			usb4_switch_remove_ports(sw);
104962306a36Sopenharmony_ci			return PTR_ERR(usb4);
105062306a36Sopenharmony_ci		}
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci		port->usb4 = usb4;
105362306a36Sopenharmony_ci	}
105462306a36Sopenharmony_ci
105562306a36Sopenharmony_ci	return 0;
105662306a36Sopenharmony_ci}
105762306a36Sopenharmony_ci
105862306a36Sopenharmony_ci/**
105962306a36Sopenharmony_ci * usb4_switch_remove_ports() - Removes USB4 ports from this router
106062306a36Sopenharmony_ci * @sw: USB4 router
106162306a36Sopenharmony_ci *
106262306a36Sopenharmony_ci * Unregisters previously registered USB4 ports.
106362306a36Sopenharmony_ci */
106462306a36Sopenharmony_civoid usb4_switch_remove_ports(struct tb_switch *sw)
106562306a36Sopenharmony_ci{
106662306a36Sopenharmony_ci	struct tb_port *port;
106762306a36Sopenharmony_ci
106862306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
106962306a36Sopenharmony_ci		if (port->usb4) {
107062306a36Sopenharmony_ci			usb4_port_device_remove(port->usb4);
107162306a36Sopenharmony_ci			port->usb4 = NULL;
107262306a36Sopenharmony_ci		}
107362306a36Sopenharmony_ci	}
107462306a36Sopenharmony_ci}
107562306a36Sopenharmony_ci
107662306a36Sopenharmony_ci/**
107762306a36Sopenharmony_ci * usb4_port_unlock() - Unlock USB4 downstream port
107862306a36Sopenharmony_ci * @port: USB4 port to unlock
107962306a36Sopenharmony_ci *
108062306a36Sopenharmony_ci * Unlocks USB4 downstream port so that the connection manager can
108162306a36Sopenharmony_ci * access the router below this port.
108262306a36Sopenharmony_ci */
108362306a36Sopenharmony_ciint usb4_port_unlock(struct tb_port *port)
108462306a36Sopenharmony_ci{
108562306a36Sopenharmony_ci	int ret;
108662306a36Sopenharmony_ci	u32 val;
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
108962306a36Sopenharmony_ci	if (ret)
109062306a36Sopenharmony_ci		return ret;
109162306a36Sopenharmony_ci
109262306a36Sopenharmony_ci	val &= ~ADP_CS_4_LCK;
109362306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
109462306a36Sopenharmony_ci}
109562306a36Sopenharmony_ci
109662306a36Sopenharmony_ci/**
109762306a36Sopenharmony_ci * usb4_port_hotplug_enable() - Enables hotplug for a port
109862306a36Sopenharmony_ci * @port: USB4 port to operate on
109962306a36Sopenharmony_ci *
110062306a36Sopenharmony_ci * Enables hot plug events on a given port. This is only intended
110162306a36Sopenharmony_ci * to be used on lane, DP-IN, and DP-OUT adapters.
110262306a36Sopenharmony_ci */
110362306a36Sopenharmony_ciint usb4_port_hotplug_enable(struct tb_port *port)
110462306a36Sopenharmony_ci{
110562306a36Sopenharmony_ci	int ret;
110662306a36Sopenharmony_ci	u32 val;
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_5, 1);
110962306a36Sopenharmony_ci	if (ret)
111062306a36Sopenharmony_ci		return ret;
111162306a36Sopenharmony_ci
111262306a36Sopenharmony_ci	val &= ~ADP_CS_5_DHP;
111362306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_5, 1);
111462306a36Sopenharmony_ci}
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_cistatic int usb4_port_set_configured(struct tb_port *port, bool configured)
111762306a36Sopenharmony_ci{
111862306a36Sopenharmony_ci	int ret;
111962306a36Sopenharmony_ci	u32 val;
112062306a36Sopenharmony_ci
112162306a36Sopenharmony_ci	if (!port->cap_usb4)
112262306a36Sopenharmony_ci		return -EINVAL;
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
112562306a36Sopenharmony_ci			   port->cap_usb4 + PORT_CS_19, 1);
112662306a36Sopenharmony_ci	if (ret)
112762306a36Sopenharmony_ci		return ret;
112862306a36Sopenharmony_ci
112962306a36Sopenharmony_ci	if (configured)
113062306a36Sopenharmony_ci		val |= PORT_CS_19_PC;
113162306a36Sopenharmony_ci	else
113262306a36Sopenharmony_ci		val &= ~PORT_CS_19_PC;
113362306a36Sopenharmony_ci
113462306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
113562306a36Sopenharmony_ci			     port->cap_usb4 + PORT_CS_19, 1);
113662306a36Sopenharmony_ci}
113762306a36Sopenharmony_ci
113862306a36Sopenharmony_ci/**
113962306a36Sopenharmony_ci * usb4_port_configure() - Set USB4 port configured
114062306a36Sopenharmony_ci * @port: USB4 router
114162306a36Sopenharmony_ci *
114262306a36Sopenharmony_ci * Sets the USB4 link to be configured for power management purposes.
114362306a36Sopenharmony_ci */
114462306a36Sopenharmony_ciint usb4_port_configure(struct tb_port *port)
114562306a36Sopenharmony_ci{
114662306a36Sopenharmony_ci	return usb4_port_set_configured(port, true);
114762306a36Sopenharmony_ci}
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_ci/**
115062306a36Sopenharmony_ci * usb4_port_unconfigure() - Set USB4 port unconfigured
115162306a36Sopenharmony_ci * @port: USB4 router
115262306a36Sopenharmony_ci *
115362306a36Sopenharmony_ci * Sets the USB4 link to be unconfigured for power management purposes.
115462306a36Sopenharmony_ci */
115562306a36Sopenharmony_civoid usb4_port_unconfigure(struct tb_port *port)
115662306a36Sopenharmony_ci{
115762306a36Sopenharmony_ci	usb4_port_set_configured(port, false);
115862306a36Sopenharmony_ci}
115962306a36Sopenharmony_ci
116062306a36Sopenharmony_cistatic int usb4_set_xdomain_configured(struct tb_port *port, bool configured)
116162306a36Sopenharmony_ci{
116262306a36Sopenharmony_ci	int ret;
116362306a36Sopenharmony_ci	u32 val;
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci	if (!port->cap_usb4)
116662306a36Sopenharmony_ci		return -EINVAL;
116762306a36Sopenharmony_ci
116862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
116962306a36Sopenharmony_ci			   port->cap_usb4 + PORT_CS_19, 1);
117062306a36Sopenharmony_ci	if (ret)
117162306a36Sopenharmony_ci		return ret;
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_ci	if (configured)
117462306a36Sopenharmony_ci		val |= PORT_CS_19_PID;
117562306a36Sopenharmony_ci	else
117662306a36Sopenharmony_ci		val &= ~PORT_CS_19_PID;
117762306a36Sopenharmony_ci
117862306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
117962306a36Sopenharmony_ci			     port->cap_usb4 + PORT_CS_19, 1);
118062306a36Sopenharmony_ci}
118162306a36Sopenharmony_ci
118262306a36Sopenharmony_ci/**
118362306a36Sopenharmony_ci * usb4_port_configure_xdomain() - Configure port for XDomain
118462306a36Sopenharmony_ci * @port: USB4 port connected to another host
118562306a36Sopenharmony_ci * @xd: XDomain that is connected to the port
118662306a36Sopenharmony_ci *
118762306a36Sopenharmony_ci * Marks the USB4 port as being connected to another host and updates
118862306a36Sopenharmony_ci * the link type. Returns %0 in success and negative errno in failure.
118962306a36Sopenharmony_ci */
119062306a36Sopenharmony_ciint usb4_port_configure_xdomain(struct tb_port *port, struct tb_xdomain *xd)
119162306a36Sopenharmony_ci{
119262306a36Sopenharmony_ci	xd->link_usb4 = link_is_usb4(port);
119362306a36Sopenharmony_ci	return usb4_set_xdomain_configured(port, true);
119462306a36Sopenharmony_ci}
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci/**
119762306a36Sopenharmony_ci * usb4_port_unconfigure_xdomain() - Unconfigure port for XDomain
119862306a36Sopenharmony_ci * @port: USB4 port that was connected to another host
119962306a36Sopenharmony_ci *
120062306a36Sopenharmony_ci * Clears USB4 port from being marked as XDomain.
120162306a36Sopenharmony_ci */
120262306a36Sopenharmony_civoid usb4_port_unconfigure_xdomain(struct tb_port *port)
120362306a36Sopenharmony_ci{
120462306a36Sopenharmony_ci	usb4_set_xdomain_configured(port, false);
120562306a36Sopenharmony_ci}
120662306a36Sopenharmony_ci
120762306a36Sopenharmony_cistatic int usb4_port_wait_for_bit(struct tb_port *port, u32 offset, u32 bit,
120862306a36Sopenharmony_ci				  u32 value, int timeout_msec)
120962306a36Sopenharmony_ci{
121062306a36Sopenharmony_ci	ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
121162306a36Sopenharmony_ci
121262306a36Sopenharmony_ci	do {
121362306a36Sopenharmony_ci		u32 val;
121462306a36Sopenharmony_ci		int ret;
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci		ret = tb_port_read(port, &val, TB_CFG_PORT, offset, 1);
121762306a36Sopenharmony_ci		if (ret)
121862306a36Sopenharmony_ci			return ret;
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci		if ((val & bit) == value)
122162306a36Sopenharmony_ci			return 0;
122262306a36Sopenharmony_ci
122362306a36Sopenharmony_ci		usleep_range(50, 100);
122462306a36Sopenharmony_ci	} while (ktime_before(ktime_get(), timeout));
122562306a36Sopenharmony_ci
122662306a36Sopenharmony_ci	return -ETIMEDOUT;
122762306a36Sopenharmony_ci}
122862306a36Sopenharmony_ci
122962306a36Sopenharmony_cistatic int usb4_port_read_data(struct tb_port *port, void *data, size_t dwords)
123062306a36Sopenharmony_ci{
123162306a36Sopenharmony_ci	if (dwords > USB4_DATA_DWORDS)
123262306a36Sopenharmony_ci		return -EINVAL;
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	return tb_port_read(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2,
123562306a36Sopenharmony_ci			    dwords);
123662306a36Sopenharmony_ci}
123762306a36Sopenharmony_ci
123862306a36Sopenharmony_cistatic int usb4_port_write_data(struct tb_port *port, const void *data,
123962306a36Sopenharmony_ci				size_t dwords)
124062306a36Sopenharmony_ci{
124162306a36Sopenharmony_ci	if (dwords > USB4_DATA_DWORDS)
124262306a36Sopenharmony_ci		return -EINVAL;
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	return tb_port_write(port, data, TB_CFG_PORT, port->cap_usb4 + PORT_CS_2,
124562306a36Sopenharmony_ci			     dwords);
124662306a36Sopenharmony_ci}
124762306a36Sopenharmony_ci
124862306a36Sopenharmony_cistatic int usb4_port_sb_read(struct tb_port *port, enum usb4_sb_target target,
124962306a36Sopenharmony_ci			     u8 index, u8 reg, void *buf, u8 size)
125062306a36Sopenharmony_ci{
125162306a36Sopenharmony_ci	size_t dwords = DIV_ROUND_UP(size, 4);
125262306a36Sopenharmony_ci	int ret;
125362306a36Sopenharmony_ci	u32 val;
125462306a36Sopenharmony_ci
125562306a36Sopenharmony_ci	if (!port->cap_usb4)
125662306a36Sopenharmony_ci		return -EINVAL;
125762306a36Sopenharmony_ci
125862306a36Sopenharmony_ci	val = reg;
125962306a36Sopenharmony_ci	val |= size << PORT_CS_1_LENGTH_SHIFT;
126062306a36Sopenharmony_ci	val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK;
126162306a36Sopenharmony_ci	if (target == USB4_SB_TARGET_RETIMER)
126262306a36Sopenharmony_ci		val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT);
126362306a36Sopenharmony_ci	val |= PORT_CS_1_PND;
126462306a36Sopenharmony_ci
126562306a36Sopenharmony_ci	ret = tb_port_write(port, &val, TB_CFG_PORT,
126662306a36Sopenharmony_ci			    port->cap_usb4 + PORT_CS_1, 1);
126762306a36Sopenharmony_ci	if (ret)
126862306a36Sopenharmony_ci		return ret;
126962306a36Sopenharmony_ci
127062306a36Sopenharmony_ci	ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1,
127162306a36Sopenharmony_ci				     PORT_CS_1_PND, 0, 500);
127262306a36Sopenharmony_ci	if (ret)
127362306a36Sopenharmony_ci		return ret;
127462306a36Sopenharmony_ci
127562306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
127662306a36Sopenharmony_ci			    port->cap_usb4 + PORT_CS_1, 1);
127762306a36Sopenharmony_ci	if (ret)
127862306a36Sopenharmony_ci		return ret;
127962306a36Sopenharmony_ci
128062306a36Sopenharmony_ci	if (val & PORT_CS_1_NR)
128162306a36Sopenharmony_ci		return -ENODEV;
128262306a36Sopenharmony_ci	if (val & PORT_CS_1_RC)
128362306a36Sopenharmony_ci		return -EIO;
128462306a36Sopenharmony_ci
128562306a36Sopenharmony_ci	return buf ? usb4_port_read_data(port, buf, dwords) : 0;
128662306a36Sopenharmony_ci}
128762306a36Sopenharmony_ci
128862306a36Sopenharmony_cistatic int usb4_port_sb_write(struct tb_port *port, enum usb4_sb_target target,
128962306a36Sopenharmony_ci			      u8 index, u8 reg, const void *buf, u8 size)
129062306a36Sopenharmony_ci{
129162306a36Sopenharmony_ci	size_t dwords = DIV_ROUND_UP(size, 4);
129262306a36Sopenharmony_ci	int ret;
129362306a36Sopenharmony_ci	u32 val;
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_ci	if (!port->cap_usb4)
129662306a36Sopenharmony_ci		return -EINVAL;
129762306a36Sopenharmony_ci
129862306a36Sopenharmony_ci	if (buf) {
129962306a36Sopenharmony_ci		ret = usb4_port_write_data(port, buf, dwords);
130062306a36Sopenharmony_ci		if (ret)
130162306a36Sopenharmony_ci			return ret;
130262306a36Sopenharmony_ci	}
130362306a36Sopenharmony_ci
130462306a36Sopenharmony_ci	val = reg;
130562306a36Sopenharmony_ci	val |= size << PORT_CS_1_LENGTH_SHIFT;
130662306a36Sopenharmony_ci	val |= PORT_CS_1_WNR_WRITE;
130762306a36Sopenharmony_ci	val |= (target << PORT_CS_1_TARGET_SHIFT) & PORT_CS_1_TARGET_MASK;
130862306a36Sopenharmony_ci	if (target == USB4_SB_TARGET_RETIMER)
130962306a36Sopenharmony_ci		val |= (index << PORT_CS_1_RETIMER_INDEX_SHIFT);
131062306a36Sopenharmony_ci	val |= PORT_CS_1_PND;
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci	ret = tb_port_write(port, &val, TB_CFG_PORT,
131362306a36Sopenharmony_ci			    port->cap_usb4 + PORT_CS_1, 1);
131462306a36Sopenharmony_ci	if (ret)
131562306a36Sopenharmony_ci		return ret;
131662306a36Sopenharmony_ci
131762306a36Sopenharmony_ci	ret = usb4_port_wait_for_bit(port, port->cap_usb4 + PORT_CS_1,
131862306a36Sopenharmony_ci				     PORT_CS_1_PND, 0, 500);
131962306a36Sopenharmony_ci	if (ret)
132062306a36Sopenharmony_ci		return ret;
132162306a36Sopenharmony_ci
132262306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
132362306a36Sopenharmony_ci			    port->cap_usb4 + PORT_CS_1, 1);
132462306a36Sopenharmony_ci	if (ret)
132562306a36Sopenharmony_ci		return ret;
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_ci	if (val & PORT_CS_1_NR)
132862306a36Sopenharmony_ci		return -ENODEV;
132962306a36Sopenharmony_ci	if (val & PORT_CS_1_RC)
133062306a36Sopenharmony_ci		return -EIO;
133162306a36Sopenharmony_ci
133262306a36Sopenharmony_ci	return 0;
133362306a36Sopenharmony_ci}
133462306a36Sopenharmony_ci
133562306a36Sopenharmony_cistatic int usb4_port_sb_opcode_err_to_errno(u32 val)
133662306a36Sopenharmony_ci{
133762306a36Sopenharmony_ci	switch (val) {
133862306a36Sopenharmony_ci	case 0:
133962306a36Sopenharmony_ci		return 0;
134062306a36Sopenharmony_ci	case USB4_SB_OPCODE_ERR:
134162306a36Sopenharmony_ci		return -EAGAIN;
134262306a36Sopenharmony_ci	case USB4_SB_OPCODE_ONS:
134362306a36Sopenharmony_ci		return -EOPNOTSUPP;
134462306a36Sopenharmony_ci	default:
134562306a36Sopenharmony_ci		return -EIO;
134662306a36Sopenharmony_ci	}
134762306a36Sopenharmony_ci}
134862306a36Sopenharmony_ci
134962306a36Sopenharmony_cistatic int usb4_port_sb_op(struct tb_port *port, enum usb4_sb_target target,
135062306a36Sopenharmony_ci			   u8 index, enum usb4_sb_opcode opcode, int timeout_msec)
135162306a36Sopenharmony_ci{
135262306a36Sopenharmony_ci	ktime_t timeout;
135362306a36Sopenharmony_ci	u32 val;
135462306a36Sopenharmony_ci	int ret;
135562306a36Sopenharmony_ci
135662306a36Sopenharmony_ci	val = opcode;
135762306a36Sopenharmony_ci	ret = usb4_port_sb_write(port, target, index, USB4_SB_OPCODE, &val,
135862306a36Sopenharmony_ci				 sizeof(val));
135962306a36Sopenharmony_ci	if (ret)
136062306a36Sopenharmony_ci		return ret;
136162306a36Sopenharmony_ci
136262306a36Sopenharmony_ci	timeout = ktime_add_ms(ktime_get(), timeout_msec);
136362306a36Sopenharmony_ci
136462306a36Sopenharmony_ci	do {
136562306a36Sopenharmony_ci		/* Check results */
136662306a36Sopenharmony_ci		ret = usb4_port_sb_read(port, target, index, USB4_SB_OPCODE,
136762306a36Sopenharmony_ci					&val, sizeof(val));
136862306a36Sopenharmony_ci		if (ret)
136962306a36Sopenharmony_ci			return ret;
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_ci		if (val != opcode)
137262306a36Sopenharmony_ci			return usb4_port_sb_opcode_err_to_errno(val);
137362306a36Sopenharmony_ci	} while (ktime_before(ktime_get(), timeout));
137462306a36Sopenharmony_ci
137562306a36Sopenharmony_ci	return -ETIMEDOUT;
137662306a36Sopenharmony_ci}
137762306a36Sopenharmony_ci
137862306a36Sopenharmony_cistatic int usb4_port_set_router_offline(struct tb_port *port, bool offline)
137962306a36Sopenharmony_ci{
138062306a36Sopenharmony_ci	u32 val = !offline;
138162306a36Sopenharmony_ci	int ret;
138262306a36Sopenharmony_ci
138362306a36Sopenharmony_ci	ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
138462306a36Sopenharmony_ci				  USB4_SB_METADATA, &val, sizeof(val));
138562306a36Sopenharmony_ci	if (ret)
138662306a36Sopenharmony_ci		return ret;
138762306a36Sopenharmony_ci
138862306a36Sopenharmony_ci	val = USB4_SB_OPCODE_ROUTER_OFFLINE;
138962306a36Sopenharmony_ci	return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
139062306a36Sopenharmony_ci				  USB4_SB_OPCODE, &val, sizeof(val));
139162306a36Sopenharmony_ci}
139262306a36Sopenharmony_ci
139362306a36Sopenharmony_ci/**
139462306a36Sopenharmony_ci * usb4_port_router_offline() - Put the USB4 port to offline mode
139562306a36Sopenharmony_ci * @port: USB4 port
139662306a36Sopenharmony_ci *
139762306a36Sopenharmony_ci * This function puts the USB4 port into offline mode. In this mode the
139862306a36Sopenharmony_ci * port does not react on hotplug events anymore. This needs to be
139962306a36Sopenharmony_ci * called before retimer access is done when the USB4 links is not up.
140062306a36Sopenharmony_ci *
140162306a36Sopenharmony_ci * Returns %0 in case of success and negative errno if there was an
140262306a36Sopenharmony_ci * error.
140362306a36Sopenharmony_ci */
140462306a36Sopenharmony_ciint usb4_port_router_offline(struct tb_port *port)
140562306a36Sopenharmony_ci{
140662306a36Sopenharmony_ci	return usb4_port_set_router_offline(port, true);
140762306a36Sopenharmony_ci}
140862306a36Sopenharmony_ci
140962306a36Sopenharmony_ci/**
141062306a36Sopenharmony_ci * usb4_port_router_online() - Put the USB4 port back to online
141162306a36Sopenharmony_ci * @port: USB4 port
141262306a36Sopenharmony_ci *
141362306a36Sopenharmony_ci * Makes the USB4 port functional again.
141462306a36Sopenharmony_ci */
141562306a36Sopenharmony_ciint usb4_port_router_online(struct tb_port *port)
141662306a36Sopenharmony_ci{
141762306a36Sopenharmony_ci	return usb4_port_set_router_offline(port, false);
141862306a36Sopenharmony_ci}
141962306a36Sopenharmony_ci
142062306a36Sopenharmony_ci/**
142162306a36Sopenharmony_ci * usb4_port_enumerate_retimers() - Send RT broadcast transaction
142262306a36Sopenharmony_ci * @port: USB4 port
142362306a36Sopenharmony_ci *
142462306a36Sopenharmony_ci * This forces the USB4 port to send broadcast RT transaction which
142562306a36Sopenharmony_ci * makes the retimers on the link to assign index to themselves. Returns
142662306a36Sopenharmony_ci * %0 in case of success and negative errno if there was an error.
142762306a36Sopenharmony_ci */
142862306a36Sopenharmony_ciint usb4_port_enumerate_retimers(struct tb_port *port)
142962306a36Sopenharmony_ci{
143062306a36Sopenharmony_ci	u32 val;
143162306a36Sopenharmony_ci
143262306a36Sopenharmony_ci	val = USB4_SB_OPCODE_ENUMERATE_RETIMERS;
143362306a36Sopenharmony_ci	return usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
143462306a36Sopenharmony_ci				  USB4_SB_OPCODE, &val, sizeof(val));
143562306a36Sopenharmony_ci}
143662306a36Sopenharmony_ci
143762306a36Sopenharmony_ci/**
143862306a36Sopenharmony_ci * usb4_port_clx_supported() - Check if CLx is supported by the link
143962306a36Sopenharmony_ci * @port: Port to check for CLx support for
144062306a36Sopenharmony_ci *
144162306a36Sopenharmony_ci * PORT_CS_18_CPS bit reflects if the link supports CLx including
144262306a36Sopenharmony_ci * active cables (if connected on the link).
144362306a36Sopenharmony_ci */
144462306a36Sopenharmony_cibool usb4_port_clx_supported(struct tb_port *port)
144562306a36Sopenharmony_ci{
144662306a36Sopenharmony_ci	int ret;
144762306a36Sopenharmony_ci	u32 val;
144862306a36Sopenharmony_ci
144962306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
145062306a36Sopenharmony_ci			   port->cap_usb4 + PORT_CS_18, 1);
145162306a36Sopenharmony_ci	if (ret)
145262306a36Sopenharmony_ci		return false;
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_ci	return !!(val & PORT_CS_18_CPS);
145562306a36Sopenharmony_ci}
145662306a36Sopenharmony_ci
145762306a36Sopenharmony_ci/**
145862306a36Sopenharmony_ci * usb4_port_margining_caps() - Read USB4 port marginig capabilities
145962306a36Sopenharmony_ci * @port: USB4 port
146062306a36Sopenharmony_ci * @caps: Array with at least two elements to hold the results
146162306a36Sopenharmony_ci *
146262306a36Sopenharmony_ci * Reads the USB4 port lane margining capabilities into @caps.
146362306a36Sopenharmony_ci */
146462306a36Sopenharmony_ciint usb4_port_margining_caps(struct tb_port *port, u32 *caps)
146562306a36Sopenharmony_ci{
146662306a36Sopenharmony_ci	int ret;
146762306a36Sopenharmony_ci
146862306a36Sopenharmony_ci	ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
146962306a36Sopenharmony_ci			      USB4_SB_OPCODE_READ_LANE_MARGINING_CAP, 500);
147062306a36Sopenharmony_ci	if (ret)
147162306a36Sopenharmony_ci		return ret;
147262306a36Sopenharmony_ci
147362306a36Sopenharmony_ci	return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
147462306a36Sopenharmony_ci				 USB4_SB_DATA, caps, sizeof(*caps) * 2);
147562306a36Sopenharmony_ci}
147662306a36Sopenharmony_ci
147762306a36Sopenharmony_ci/**
147862306a36Sopenharmony_ci * usb4_port_hw_margin() - Run hardware lane margining on port
147962306a36Sopenharmony_ci * @port: USB4 port
148062306a36Sopenharmony_ci * @lanes: Which lanes to run (must match the port capabilities). Can be
148162306a36Sopenharmony_ci *	   %0, %1 or %7.
148262306a36Sopenharmony_ci * @ber_level: BER level contour value
148362306a36Sopenharmony_ci * @timing: Perform timing margining instead of voltage
148462306a36Sopenharmony_ci * @right_high: Use Right/high margin instead of left/low
148562306a36Sopenharmony_ci * @results: Array with at least two elements to hold the results
148662306a36Sopenharmony_ci *
148762306a36Sopenharmony_ci * Runs hardware lane margining on USB4 port and returns the result in
148862306a36Sopenharmony_ci * @results.
148962306a36Sopenharmony_ci */
149062306a36Sopenharmony_ciint usb4_port_hw_margin(struct tb_port *port, unsigned int lanes,
149162306a36Sopenharmony_ci			unsigned int ber_level, bool timing, bool right_high,
149262306a36Sopenharmony_ci			u32 *results)
149362306a36Sopenharmony_ci{
149462306a36Sopenharmony_ci	u32 val;
149562306a36Sopenharmony_ci	int ret;
149662306a36Sopenharmony_ci
149762306a36Sopenharmony_ci	val = lanes;
149862306a36Sopenharmony_ci	if (timing)
149962306a36Sopenharmony_ci		val |= USB4_MARGIN_HW_TIME;
150062306a36Sopenharmony_ci	if (right_high)
150162306a36Sopenharmony_ci		val |= USB4_MARGIN_HW_RH;
150262306a36Sopenharmony_ci	if (ber_level)
150362306a36Sopenharmony_ci		val |= (ber_level << USB4_MARGIN_HW_BER_SHIFT) &
150462306a36Sopenharmony_ci			USB4_MARGIN_HW_BER_MASK;
150562306a36Sopenharmony_ci
150662306a36Sopenharmony_ci	ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
150762306a36Sopenharmony_ci				 USB4_SB_METADATA, &val, sizeof(val));
150862306a36Sopenharmony_ci	if (ret)
150962306a36Sopenharmony_ci		return ret;
151062306a36Sopenharmony_ci
151162306a36Sopenharmony_ci	ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
151262306a36Sopenharmony_ci			      USB4_SB_OPCODE_RUN_HW_LANE_MARGINING, 2500);
151362306a36Sopenharmony_ci	if (ret)
151462306a36Sopenharmony_ci		return ret;
151562306a36Sopenharmony_ci
151662306a36Sopenharmony_ci	return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
151762306a36Sopenharmony_ci				 USB4_SB_DATA, results, sizeof(*results) * 2);
151862306a36Sopenharmony_ci}
151962306a36Sopenharmony_ci
152062306a36Sopenharmony_ci/**
152162306a36Sopenharmony_ci * usb4_port_sw_margin() - Run software lane margining on port
152262306a36Sopenharmony_ci * @port: USB4 port
152362306a36Sopenharmony_ci * @lanes: Which lanes to run (must match the port capabilities). Can be
152462306a36Sopenharmony_ci *	   %0, %1 or %7.
152562306a36Sopenharmony_ci * @timing: Perform timing margining instead of voltage
152662306a36Sopenharmony_ci * @right_high: Use Right/high margin instead of left/low
152762306a36Sopenharmony_ci * @counter: What to do with the error counter
152862306a36Sopenharmony_ci *
152962306a36Sopenharmony_ci * Runs software lane margining on USB4 port. Read back the error
153062306a36Sopenharmony_ci * counters by calling usb4_port_sw_margin_errors(). Returns %0 in
153162306a36Sopenharmony_ci * success and negative errno otherwise.
153262306a36Sopenharmony_ci */
153362306a36Sopenharmony_ciint usb4_port_sw_margin(struct tb_port *port, unsigned int lanes, bool timing,
153462306a36Sopenharmony_ci			bool right_high, u32 counter)
153562306a36Sopenharmony_ci{
153662306a36Sopenharmony_ci	u32 val;
153762306a36Sopenharmony_ci	int ret;
153862306a36Sopenharmony_ci
153962306a36Sopenharmony_ci	val = lanes;
154062306a36Sopenharmony_ci	if (timing)
154162306a36Sopenharmony_ci		val |= USB4_MARGIN_SW_TIME;
154262306a36Sopenharmony_ci	if (right_high)
154362306a36Sopenharmony_ci		val |= USB4_MARGIN_SW_RH;
154462306a36Sopenharmony_ci	val |= (counter << USB4_MARGIN_SW_COUNTER_SHIFT) &
154562306a36Sopenharmony_ci		USB4_MARGIN_SW_COUNTER_MASK;
154662306a36Sopenharmony_ci
154762306a36Sopenharmony_ci	ret = usb4_port_sb_write(port, USB4_SB_TARGET_ROUTER, 0,
154862306a36Sopenharmony_ci				 USB4_SB_METADATA, &val, sizeof(val));
154962306a36Sopenharmony_ci	if (ret)
155062306a36Sopenharmony_ci		return ret;
155162306a36Sopenharmony_ci
155262306a36Sopenharmony_ci	return usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
155362306a36Sopenharmony_ci			       USB4_SB_OPCODE_RUN_SW_LANE_MARGINING, 2500);
155462306a36Sopenharmony_ci}
155562306a36Sopenharmony_ci
155662306a36Sopenharmony_ci/**
155762306a36Sopenharmony_ci * usb4_port_sw_margin_errors() - Read the software margining error counters
155862306a36Sopenharmony_ci * @port: USB4 port
155962306a36Sopenharmony_ci * @errors: Error metadata is copied here.
156062306a36Sopenharmony_ci *
156162306a36Sopenharmony_ci * This reads back the software margining error counters from the port.
156262306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
156362306a36Sopenharmony_ci */
156462306a36Sopenharmony_ciint usb4_port_sw_margin_errors(struct tb_port *port, u32 *errors)
156562306a36Sopenharmony_ci{
156662306a36Sopenharmony_ci	int ret;
156762306a36Sopenharmony_ci
156862306a36Sopenharmony_ci	ret = usb4_port_sb_op(port, USB4_SB_TARGET_ROUTER, 0,
156962306a36Sopenharmony_ci			      USB4_SB_OPCODE_READ_SW_MARGIN_ERR, 150);
157062306a36Sopenharmony_ci	if (ret)
157162306a36Sopenharmony_ci		return ret;
157262306a36Sopenharmony_ci
157362306a36Sopenharmony_ci	return usb4_port_sb_read(port, USB4_SB_TARGET_ROUTER, 0,
157462306a36Sopenharmony_ci				 USB4_SB_METADATA, errors, sizeof(*errors));
157562306a36Sopenharmony_ci}
157662306a36Sopenharmony_ci
157762306a36Sopenharmony_cistatic inline int usb4_port_retimer_op(struct tb_port *port, u8 index,
157862306a36Sopenharmony_ci				       enum usb4_sb_opcode opcode,
157962306a36Sopenharmony_ci				       int timeout_msec)
158062306a36Sopenharmony_ci{
158162306a36Sopenharmony_ci	return usb4_port_sb_op(port, USB4_SB_TARGET_RETIMER, index, opcode,
158262306a36Sopenharmony_ci			       timeout_msec);
158362306a36Sopenharmony_ci}
158462306a36Sopenharmony_ci
158562306a36Sopenharmony_ci/**
158662306a36Sopenharmony_ci * usb4_port_retimer_set_inbound_sbtx() - Enable sideband channel transactions
158762306a36Sopenharmony_ci * @port: USB4 port
158862306a36Sopenharmony_ci * @index: Retimer index
158962306a36Sopenharmony_ci *
159062306a36Sopenharmony_ci * Enables sideband channel transations on SBTX. Can be used when USB4
159162306a36Sopenharmony_ci * link does not go up, for example if there is no device connected.
159262306a36Sopenharmony_ci */
159362306a36Sopenharmony_ciint usb4_port_retimer_set_inbound_sbtx(struct tb_port *port, u8 index)
159462306a36Sopenharmony_ci{
159562306a36Sopenharmony_ci	int ret;
159662306a36Sopenharmony_ci
159762306a36Sopenharmony_ci	ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX,
159862306a36Sopenharmony_ci				   500);
159962306a36Sopenharmony_ci
160062306a36Sopenharmony_ci	if (ret != -ENODEV)
160162306a36Sopenharmony_ci		return ret;
160262306a36Sopenharmony_ci
160362306a36Sopenharmony_ci	/*
160462306a36Sopenharmony_ci	 * Per the USB4 retimer spec, the retimer is not required to
160562306a36Sopenharmony_ci	 * send an RT (Retimer Transaction) response for the first
160662306a36Sopenharmony_ci	 * SET_INBOUND_SBTX command
160762306a36Sopenharmony_ci	 */
160862306a36Sopenharmony_ci	return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_SET_INBOUND_SBTX,
160962306a36Sopenharmony_ci				    500);
161062306a36Sopenharmony_ci}
161162306a36Sopenharmony_ci
161262306a36Sopenharmony_ci/**
161362306a36Sopenharmony_ci * usb4_port_retimer_unset_inbound_sbtx() - Disable sideband channel transactions
161462306a36Sopenharmony_ci * @port: USB4 port
161562306a36Sopenharmony_ci * @index: Retimer index
161662306a36Sopenharmony_ci *
161762306a36Sopenharmony_ci * Disables sideband channel transations on SBTX. The reverse of
161862306a36Sopenharmony_ci * usb4_port_retimer_set_inbound_sbtx().
161962306a36Sopenharmony_ci */
162062306a36Sopenharmony_ciint usb4_port_retimer_unset_inbound_sbtx(struct tb_port *port, u8 index)
162162306a36Sopenharmony_ci{
162262306a36Sopenharmony_ci	return usb4_port_retimer_op(port, index,
162362306a36Sopenharmony_ci				    USB4_SB_OPCODE_UNSET_INBOUND_SBTX, 500);
162462306a36Sopenharmony_ci}
162562306a36Sopenharmony_ci
162662306a36Sopenharmony_ci/**
162762306a36Sopenharmony_ci * usb4_port_retimer_read() - Read from retimer sideband registers
162862306a36Sopenharmony_ci * @port: USB4 port
162962306a36Sopenharmony_ci * @index: Retimer index
163062306a36Sopenharmony_ci * @reg: Sideband register to read
163162306a36Sopenharmony_ci * @buf: Data from @reg is stored here
163262306a36Sopenharmony_ci * @size: Number of bytes to read
163362306a36Sopenharmony_ci *
163462306a36Sopenharmony_ci * Function reads retimer sideband registers starting from @reg. The
163562306a36Sopenharmony_ci * retimer is connected to @port at @index. Returns %0 in case of
163662306a36Sopenharmony_ci * success, and read data is copied to @buf. If there is no retimer
163762306a36Sopenharmony_ci * present at given @index returns %-ENODEV. In any other failure
163862306a36Sopenharmony_ci * returns negative errno.
163962306a36Sopenharmony_ci */
164062306a36Sopenharmony_ciint usb4_port_retimer_read(struct tb_port *port, u8 index, u8 reg, void *buf,
164162306a36Sopenharmony_ci			   u8 size)
164262306a36Sopenharmony_ci{
164362306a36Sopenharmony_ci	return usb4_port_sb_read(port, USB4_SB_TARGET_RETIMER, index, reg, buf,
164462306a36Sopenharmony_ci				 size);
164562306a36Sopenharmony_ci}
164662306a36Sopenharmony_ci
164762306a36Sopenharmony_ci/**
164862306a36Sopenharmony_ci * usb4_port_retimer_write() - Write to retimer sideband registers
164962306a36Sopenharmony_ci * @port: USB4 port
165062306a36Sopenharmony_ci * @index: Retimer index
165162306a36Sopenharmony_ci * @reg: Sideband register to write
165262306a36Sopenharmony_ci * @buf: Data that is written starting from @reg
165362306a36Sopenharmony_ci * @size: Number of bytes to write
165462306a36Sopenharmony_ci *
165562306a36Sopenharmony_ci * Writes retimer sideband registers starting from @reg. The retimer is
165662306a36Sopenharmony_ci * connected to @port at @index. Returns %0 in case of success. If there
165762306a36Sopenharmony_ci * is no retimer present at given @index returns %-ENODEV. In any other
165862306a36Sopenharmony_ci * failure returns negative errno.
165962306a36Sopenharmony_ci */
166062306a36Sopenharmony_ciint usb4_port_retimer_write(struct tb_port *port, u8 index, u8 reg,
166162306a36Sopenharmony_ci			    const void *buf, u8 size)
166262306a36Sopenharmony_ci{
166362306a36Sopenharmony_ci	return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index, reg, buf,
166462306a36Sopenharmony_ci				  size);
166562306a36Sopenharmony_ci}
166662306a36Sopenharmony_ci
166762306a36Sopenharmony_ci/**
166862306a36Sopenharmony_ci * usb4_port_retimer_is_last() - Is the retimer last on-board retimer
166962306a36Sopenharmony_ci * @port: USB4 port
167062306a36Sopenharmony_ci * @index: Retimer index
167162306a36Sopenharmony_ci *
167262306a36Sopenharmony_ci * If the retimer at @index is last one (connected directly to the
167362306a36Sopenharmony_ci * Type-C port) this function returns %1. If it is not returns %0. If
167462306a36Sopenharmony_ci * the retimer is not present returns %-ENODEV. Otherwise returns
167562306a36Sopenharmony_ci * negative errno.
167662306a36Sopenharmony_ci */
167762306a36Sopenharmony_ciint usb4_port_retimer_is_last(struct tb_port *port, u8 index)
167862306a36Sopenharmony_ci{
167962306a36Sopenharmony_ci	u32 metadata;
168062306a36Sopenharmony_ci	int ret;
168162306a36Sopenharmony_ci
168262306a36Sopenharmony_ci	ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_QUERY_LAST_RETIMER,
168362306a36Sopenharmony_ci				   500);
168462306a36Sopenharmony_ci	if (ret)
168562306a36Sopenharmony_ci		return ret;
168662306a36Sopenharmony_ci
168762306a36Sopenharmony_ci	ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata,
168862306a36Sopenharmony_ci				     sizeof(metadata));
168962306a36Sopenharmony_ci	return ret ? ret : metadata & 1;
169062306a36Sopenharmony_ci}
169162306a36Sopenharmony_ci
169262306a36Sopenharmony_ci/**
169362306a36Sopenharmony_ci * usb4_port_retimer_nvm_sector_size() - Read retimer NVM sector size
169462306a36Sopenharmony_ci * @port: USB4 port
169562306a36Sopenharmony_ci * @index: Retimer index
169662306a36Sopenharmony_ci *
169762306a36Sopenharmony_ci * Reads NVM sector size (in bytes) of a retimer at @index. This
169862306a36Sopenharmony_ci * operation can be used to determine whether the retimer supports NVM
169962306a36Sopenharmony_ci * upgrade for example. Returns sector size in bytes or negative errno
170062306a36Sopenharmony_ci * in case of error. Specifically returns %-ENODEV if there is no
170162306a36Sopenharmony_ci * retimer at @index.
170262306a36Sopenharmony_ci */
170362306a36Sopenharmony_ciint usb4_port_retimer_nvm_sector_size(struct tb_port *port, u8 index)
170462306a36Sopenharmony_ci{
170562306a36Sopenharmony_ci	u32 metadata;
170662306a36Sopenharmony_ci	int ret;
170762306a36Sopenharmony_ci
170862306a36Sopenharmony_ci	ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_GET_NVM_SECTOR_SIZE,
170962306a36Sopenharmony_ci				   500);
171062306a36Sopenharmony_ci	if (ret)
171162306a36Sopenharmony_ci		return ret;
171262306a36Sopenharmony_ci
171362306a36Sopenharmony_ci	ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA, &metadata,
171462306a36Sopenharmony_ci				     sizeof(metadata));
171562306a36Sopenharmony_ci	return ret ? ret : metadata & USB4_NVM_SECTOR_SIZE_MASK;
171662306a36Sopenharmony_ci}
171762306a36Sopenharmony_ci
171862306a36Sopenharmony_ci/**
171962306a36Sopenharmony_ci * usb4_port_retimer_nvm_set_offset() - Set NVM write offset
172062306a36Sopenharmony_ci * @port: USB4 port
172162306a36Sopenharmony_ci * @index: Retimer index
172262306a36Sopenharmony_ci * @address: Start offset
172362306a36Sopenharmony_ci *
172462306a36Sopenharmony_ci * Exlicitly sets NVM write offset. Normally when writing to NVM this is
172562306a36Sopenharmony_ci * done automatically by usb4_port_retimer_nvm_write().
172662306a36Sopenharmony_ci *
172762306a36Sopenharmony_ci * Returns %0 in success and negative errno if there was a failure.
172862306a36Sopenharmony_ci */
172962306a36Sopenharmony_ciint usb4_port_retimer_nvm_set_offset(struct tb_port *port, u8 index,
173062306a36Sopenharmony_ci				     unsigned int address)
173162306a36Sopenharmony_ci{
173262306a36Sopenharmony_ci	u32 metadata, dwaddress;
173362306a36Sopenharmony_ci	int ret;
173462306a36Sopenharmony_ci
173562306a36Sopenharmony_ci	dwaddress = address / 4;
173662306a36Sopenharmony_ci	metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) &
173762306a36Sopenharmony_ci		  USB4_NVM_SET_OFFSET_MASK;
173862306a36Sopenharmony_ci
173962306a36Sopenharmony_ci	ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata,
174062306a36Sopenharmony_ci				      sizeof(metadata));
174162306a36Sopenharmony_ci	if (ret)
174262306a36Sopenharmony_ci		return ret;
174362306a36Sopenharmony_ci
174462306a36Sopenharmony_ci	return usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_SET_OFFSET,
174562306a36Sopenharmony_ci				    500);
174662306a36Sopenharmony_ci}
174762306a36Sopenharmony_ci
174862306a36Sopenharmony_cistruct retimer_info {
174962306a36Sopenharmony_ci	struct tb_port *port;
175062306a36Sopenharmony_ci	u8 index;
175162306a36Sopenharmony_ci};
175262306a36Sopenharmony_ci
175362306a36Sopenharmony_cistatic int usb4_port_retimer_nvm_write_next_block(void *data,
175462306a36Sopenharmony_ci	unsigned int dwaddress, const void *buf, size_t dwords)
175562306a36Sopenharmony_ci
175662306a36Sopenharmony_ci{
175762306a36Sopenharmony_ci	const struct retimer_info *info = data;
175862306a36Sopenharmony_ci	struct tb_port *port = info->port;
175962306a36Sopenharmony_ci	u8 index = info->index;
176062306a36Sopenharmony_ci	int ret;
176162306a36Sopenharmony_ci
176262306a36Sopenharmony_ci	ret = usb4_port_retimer_write(port, index, USB4_SB_DATA,
176362306a36Sopenharmony_ci				      buf, dwords * 4);
176462306a36Sopenharmony_ci	if (ret)
176562306a36Sopenharmony_ci		return ret;
176662306a36Sopenharmony_ci
176762306a36Sopenharmony_ci	return usb4_port_retimer_op(port, index,
176862306a36Sopenharmony_ci			USB4_SB_OPCODE_NVM_BLOCK_WRITE, 1000);
176962306a36Sopenharmony_ci}
177062306a36Sopenharmony_ci
177162306a36Sopenharmony_ci/**
177262306a36Sopenharmony_ci * usb4_port_retimer_nvm_write() - Write to retimer NVM
177362306a36Sopenharmony_ci * @port: USB4 port
177462306a36Sopenharmony_ci * @index: Retimer index
177562306a36Sopenharmony_ci * @address: Byte address where to start the write
177662306a36Sopenharmony_ci * @buf: Data to write
177762306a36Sopenharmony_ci * @size: Size in bytes how much to write
177862306a36Sopenharmony_ci *
177962306a36Sopenharmony_ci * Writes @size bytes from @buf to the retimer NVM. Used for NVM
178062306a36Sopenharmony_ci * upgrade. Returns %0 if the data was written successfully and negative
178162306a36Sopenharmony_ci * errno in case of failure. Specifically returns %-ENODEV if there is
178262306a36Sopenharmony_ci * no retimer at @index.
178362306a36Sopenharmony_ci */
178462306a36Sopenharmony_ciint usb4_port_retimer_nvm_write(struct tb_port *port, u8 index, unsigned int address,
178562306a36Sopenharmony_ci				const void *buf, size_t size)
178662306a36Sopenharmony_ci{
178762306a36Sopenharmony_ci	struct retimer_info info = { .port = port, .index = index };
178862306a36Sopenharmony_ci	int ret;
178962306a36Sopenharmony_ci
179062306a36Sopenharmony_ci	ret = usb4_port_retimer_nvm_set_offset(port, index, address);
179162306a36Sopenharmony_ci	if (ret)
179262306a36Sopenharmony_ci		return ret;
179362306a36Sopenharmony_ci
179462306a36Sopenharmony_ci	return tb_nvm_write_data(address, buf, size, USB4_DATA_RETRIES,
179562306a36Sopenharmony_ci				 usb4_port_retimer_nvm_write_next_block, &info);
179662306a36Sopenharmony_ci}
179762306a36Sopenharmony_ci
179862306a36Sopenharmony_ci/**
179962306a36Sopenharmony_ci * usb4_port_retimer_nvm_authenticate() - Start retimer NVM upgrade
180062306a36Sopenharmony_ci * @port: USB4 port
180162306a36Sopenharmony_ci * @index: Retimer index
180262306a36Sopenharmony_ci *
180362306a36Sopenharmony_ci * After the new NVM image has been written via usb4_port_retimer_nvm_write()
180462306a36Sopenharmony_ci * this function can be used to trigger the NVM upgrade process. If
180562306a36Sopenharmony_ci * successful the retimer restarts with the new NVM and may not have the
180662306a36Sopenharmony_ci * index set so one needs to call usb4_port_enumerate_retimers() to
180762306a36Sopenharmony_ci * force index to be assigned.
180862306a36Sopenharmony_ci */
180962306a36Sopenharmony_ciint usb4_port_retimer_nvm_authenticate(struct tb_port *port, u8 index)
181062306a36Sopenharmony_ci{
181162306a36Sopenharmony_ci	u32 val;
181262306a36Sopenharmony_ci
181362306a36Sopenharmony_ci	/*
181462306a36Sopenharmony_ci	 * We need to use the raw operation here because once the
181562306a36Sopenharmony_ci	 * authentication completes the retimer index is not set anymore
181662306a36Sopenharmony_ci	 * so we do not get back the status now.
181762306a36Sopenharmony_ci	 */
181862306a36Sopenharmony_ci	val = USB4_SB_OPCODE_NVM_AUTH_WRITE;
181962306a36Sopenharmony_ci	return usb4_port_sb_write(port, USB4_SB_TARGET_RETIMER, index,
182062306a36Sopenharmony_ci				  USB4_SB_OPCODE, &val, sizeof(val));
182162306a36Sopenharmony_ci}
182262306a36Sopenharmony_ci
182362306a36Sopenharmony_ci/**
182462306a36Sopenharmony_ci * usb4_port_retimer_nvm_authenticate_status() - Read status of NVM upgrade
182562306a36Sopenharmony_ci * @port: USB4 port
182662306a36Sopenharmony_ci * @index: Retimer index
182762306a36Sopenharmony_ci * @status: Raw status code read from metadata
182862306a36Sopenharmony_ci *
182962306a36Sopenharmony_ci * This can be called after usb4_port_retimer_nvm_authenticate() and
183062306a36Sopenharmony_ci * usb4_port_enumerate_retimers() to fetch status of the NVM upgrade.
183162306a36Sopenharmony_ci *
183262306a36Sopenharmony_ci * Returns %0 if the authentication status was successfully read. The
183362306a36Sopenharmony_ci * completion metadata (the result) is then stored into @status. If
183462306a36Sopenharmony_ci * reading the status fails, returns negative errno.
183562306a36Sopenharmony_ci */
183662306a36Sopenharmony_ciint usb4_port_retimer_nvm_authenticate_status(struct tb_port *port, u8 index,
183762306a36Sopenharmony_ci					      u32 *status)
183862306a36Sopenharmony_ci{
183962306a36Sopenharmony_ci	u32 metadata, val;
184062306a36Sopenharmony_ci	int ret;
184162306a36Sopenharmony_ci
184262306a36Sopenharmony_ci	ret = usb4_port_retimer_read(port, index, USB4_SB_OPCODE, &val,
184362306a36Sopenharmony_ci				     sizeof(val));
184462306a36Sopenharmony_ci	if (ret)
184562306a36Sopenharmony_ci		return ret;
184662306a36Sopenharmony_ci
184762306a36Sopenharmony_ci	ret = usb4_port_sb_opcode_err_to_errno(val);
184862306a36Sopenharmony_ci	switch (ret) {
184962306a36Sopenharmony_ci	case 0:
185062306a36Sopenharmony_ci		*status = 0;
185162306a36Sopenharmony_ci		return 0;
185262306a36Sopenharmony_ci
185362306a36Sopenharmony_ci	case -EAGAIN:
185462306a36Sopenharmony_ci		ret = usb4_port_retimer_read(port, index, USB4_SB_METADATA,
185562306a36Sopenharmony_ci					     &metadata, sizeof(metadata));
185662306a36Sopenharmony_ci		if (ret)
185762306a36Sopenharmony_ci			return ret;
185862306a36Sopenharmony_ci
185962306a36Sopenharmony_ci		*status = metadata & USB4_SB_METADATA_NVM_AUTH_WRITE_MASK;
186062306a36Sopenharmony_ci		return 0;
186162306a36Sopenharmony_ci
186262306a36Sopenharmony_ci	default:
186362306a36Sopenharmony_ci		return ret;
186462306a36Sopenharmony_ci	}
186562306a36Sopenharmony_ci}
186662306a36Sopenharmony_ci
186762306a36Sopenharmony_cistatic int usb4_port_retimer_nvm_read_block(void *data, unsigned int dwaddress,
186862306a36Sopenharmony_ci					    void *buf, size_t dwords)
186962306a36Sopenharmony_ci{
187062306a36Sopenharmony_ci	const struct retimer_info *info = data;
187162306a36Sopenharmony_ci	struct tb_port *port = info->port;
187262306a36Sopenharmony_ci	u8 index = info->index;
187362306a36Sopenharmony_ci	u32 metadata;
187462306a36Sopenharmony_ci	int ret;
187562306a36Sopenharmony_ci
187662306a36Sopenharmony_ci	metadata = dwaddress << USB4_NVM_READ_OFFSET_SHIFT;
187762306a36Sopenharmony_ci	if (dwords < USB4_DATA_DWORDS)
187862306a36Sopenharmony_ci		metadata |= dwords << USB4_NVM_READ_LENGTH_SHIFT;
187962306a36Sopenharmony_ci
188062306a36Sopenharmony_ci	ret = usb4_port_retimer_write(port, index, USB4_SB_METADATA, &metadata,
188162306a36Sopenharmony_ci				      sizeof(metadata));
188262306a36Sopenharmony_ci	if (ret)
188362306a36Sopenharmony_ci		return ret;
188462306a36Sopenharmony_ci
188562306a36Sopenharmony_ci	ret = usb4_port_retimer_op(port, index, USB4_SB_OPCODE_NVM_READ, 500);
188662306a36Sopenharmony_ci	if (ret)
188762306a36Sopenharmony_ci		return ret;
188862306a36Sopenharmony_ci
188962306a36Sopenharmony_ci	return usb4_port_retimer_read(port, index, USB4_SB_DATA, buf,
189062306a36Sopenharmony_ci				      dwords * 4);
189162306a36Sopenharmony_ci}
189262306a36Sopenharmony_ci
189362306a36Sopenharmony_ci/**
189462306a36Sopenharmony_ci * usb4_port_retimer_nvm_read() - Read contents of retimer NVM
189562306a36Sopenharmony_ci * @port: USB4 port
189662306a36Sopenharmony_ci * @index: Retimer index
189762306a36Sopenharmony_ci * @address: NVM address (in bytes) to start reading
189862306a36Sopenharmony_ci * @buf: Data read from NVM is stored here
189962306a36Sopenharmony_ci * @size: Number of bytes to read
190062306a36Sopenharmony_ci *
190162306a36Sopenharmony_ci * Reads retimer NVM and copies the contents to @buf. Returns %0 if the
190262306a36Sopenharmony_ci * read was successful and negative errno in case of failure.
190362306a36Sopenharmony_ci * Specifically returns %-ENODEV if there is no retimer at @index.
190462306a36Sopenharmony_ci */
190562306a36Sopenharmony_ciint usb4_port_retimer_nvm_read(struct tb_port *port, u8 index,
190662306a36Sopenharmony_ci			       unsigned int address, void *buf, size_t size)
190762306a36Sopenharmony_ci{
190862306a36Sopenharmony_ci	struct retimer_info info = { .port = port, .index = index };
190962306a36Sopenharmony_ci
191062306a36Sopenharmony_ci	return tb_nvm_read_data(address, buf, size, USB4_DATA_RETRIES,
191162306a36Sopenharmony_ci				usb4_port_retimer_nvm_read_block, &info);
191262306a36Sopenharmony_ci}
191362306a36Sopenharmony_ci
191462306a36Sopenharmony_cistatic inline unsigned int
191562306a36Sopenharmony_ciusb4_usb3_port_max_bandwidth(const struct tb_port *port, unsigned int bw)
191662306a36Sopenharmony_ci{
191762306a36Sopenharmony_ci	/* Take the possible bandwidth limitation into account */
191862306a36Sopenharmony_ci	if (port->max_bw)
191962306a36Sopenharmony_ci		return min(bw, port->max_bw);
192062306a36Sopenharmony_ci	return bw;
192162306a36Sopenharmony_ci}
192262306a36Sopenharmony_ci
192362306a36Sopenharmony_ci/**
192462306a36Sopenharmony_ci * usb4_usb3_port_max_link_rate() - Maximum support USB3 link rate
192562306a36Sopenharmony_ci * @port: USB3 adapter port
192662306a36Sopenharmony_ci *
192762306a36Sopenharmony_ci * Return maximum supported link rate of a USB3 adapter in Mb/s.
192862306a36Sopenharmony_ci * Negative errno in case of error.
192962306a36Sopenharmony_ci */
193062306a36Sopenharmony_ciint usb4_usb3_port_max_link_rate(struct tb_port *port)
193162306a36Sopenharmony_ci{
193262306a36Sopenharmony_ci	int ret, lr;
193362306a36Sopenharmony_ci	u32 val;
193462306a36Sopenharmony_ci
193562306a36Sopenharmony_ci	if (!tb_port_is_usb3_down(port) && !tb_port_is_usb3_up(port))
193662306a36Sopenharmony_ci		return -EINVAL;
193762306a36Sopenharmony_ci
193862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
193962306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_4, 1);
194062306a36Sopenharmony_ci	if (ret)
194162306a36Sopenharmony_ci		return ret;
194262306a36Sopenharmony_ci
194362306a36Sopenharmony_ci	lr = (val & ADP_USB3_CS_4_MSLR_MASK) >> ADP_USB3_CS_4_MSLR_SHIFT;
194462306a36Sopenharmony_ci	ret = lr == ADP_USB3_CS_4_MSLR_20G ? 20000 : 10000;
194562306a36Sopenharmony_ci
194662306a36Sopenharmony_ci	return usb4_usb3_port_max_bandwidth(port, ret);
194762306a36Sopenharmony_ci}
194862306a36Sopenharmony_ci
194962306a36Sopenharmony_ci/**
195062306a36Sopenharmony_ci * usb4_usb3_port_actual_link_rate() - Established USB3 link rate
195162306a36Sopenharmony_ci * @port: USB3 adapter port
195262306a36Sopenharmony_ci *
195362306a36Sopenharmony_ci * Return actual established link rate of a USB3 adapter in Mb/s. If the
195462306a36Sopenharmony_ci * link is not up returns %0 and negative errno in case of failure.
195562306a36Sopenharmony_ci */
195662306a36Sopenharmony_ciint usb4_usb3_port_actual_link_rate(struct tb_port *port)
195762306a36Sopenharmony_ci{
195862306a36Sopenharmony_ci	int ret, lr;
195962306a36Sopenharmony_ci	u32 val;
196062306a36Sopenharmony_ci
196162306a36Sopenharmony_ci	if (!tb_port_is_usb3_down(port) && !tb_port_is_usb3_up(port))
196262306a36Sopenharmony_ci		return -EINVAL;
196362306a36Sopenharmony_ci
196462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
196562306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_4, 1);
196662306a36Sopenharmony_ci	if (ret)
196762306a36Sopenharmony_ci		return ret;
196862306a36Sopenharmony_ci
196962306a36Sopenharmony_ci	if (!(val & ADP_USB3_CS_4_ULV))
197062306a36Sopenharmony_ci		return 0;
197162306a36Sopenharmony_ci
197262306a36Sopenharmony_ci	lr = val & ADP_USB3_CS_4_ALR_MASK;
197362306a36Sopenharmony_ci	ret = lr == ADP_USB3_CS_4_ALR_20G ? 20000 : 10000;
197462306a36Sopenharmony_ci
197562306a36Sopenharmony_ci	return usb4_usb3_port_max_bandwidth(port, ret);
197662306a36Sopenharmony_ci}
197762306a36Sopenharmony_ci
197862306a36Sopenharmony_cistatic int usb4_usb3_port_cm_request(struct tb_port *port, bool request)
197962306a36Sopenharmony_ci{
198062306a36Sopenharmony_ci	int ret;
198162306a36Sopenharmony_ci	u32 val;
198262306a36Sopenharmony_ci
198362306a36Sopenharmony_ci	if (!tb_port_is_usb3_down(port))
198462306a36Sopenharmony_ci		return -EINVAL;
198562306a36Sopenharmony_ci	if (tb_route(port->sw))
198662306a36Sopenharmony_ci		return -EINVAL;
198762306a36Sopenharmony_ci
198862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
198962306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_2, 1);
199062306a36Sopenharmony_ci	if (ret)
199162306a36Sopenharmony_ci		return ret;
199262306a36Sopenharmony_ci
199362306a36Sopenharmony_ci	if (request)
199462306a36Sopenharmony_ci		val |= ADP_USB3_CS_2_CMR;
199562306a36Sopenharmony_ci	else
199662306a36Sopenharmony_ci		val &= ~ADP_USB3_CS_2_CMR;
199762306a36Sopenharmony_ci
199862306a36Sopenharmony_ci	ret = tb_port_write(port, &val, TB_CFG_PORT,
199962306a36Sopenharmony_ci			    port->cap_adap + ADP_USB3_CS_2, 1);
200062306a36Sopenharmony_ci	if (ret)
200162306a36Sopenharmony_ci		return ret;
200262306a36Sopenharmony_ci
200362306a36Sopenharmony_ci	/*
200462306a36Sopenharmony_ci	 * We can use val here directly as the CMR bit is in the same place
200562306a36Sopenharmony_ci	 * as HCA. Just mask out others.
200662306a36Sopenharmony_ci	 */
200762306a36Sopenharmony_ci	val &= ADP_USB3_CS_2_CMR;
200862306a36Sopenharmony_ci	return usb4_port_wait_for_bit(port, port->cap_adap + ADP_USB3_CS_1,
200962306a36Sopenharmony_ci				      ADP_USB3_CS_1_HCA, val, 1500);
201062306a36Sopenharmony_ci}
201162306a36Sopenharmony_ci
201262306a36Sopenharmony_cistatic inline int usb4_usb3_port_set_cm_request(struct tb_port *port)
201362306a36Sopenharmony_ci{
201462306a36Sopenharmony_ci	return usb4_usb3_port_cm_request(port, true);
201562306a36Sopenharmony_ci}
201662306a36Sopenharmony_ci
201762306a36Sopenharmony_cistatic inline int usb4_usb3_port_clear_cm_request(struct tb_port *port)
201862306a36Sopenharmony_ci{
201962306a36Sopenharmony_ci	return usb4_usb3_port_cm_request(port, false);
202062306a36Sopenharmony_ci}
202162306a36Sopenharmony_ci
202262306a36Sopenharmony_cistatic unsigned int usb3_bw_to_mbps(u32 bw, u8 scale)
202362306a36Sopenharmony_ci{
202462306a36Sopenharmony_ci	unsigned long uframes;
202562306a36Sopenharmony_ci
202662306a36Sopenharmony_ci	uframes = bw * 512UL << scale;
202762306a36Sopenharmony_ci	return DIV_ROUND_CLOSEST(uframes * 8000, MEGA);
202862306a36Sopenharmony_ci}
202962306a36Sopenharmony_ci
203062306a36Sopenharmony_cistatic u32 mbps_to_usb3_bw(unsigned int mbps, u8 scale)
203162306a36Sopenharmony_ci{
203262306a36Sopenharmony_ci	unsigned long uframes;
203362306a36Sopenharmony_ci
203462306a36Sopenharmony_ci	/* 1 uframe is 1/8 ms (125 us) -> 1 / 8000 s */
203562306a36Sopenharmony_ci	uframes = ((unsigned long)mbps * MEGA) / 8000;
203662306a36Sopenharmony_ci	return DIV_ROUND_UP(uframes, 512UL << scale);
203762306a36Sopenharmony_ci}
203862306a36Sopenharmony_ci
203962306a36Sopenharmony_cistatic int usb4_usb3_port_read_allocated_bandwidth(struct tb_port *port,
204062306a36Sopenharmony_ci						   int *upstream_bw,
204162306a36Sopenharmony_ci						   int *downstream_bw)
204262306a36Sopenharmony_ci{
204362306a36Sopenharmony_ci	u32 val, bw, scale;
204462306a36Sopenharmony_ci	int ret;
204562306a36Sopenharmony_ci
204662306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
204762306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_2, 1);
204862306a36Sopenharmony_ci	if (ret)
204962306a36Sopenharmony_ci		return ret;
205062306a36Sopenharmony_ci
205162306a36Sopenharmony_ci	ret = tb_port_read(port, &scale, TB_CFG_PORT,
205262306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_3, 1);
205362306a36Sopenharmony_ci	if (ret)
205462306a36Sopenharmony_ci		return ret;
205562306a36Sopenharmony_ci
205662306a36Sopenharmony_ci	scale &= ADP_USB3_CS_3_SCALE_MASK;
205762306a36Sopenharmony_ci
205862306a36Sopenharmony_ci	bw = val & ADP_USB3_CS_2_AUBW_MASK;
205962306a36Sopenharmony_ci	*upstream_bw = usb3_bw_to_mbps(bw, scale);
206062306a36Sopenharmony_ci
206162306a36Sopenharmony_ci	bw = (val & ADP_USB3_CS_2_ADBW_MASK) >> ADP_USB3_CS_2_ADBW_SHIFT;
206262306a36Sopenharmony_ci	*downstream_bw = usb3_bw_to_mbps(bw, scale);
206362306a36Sopenharmony_ci
206462306a36Sopenharmony_ci	return 0;
206562306a36Sopenharmony_ci}
206662306a36Sopenharmony_ci
206762306a36Sopenharmony_ci/**
206862306a36Sopenharmony_ci * usb4_usb3_port_allocated_bandwidth() - Bandwidth allocated for USB3
206962306a36Sopenharmony_ci * @port: USB3 adapter port
207062306a36Sopenharmony_ci * @upstream_bw: Allocated upstream bandwidth is stored here
207162306a36Sopenharmony_ci * @downstream_bw: Allocated downstream bandwidth is stored here
207262306a36Sopenharmony_ci *
207362306a36Sopenharmony_ci * Stores currently allocated USB3 bandwidth into @upstream_bw and
207462306a36Sopenharmony_ci * @downstream_bw in Mb/s. Returns %0 in case of success and negative
207562306a36Sopenharmony_ci * errno in failure.
207662306a36Sopenharmony_ci */
207762306a36Sopenharmony_ciint usb4_usb3_port_allocated_bandwidth(struct tb_port *port, int *upstream_bw,
207862306a36Sopenharmony_ci				       int *downstream_bw)
207962306a36Sopenharmony_ci{
208062306a36Sopenharmony_ci	int ret;
208162306a36Sopenharmony_ci
208262306a36Sopenharmony_ci	ret = usb4_usb3_port_set_cm_request(port);
208362306a36Sopenharmony_ci	if (ret)
208462306a36Sopenharmony_ci		return ret;
208562306a36Sopenharmony_ci
208662306a36Sopenharmony_ci	ret = usb4_usb3_port_read_allocated_bandwidth(port, upstream_bw,
208762306a36Sopenharmony_ci						      downstream_bw);
208862306a36Sopenharmony_ci	usb4_usb3_port_clear_cm_request(port);
208962306a36Sopenharmony_ci
209062306a36Sopenharmony_ci	return ret;
209162306a36Sopenharmony_ci}
209262306a36Sopenharmony_ci
209362306a36Sopenharmony_cistatic int usb4_usb3_port_read_consumed_bandwidth(struct tb_port *port,
209462306a36Sopenharmony_ci						  int *upstream_bw,
209562306a36Sopenharmony_ci						  int *downstream_bw)
209662306a36Sopenharmony_ci{
209762306a36Sopenharmony_ci	u32 val, bw, scale;
209862306a36Sopenharmony_ci	int ret;
209962306a36Sopenharmony_ci
210062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
210162306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_1, 1);
210262306a36Sopenharmony_ci	if (ret)
210362306a36Sopenharmony_ci		return ret;
210462306a36Sopenharmony_ci
210562306a36Sopenharmony_ci	ret = tb_port_read(port, &scale, TB_CFG_PORT,
210662306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_3, 1);
210762306a36Sopenharmony_ci	if (ret)
210862306a36Sopenharmony_ci		return ret;
210962306a36Sopenharmony_ci
211062306a36Sopenharmony_ci	scale &= ADP_USB3_CS_3_SCALE_MASK;
211162306a36Sopenharmony_ci
211262306a36Sopenharmony_ci	bw = val & ADP_USB3_CS_1_CUBW_MASK;
211362306a36Sopenharmony_ci	*upstream_bw = usb3_bw_to_mbps(bw, scale);
211462306a36Sopenharmony_ci
211562306a36Sopenharmony_ci	bw = (val & ADP_USB3_CS_1_CDBW_MASK) >> ADP_USB3_CS_1_CDBW_SHIFT;
211662306a36Sopenharmony_ci	*downstream_bw = usb3_bw_to_mbps(bw, scale);
211762306a36Sopenharmony_ci
211862306a36Sopenharmony_ci	return 0;
211962306a36Sopenharmony_ci}
212062306a36Sopenharmony_ci
212162306a36Sopenharmony_cistatic int usb4_usb3_port_write_allocated_bandwidth(struct tb_port *port,
212262306a36Sopenharmony_ci						    int upstream_bw,
212362306a36Sopenharmony_ci						    int downstream_bw)
212462306a36Sopenharmony_ci{
212562306a36Sopenharmony_ci	u32 val, ubw, dbw, scale;
212662306a36Sopenharmony_ci	int ret, max_bw;
212762306a36Sopenharmony_ci
212862306a36Sopenharmony_ci	/* Figure out suitable scale */
212962306a36Sopenharmony_ci	scale = 0;
213062306a36Sopenharmony_ci	max_bw = max(upstream_bw, downstream_bw);
213162306a36Sopenharmony_ci	while (scale < 64) {
213262306a36Sopenharmony_ci		if (mbps_to_usb3_bw(max_bw, scale) < 4096)
213362306a36Sopenharmony_ci			break;
213462306a36Sopenharmony_ci		scale++;
213562306a36Sopenharmony_ci	}
213662306a36Sopenharmony_ci
213762306a36Sopenharmony_ci	if (WARN_ON(scale >= 64))
213862306a36Sopenharmony_ci		return -EINVAL;
213962306a36Sopenharmony_ci
214062306a36Sopenharmony_ci	ret = tb_port_write(port, &scale, TB_CFG_PORT,
214162306a36Sopenharmony_ci			    port->cap_adap + ADP_USB3_CS_3, 1);
214262306a36Sopenharmony_ci	if (ret)
214362306a36Sopenharmony_ci		return ret;
214462306a36Sopenharmony_ci
214562306a36Sopenharmony_ci	ubw = mbps_to_usb3_bw(upstream_bw, scale);
214662306a36Sopenharmony_ci	dbw = mbps_to_usb3_bw(downstream_bw, scale);
214762306a36Sopenharmony_ci
214862306a36Sopenharmony_ci	tb_port_dbg(port, "scaled bandwidth %u/%u, scale %u\n", ubw, dbw, scale);
214962306a36Sopenharmony_ci
215062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
215162306a36Sopenharmony_ci			   port->cap_adap + ADP_USB3_CS_2, 1);
215262306a36Sopenharmony_ci	if (ret)
215362306a36Sopenharmony_ci		return ret;
215462306a36Sopenharmony_ci
215562306a36Sopenharmony_ci	val &= ~(ADP_USB3_CS_2_AUBW_MASK | ADP_USB3_CS_2_ADBW_MASK);
215662306a36Sopenharmony_ci	val |= dbw << ADP_USB3_CS_2_ADBW_SHIFT;
215762306a36Sopenharmony_ci	val |= ubw;
215862306a36Sopenharmony_ci
215962306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
216062306a36Sopenharmony_ci			     port->cap_adap + ADP_USB3_CS_2, 1);
216162306a36Sopenharmony_ci}
216262306a36Sopenharmony_ci
216362306a36Sopenharmony_ci/**
216462306a36Sopenharmony_ci * usb4_usb3_port_allocate_bandwidth() - Allocate bandwidth for USB3
216562306a36Sopenharmony_ci * @port: USB3 adapter port
216662306a36Sopenharmony_ci * @upstream_bw: New upstream bandwidth
216762306a36Sopenharmony_ci * @downstream_bw: New downstream bandwidth
216862306a36Sopenharmony_ci *
216962306a36Sopenharmony_ci * This can be used to set how much bandwidth is allocated for the USB3
217062306a36Sopenharmony_ci * tunneled isochronous traffic. @upstream_bw and @downstream_bw are the
217162306a36Sopenharmony_ci * new values programmed to the USB3 adapter allocation registers. If
217262306a36Sopenharmony_ci * the values are lower than what is currently consumed the allocation
217362306a36Sopenharmony_ci * is set to what is currently consumed instead (consumed bandwidth
217462306a36Sopenharmony_ci * cannot be taken away by CM). The actual new values are returned in
217562306a36Sopenharmony_ci * @upstream_bw and @downstream_bw.
217662306a36Sopenharmony_ci *
217762306a36Sopenharmony_ci * Returns %0 in case of success and negative errno if there was a
217862306a36Sopenharmony_ci * failure.
217962306a36Sopenharmony_ci */
218062306a36Sopenharmony_ciint usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
218162306a36Sopenharmony_ci				      int *downstream_bw)
218262306a36Sopenharmony_ci{
218362306a36Sopenharmony_ci	int ret, consumed_up, consumed_down, allocate_up, allocate_down;
218462306a36Sopenharmony_ci
218562306a36Sopenharmony_ci	ret = usb4_usb3_port_set_cm_request(port);
218662306a36Sopenharmony_ci	if (ret)
218762306a36Sopenharmony_ci		return ret;
218862306a36Sopenharmony_ci
218962306a36Sopenharmony_ci	ret = usb4_usb3_port_read_consumed_bandwidth(port, &consumed_up,
219062306a36Sopenharmony_ci						     &consumed_down);
219162306a36Sopenharmony_ci	if (ret)
219262306a36Sopenharmony_ci		goto err_request;
219362306a36Sopenharmony_ci
219462306a36Sopenharmony_ci	/* Don't allow it go lower than what is consumed */
219562306a36Sopenharmony_ci	allocate_up = max(*upstream_bw, consumed_up);
219662306a36Sopenharmony_ci	allocate_down = max(*downstream_bw, consumed_down);
219762306a36Sopenharmony_ci
219862306a36Sopenharmony_ci	ret = usb4_usb3_port_write_allocated_bandwidth(port, allocate_up,
219962306a36Sopenharmony_ci						       allocate_down);
220062306a36Sopenharmony_ci	if (ret)
220162306a36Sopenharmony_ci		goto err_request;
220262306a36Sopenharmony_ci
220362306a36Sopenharmony_ci	*upstream_bw = allocate_up;
220462306a36Sopenharmony_ci	*downstream_bw = allocate_down;
220562306a36Sopenharmony_ci
220662306a36Sopenharmony_cierr_request:
220762306a36Sopenharmony_ci	usb4_usb3_port_clear_cm_request(port);
220862306a36Sopenharmony_ci	return ret;
220962306a36Sopenharmony_ci}
221062306a36Sopenharmony_ci
221162306a36Sopenharmony_ci/**
221262306a36Sopenharmony_ci * usb4_usb3_port_release_bandwidth() - Release allocated USB3 bandwidth
221362306a36Sopenharmony_ci * @port: USB3 adapter port
221462306a36Sopenharmony_ci * @upstream_bw: New allocated upstream bandwidth
221562306a36Sopenharmony_ci * @downstream_bw: New allocated downstream bandwidth
221662306a36Sopenharmony_ci *
221762306a36Sopenharmony_ci * Releases USB3 allocated bandwidth down to what is actually consumed.
221862306a36Sopenharmony_ci * The new bandwidth is returned in @upstream_bw and @downstream_bw.
221962306a36Sopenharmony_ci *
222062306a36Sopenharmony_ci * Returns 0% in success and negative errno in case of failure.
222162306a36Sopenharmony_ci */
222262306a36Sopenharmony_ciint usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
222362306a36Sopenharmony_ci				     int *downstream_bw)
222462306a36Sopenharmony_ci{
222562306a36Sopenharmony_ci	int ret, consumed_up, consumed_down;
222662306a36Sopenharmony_ci
222762306a36Sopenharmony_ci	ret = usb4_usb3_port_set_cm_request(port);
222862306a36Sopenharmony_ci	if (ret)
222962306a36Sopenharmony_ci		return ret;
223062306a36Sopenharmony_ci
223162306a36Sopenharmony_ci	ret = usb4_usb3_port_read_consumed_bandwidth(port, &consumed_up,
223262306a36Sopenharmony_ci						     &consumed_down);
223362306a36Sopenharmony_ci	if (ret)
223462306a36Sopenharmony_ci		goto err_request;
223562306a36Sopenharmony_ci
223662306a36Sopenharmony_ci	/*
223762306a36Sopenharmony_ci	 * Always keep 1000 Mb/s to make sure xHCI has at least some
223862306a36Sopenharmony_ci	 * bandwidth available for isochronous traffic.
223962306a36Sopenharmony_ci	 */
224062306a36Sopenharmony_ci	if (consumed_up < 1000)
224162306a36Sopenharmony_ci		consumed_up = 1000;
224262306a36Sopenharmony_ci	if (consumed_down < 1000)
224362306a36Sopenharmony_ci		consumed_down = 1000;
224462306a36Sopenharmony_ci
224562306a36Sopenharmony_ci	ret = usb4_usb3_port_write_allocated_bandwidth(port, consumed_up,
224662306a36Sopenharmony_ci						       consumed_down);
224762306a36Sopenharmony_ci	if (ret)
224862306a36Sopenharmony_ci		goto err_request;
224962306a36Sopenharmony_ci
225062306a36Sopenharmony_ci	*upstream_bw = consumed_up;
225162306a36Sopenharmony_ci	*downstream_bw = consumed_down;
225262306a36Sopenharmony_ci
225362306a36Sopenharmony_cierr_request:
225462306a36Sopenharmony_ci	usb4_usb3_port_clear_cm_request(port);
225562306a36Sopenharmony_ci	return ret;
225662306a36Sopenharmony_ci}
225762306a36Sopenharmony_ci
225862306a36Sopenharmony_cistatic bool is_usb4_dpin(const struct tb_port *port)
225962306a36Sopenharmony_ci{
226062306a36Sopenharmony_ci	if (!tb_port_is_dpin(port))
226162306a36Sopenharmony_ci		return false;
226262306a36Sopenharmony_ci	if (!tb_switch_is_usb4(port->sw))
226362306a36Sopenharmony_ci		return false;
226462306a36Sopenharmony_ci	return true;
226562306a36Sopenharmony_ci}
226662306a36Sopenharmony_ci
226762306a36Sopenharmony_ci/**
226862306a36Sopenharmony_ci * usb4_dp_port_set_cm_id() - Assign CM ID to the DP IN adapter
226962306a36Sopenharmony_ci * @port: DP IN adapter
227062306a36Sopenharmony_ci * @cm_id: CM ID to assign
227162306a36Sopenharmony_ci *
227262306a36Sopenharmony_ci * Sets CM ID for the @port. Returns %0 on success and negative errno
227362306a36Sopenharmony_ci * otherwise. Speficially returns %-EOPNOTSUPP if the @port does not
227462306a36Sopenharmony_ci * support this.
227562306a36Sopenharmony_ci */
227662306a36Sopenharmony_ciint usb4_dp_port_set_cm_id(struct tb_port *port, int cm_id)
227762306a36Sopenharmony_ci{
227862306a36Sopenharmony_ci	u32 val;
227962306a36Sopenharmony_ci	int ret;
228062306a36Sopenharmony_ci
228162306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
228262306a36Sopenharmony_ci		return -EOPNOTSUPP;
228362306a36Sopenharmony_ci
228462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
228562306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
228662306a36Sopenharmony_ci	if (ret)
228762306a36Sopenharmony_ci		return ret;
228862306a36Sopenharmony_ci
228962306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_CM_ID_MASK;
229062306a36Sopenharmony_ci	val |= cm_id << ADP_DP_CS_2_CM_ID_SHIFT;
229162306a36Sopenharmony_ci
229262306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
229362306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
229462306a36Sopenharmony_ci}
229562306a36Sopenharmony_ci
229662306a36Sopenharmony_ci/**
229762306a36Sopenharmony_ci * usb4_dp_port_bandwidth_mode_supported() - Is the bandwidth allocation mode
229862306a36Sopenharmony_ci *					     supported
229962306a36Sopenharmony_ci * @port: DP IN adapter to check
230062306a36Sopenharmony_ci *
230162306a36Sopenharmony_ci * Can be called to any DP IN adapter. Returns true if the adapter
230262306a36Sopenharmony_ci * supports USB4 bandwidth allocation mode, false otherwise.
230362306a36Sopenharmony_ci */
230462306a36Sopenharmony_cibool usb4_dp_port_bandwidth_mode_supported(struct tb_port *port)
230562306a36Sopenharmony_ci{
230662306a36Sopenharmony_ci	int ret;
230762306a36Sopenharmony_ci	u32 val;
230862306a36Sopenharmony_ci
230962306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
231062306a36Sopenharmony_ci		return false;
231162306a36Sopenharmony_ci
231262306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
231362306a36Sopenharmony_ci			   port->cap_adap + DP_LOCAL_CAP, 1);
231462306a36Sopenharmony_ci	if (ret)
231562306a36Sopenharmony_ci		return false;
231662306a36Sopenharmony_ci
231762306a36Sopenharmony_ci	return !!(val & DP_COMMON_CAP_BW_MODE);
231862306a36Sopenharmony_ci}
231962306a36Sopenharmony_ci
232062306a36Sopenharmony_ci/**
232162306a36Sopenharmony_ci * usb4_dp_port_bandwidth_mode_enabled() - Is the bandwidth allocation mode
232262306a36Sopenharmony_ci *					   enabled
232362306a36Sopenharmony_ci * @port: DP IN adapter to check
232462306a36Sopenharmony_ci *
232562306a36Sopenharmony_ci * Can be called to any DP IN adapter. Returns true if the bandwidth
232662306a36Sopenharmony_ci * allocation mode has been enabled, false otherwise.
232762306a36Sopenharmony_ci */
232862306a36Sopenharmony_cibool usb4_dp_port_bandwidth_mode_enabled(struct tb_port *port)
232962306a36Sopenharmony_ci{
233062306a36Sopenharmony_ci	int ret;
233162306a36Sopenharmony_ci	u32 val;
233262306a36Sopenharmony_ci
233362306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
233462306a36Sopenharmony_ci		return false;
233562306a36Sopenharmony_ci
233662306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
233762306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_8, 1);
233862306a36Sopenharmony_ci	if (ret)
233962306a36Sopenharmony_ci		return false;
234062306a36Sopenharmony_ci
234162306a36Sopenharmony_ci	return !!(val & ADP_DP_CS_8_DPME);
234262306a36Sopenharmony_ci}
234362306a36Sopenharmony_ci
234462306a36Sopenharmony_ci/**
234562306a36Sopenharmony_ci * usb4_dp_port_set_cm_bandwidth_mode_supported() - Set/clear CM support for
234662306a36Sopenharmony_ci *						    bandwidth allocation mode
234762306a36Sopenharmony_ci * @port: DP IN adapter
234862306a36Sopenharmony_ci * @supported: Does the CM support bandwidth allocation mode
234962306a36Sopenharmony_ci *
235062306a36Sopenharmony_ci * Can be called to any DP IN adapter. Sets or clears the CM support bit
235162306a36Sopenharmony_ci * of the DP IN adapter. Returns %0 in success and negative errno
235262306a36Sopenharmony_ci * otherwise. Specifically returns %-OPNOTSUPP if the passed in adapter
235362306a36Sopenharmony_ci * does not support this.
235462306a36Sopenharmony_ci */
235562306a36Sopenharmony_ciint usb4_dp_port_set_cm_bandwidth_mode_supported(struct tb_port *port,
235662306a36Sopenharmony_ci						 bool supported)
235762306a36Sopenharmony_ci{
235862306a36Sopenharmony_ci	u32 val;
235962306a36Sopenharmony_ci	int ret;
236062306a36Sopenharmony_ci
236162306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
236262306a36Sopenharmony_ci		return -EOPNOTSUPP;
236362306a36Sopenharmony_ci
236462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
236562306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
236662306a36Sopenharmony_ci	if (ret)
236762306a36Sopenharmony_ci		return ret;
236862306a36Sopenharmony_ci
236962306a36Sopenharmony_ci	if (supported)
237062306a36Sopenharmony_ci		val |= ADP_DP_CS_2_CMMS;
237162306a36Sopenharmony_ci	else
237262306a36Sopenharmony_ci		val &= ~ADP_DP_CS_2_CMMS;
237362306a36Sopenharmony_ci
237462306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
237562306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
237662306a36Sopenharmony_ci}
237762306a36Sopenharmony_ci
237862306a36Sopenharmony_ci/**
237962306a36Sopenharmony_ci * usb4_dp_port_group_id() - Return Group ID assigned for the adapter
238062306a36Sopenharmony_ci * @port: DP IN adapter
238162306a36Sopenharmony_ci *
238262306a36Sopenharmony_ci * Reads bandwidth allocation Group ID from the DP IN adapter and
238362306a36Sopenharmony_ci * returns it. If the adapter does not support setting Group_ID
238462306a36Sopenharmony_ci * %-EOPNOTSUPP is returned.
238562306a36Sopenharmony_ci */
238662306a36Sopenharmony_ciint usb4_dp_port_group_id(struct tb_port *port)
238762306a36Sopenharmony_ci{
238862306a36Sopenharmony_ci	u32 val;
238962306a36Sopenharmony_ci	int ret;
239062306a36Sopenharmony_ci
239162306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
239262306a36Sopenharmony_ci		return -EOPNOTSUPP;
239362306a36Sopenharmony_ci
239462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
239562306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
239662306a36Sopenharmony_ci	if (ret)
239762306a36Sopenharmony_ci		return ret;
239862306a36Sopenharmony_ci
239962306a36Sopenharmony_ci	return (val & ADP_DP_CS_2_GROUP_ID_MASK) >> ADP_DP_CS_2_GROUP_ID_SHIFT;
240062306a36Sopenharmony_ci}
240162306a36Sopenharmony_ci
240262306a36Sopenharmony_ci/**
240362306a36Sopenharmony_ci * usb4_dp_port_set_group_id() - Set adapter Group ID
240462306a36Sopenharmony_ci * @port: DP IN adapter
240562306a36Sopenharmony_ci * @group_id: Group ID for the adapter
240662306a36Sopenharmony_ci *
240762306a36Sopenharmony_ci * Sets bandwidth allocation mode Group ID for the DP IN adapter.
240862306a36Sopenharmony_ci * Returns %0 in case of success and negative errno otherwise.
240962306a36Sopenharmony_ci * Specifically returns %-EOPNOTSUPP if the adapter does not support
241062306a36Sopenharmony_ci * this.
241162306a36Sopenharmony_ci */
241262306a36Sopenharmony_ciint usb4_dp_port_set_group_id(struct tb_port *port, int group_id)
241362306a36Sopenharmony_ci{
241462306a36Sopenharmony_ci	u32 val;
241562306a36Sopenharmony_ci	int ret;
241662306a36Sopenharmony_ci
241762306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
241862306a36Sopenharmony_ci		return -EOPNOTSUPP;
241962306a36Sopenharmony_ci
242062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
242162306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
242262306a36Sopenharmony_ci	if (ret)
242362306a36Sopenharmony_ci		return ret;
242462306a36Sopenharmony_ci
242562306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_GROUP_ID_MASK;
242662306a36Sopenharmony_ci	val |= group_id << ADP_DP_CS_2_GROUP_ID_SHIFT;
242762306a36Sopenharmony_ci
242862306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
242962306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
243062306a36Sopenharmony_ci}
243162306a36Sopenharmony_ci
243262306a36Sopenharmony_ci/**
243362306a36Sopenharmony_ci * usb4_dp_port_nrd() - Read non-reduced rate and lanes
243462306a36Sopenharmony_ci * @port: DP IN adapter
243562306a36Sopenharmony_ci * @rate: Non-reduced rate in Mb/s is placed here
243662306a36Sopenharmony_ci * @lanes: Non-reduced lanes are placed here
243762306a36Sopenharmony_ci *
243862306a36Sopenharmony_ci * Reads the non-reduced rate and lanes from the DP IN adapter. Returns
243962306a36Sopenharmony_ci * %0 in success and negative errno otherwise. Specifically returns
244062306a36Sopenharmony_ci * %-EOPNOTSUPP if the adapter does not support this.
244162306a36Sopenharmony_ci */
244262306a36Sopenharmony_ciint usb4_dp_port_nrd(struct tb_port *port, int *rate, int *lanes)
244362306a36Sopenharmony_ci{
244462306a36Sopenharmony_ci	u32 val, tmp;
244562306a36Sopenharmony_ci	int ret;
244662306a36Sopenharmony_ci
244762306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
244862306a36Sopenharmony_ci		return -EOPNOTSUPP;
244962306a36Sopenharmony_ci
245062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
245162306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
245262306a36Sopenharmony_ci	if (ret)
245362306a36Sopenharmony_ci		return ret;
245462306a36Sopenharmony_ci
245562306a36Sopenharmony_ci	tmp = (val & ADP_DP_CS_2_NRD_MLR_MASK) >> ADP_DP_CS_2_NRD_MLR_SHIFT;
245662306a36Sopenharmony_ci	switch (tmp) {
245762306a36Sopenharmony_ci	case DP_COMMON_CAP_RATE_RBR:
245862306a36Sopenharmony_ci		*rate = 1620;
245962306a36Sopenharmony_ci		break;
246062306a36Sopenharmony_ci	case DP_COMMON_CAP_RATE_HBR:
246162306a36Sopenharmony_ci		*rate = 2700;
246262306a36Sopenharmony_ci		break;
246362306a36Sopenharmony_ci	case DP_COMMON_CAP_RATE_HBR2:
246462306a36Sopenharmony_ci		*rate = 5400;
246562306a36Sopenharmony_ci		break;
246662306a36Sopenharmony_ci	case DP_COMMON_CAP_RATE_HBR3:
246762306a36Sopenharmony_ci		*rate = 8100;
246862306a36Sopenharmony_ci		break;
246962306a36Sopenharmony_ci	}
247062306a36Sopenharmony_ci
247162306a36Sopenharmony_ci	tmp = val & ADP_DP_CS_2_NRD_MLC_MASK;
247262306a36Sopenharmony_ci	switch (tmp) {
247362306a36Sopenharmony_ci	case DP_COMMON_CAP_1_LANE:
247462306a36Sopenharmony_ci		*lanes = 1;
247562306a36Sopenharmony_ci		break;
247662306a36Sopenharmony_ci	case DP_COMMON_CAP_2_LANES:
247762306a36Sopenharmony_ci		*lanes = 2;
247862306a36Sopenharmony_ci		break;
247962306a36Sopenharmony_ci	case DP_COMMON_CAP_4_LANES:
248062306a36Sopenharmony_ci		*lanes = 4;
248162306a36Sopenharmony_ci		break;
248262306a36Sopenharmony_ci	}
248362306a36Sopenharmony_ci
248462306a36Sopenharmony_ci	return 0;
248562306a36Sopenharmony_ci}
248662306a36Sopenharmony_ci
248762306a36Sopenharmony_ci/**
248862306a36Sopenharmony_ci * usb4_dp_port_set_nrd() - Set non-reduced rate and lanes
248962306a36Sopenharmony_ci * @port: DP IN adapter
249062306a36Sopenharmony_ci * @rate: Non-reduced rate in Mb/s
249162306a36Sopenharmony_ci * @lanes: Non-reduced lanes
249262306a36Sopenharmony_ci *
249362306a36Sopenharmony_ci * Before the capabilities reduction this function can be used to set
249462306a36Sopenharmony_ci * the non-reduced values for the DP IN adapter. Returns %0 in success
249562306a36Sopenharmony_ci * and negative errno otherwise. If the adapter does not support this
249662306a36Sopenharmony_ci * %-EOPNOTSUPP is returned.
249762306a36Sopenharmony_ci */
249862306a36Sopenharmony_ciint usb4_dp_port_set_nrd(struct tb_port *port, int rate, int lanes)
249962306a36Sopenharmony_ci{
250062306a36Sopenharmony_ci	u32 val;
250162306a36Sopenharmony_ci	int ret;
250262306a36Sopenharmony_ci
250362306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
250462306a36Sopenharmony_ci		return -EOPNOTSUPP;
250562306a36Sopenharmony_ci
250662306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
250762306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
250862306a36Sopenharmony_ci	if (ret)
250962306a36Sopenharmony_ci		return ret;
251062306a36Sopenharmony_ci
251162306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_NRD_MLR_MASK;
251262306a36Sopenharmony_ci
251362306a36Sopenharmony_ci	switch (rate) {
251462306a36Sopenharmony_ci	case 1620:
251562306a36Sopenharmony_ci		break;
251662306a36Sopenharmony_ci	case 2700:
251762306a36Sopenharmony_ci		val |= (DP_COMMON_CAP_RATE_HBR << ADP_DP_CS_2_NRD_MLR_SHIFT)
251862306a36Sopenharmony_ci			& ADP_DP_CS_2_NRD_MLR_MASK;
251962306a36Sopenharmony_ci		break;
252062306a36Sopenharmony_ci	case 5400:
252162306a36Sopenharmony_ci		val |= (DP_COMMON_CAP_RATE_HBR2 << ADP_DP_CS_2_NRD_MLR_SHIFT)
252262306a36Sopenharmony_ci			& ADP_DP_CS_2_NRD_MLR_MASK;
252362306a36Sopenharmony_ci		break;
252462306a36Sopenharmony_ci	case 8100:
252562306a36Sopenharmony_ci		val |= (DP_COMMON_CAP_RATE_HBR3 << ADP_DP_CS_2_NRD_MLR_SHIFT)
252662306a36Sopenharmony_ci			& ADP_DP_CS_2_NRD_MLR_MASK;
252762306a36Sopenharmony_ci		break;
252862306a36Sopenharmony_ci	default:
252962306a36Sopenharmony_ci		return -EINVAL;
253062306a36Sopenharmony_ci	}
253162306a36Sopenharmony_ci
253262306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_NRD_MLC_MASK;
253362306a36Sopenharmony_ci
253462306a36Sopenharmony_ci	switch (lanes) {
253562306a36Sopenharmony_ci	case 1:
253662306a36Sopenharmony_ci		break;
253762306a36Sopenharmony_ci	case 2:
253862306a36Sopenharmony_ci		val |= DP_COMMON_CAP_2_LANES;
253962306a36Sopenharmony_ci		break;
254062306a36Sopenharmony_ci	case 4:
254162306a36Sopenharmony_ci		val |= DP_COMMON_CAP_4_LANES;
254262306a36Sopenharmony_ci		break;
254362306a36Sopenharmony_ci	default:
254462306a36Sopenharmony_ci		return -EINVAL;
254562306a36Sopenharmony_ci	}
254662306a36Sopenharmony_ci
254762306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
254862306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
254962306a36Sopenharmony_ci}
255062306a36Sopenharmony_ci
255162306a36Sopenharmony_ci/**
255262306a36Sopenharmony_ci * usb4_dp_port_granularity() - Return granularity for the bandwidth values
255362306a36Sopenharmony_ci * @port: DP IN adapter
255462306a36Sopenharmony_ci *
255562306a36Sopenharmony_ci * Reads the programmed granularity from @port. If the DP IN adapter does
255662306a36Sopenharmony_ci * not support bandwidth allocation mode returns %-EOPNOTSUPP and negative
255762306a36Sopenharmony_ci * errno in other error cases.
255862306a36Sopenharmony_ci */
255962306a36Sopenharmony_ciint usb4_dp_port_granularity(struct tb_port *port)
256062306a36Sopenharmony_ci{
256162306a36Sopenharmony_ci	u32 val;
256262306a36Sopenharmony_ci	int ret;
256362306a36Sopenharmony_ci
256462306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
256562306a36Sopenharmony_ci		return -EOPNOTSUPP;
256662306a36Sopenharmony_ci
256762306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
256862306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
256962306a36Sopenharmony_ci	if (ret)
257062306a36Sopenharmony_ci		return ret;
257162306a36Sopenharmony_ci
257262306a36Sopenharmony_ci	val &= ADP_DP_CS_2_GR_MASK;
257362306a36Sopenharmony_ci	val >>= ADP_DP_CS_2_GR_SHIFT;
257462306a36Sopenharmony_ci
257562306a36Sopenharmony_ci	switch (val) {
257662306a36Sopenharmony_ci	case ADP_DP_CS_2_GR_0_25G:
257762306a36Sopenharmony_ci		return 250;
257862306a36Sopenharmony_ci	case ADP_DP_CS_2_GR_0_5G:
257962306a36Sopenharmony_ci		return 500;
258062306a36Sopenharmony_ci	case ADP_DP_CS_2_GR_1G:
258162306a36Sopenharmony_ci		return 1000;
258262306a36Sopenharmony_ci	}
258362306a36Sopenharmony_ci
258462306a36Sopenharmony_ci	return -EINVAL;
258562306a36Sopenharmony_ci}
258662306a36Sopenharmony_ci
258762306a36Sopenharmony_ci/**
258862306a36Sopenharmony_ci * usb4_dp_port_set_granularity() - Set granularity for the bandwidth values
258962306a36Sopenharmony_ci * @port: DP IN adapter
259062306a36Sopenharmony_ci * @granularity: Granularity in Mb/s. Supported values: 1000, 500 and 250.
259162306a36Sopenharmony_ci *
259262306a36Sopenharmony_ci * Sets the granularity used with the estimated, allocated and requested
259362306a36Sopenharmony_ci * bandwidth. Returns %0 in success and negative errno otherwise. If the
259462306a36Sopenharmony_ci * adapter does not support this %-EOPNOTSUPP is returned.
259562306a36Sopenharmony_ci */
259662306a36Sopenharmony_ciint usb4_dp_port_set_granularity(struct tb_port *port, int granularity)
259762306a36Sopenharmony_ci{
259862306a36Sopenharmony_ci	u32 val;
259962306a36Sopenharmony_ci	int ret;
260062306a36Sopenharmony_ci
260162306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
260262306a36Sopenharmony_ci		return -EOPNOTSUPP;
260362306a36Sopenharmony_ci
260462306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
260562306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
260662306a36Sopenharmony_ci	if (ret)
260762306a36Sopenharmony_ci		return ret;
260862306a36Sopenharmony_ci
260962306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_GR_MASK;
261062306a36Sopenharmony_ci
261162306a36Sopenharmony_ci	switch (granularity) {
261262306a36Sopenharmony_ci	case 250:
261362306a36Sopenharmony_ci		val |= ADP_DP_CS_2_GR_0_25G << ADP_DP_CS_2_GR_SHIFT;
261462306a36Sopenharmony_ci		break;
261562306a36Sopenharmony_ci	case 500:
261662306a36Sopenharmony_ci		val |= ADP_DP_CS_2_GR_0_5G << ADP_DP_CS_2_GR_SHIFT;
261762306a36Sopenharmony_ci		break;
261862306a36Sopenharmony_ci	case 1000:
261962306a36Sopenharmony_ci		val |= ADP_DP_CS_2_GR_1G << ADP_DP_CS_2_GR_SHIFT;
262062306a36Sopenharmony_ci		break;
262162306a36Sopenharmony_ci	default:
262262306a36Sopenharmony_ci		return -EINVAL;
262362306a36Sopenharmony_ci	}
262462306a36Sopenharmony_ci
262562306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
262662306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
262762306a36Sopenharmony_ci}
262862306a36Sopenharmony_ci
262962306a36Sopenharmony_ci/**
263062306a36Sopenharmony_ci * usb4_dp_port_set_estimated_bandwidth() - Set estimated bandwidth
263162306a36Sopenharmony_ci * @port: DP IN adapter
263262306a36Sopenharmony_ci * @bw: Estimated bandwidth in Mb/s.
263362306a36Sopenharmony_ci *
263462306a36Sopenharmony_ci * Sets the estimated bandwidth to @bw. Set the granularity by calling
263562306a36Sopenharmony_ci * usb4_dp_port_set_granularity() before calling this. The @bw is round
263662306a36Sopenharmony_ci * down to the closest granularity multiplier. Returns %0 in success
263762306a36Sopenharmony_ci * and negative errno otherwise. Specifically returns %-EOPNOTSUPP if
263862306a36Sopenharmony_ci * the adapter does not support this.
263962306a36Sopenharmony_ci */
264062306a36Sopenharmony_ciint usb4_dp_port_set_estimated_bandwidth(struct tb_port *port, int bw)
264162306a36Sopenharmony_ci{
264262306a36Sopenharmony_ci	u32 val, granularity;
264362306a36Sopenharmony_ci	int ret;
264462306a36Sopenharmony_ci
264562306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
264662306a36Sopenharmony_ci		return -EOPNOTSUPP;
264762306a36Sopenharmony_ci
264862306a36Sopenharmony_ci	ret = usb4_dp_port_granularity(port);
264962306a36Sopenharmony_ci	if (ret < 0)
265062306a36Sopenharmony_ci		return ret;
265162306a36Sopenharmony_ci	granularity = ret;
265262306a36Sopenharmony_ci
265362306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
265462306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
265562306a36Sopenharmony_ci	if (ret)
265662306a36Sopenharmony_ci		return ret;
265762306a36Sopenharmony_ci
265862306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_ESTIMATED_BW_MASK;
265962306a36Sopenharmony_ci	val |= (bw / granularity) << ADP_DP_CS_2_ESTIMATED_BW_SHIFT;
266062306a36Sopenharmony_ci
266162306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
266262306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
266362306a36Sopenharmony_ci}
266462306a36Sopenharmony_ci
266562306a36Sopenharmony_ci/**
266662306a36Sopenharmony_ci * usb4_dp_port_allocated_bandwidth() - Return allocated bandwidth
266762306a36Sopenharmony_ci * @port: DP IN adapter
266862306a36Sopenharmony_ci *
266962306a36Sopenharmony_ci * Reads and returns allocated bandwidth for @port in Mb/s (taking into
267062306a36Sopenharmony_ci * account the programmed granularity). Returns negative errno in case
267162306a36Sopenharmony_ci * of error.
267262306a36Sopenharmony_ci */
267362306a36Sopenharmony_ciint usb4_dp_port_allocated_bandwidth(struct tb_port *port)
267462306a36Sopenharmony_ci{
267562306a36Sopenharmony_ci	u32 val, granularity;
267662306a36Sopenharmony_ci	int ret;
267762306a36Sopenharmony_ci
267862306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
267962306a36Sopenharmony_ci		return -EOPNOTSUPP;
268062306a36Sopenharmony_ci
268162306a36Sopenharmony_ci	ret = usb4_dp_port_granularity(port);
268262306a36Sopenharmony_ci	if (ret < 0)
268362306a36Sopenharmony_ci		return ret;
268462306a36Sopenharmony_ci	granularity = ret;
268562306a36Sopenharmony_ci
268662306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
268762306a36Sopenharmony_ci			   port->cap_adap + DP_STATUS, 1);
268862306a36Sopenharmony_ci	if (ret)
268962306a36Sopenharmony_ci		return ret;
269062306a36Sopenharmony_ci
269162306a36Sopenharmony_ci	val &= DP_STATUS_ALLOCATED_BW_MASK;
269262306a36Sopenharmony_ci	val >>= DP_STATUS_ALLOCATED_BW_SHIFT;
269362306a36Sopenharmony_ci
269462306a36Sopenharmony_ci	return val * granularity;
269562306a36Sopenharmony_ci}
269662306a36Sopenharmony_ci
269762306a36Sopenharmony_cistatic int __usb4_dp_port_set_cm_ack(struct tb_port *port, bool ack)
269862306a36Sopenharmony_ci{
269962306a36Sopenharmony_ci	u32 val;
270062306a36Sopenharmony_ci	int ret;
270162306a36Sopenharmony_ci
270262306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
270362306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
270462306a36Sopenharmony_ci	if (ret)
270562306a36Sopenharmony_ci		return ret;
270662306a36Sopenharmony_ci
270762306a36Sopenharmony_ci	if (ack)
270862306a36Sopenharmony_ci		val |= ADP_DP_CS_2_CA;
270962306a36Sopenharmony_ci	else
271062306a36Sopenharmony_ci		val &= ~ADP_DP_CS_2_CA;
271162306a36Sopenharmony_ci
271262306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
271362306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
271462306a36Sopenharmony_ci}
271562306a36Sopenharmony_ci
271662306a36Sopenharmony_cistatic inline int usb4_dp_port_set_cm_ack(struct tb_port *port)
271762306a36Sopenharmony_ci{
271862306a36Sopenharmony_ci	return __usb4_dp_port_set_cm_ack(port, true);
271962306a36Sopenharmony_ci}
272062306a36Sopenharmony_ci
272162306a36Sopenharmony_cistatic int usb4_dp_port_wait_and_clear_cm_ack(struct tb_port *port,
272262306a36Sopenharmony_ci					      int timeout_msec)
272362306a36Sopenharmony_ci{
272462306a36Sopenharmony_ci	ktime_t end;
272562306a36Sopenharmony_ci	u32 val;
272662306a36Sopenharmony_ci	int ret;
272762306a36Sopenharmony_ci
272862306a36Sopenharmony_ci	ret = __usb4_dp_port_set_cm_ack(port, false);
272962306a36Sopenharmony_ci	if (ret)
273062306a36Sopenharmony_ci		return ret;
273162306a36Sopenharmony_ci
273262306a36Sopenharmony_ci	end = ktime_add_ms(ktime_get(), timeout_msec);
273362306a36Sopenharmony_ci	do {
273462306a36Sopenharmony_ci		ret = tb_port_read(port, &val, TB_CFG_PORT,
273562306a36Sopenharmony_ci				   port->cap_adap + ADP_DP_CS_8, 1);
273662306a36Sopenharmony_ci		if (ret)
273762306a36Sopenharmony_ci			return ret;
273862306a36Sopenharmony_ci
273962306a36Sopenharmony_ci		if (!(val & ADP_DP_CS_8_DR))
274062306a36Sopenharmony_ci			break;
274162306a36Sopenharmony_ci
274262306a36Sopenharmony_ci		usleep_range(50, 100);
274362306a36Sopenharmony_ci	} while (ktime_before(ktime_get(), end));
274462306a36Sopenharmony_ci
274562306a36Sopenharmony_ci	if (val & ADP_DP_CS_8_DR)
274662306a36Sopenharmony_ci		return -ETIMEDOUT;
274762306a36Sopenharmony_ci
274862306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
274962306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
275062306a36Sopenharmony_ci	if (ret)
275162306a36Sopenharmony_ci		return ret;
275262306a36Sopenharmony_ci
275362306a36Sopenharmony_ci	val &= ~ADP_DP_CS_2_CA;
275462306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
275562306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_2, 1);
275662306a36Sopenharmony_ci}
275762306a36Sopenharmony_ci
275862306a36Sopenharmony_ci/**
275962306a36Sopenharmony_ci * usb4_dp_port_allocate_bandwidth() - Set allocated bandwidth
276062306a36Sopenharmony_ci * @port: DP IN adapter
276162306a36Sopenharmony_ci * @bw: New allocated bandwidth in Mb/s
276262306a36Sopenharmony_ci *
276362306a36Sopenharmony_ci * Communicates the new allocated bandwidth with the DPCD (graphics
276462306a36Sopenharmony_ci * driver). Takes into account the programmed granularity. Returns %0 in
276562306a36Sopenharmony_ci * success and negative errno in case of error.
276662306a36Sopenharmony_ci */
276762306a36Sopenharmony_ciint usb4_dp_port_allocate_bandwidth(struct tb_port *port, int bw)
276862306a36Sopenharmony_ci{
276962306a36Sopenharmony_ci	u32 val, granularity;
277062306a36Sopenharmony_ci	int ret;
277162306a36Sopenharmony_ci
277262306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
277362306a36Sopenharmony_ci		return -EOPNOTSUPP;
277462306a36Sopenharmony_ci
277562306a36Sopenharmony_ci	ret = usb4_dp_port_granularity(port);
277662306a36Sopenharmony_ci	if (ret < 0)
277762306a36Sopenharmony_ci		return ret;
277862306a36Sopenharmony_ci	granularity = ret;
277962306a36Sopenharmony_ci
278062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
278162306a36Sopenharmony_ci			   port->cap_adap + DP_STATUS, 1);
278262306a36Sopenharmony_ci	if (ret)
278362306a36Sopenharmony_ci		return ret;
278462306a36Sopenharmony_ci
278562306a36Sopenharmony_ci	val &= ~DP_STATUS_ALLOCATED_BW_MASK;
278662306a36Sopenharmony_ci	val |= (bw / granularity) << DP_STATUS_ALLOCATED_BW_SHIFT;
278762306a36Sopenharmony_ci
278862306a36Sopenharmony_ci	ret = tb_port_write(port, &val, TB_CFG_PORT,
278962306a36Sopenharmony_ci			    port->cap_adap + DP_STATUS, 1);
279062306a36Sopenharmony_ci	if (ret)
279162306a36Sopenharmony_ci		return ret;
279262306a36Sopenharmony_ci
279362306a36Sopenharmony_ci	ret = usb4_dp_port_set_cm_ack(port);
279462306a36Sopenharmony_ci	if (ret)
279562306a36Sopenharmony_ci		return ret;
279662306a36Sopenharmony_ci
279762306a36Sopenharmony_ci	return usb4_dp_port_wait_and_clear_cm_ack(port, 500);
279862306a36Sopenharmony_ci}
279962306a36Sopenharmony_ci
280062306a36Sopenharmony_ci/**
280162306a36Sopenharmony_ci * usb4_dp_port_requested_bandwidth() - Read requested bandwidth
280262306a36Sopenharmony_ci * @port: DP IN adapter
280362306a36Sopenharmony_ci *
280462306a36Sopenharmony_ci * Reads the DPCD (graphics driver) requested bandwidth and returns it
280562306a36Sopenharmony_ci * in Mb/s. Takes the programmed granularity into account. In case of
280662306a36Sopenharmony_ci * error returns negative errno. Specifically returns %-EOPNOTSUPP if
280762306a36Sopenharmony_ci * the adapter does not support bandwidth allocation mode, and %ENODATA
280862306a36Sopenharmony_ci * if there is no active bandwidth request from the graphics driver.
280962306a36Sopenharmony_ci */
281062306a36Sopenharmony_ciint usb4_dp_port_requested_bandwidth(struct tb_port *port)
281162306a36Sopenharmony_ci{
281262306a36Sopenharmony_ci	u32 val, granularity;
281362306a36Sopenharmony_ci	int ret;
281462306a36Sopenharmony_ci
281562306a36Sopenharmony_ci	if (!is_usb4_dpin(port))
281662306a36Sopenharmony_ci		return -EOPNOTSUPP;
281762306a36Sopenharmony_ci
281862306a36Sopenharmony_ci	ret = usb4_dp_port_granularity(port);
281962306a36Sopenharmony_ci	if (ret < 0)
282062306a36Sopenharmony_ci		return ret;
282162306a36Sopenharmony_ci	granularity = ret;
282262306a36Sopenharmony_ci
282362306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
282462306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_8, 1);
282562306a36Sopenharmony_ci	if (ret)
282662306a36Sopenharmony_ci		return ret;
282762306a36Sopenharmony_ci
282862306a36Sopenharmony_ci	if (!(val & ADP_DP_CS_8_DR))
282962306a36Sopenharmony_ci		return -ENODATA;
283062306a36Sopenharmony_ci
283162306a36Sopenharmony_ci	return (val & ADP_DP_CS_8_REQUESTED_BW_MASK) * granularity;
283262306a36Sopenharmony_ci}
283362306a36Sopenharmony_ci
283462306a36Sopenharmony_ci/**
283562306a36Sopenharmony_ci * usb4_pci_port_set_ext_encapsulation() - Enable/disable extended encapsulation
283662306a36Sopenharmony_ci * @port: PCIe adapter
283762306a36Sopenharmony_ci * @enable: Enable/disable extended encapsulation
283862306a36Sopenharmony_ci *
283962306a36Sopenharmony_ci * Enables or disables extended encapsulation used in PCIe tunneling. Caller
284062306a36Sopenharmony_ci * needs to make sure both adapters support this before enabling. Returns %0 on
284162306a36Sopenharmony_ci * success and negative errno otherwise.
284262306a36Sopenharmony_ci */
284362306a36Sopenharmony_ciint usb4_pci_port_set_ext_encapsulation(struct tb_port *port, bool enable)
284462306a36Sopenharmony_ci{
284562306a36Sopenharmony_ci	u32 val;
284662306a36Sopenharmony_ci	int ret;
284762306a36Sopenharmony_ci
284862306a36Sopenharmony_ci	if (!tb_port_is_pcie_up(port) && !tb_port_is_pcie_down(port))
284962306a36Sopenharmony_ci		return -EINVAL;
285062306a36Sopenharmony_ci
285162306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
285262306a36Sopenharmony_ci			   port->cap_adap + ADP_PCIE_CS_1, 1);
285362306a36Sopenharmony_ci	if (ret)
285462306a36Sopenharmony_ci		return ret;
285562306a36Sopenharmony_ci
285662306a36Sopenharmony_ci	if (enable)
285762306a36Sopenharmony_ci		val |= ADP_PCIE_CS_1_EE;
285862306a36Sopenharmony_ci	else
285962306a36Sopenharmony_ci		val &= ~ADP_PCIE_CS_1_EE;
286062306a36Sopenharmony_ci
286162306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
286262306a36Sopenharmony_ci			     port->cap_adap + ADP_PCIE_CS_1, 1);
286362306a36Sopenharmony_ci}
2864