162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Thunderbolt driver - switch/port utility functions
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
662306a36Sopenharmony_ci * Copyright (C) 2018, Intel Corporation
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/idr.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/nvmem-provider.h>
1362306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1462306a36Sopenharmony_ci#include <linux/sched/signal.h>
1562306a36Sopenharmony_ci#include <linux/sizes.h>
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <linux/string_helpers.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "tb.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* Switch NVM support */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct nvm_auth_status {
2462306a36Sopenharmony_ci	struct list_head list;
2562306a36Sopenharmony_ci	uuid_t uuid;
2662306a36Sopenharmony_ci	u32 status;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * Hold NVM authentication failure status per switch This information
3162306a36Sopenharmony_ci * needs to stay around even when the switch gets power cycled so we
3262306a36Sopenharmony_ci * keep it separately.
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_cistatic LIST_HEAD(nvm_auth_status_cache);
3562306a36Sopenharmony_cistatic DEFINE_MUTEX(nvm_auth_status_lock);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic struct nvm_auth_status *__nvm_get_auth_status(const struct tb_switch *sw)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct nvm_auth_status *st;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	list_for_each_entry(st, &nvm_auth_status_cache, list) {
4262306a36Sopenharmony_ci		if (uuid_equal(&st->uuid, sw->uuid))
4362306a36Sopenharmony_ci			return st;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return NULL;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic void nvm_get_auth_status(const struct tb_switch *sw, u32 *status)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct nvm_auth_status *st;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	mutex_lock(&nvm_auth_status_lock);
5462306a36Sopenharmony_ci	st = __nvm_get_auth_status(sw);
5562306a36Sopenharmony_ci	mutex_unlock(&nvm_auth_status_lock);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	*status = st ? st->status : 0;
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void nvm_set_auth_status(const struct tb_switch *sw, u32 status)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct nvm_auth_status *st;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (WARN_ON(!sw->uuid))
6562306a36Sopenharmony_ci		return;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	mutex_lock(&nvm_auth_status_lock);
6862306a36Sopenharmony_ci	st = __nvm_get_auth_status(sw);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (!st) {
7162306a36Sopenharmony_ci		st = kzalloc(sizeof(*st), GFP_KERNEL);
7262306a36Sopenharmony_ci		if (!st)
7362306a36Sopenharmony_ci			goto unlock;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		memcpy(&st->uuid, sw->uuid, sizeof(st->uuid));
7662306a36Sopenharmony_ci		INIT_LIST_HEAD(&st->list);
7762306a36Sopenharmony_ci		list_add_tail(&st->list, &nvm_auth_status_cache);
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	st->status = status;
8162306a36Sopenharmony_ciunlock:
8262306a36Sopenharmony_ci	mutex_unlock(&nvm_auth_status_lock);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic void nvm_clear_auth_status(const struct tb_switch *sw)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct nvm_auth_status *st;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	mutex_lock(&nvm_auth_status_lock);
9062306a36Sopenharmony_ci	st = __nvm_get_auth_status(sw);
9162306a36Sopenharmony_ci	if (st) {
9262306a36Sopenharmony_ci		list_del(&st->list);
9362306a36Sopenharmony_ci		kfree(st);
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci	mutex_unlock(&nvm_auth_status_lock);
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int nvm_validate_and_write(struct tb_switch *sw)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	unsigned int image_size;
10162306a36Sopenharmony_ci	const u8 *buf;
10262306a36Sopenharmony_ci	int ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ret = tb_nvm_validate(sw->nvm);
10562306a36Sopenharmony_ci	if (ret)
10662306a36Sopenharmony_ci		return ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	ret = tb_nvm_write_headers(sw->nvm);
10962306a36Sopenharmony_ci	if (ret)
11062306a36Sopenharmony_ci		return ret;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	buf = sw->nvm->buf_data_start;
11362306a36Sopenharmony_ci	image_size = sw->nvm->buf_data_size;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
11662306a36Sopenharmony_ci		ret = usb4_switch_nvm_write(sw, 0, buf, image_size);
11762306a36Sopenharmony_ci	else
11862306a36Sopenharmony_ci		ret = dma_port_flash_write(sw->dma_port, 0, buf, image_size);
11962306a36Sopenharmony_ci	if (ret)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	sw->nvm->flushed = true;
12362306a36Sopenharmony_ci	return 0;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int nvm_authenticate_host_dma_port(struct tb_switch *sw)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	int ret = 0;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/*
13162306a36Sopenharmony_ci	 * Root switch NVM upgrade requires that we disconnect the
13262306a36Sopenharmony_ci	 * existing paths first (in case it is not in safe mode
13362306a36Sopenharmony_ci	 * already).
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	if (!sw->safe_mode) {
13662306a36Sopenharmony_ci		u32 status;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		ret = tb_domain_disconnect_all_paths(sw->tb);
13962306a36Sopenharmony_ci		if (ret)
14062306a36Sopenharmony_ci			return ret;
14162306a36Sopenharmony_ci		/*
14262306a36Sopenharmony_ci		 * The host controller goes away pretty soon after this if
14362306a36Sopenharmony_ci		 * everything goes well so getting timeout is expected.
14462306a36Sopenharmony_ci		 */
14562306a36Sopenharmony_ci		ret = dma_port_flash_update_auth(sw->dma_port);
14662306a36Sopenharmony_ci		if (!ret || ret == -ETIMEDOUT)
14762306a36Sopenharmony_ci			return 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci		/*
15062306a36Sopenharmony_ci		 * Any error from update auth operation requires power
15162306a36Sopenharmony_ci		 * cycling of the host router.
15262306a36Sopenharmony_ci		 */
15362306a36Sopenharmony_ci		tb_sw_warn(sw, "failed to authenticate NVM, power cycling\n");
15462306a36Sopenharmony_ci		if (dma_port_flash_update_auth_status(sw->dma_port, &status) > 0)
15562306a36Sopenharmony_ci			nvm_set_auth_status(sw, status);
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	/*
15962306a36Sopenharmony_ci	 * From safe mode we can get out by just power cycling the
16062306a36Sopenharmony_ci	 * switch.
16162306a36Sopenharmony_ci	 */
16262306a36Sopenharmony_ci	dma_port_power_cycle(sw->dma_port);
16362306a36Sopenharmony_ci	return ret;
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_cistatic int nvm_authenticate_device_dma_port(struct tb_switch *sw)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	int ret, retries = 10;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	ret = dma_port_flash_update_auth(sw->dma_port);
17162306a36Sopenharmony_ci	switch (ret) {
17262306a36Sopenharmony_ci	case 0:
17362306a36Sopenharmony_ci	case -ETIMEDOUT:
17462306a36Sopenharmony_ci	case -EACCES:
17562306a36Sopenharmony_ci	case -EINVAL:
17662306a36Sopenharmony_ci		/* Power cycle is required */
17762306a36Sopenharmony_ci		break;
17862306a36Sopenharmony_ci	default:
17962306a36Sopenharmony_ci		return ret;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/*
18362306a36Sopenharmony_ci	 * Poll here for the authentication status. It takes some time
18462306a36Sopenharmony_ci	 * for the device to respond (we get timeout for a while). Once
18562306a36Sopenharmony_ci	 * we get response the device needs to be power cycled in order
18662306a36Sopenharmony_ci	 * to the new NVM to be taken into use.
18762306a36Sopenharmony_ci	 */
18862306a36Sopenharmony_ci	do {
18962306a36Sopenharmony_ci		u32 status;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		ret = dma_port_flash_update_auth_status(sw->dma_port, &status);
19262306a36Sopenharmony_ci		if (ret < 0 && ret != -ETIMEDOUT)
19362306a36Sopenharmony_ci			return ret;
19462306a36Sopenharmony_ci		if (ret > 0) {
19562306a36Sopenharmony_ci			if (status) {
19662306a36Sopenharmony_ci				tb_sw_warn(sw, "failed to authenticate NVM\n");
19762306a36Sopenharmony_ci				nvm_set_auth_status(sw, status);
19862306a36Sopenharmony_ci			}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci			tb_sw_info(sw, "power cycling the switch now\n");
20162306a36Sopenharmony_ci			dma_port_power_cycle(sw->dma_port);
20262306a36Sopenharmony_ci			return 0;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		msleep(500);
20662306a36Sopenharmony_ci	} while (--retries);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return -ETIMEDOUT;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic void nvm_authenticate_start_dma_port(struct tb_switch *sw)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct pci_dev *root_port;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/*
21662306a36Sopenharmony_ci	 * During host router NVM upgrade we should not allow root port to
21762306a36Sopenharmony_ci	 * go into D3cold because some root ports cannot trigger PME
21862306a36Sopenharmony_ci	 * itself. To be on the safe side keep the root port in D0 during
21962306a36Sopenharmony_ci	 * the whole upgrade process.
22062306a36Sopenharmony_ci	 */
22162306a36Sopenharmony_ci	root_port = pcie_find_root_port(sw->tb->nhi->pdev);
22262306a36Sopenharmony_ci	if (root_port)
22362306a36Sopenharmony_ci		pm_runtime_get_noresume(&root_port->dev);
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic void nvm_authenticate_complete_dma_port(struct tb_switch *sw)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	struct pci_dev *root_port;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	root_port = pcie_find_root_port(sw->tb->nhi->pdev);
23162306a36Sopenharmony_ci	if (root_port)
23262306a36Sopenharmony_ci		pm_runtime_put(&root_port->dev);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic inline bool nvm_readable(struct tb_switch *sw)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
23862306a36Sopenharmony_ci		/*
23962306a36Sopenharmony_ci		 * USB4 devices must support NVM operations but it is
24062306a36Sopenharmony_ci		 * optional for hosts. Therefore we query the NVM sector
24162306a36Sopenharmony_ci		 * size here and if it is supported assume NVM
24262306a36Sopenharmony_ci		 * operations are implemented.
24362306a36Sopenharmony_ci		 */
24462306a36Sopenharmony_ci		return usb4_switch_nvm_sector_size(sw) > 0;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	/* Thunderbolt 2 and 3 devices support NVM through DMA port */
24862306a36Sopenharmony_ci	return !!sw->dma_port;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic inline bool nvm_upgradeable(struct tb_switch *sw)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	if (sw->no_nvm_upgrade)
25462306a36Sopenharmony_ci		return false;
25562306a36Sopenharmony_ci	return nvm_readable(sw);
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic int nvm_authenticate(struct tb_switch *sw, bool auth_only)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	int ret;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
26362306a36Sopenharmony_ci		if (auth_only) {
26462306a36Sopenharmony_ci			ret = usb4_switch_nvm_set_offset(sw, 0);
26562306a36Sopenharmony_ci			if (ret)
26662306a36Sopenharmony_ci				return ret;
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci		sw->nvm->authenticating = true;
26962306a36Sopenharmony_ci		return usb4_switch_nvm_authenticate(sw);
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci	if (auth_only)
27262306a36Sopenharmony_ci		return -EOPNOTSUPP;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	sw->nvm->authenticating = true;
27562306a36Sopenharmony_ci	if (!tb_route(sw)) {
27662306a36Sopenharmony_ci		nvm_authenticate_start_dma_port(sw);
27762306a36Sopenharmony_ci		ret = nvm_authenticate_host_dma_port(sw);
27862306a36Sopenharmony_ci	} else {
27962306a36Sopenharmony_ci		ret = nvm_authenticate_device_dma_port(sw);
28062306a36Sopenharmony_ci	}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	return ret;
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci/**
28662306a36Sopenharmony_ci * tb_switch_nvm_read() - Read router NVM
28762306a36Sopenharmony_ci * @sw: Router whose NVM to read
28862306a36Sopenharmony_ci * @address: Start address on the NVM
28962306a36Sopenharmony_ci * @buf: Buffer where the read data is copied
29062306a36Sopenharmony_ci * @size: Size of the buffer in bytes
29162306a36Sopenharmony_ci *
29262306a36Sopenharmony_ci * Reads from router NVM and returns the requested data in @buf. Locking
29362306a36Sopenharmony_ci * is up to the caller. Returns %0 in success and negative errno in case
29462306a36Sopenharmony_ci * of failure.
29562306a36Sopenharmony_ci */
29662306a36Sopenharmony_ciint tb_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
29762306a36Sopenharmony_ci		       size_t size)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
30062306a36Sopenharmony_ci		return usb4_switch_nvm_read(sw, address, buf, size);
30162306a36Sopenharmony_ci	return dma_port_flash_read(sw->dma_port, address, buf, size);
30262306a36Sopenharmony_ci}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic int nvm_read(void *priv, unsigned int offset, void *val, size_t bytes)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	struct tb_nvm *nvm = priv;
30762306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
30862306a36Sopenharmony_ci	int ret;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock)) {
31362306a36Sopenharmony_ci		ret = restart_syscall();
31462306a36Sopenharmony_ci		goto out;
31562306a36Sopenharmony_ci	}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	ret = tb_switch_nvm_read(sw, offset, val, bytes);
31862306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ciout:
32162306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
32262306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return ret;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic int nvm_write(void *priv, unsigned int offset, void *val, size_t bytes)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	struct tb_nvm *nvm = priv;
33062306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(nvm->dev);
33162306a36Sopenharmony_ci	int ret;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock))
33462306a36Sopenharmony_ci		return restart_syscall();
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	/*
33762306a36Sopenharmony_ci	 * Since writing the NVM image might require some special steps,
33862306a36Sopenharmony_ci	 * for example when CSS headers are written, we cache the image
33962306a36Sopenharmony_ci	 * locally here and handle the special cases when the user asks
34062306a36Sopenharmony_ci	 * us to authenticate the image.
34162306a36Sopenharmony_ci	 */
34262306a36Sopenharmony_ci	ret = tb_nvm_write_buf(nvm, offset, val, bytes);
34362306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	return ret;
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_cistatic int tb_switch_nvm_add(struct tb_switch *sw)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	struct tb_nvm *nvm;
35162306a36Sopenharmony_ci	int ret;
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	if (!nvm_readable(sw))
35462306a36Sopenharmony_ci		return 0;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	nvm = tb_nvm_alloc(&sw->dev);
35762306a36Sopenharmony_ci	if (IS_ERR(nvm)) {
35862306a36Sopenharmony_ci		ret = PTR_ERR(nvm) == -EOPNOTSUPP ? 0 : PTR_ERR(nvm);
35962306a36Sopenharmony_ci		goto err_nvm;
36062306a36Sopenharmony_ci	}
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	ret = tb_nvm_read_version(nvm);
36362306a36Sopenharmony_ci	if (ret)
36462306a36Sopenharmony_ci		goto err_nvm;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	/*
36762306a36Sopenharmony_ci	 * If the switch is in safe-mode the only accessible portion of
36862306a36Sopenharmony_ci	 * the NVM is the non-active one where userspace is expected to
36962306a36Sopenharmony_ci	 * write new functional NVM.
37062306a36Sopenharmony_ci	 */
37162306a36Sopenharmony_ci	if (!sw->safe_mode) {
37262306a36Sopenharmony_ci		ret = tb_nvm_add_active(nvm, nvm_read);
37362306a36Sopenharmony_ci		if (ret)
37462306a36Sopenharmony_ci			goto err_nvm;
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	if (!sw->no_nvm_upgrade) {
37862306a36Sopenharmony_ci		ret = tb_nvm_add_non_active(nvm, nvm_write);
37962306a36Sopenharmony_ci		if (ret)
38062306a36Sopenharmony_ci			goto err_nvm;
38162306a36Sopenharmony_ci	}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	sw->nvm = nvm;
38462306a36Sopenharmony_ci	return 0;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cierr_nvm:
38762306a36Sopenharmony_ci	tb_sw_dbg(sw, "NVM upgrade disabled\n");
38862306a36Sopenharmony_ci	sw->no_nvm_upgrade = true;
38962306a36Sopenharmony_ci	if (!IS_ERR(nvm))
39062306a36Sopenharmony_ci		tb_nvm_free(nvm);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	return ret;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic void tb_switch_nvm_remove(struct tb_switch *sw)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	struct tb_nvm *nvm;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	nvm = sw->nvm;
40062306a36Sopenharmony_ci	sw->nvm = NULL;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	if (!nvm)
40362306a36Sopenharmony_ci		return;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	/* Remove authentication status in case the switch is unplugged */
40662306a36Sopenharmony_ci	if (!nvm->authenticating)
40762306a36Sopenharmony_ci		nvm_clear_auth_status(sw);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	tb_nvm_free(nvm);
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci/* port utility functions */
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_cistatic const char *tb_port_type(const struct tb_regs_port_header *port)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	switch (port->type >> 16) {
41762306a36Sopenharmony_ci	case 0:
41862306a36Sopenharmony_ci		switch ((u8) port->type) {
41962306a36Sopenharmony_ci		case 0:
42062306a36Sopenharmony_ci			return "Inactive";
42162306a36Sopenharmony_ci		case 1:
42262306a36Sopenharmony_ci			return "Port";
42362306a36Sopenharmony_ci		case 2:
42462306a36Sopenharmony_ci			return "NHI";
42562306a36Sopenharmony_ci		default:
42662306a36Sopenharmony_ci			return "unknown";
42762306a36Sopenharmony_ci		}
42862306a36Sopenharmony_ci	case 0x2:
42962306a36Sopenharmony_ci		return "Ethernet";
43062306a36Sopenharmony_ci	case 0x8:
43162306a36Sopenharmony_ci		return "SATA";
43262306a36Sopenharmony_ci	case 0xe:
43362306a36Sopenharmony_ci		return "DP/HDMI";
43462306a36Sopenharmony_ci	case 0x10:
43562306a36Sopenharmony_ci		return "PCIe";
43662306a36Sopenharmony_ci	case 0x20:
43762306a36Sopenharmony_ci		return "USB";
43862306a36Sopenharmony_ci	default:
43962306a36Sopenharmony_ci		return "unknown";
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci}
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_cistatic void tb_dump_port(struct tb *tb, const struct tb_port *port)
44462306a36Sopenharmony_ci{
44562306a36Sopenharmony_ci	const struct tb_regs_port_header *regs = &port->config;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	tb_dbg(tb,
44862306a36Sopenharmony_ci	       " Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n",
44962306a36Sopenharmony_ci	       regs->port_number, regs->vendor_id, regs->device_id,
45062306a36Sopenharmony_ci	       regs->revision, regs->thunderbolt_version, tb_port_type(regs),
45162306a36Sopenharmony_ci	       regs->type);
45262306a36Sopenharmony_ci	tb_dbg(tb, "  Max hop id (in/out): %d/%d\n",
45362306a36Sopenharmony_ci	       regs->max_in_hop_id, regs->max_out_hop_id);
45462306a36Sopenharmony_ci	tb_dbg(tb, "  Max counters: %d\n", regs->max_counters);
45562306a36Sopenharmony_ci	tb_dbg(tb, "  NFC Credits: %#x\n", regs->nfc_credits);
45662306a36Sopenharmony_ci	tb_dbg(tb, "  Credits (total/control): %u/%u\n", port->total_credits,
45762306a36Sopenharmony_ci	       port->ctl_credits);
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci/**
46162306a36Sopenharmony_ci * tb_port_state() - get connectedness state of a port
46262306a36Sopenharmony_ci * @port: the port to check
46362306a36Sopenharmony_ci *
46462306a36Sopenharmony_ci * The port must have a TB_CAP_PHY (i.e. it should be a real port).
46562306a36Sopenharmony_ci *
46662306a36Sopenharmony_ci * Return: Returns an enum tb_port_state on success or an error code on failure.
46762306a36Sopenharmony_ci */
46862306a36Sopenharmony_ciint tb_port_state(struct tb_port *port)
46962306a36Sopenharmony_ci{
47062306a36Sopenharmony_ci	struct tb_cap_phy phy;
47162306a36Sopenharmony_ci	int res;
47262306a36Sopenharmony_ci	if (port->cap_phy == 0) {
47362306a36Sopenharmony_ci		tb_port_WARN(port, "does not have a PHY\n");
47462306a36Sopenharmony_ci		return -EINVAL;
47562306a36Sopenharmony_ci	}
47662306a36Sopenharmony_ci	res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2);
47762306a36Sopenharmony_ci	if (res)
47862306a36Sopenharmony_ci		return res;
47962306a36Sopenharmony_ci	return phy.state;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci/**
48362306a36Sopenharmony_ci * tb_wait_for_port() - wait for a port to become ready
48462306a36Sopenharmony_ci * @port: Port to wait
48562306a36Sopenharmony_ci * @wait_if_unplugged: Wait also when port is unplugged
48662306a36Sopenharmony_ci *
48762306a36Sopenharmony_ci * Wait up to 1 second for a port to reach state TB_PORT_UP. If
48862306a36Sopenharmony_ci * wait_if_unplugged is set then we also wait if the port is in state
48962306a36Sopenharmony_ci * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after
49062306a36Sopenharmony_ci * switch resume). Otherwise we only wait if a device is registered but the link
49162306a36Sopenharmony_ci * has not yet been established.
49262306a36Sopenharmony_ci *
49362306a36Sopenharmony_ci * Return: Returns an error code on failure. Returns 0 if the port is not
49462306a36Sopenharmony_ci * connected or failed to reach state TB_PORT_UP within one second. Returns 1
49562306a36Sopenharmony_ci * if the port is connected and in state TB_PORT_UP.
49662306a36Sopenharmony_ci */
49762306a36Sopenharmony_ciint tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged)
49862306a36Sopenharmony_ci{
49962306a36Sopenharmony_ci	int retries = 10;
50062306a36Sopenharmony_ci	int state;
50162306a36Sopenharmony_ci	if (!port->cap_phy) {
50262306a36Sopenharmony_ci		tb_port_WARN(port, "does not have PHY\n");
50362306a36Sopenharmony_ci		return -EINVAL;
50462306a36Sopenharmony_ci	}
50562306a36Sopenharmony_ci	if (tb_is_upstream_port(port)) {
50662306a36Sopenharmony_ci		tb_port_WARN(port, "is the upstream port\n");
50762306a36Sopenharmony_ci		return -EINVAL;
50862306a36Sopenharmony_ci	}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	while (retries--) {
51162306a36Sopenharmony_ci		state = tb_port_state(port);
51262306a36Sopenharmony_ci		switch (state) {
51362306a36Sopenharmony_ci		case TB_PORT_DISABLED:
51462306a36Sopenharmony_ci			tb_port_dbg(port, "is disabled (state: 0)\n");
51562306a36Sopenharmony_ci			return 0;
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		case TB_PORT_UNPLUGGED:
51862306a36Sopenharmony_ci			if (wait_if_unplugged) {
51962306a36Sopenharmony_ci				/* used during resume */
52062306a36Sopenharmony_ci				tb_port_dbg(port,
52162306a36Sopenharmony_ci					    "is unplugged (state: 7), retrying...\n");
52262306a36Sopenharmony_ci				msleep(100);
52362306a36Sopenharmony_ci				break;
52462306a36Sopenharmony_ci			}
52562306a36Sopenharmony_ci			tb_port_dbg(port, "is unplugged (state: 7)\n");
52662306a36Sopenharmony_ci			return 0;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci		case TB_PORT_UP:
52962306a36Sopenharmony_ci		case TB_PORT_TX_CL0S:
53062306a36Sopenharmony_ci		case TB_PORT_RX_CL0S:
53162306a36Sopenharmony_ci		case TB_PORT_CL1:
53262306a36Sopenharmony_ci		case TB_PORT_CL2:
53362306a36Sopenharmony_ci			tb_port_dbg(port, "is connected, link is up (state: %d)\n", state);
53462306a36Sopenharmony_ci			return 1;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci		default:
53762306a36Sopenharmony_ci			if (state < 0)
53862306a36Sopenharmony_ci				return state;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci			/*
54162306a36Sopenharmony_ci			 * After plug-in the state is TB_PORT_CONNECTING. Give it some
54262306a36Sopenharmony_ci			 * time.
54362306a36Sopenharmony_ci			 */
54462306a36Sopenharmony_ci			tb_port_dbg(port,
54562306a36Sopenharmony_ci				    "is connected, link is not up (state: %d), retrying...\n",
54662306a36Sopenharmony_ci				    state);
54762306a36Sopenharmony_ci			msleep(100);
54862306a36Sopenharmony_ci		}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	}
55162306a36Sopenharmony_ci	tb_port_warn(port,
55262306a36Sopenharmony_ci		     "failed to reach state TB_PORT_UP. Ignoring port...\n");
55362306a36Sopenharmony_ci	return 0;
55462306a36Sopenharmony_ci}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci/**
55762306a36Sopenharmony_ci * tb_port_add_nfc_credits() - add/remove non flow controlled credits to port
55862306a36Sopenharmony_ci * @port: Port to add/remove NFC credits
55962306a36Sopenharmony_ci * @credits: Credits to add/remove
56062306a36Sopenharmony_ci *
56162306a36Sopenharmony_ci * Change the number of NFC credits allocated to @port by @credits. To remove
56262306a36Sopenharmony_ci * NFC credits pass a negative amount of credits.
56362306a36Sopenharmony_ci *
56462306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure.
56562306a36Sopenharmony_ci */
56662306a36Sopenharmony_ciint tb_port_add_nfc_credits(struct tb_port *port, int credits)
56762306a36Sopenharmony_ci{
56862306a36Sopenharmony_ci	u32 nfc_credits;
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	if (credits == 0 || port->sw->is_unplugged)
57162306a36Sopenharmony_ci		return 0;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	/*
57462306a36Sopenharmony_ci	 * USB4 restricts programming NFC buffers to lane adapters only
57562306a36Sopenharmony_ci	 * so skip other ports.
57662306a36Sopenharmony_ci	 */
57762306a36Sopenharmony_ci	if (tb_switch_is_usb4(port->sw) && !tb_port_is_null(port))
57862306a36Sopenharmony_ci		return 0;
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	nfc_credits = port->config.nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK;
58162306a36Sopenharmony_ci	if (credits < 0)
58262306a36Sopenharmony_ci		credits = max_t(int, -nfc_credits, credits);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	nfc_credits += credits;
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	tb_port_dbg(port, "adding %d NFC credits to %lu", credits,
58762306a36Sopenharmony_ci		    port->config.nfc_credits & ADP_CS_4_NFC_BUFFERS_MASK);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	port->config.nfc_credits &= ~ADP_CS_4_NFC_BUFFERS_MASK;
59062306a36Sopenharmony_ci	port->config.nfc_credits |= nfc_credits;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	return tb_port_write(port, &port->config.nfc_credits,
59362306a36Sopenharmony_ci			     TB_CFG_PORT, ADP_CS_4, 1);
59462306a36Sopenharmony_ci}
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci/**
59762306a36Sopenharmony_ci * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER
59862306a36Sopenharmony_ci * @port: Port whose counters to clear
59962306a36Sopenharmony_ci * @counter: Counter index to clear
60062306a36Sopenharmony_ci *
60162306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure.
60262306a36Sopenharmony_ci */
60362306a36Sopenharmony_ciint tb_port_clear_counter(struct tb_port *port, int counter)
60462306a36Sopenharmony_ci{
60562306a36Sopenharmony_ci	u32 zero[3] = { 0, 0, 0 };
60662306a36Sopenharmony_ci	tb_port_dbg(port, "clearing counter %d\n", counter);
60762306a36Sopenharmony_ci	return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3);
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci/**
61162306a36Sopenharmony_ci * tb_port_unlock() - Unlock downstream port
61262306a36Sopenharmony_ci * @port: Port to unlock
61362306a36Sopenharmony_ci *
61462306a36Sopenharmony_ci * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the
61562306a36Sopenharmony_ci * downstream router accessible for CM.
61662306a36Sopenharmony_ci */
61762306a36Sopenharmony_ciint tb_port_unlock(struct tb_port *port)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	if (tb_switch_is_icm(port->sw))
62062306a36Sopenharmony_ci		return 0;
62162306a36Sopenharmony_ci	if (!tb_port_is_null(port))
62262306a36Sopenharmony_ci		return -EINVAL;
62362306a36Sopenharmony_ci	if (tb_switch_is_usb4(port->sw))
62462306a36Sopenharmony_ci		return usb4_port_unlock(port);
62562306a36Sopenharmony_ci	return 0;
62662306a36Sopenharmony_ci}
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_cistatic int __tb_port_enable(struct tb_port *port, bool enable)
62962306a36Sopenharmony_ci{
63062306a36Sopenharmony_ci	int ret;
63162306a36Sopenharmony_ci	u32 phy;
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	if (!tb_port_is_null(port))
63462306a36Sopenharmony_ci		return -EINVAL;
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	ret = tb_port_read(port, &phy, TB_CFG_PORT,
63762306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
63862306a36Sopenharmony_ci	if (ret)
63962306a36Sopenharmony_ci		return ret;
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	if (enable)
64262306a36Sopenharmony_ci		phy &= ~LANE_ADP_CS_1_LD;
64362306a36Sopenharmony_ci	else
64462306a36Sopenharmony_ci		phy |= LANE_ADP_CS_1_LD;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	ret = tb_port_write(port, &phy, TB_CFG_PORT,
64862306a36Sopenharmony_ci			    port->cap_phy + LANE_ADP_CS_1, 1);
64962306a36Sopenharmony_ci	if (ret)
65062306a36Sopenharmony_ci		return ret;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	tb_port_dbg(port, "lane %s\n", str_enabled_disabled(enable));
65362306a36Sopenharmony_ci	return 0;
65462306a36Sopenharmony_ci}
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci/**
65762306a36Sopenharmony_ci * tb_port_enable() - Enable lane adapter
65862306a36Sopenharmony_ci * @port: Port to enable (can be %NULL)
65962306a36Sopenharmony_ci *
66062306a36Sopenharmony_ci * This is used for lane 0 and 1 adapters to enable it.
66162306a36Sopenharmony_ci */
66262306a36Sopenharmony_ciint tb_port_enable(struct tb_port *port)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	return __tb_port_enable(port, true);
66562306a36Sopenharmony_ci}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci/**
66862306a36Sopenharmony_ci * tb_port_disable() - Disable lane adapter
66962306a36Sopenharmony_ci * @port: Port to disable (can be %NULL)
67062306a36Sopenharmony_ci *
67162306a36Sopenharmony_ci * This is used for lane 0 and 1 adapters to disable it.
67262306a36Sopenharmony_ci */
67362306a36Sopenharmony_ciint tb_port_disable(struct tb_port *port)
67462306a36Sopenharmony_ci{
67562306a36Sopenharmony_ci	return __tb_port_enable(port, false);
67662306a36Sopenharmony_ci}
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci/*
67962306a36Sopenharmony_ci * tb_init_port() - initialize a port
68062306a36Sopenharmony_ci *
68162306a36Sopenharmony_ci * This is a helper method for tb_switch_alloc. Does not check or initialize
68262306a36Sopenharmony_ci * any downstream switches.
68362306a36Sopenharmony_ci *
68462306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure.
68562306a36Sopenharmony_ci */
68662306a36Sopenharmony_cistatic int tb_init_port(struct tb_port *port)
68762306a36Sopenharmony_ci{
68862306a36Sopenharmony_ci	int res;
68962306a36Sopenharmony_ci	int cap;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	INIT_LIST_HEAD(&port->list);
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	/* Control adapter does not have configuration space */
69462306a36Sopenharmony_ci	if (!port->port)
69562306a36Sopenharmony_ci		return 0;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8);
69862306a36Sopenharmony_ci	if (res) {
69962306a36Sopenharmony_ci		if (res == -ENODEV) {
70062306a36Sopenharmony_ci			tb_dbg(port->sw->tb, " Port %d: not implemented\n",
70162306a36Sopenharmony_ci			       port->port);
70262306a36Sopenharmony_ci			port->disabled = true;
70362306a36Sopenharmony_ci			return 0;
70462306a36Sopenharmony_ci		}
70562306a36Sopenharmony_ci		return res;
70662306a36Sopenharmony_ci	}
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	/* Port 0 is the switch itself and has no PHY. */
70962306a36Sopenharmony_ci	if (port->config.type == TB_TYPE_PORT) {
71062306a36Sopenharmony_ci		cap = tb_port_find_cap(port, TB_PORT_CAP_PHY);
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci		if (cap > 0)
71362306a36Sopenharmony_ci			port->cap_phy = cap;
71462306a36Sopenharmony_ci		else
71562306a36Sopenharmony_ci			tb_port_WARN(port, "non switch port without a PHY\n");
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci		cap = tb_port_find_cap(port, TB_PORT_CAP_USB4);
71862306a36Sopenharmony_ci		if (cap > 0)
71962306a36Sopenharmony_ci			port->cap_usb4 = cap;
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_ci		/*
72262306a36Sopenharmony_ci		 * USB4 ports the buffers allocated for the control path
72362306a36Sopenharmony_ci		 * can be read from the path config space. Legacy
72462306a36Sopenharmony_ci		 * devices we use hard-coded value.
72562306a36Sopenharmony_ci		 */
72662306a36Sopenharmony_ci		if (port->cap_usb4) {
72762306a36Sopenharmony_ci			struct tb_regs_hop hop;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci			if (!tb_port_read(port, &hop, TB_CFG_HOPS, 0, 2))
73062306a36Sopenharmony_ci				port->ctl_credits = hop.initial_credits;
73162306a36Sopenharmony_ci		}
73262306a36Sopenharmony_ci		if (!port->ctl_credits)
73362306a36Sopenharmony_ci			port->ctl_credits = 2;
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci	} else {
73662306a36Sopenharmony_ci		cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP);
73762306a36Sopenharmony_ci		if (cap > 0)
73862306a36Sopenharmony_ci			port->cap_adap = cap;
73962306a36Sopenharmony_ci	}
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	port->total_credits =
74262306a36Sopenharmony_ci		(port->config.nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >>
74362306a36Sopenharmony_ci		ADP_CS_4_TOTAL_BUFFERS_SHIFT;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	tb_dump_port(port->sw->tb, port);
74662306a36Sopenharmony_ci	return 0;
74762306a36Sopenharmony_ci}
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_cistatic int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid,
75062306a36Sopenharmony_ci			       int max_hopid)
75162306a36Sopenharmony_ci{
75262306a36Sopenharmony_ci	int port_max_hopid;
75362306a36Sopenharmony_ci	struct ida *ida;
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	if (in) {
75662306a36Sopenharmony_ci		port_max_hopid = port->config.max_in_hop_id;
75762306a36Sopenharmony_ci		ida = &port->in_hopids;
75862306a36Sopenharmony_ci	} else {
75962306a36Sopenharmony_ci		port_max_hopid = port->config.max_out_hop_id;
76062306a36Sopenharmony_ci		ida = &port->out_hopids;
76162306a36Sopenharmony_ci	}
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	/*
76462306a36Sopenharmony_ci	 * NHI can use HopIDs 1-max for other adapters HopIDs 0-7 are
76562306a36Sopenharmony_ci	 * reserved.
76662306a36Sopenharmony_ci	 */
76762306a36Sopenharmony_ci	if (!tb_port_is_nhi(port) && min_hopid < TB_PATH_MIN_HOPID)
76862306a36Sopenharmony_ci		min_hopid = TB_PATH_MIN_HOPID;
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	if (max_hopid < 0 || max_hopid > port_max_hopid)
77162306a36Sopenharmony_ci		max_hopid = port_max_hopid;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci	return ida_simple_get(ida, min_hopid, max_hopid + 1, GFP_KERNEL);
77462306a36Sopenharmony_ci}
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_ci/**
77762306a36Sopenharmony_ci * tb_port_alloc_in_hopid() - Allocate input HopID from port
77862306a36Sopenharmony_ci * @port: Port to allocate HopID for
77962306a36Sopenharmony_ci * @min_hopid: Minimum acceptable input HopID
78062306a36Sopenharmony_ci * @max_hopid: Maximum acceptable input HopID
78162306a36Sopenharmony_ci *
78262306a36Sopenharmony_ci * Return: HopID between @min_hopid and @max_hopid or negative errno in
78362306a36Sopenharmony_ci * case of error.
78462306a36Sopenharmony_ci */
78562306a36Sopenharmony_ciint tb_port_alloc_in_hopid(struct tb_port *port, int min_hopid, int max_hopid)
78662306a36Sopenharmony_ci{
78762306a36Sopenharmony_ci	return tb_port_alloc_hopid(port, true, min_hopid, max_hopid);
78862306a36Sopenharmony_ci}
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci/**
79162306a36Sopenharmony_ci * tb_port_alloc_out_hopid() - Allocate output HopID from port
79262306a36Sopenharmony_ci * @port: Port to allocate HopID for
79362306a36Sopenharmony_ci * @min_hopid: Minimum acceptable output HopID
79462306a36Sopenharmony_ci * @max_hopid: Maximum acceptable output HopID
79562306a36Sopenharmony_ci *
79662306a36Sopenharmony_ci * Return: HopID between @min_hopid and @max_hopid or negative errno in
79762306a36Sopenharmony_ci * case of error.
79862306a36Sopenharmony_ci */
79962306a36Sopenharmony_ciint tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid)
80062306a36Sopenharmony_ci{
80162306a36Sopenharmony_ci	return tb_port_alloc_hopid(port, false, min_hopid, max_hopid);
80262306a36Sopenharmony_ci}
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci/**
80562306a36Sopenharmony_ci * tb_port_release_in_hopid() - Release allocated input HopID from port
80662306a36Sopenharmony_ci * @port: Port whose HopID to release
80762306a36Sopenharmony_ci * @hopid: HopID to release
80862306a36Sopenharmony_ci */
80962306a36Sopenharmony_civoid tb_port_release_in_hopid(struct tb_port *port, int hopid)
81062306a36Sopenharmony_ci{
81162306a36Sopenharmony_ci	ida_simple_remove(&port->in_hopids, hopid);
81262306a36Sopenharmony_ci}
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci/**
81562306a36Sopenharmony_ci * tb_port_release_out_hopid() - Release allocated output HopID from port
81662306a36Sopenharmony_ci * @port: Port whose HopID to release
81762306a36Sopenharmony_ci * @hopid: HopID to release
81862306a36Sopenharmony_ci */
81962306a36Sopenharmony_civoid tb_port_release_out_hopid(struct tb_port *port, int hopid)
82062306a36Sopenharmony_ci{
82162306a36Sopenharmony_ci	ida_simple_remove(&port->out_hopids, hopid);
82262306a36Sopenharmony_ci}
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_cistatic inline bool tb_switch_is_reachable(const struct tb_switch *parent,
82562306a36Sopenharmony_ci					  const struct tb_switch *sw)
82662306a36Sopenharmony_ci{
82762306a36Sopenharmony_ci	u64 mask = (1ULL << parent->config.depth * 8) - 1;
82862306a36Sopenharmony_ci	return (tb_route(parent) & mask) == (tb_route(sw) & mask);
82962306a36Sopenharmony_ci}
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci/**
83262306a36Sopenharmony_ci * tb_next_port_on_path() - Return next port for given port on a path
83362306a36Sopenharmony_ci * @start: Start port of the walk
83462306a36Sopenharmony_ci * @end: End port of the walk
83562306a36Sopenharmony_ci * @prev: Previous port (%NULL if this is the first)
83662306a36Sopenharmony_ci *
83762306a36Sopenharmony_ci * This function can be used to walk from one port to another if they
83862306a36Sopenharmony_ci * are connected through zero or more switches. If the @prev is dual
83962306a36Sopenharmony_ci * link port, the function follows that link and returns another end on
84062306a36Sopenharmony_ci * that same link.
84162306a36Sopenharmony_ci *
84262306a36Sopenharmony_ci * If the @end port has been reached, return %NULL.
84362306a36Sopenharmony_ci *
84462306a36Sopenharmony_ci * Domain tb->lock must be held when this function is called.
84562306a36Sopenharmony_ci */
84662306a36Sopenharmony_cistruct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
84762306a36Sopenharmony_ci				     struct tb_port *prev)
84862306a36Sopenharmony_ci{
84962306a36Sopenharmony_ci	struct tb_port *next;
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	if (!prev)
85262306a36Sopenharmony_ci		return start;
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	if (prev->sw == end->sw) {
85562306a36Sopenharmony_ci		if (prev == end)
85662306a36Sopenharmony_ci			return NULL;
85762306a36Sopenharmony_ci		return end;
85862306a36Sopenharmony_ci	}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_ci	if (tb_switch_is_reachable(prev->sw, end->sw)) {
86162306a36Sopenharmony_ci		next = tb_port_at(tb_route(end->sw), prev->sw);
86262306a36Sopenharmony_ci		/* Walk down the topology if next == prev */
86362306a36Sopenharmony_ci		if (prev->remote &&
86462306a36Sopenharmony_ci		    (next == prev || next->dual_link_port == prev))
86562306a36Sopenharmony_ci			next = prev->remote;
86662306a36Sopenharmony_ci	} else {
86762306a36Sopenharmony_ci		if (tb_is_upstream_port(prev)) {
86862306a36Sopenharmony_ci			next = prev->remote;
86962306a36Sopenharmony_ci		} else {
87062306a36Sopenharmony_ci			next = tb_upstream_port(prev->sw);
87162306a36Sopenharmony_ci			/*
87262306a36Sopenharmony_ci			 * Keep the same link if prev and next are both
87362306a36Sopenharmony_ci			 * dual link ports.
87462306a36Sopenharmony_ci			 */
87562306a36Sopenharmony_ci			if (next->dual_link_port &&
87662306a36Sopenharmony_ci			    next->link_nr != prev->link_nr) {
87762306a36Sopenharmony_ci				next = next->dual_link_port;
87862306a36Sopenharmony_ci			}
87962306a36Sopenharmony_ci		}
88062306a36Sopenharmony_ci	}
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	return next != prev ? next : NULL;
88362306a36Sopenharmony_ci}
88462306a36Sopenharmony_ci
88562306a36Sopenharmony_ci/**
88662306a36Sopenharmony_ci * tb_port_get_link_speed() - Get current link speed
88762306a36Sopenharmony_ci * @port: Port to check (USB4 or CIO)
88862306a36Sopenharmony_ci *
88962306a36Sopenharmony_ci * Returns link speed in Gb/s or negative errno in case of failure.
89062306a36Sopenharmony_ci */
89162306a36Sopenharmony_ciint tb_port_get_link_speed(struct tb_port *port)
89262306a36Sopenharmony_ci{
89362306a36Sopenharmony_ci	u32 val, speed;
89462306a36Sopenharmony_ci	int ret;
89562306a36Sopenharmony_ci
89662306a36Sopenharmony_ci	if (!port->cap_phy)
89762306a36Sopenharmony_ci		return -EINVAL;
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
90062306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
90162306a36Sopenharmony_ci	if (ret)
90262306a36Sopenharmony_ci		return ret;
90362306a36Sopenharmony_ci
90462306a36Sopenharmony_ci	speed = (val & LANE_ADP_CS_1_CURRENT_SPEED_MASK) >>
90562306a36Sopenharmony_ci		LANE_ADP_CS_1_CURRENT_SPEED_SHIFT;
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	switch (speed) {
90862306a36Sopenharmony_ci	case LANE_ADP_CS_1_CURRENT_SPEED_GEN4:
90962306a36Sopenharmony_ci		return 40;
91062306a36Sopenharmony_ci	case LANE_ADP_CS_1_CURRENT_SPEED_GEN3:
91162306a36Sopenharmony_ci		return 20;
91262306a36Sopenharmony_ci	default:
91362306a36Sopenharmony_ci		return 10;
91462306a36Sopenharmony_ci	}
91562306a36Sopenharmony_ci}
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci/**
91862306a36Sopenharmony_ci * tb_port_get_link_width() - Get current link width
91962306a36Sopenharmony_ci * @port: Port to check (USB4 or CIO)
92062306a36Sopenharmony_ci *
92162306a36Sopenharmony_ci * Returns link width. Return the link width as encoded in &enum
92262306a36Sopenharmony_ci * tb_link_width or negative errno in case of failure.
92362306a36Sopenharmony_ci */
92462306a36Sopenharmony_ciint tb_port_get_link_width(struct tb_port *port)
92562306a36Sopenharmony_ci{
92662306a36Sopenharmony_ci	u32 val;
92762306a36Sopenharmony_ci	int ret;
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	if (!port->cap_phy)
93062306a36Sopenharmony_ci		return -EINVAL;
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
93362306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
93462306a36Sopenharmony_ci	if (ret)
93562306a36Sopenharmony_ci		return ret;
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci	/* Matches the values in enum tb_link_width */
93862306a36Sopenharmony_ci	return (val & LANE_ADP_CS_1_CURRENT_WIDTH_MASK) >>
93962306a36Sopenharmony_ci		LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT;
94062306a36Sopenharmony_ci}
94162306a36Sopenharmony_ci
94262306a36Sopenharmony_cistatic bool tb_port_is_width_supported(struct tb_port *port,
94362306a36Sopenharmony_ci				       unsigned int width_mask)
94462306a36Sopenharmony_ci{
94562306a36Sopenharmony_ci	u32 phy, widths;
94662306a36Sopenharmony_ci	int ret;
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_ci	if (!port->cap_phy)
94962306a36Sopenharmony_ci		return false;
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	ret = tb_port_read(port, &phy, TB_CFG_PORT,
95262306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_0, 1);
95362306a36Sopenharmony_ci	if (ret)
95462306a36Sopenharmony_ci		return false;
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	widths = (phy & LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK) >>
95762306a36Sopenharmony_ci		LANE_ADP_CS_0_SUPPORTED_WIDTH_SHIFT;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	return widths & width_mask;
96062306a36Sopenharmony_ci}
96162306a36Sopenharmony_ci
96262306a36Sopenharmony_cistatic bool is_gen4_link(struct tb_port *port)
96362306a36Sopenharmony_ci{
96462306a36Sopenharmony_ci	return tb_port_get_link_speed(port) > 20;
96562306a36Sopenharmony_ci}
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci/**
96862306a36Sopenharmony_ci * tb_port_set_link_width() - Set target link width of the lane adapter
96962306a36Sopenharmony_ci * @port: Lane adapter
97062306a36Sopenharmony_ci * @width: Target link width
97162306a36Sopenharmony_ci *
97262306a36Sopenharmony_ci * Sets the target link width of the lane adapter to @width. Does not
97362306a36Sopenharmony_ci * enable/disable lane bonding. For that call tb_port_set_lane_bonding().
97462306a36Sopenharmony_ci *
97562306a36Sopenharmony_ci * Return: %0 in case of success and negative errno in case of error
97662306a36Sopenharmony_ci */
97762306a36Sopenharmony_ciint tb_port_set_link_width(struct tb_port *port, enum tb_link_width width)
97862306a36Sopenharmony_ci{
97962306a36Sopenharmony_ci	u32 val;
98062306a36Sopenharmony_ci	int ret;
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	if (!port->cap_phy)
98362306a36Sopenharmony_ci		return -EINVAL;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
98662306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
98762306a36Sopenharmony_ci	if (ret)
98862306a36Sopenharmony_ci		return ret;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	val &= ~LANE_ADP_CS_1_TARGET_WIDTH_MASK;
99162306a36Sopenharmony_ci	switch (width) {
99262306a36Sopenharmony_ci	case TB_LINK_WIDTH_SINGLE:
99362306a36Sopenharmony_ci		/* Gen 4 link cannot be single */
99462306a36Sopenharmony_ci		if (is_gen4_link(port))
99562306a36Sopenharmony_ci			return -EOPNOTSUPP;
99662306a36Sopenharmony_ci		val |= LANE_ADP_CS_1_TARGET_WIDTH_SINGLE <<
99762306a36Sopenharmony_ci			LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
99862306a36Sopenharmony_ci		break;
99962306a36Sopenharmony_ci	case TB_LINK_WIDTH_DUAL:
100062306a36Sopenharmony_ci		val |= LANE_ADP_CS_1_TARGET_WIDTH_DUAL <<
100162306a36Sopenharmony_ci			LANE_ADP_CS_1_TARGET_WIDTH_SHIFT;
100262306a36Sopenharmony_ci		break;
100362306a36Sopenharmony_ci	default:
100462306a36Sopenharmony_ci		return -EINVAL;
100562306a36Sopenharmony_ci	}
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
100862306a36Sopenharmony_ci			     port->cap_phy + LANE_ADP_CS_1, 1);
100962306a36Sopenharmony_ci}
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci/**
101262306a36Sopenharmony_ci * tb_port_set_lane_bonding() - Enable/disable lane bonding
101362306a36Sopenharmony_ci * @port: Lane adapter
101462306a36Sopenharmony_ci * @bonding: enable/disable bonding
101562306a36Sopenharmony_ci *
101662306a36Sopenharmony_ci * Enables or disables lane bonding. This should be called after target
101762306a36Sopenharmony_ci * link width has been set (tb_port_set_link_width()). Note in most
101862306a36Sopenharmony_ci * cases one should use tb_port_lane_bonding_enable() instead to enable
101962306a36Sopenharmony_ci * lane bonding.
102062306a36Sopenharmony_ci *
102162306a36Sopenharmony_ci * Return: %0 in case of success and negative errno in case of error
102262306a36Sopenharmony_ci */
102362306a36Sopenharmony_cistatic int tb_port_set_lane_bonding(struct tb_port *port, bool bonding)
102462306a36Sopenharmony_ci{
102562306a36Sopenharmony_ci	u32 val;
102662306a36Sopenharmony_ci	int ret;
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	if (!port->cap_phy)
102962306a36Sopenharmony_ci		return -EINVAL;
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
103262306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
103362306a36Sopenharmony_ci	if (ret)
103462306a36Sopenharmony_ci		return ret;
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_ci	if (bonding)
103762306a36Sopenharmony_ci		val |= LANE_ADP_CS_1_LB;
103862306a36Sopenharmony_ci	else
103962306a36Sopenharmony_ci		val &= ~LANE_ADP_CS_1_LB;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci	return tb_port_write(port, &val, TB_CFG_PORT,
104262306a36Sopenharmony_ci			     port->cap_phy + LANE_ADP_CS_1, 1);
104362306a36Sopenharmony_ci}
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_ci/**
104662306a36Sopenharmony_ci * tb_port_lane_bonding_enable() - Enable bonding on port
104762306a36Sopenharmony_ci * @port: port to enable
104862306a36Sopenharmony_ci *
104962306a36Sopenharmony_ci * Enable bonding by setting the link width of the port and the other
105062306a36Sopenharmony_ci * port in case of dual link port. Does not wait for the link to
105162306a36Sopenharmony_ci * actually reach the bonded state so caller needs to call
105262306a36Sopenharmony_ci * tb_port_wait_for_link_width() before enabling any paths through the
105362306a36Sopenharmony_ci * link to make sure the link is in expected state.
105462306a36Sopenharmony_ci *
105562306a36Sopenharmony_ci * Return: %0 in case of success and negative errno in case of error
105662306a36Sopenharmony_ci */
105762306a36Sopenharmony_ciint tb_port_lane_bonding_enable(struct tb_port *port)
105862306a36Sopenharmony_ci{
105962306a36Sopenharmony_ci	enum tb_link_width width;
106062306a36Sopenharmony_ci	int ret;
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	/*
106362306a36Sopenharmony_ci	 * Enable lane bonding for both links if not already enabled by
106462306a36Sopenharmony_ci	 * for example the boot firmware.
106562306a36Sopenharmony_ci	 */
106662306a36Sopenharmony_ci	width = tb_port_get_link_width(port);
106762306a36Sopenharmony_ci	if (width == TB_LINK_WIDTH_SINGLE) {
106862306a36Sopenharmony_ci		ret = tb_port_set_link_width(port, TB_LINK_WIDTH_DUAL);
106962306a36Sopenharmony_ci		if (ret)
107062306a36Sopenharmony_ci			goto err_lane0;
107162306a36Sopenharmony_ci	}
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	width = tb_port_get_link_width(port->dual_link_port);
107462306a36Sopenharmony_ci	if (width == TB_LINK_WIDTH_SINGLE) {
107562306a36Sopenharmony_ci		ret = tb_port_set_link_width(port->dual_link_port,
107662306a36Sopenharmony_ci					     TB_LINK_WIDTH_DUAL);
107762306a36Sopenharmony_ci		if (ret)
107862306a36Sopenharmony_ci			goto err_lane0;
107962306a36Sopenharmony_ci	}
108062306a36Sopenharmony_ci
108162306a36Sopenharmony_ci	/*
108262306a36Sopenharmony_ci	 * Only set bonding if the link was not already bonded. This
108362306a36Sopenharmony_ci	 * avoids the lane adapter to re-enter bonding state.
108462306a36Sopenharmony_ci	 */
108562306a36Sopenharmony_ci	if (width == TB_LINK_WIDTH_SINGLE && !tb_is_upstream_port(port)) {
108662306a36Sopenharmony_ci		ret = tb_port_set_lane_bonding(port, true);
108762306a36Sopenharmony_ci		if (ret)
108862306a36Sopenharmony_ci			goto err_lane1;
108962306a36Sopenharmony_ci	}
109062306a36Sopenharmony_ci
109162306a36Sopenharmony_ci	/*
109262306a36Sopenharmony_ci	 * When lane 0 bonding is set it will affect lane 1 too so
109362306a36Sopenharmony_ci	 * update both.
109462306a36Sopenharmony_ci	 */
109562306a36Sopenharmony_ci	port->bonded = true;
109662306a36Sopenharmony_ci	port->dual_link_port->bonded = true;
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci	return 0;
109962306a36Sopenharmony_ci
110062306a36Sopenharmony_cierr_lane1:
110162306a36Sopenharmony_ci	tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE);
110262306a36Sopenharmony_cierr_lane0:
110362306a36Sopenharmony_ci	tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE);
110462306a36Sopenharmony_ci
110562306a36Sopenharmony_ci	return ret;
110662306a36Sopenharmony_ci}
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci/**
110962306a36Sopenharmony_ci * tb_port_lane_bonding_disable() - Disable bonding on port
111062306a36Sopenharmony_ci * @port: port to disable
111162306a36Sopenharmony_ci *
111262306a36Sopenharmony_ci * Disable bonding by setting the link width of the port and the
111362306a36Sopenharmony_ci * other port in case of dual link port.
111462306a36Sopenharmony_ci */
111562306a36Sopenharmony_civoid tb_port_lane_bonding_disable(struct tb_port *port)
111662306a36Sopenharmony_ci{
111762306a36Sopenharmony_ci	tb_port_set_lane_bonding(port, false);
111862306a36Sopenharmony_ci	tb_port_set_link_width(port->dual_link_port, TB_LINK_WIDTH_SINGLE);
111962306a36Sopenharmony_ci	tb_port_set_link_width(port, TB_LINK_WIDTH_SINGLE);
112062306a36Sopenharmony_ci	port->dual_link_port->bonded = false;
112162306a36Sopenharmony_ci	port->bonded = false;
112262306a36Sopenharmony_ci}
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci/**
112562306a36Sopenharmony_ci * tb_port_wait_for_link_width() - Wait until link reaches specific width
112662306a36Sopenharmony_ci * @port: Port to wait for
112762306a36Sopenharmony_ci * @width_mask: Expected link width mask
112862306a36Sopenharmony_ci * @timeout_msec: Timeout in ms how long to wait
112962306a36Sopenharmony_ci *
113062306a36Sopenharmony_ci * Should be used after both ends of the link have been bonded (or
113162306a36Sopenharmony_ci * bonding has been disabled) to wait until the link actually reaches
113262306a36Sopenharmony_ci * the expected state. Returns %-ETIMEDOUT if the width was not reached
113362306a36Sopenharmony_ci * within the given timeout, %0 if it did. Can be passed a mask of
113462306a36Sopenharmony_ci * expected widths and succeeds if any of the widths is reached.
113562306a36Sopenharmony_ci */
113662306a36Sopenharmony_ciint tb_port_wait_for_link_width(struct tb_port *port, unsigned int width_mask,
113762306a36Sopenharmony_ci				int timeout_msec)
113862306a36Sopenharmony_ci{
113962306a36Sopenharmony_ci	ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
114062306a36Sopenharmony_ci	int ret;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	/* Gen 4 link does not support single lane */
114362306a36Sopenharmony_ci	if ((width_mask & TB_LINK_WIDTH_SINGLE) && is_gen4_link(port))
114462306a36Sopenharmony_ci		return -EOPNOTSUPP;
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	do {
114762306a36Sopenharmony_ci		ret = tb_port_get_link_width(port);
114862306a36Sopenharmony_ci		if (ret < 0) {
114962306a36Sopenharmony_ci			/*
115062306a36Sopenharmony_ci			 * Sometimes we get port locked error when
115162306a36Sopenharmony_ci			 * polling the lanes so we can ignore it and
115262306a36Sopenharmony_ci			 * retry.
115362306a36Sopenharmony_ci			 */
115462306a36Sopenharmony_ci			if (ret != -EACCES)
115562306a36Sopenharmony_ci				return ret;
115662306a36Sopenharmony_ci		} else if (ret & width_mask) {
115762306a36Sopenharmony_ci			return 0;
115862306a36Sopenharmony_ci		}
115962306a36Sopenharmony_ci
116062306a36Sopenharmony_ci		usleep_range(1000, 2000);
116162306a36Sopenharmony_ci	} while (ktime_before(ktime_get(), timeout));
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci	return -ETIMEDOUT;
116462306a36Sopenharmony_ci}
116562306a36Sopenharmony_ci
116662306a36Sopenharmony_cistatic int tb_port_do_update_credits(struct tb_port *port)
116762306a36Sopenharmony_ci{
116862306a36Sopenharmony_ci	u32 nfc_credits;
116962306a36Sopenharmony_ci	int ret;
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_ci	ret = tb_port_read(port, &nfc_credits, TB_CFG_PORT, ADP_CS_4, 1);
117262306a36Sopenharmony_ci	if (ret)
117362306a36Sopenharmony_ci		return ret;
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	if (nfc_credits != port->config.nfc_credits) {
117662306a36Sopenharmony_ci		u32 total;
117762306a36Sopenharmony_ci
117862306a36Sopenharmony_ci		total = (nfc_credits & ADP_CS_4_TOTAL_BUFFERS_MASK) >>
117962306a36Sopenharmony_ci			ADP_CS_4_TOTAL_BUFFERS_SHIFT;
118062306a36Sopenharmony_ci
118162306a36Sopenharmony_ci		tb_port_dbg(port, "total credits changed %u -> %u\n",
118262306a36Sopenharmony_ci			    port->total_credits, total);
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_ci		port->config.nfc_credits = nfc_credits;
118562306a36Sopenharmony_ci		port->total_credits = total;
118662306a36Sopenharmony_ci	}
118762306a36Sopenharmony_ci
118862306a36Sopenharmony_ci	return 0;
118962306a36Sopenharmony_ci}
119062306a36Sopenharmony_ci
119162306a36Sopenharmony_ci/**
119262306a36Sopenharmony_ci * tb_port_update_credits() - Re-read port total credits
119362306a36Sopenharmony_ci * @port: Port to update
119462306a36Sopenharmony_ci *
119562306a36Sopenharmony_ci * After the link is bonded (or bonding was disabled) the port total
119662306a36Sopenharmony_ci * credits may change, so this function needs to be called to re-read
119762306a36Sopenharmony_ci * the credits. Updates also the second lane adapter.
119862306a36Sopenharmony_ci */
119962306a36Sopenharmony_ciint tb_port_update_credits(struct tb_port *port)
120062306a36Sopenharmony_ci{
120162306a36Sopenharmony_ci	int ret;
120262306a36Sopenharmony_ci
120362306a36Sopenharmony_ci	ret = tb_port_do_update_credits(port);
120462306a36Sopenharmony_ci	if (ret)
120562306a36Sopenharmony_ci		return ret;
120662306a36Sopenharmony_ci	return tb_port_do_update_credits(port->dual_link_port);
120762306a36Sopenharmony_ci}
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_cistatic int tb_port_start_lane_initialization(struct tb_port *port)
121062306a36Sopenharmony_ci{
121162306a36Sopenharmony_ci	int ret;
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci	if (tb_switch_is_usb4(port->sw))
121462306a36Sopenharmony_ci		return 0;
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci	ret = tb_lc_start_lane_initialization(port);
121762306a36Sopenharmony_ci	return ret == -EINVAL ? 0 : ret;
121862306a36Sopenharmony_ci}
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci/*
122162306a36Sopenharmony_ci * Returns true if the port had something (router, XDomain) connected
122262306a36Sopenharmony_ci * before suspend.
122362306a36Sopenharmony_ci */
122462306a36Sopenharmony_cistatic bool tb_port_resume(struct tb_port *port)
122562306a36Sopenharmony_ci{
122662306a36Sopenharmony_ci	bool has_remote = tb_port_has_remote(port);
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_ci	if (port->usb4) {
122962306a36Sopenharmony_ci		usb4_port_device_resume(port->usb4);
123062306a36Sopenharmony_ci	} else if (!has_remote) {
123162306a36Sopenharmony_ci		/*
123262306a36Sopenharmony_ci		 * For disconnected downstream lane adapters start lane
123362306a36Sopenharmony_ci		 * initialization now so we detect future connects.
123462306a36Sopenharmony_ci		 *
123562306a36Sopenharmony_ci		 * For XDomain start the lane initialzation now so the
123662306a36Sopenharmony_ci		 * link gets re-established.
123762306a36Sopenharmony_ci		 *
123862306a36Sopenharmony_ci		 * This is only needed for non-USB4 ports.
123962306a36Sopenharmony_ci		 */
124062306a36Sopenharmony_ci		if (!tb_is_upstream_port(port) || port->xdomain)
124162306a36Sopenharmony_ci			tb_port_start_lane_initialization(port);
124262306a36Sopenharmony_ci	}
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	return has_remote || port->xdomain;
124562306a36Sopenharmony_ci}
124662306a36Sopenharmony_ci
124762306a36Sopenharmony_ci/**
124862306a36Sopenharmony_ci * tb_port_is_enabled() - Is the adapter port enabled
124962306a36Sopenharmony_ci * @port: Port to check
125062306a36Sopenharmony_ci */
125162306a36Sopenharmony_cibool tb_port_is_enabled(struct tb_port *port)
125262306a36Sopenharmony_ci{
125362306a36Sopenharmony_ci	switch (port->config.type) {
125462306a36Sopenharmony_ci	case TB_TYPE_PCIE_UP:
125562306a36Sopenharmony_ci	case TB_TYPE_PCIE_DOWN:
125662306a36Sopenharmony_ci		return tb_pci_port_is_enabled(port);
125762306a36Sopenharmony_ci
125862306a36Sopenharmony_ci	case TB_TYPE_DP_HDMI_IN:
125962306a36Sopenharmony_ci	case TB_TYPE_DP_HDMI_OUT:
126062306a36Sopenharmony_ci		return tb_dp_port_is_enabled(port);
126162306a36Sopenharmony_ci
126262306a36Sopenharmony_ci	case TB_TYPE_USB3_UP:
126362306a36Sopenharmony_ci	case TB_TYPE_USB3_DOWN:
126462306a36Sopenharmony_ci		return tb_usb3_port_is_enabled(port);
126562306a36Sopenharmony_ci
126662306a36Sopenharmony_ci	default:
126762306a36Sopenharmony_ci		return false;
126862306a36Sopenharmony_ci	}
126962306a36Sopenharmony_ci}
127062306a36Sopenharmony_ci
127162306a36Sopenharmony_ci/**
127262306a36Sopenharmony_ci * tb_usb3_port_is_enabled() - Is the USB3 adapter port enabled
127362306a36Sopenharmony_ci * @port: USB3 adapter port to check
127462306a36Sopenharmony_ci */
127562306a36Sopenharmony_cibool tb_usb3_port_is_enabled(struct tb_port *port)
127662306a36Sopenharmony_ci{
127762306a36Sopenharmony_ci	u32 data;
127862306a36Sopenharmony_ci
127962306a36Sopenharmony_ci	if (tb_port_read(port, &data, TB_CFG_PORT,
128062306a36Sopenharmony_ci			 port->cap_adap + ADP_USB3_CS_0, 1))
128162306a36Sopenharmony_ci		return false;
128262306a36Sopenharmony_ci
128362306a36Sopenharmony_ci	return !!(data & ADP_USB3_CS_0_PE);
128462306a36Sopenharmony_ci}
128562306a36Sopenharmony_ci
128662306a36Sopenharmony_ci/**
128762306a36Sopenharmony_ci * tb_usb3_port_enable() - Enable USB3 adapter port
128862306a36Sopenharmony_ci * @port: USB3 adapter port to enable
128962306a36Sopenharmony_ci * @enable: Enable/disable the USB3 adapter
129062306a36Sopenharmony_ci */
129162306a36Sopenharmony_ciint tb_usb3_port_enable(struct tb_port *port, bool enable)
129262306a36Sopenharmony_ci{
129362306a36Sopenharmony_ci	u32 word = enable ? (ADP_USB3_CS_0_PE | ADP_USB3_CS_0_V)
129462306a36Sopenharmony_ci			  : ADP_USB3_CS_0_V;
129562306a36Sopenharmony_ci
129662306a36Sopenharmony_ci	if (!port->cap_adap)
129762306a36Sopenharmony_ci		return -ENXIO;
129862306a36Sopenharmony_ci	return tb_port_write(port, &word, TB_CFG_PORT,
129962306a36Sopenharmony_ci			     port->cap_adap + ADP_USB3_CS_0, 1);
130062306a36Sopenharmony_ci}
130162306a36Sopenharmony_ci
130262306a36Sopenharmony_ci/**
130362306a36Sopenharmony_ci * tb_pci_port_is_enabled() - Is the PCIe adapter port enabled
130462306a36Sopenharmony_ci * @port: PCIe port to check
130562306a36Sopenharmony_ci */
130662306a36Sopenharmony_cibool tb_pci_port_is_enabled(struct tb_port *port)
130762306a36Sopenharmony_ci{
130862306a36Sopenharmony_ci	u32 data;
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_ci	if (tb_port_read(port, &data, TB_CFG_PORT,
131162306a36Sopenharmony_ci			 port->cap_adap + ADP_PCIE_CS_0, 1))
131262306a36Sopenharmony_ci		return false;
131362306a36Sopenharmony_ci
131462306a36Sopenharmony_ci	return !!(data & ADP_PCIE_CS_0_PE);
131562306a36Sopenharmony_ci}
131662306a36Sopenharmony_ci
131762306a36Sopenharmony_ci/**
131862306a36Sopenharmony_ci * tb_pci_port_enable() - Enable PCIe adapter port
131962306a36Sopenharmony_ci * @port: PCIe port to enable
132062306a36Sopenharmony_ci * @enable: Enable/disable the PCIe adapter
132162306a36Sopenharmony_ci */
132262306a36Sopenharmony_ciint tb_pci_port_enable(struct tb_port *port, bool enable)
132362306a36Sopenharmony_ci{
132462306a36Sopenharmony_ci	u32 word = enable ? ADP_PCIE_CS_0_PE : 0x0;
132562306a36Sopenharmony_ci	if (!port->cap_adap)
132662306a36Sopenharmony_ci		return -ENXIO;
132762306a36Sopenharmony_ci	return tb_port_write(port, &word, TB_CFG_PORT,
132862306a36Sopenharmony_ci			     port->cap_adap + ADP_PCIE_CS_0, 1);
132962306a36Sopenharmony_ci}
133062306a36Sopenharmony_ci
133162306a36Sopenharmony_ci/**
133262306a36Sopenharmony_ci * tb_dp_port_hpd_is_active() - Is HPD already active
133362306a36Sopenharmony_ci * @port: DP out port to check
133462306a36Sopenharmony_ci *
133562306a36Sopenharmony_ci * Checks if the DP OUT adapter port has HDP bit already set.
133662306a36Sopenharmony_ci */
133762306a36Sopenharmony_ciint tb_dp_port_hpd_is_active(struct tb_port *port)
133862306a36Sopenharmony_ci{
133962306a36Sopenharmony_ci	u32 data;
134062306a36Sopenharmony_ci	int ret;
134162306a36Sopenharmony_ci
134262306a36Sopenharmony_ci	ret = tb_port_read(port, &data, TB_CFG_PORT,
134362306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_2, 1);
134462306a36Sopenharmony_ci	if (ret)
134562306a36Sopenharmony_ci		return ret;
134662306a36Sopenharmony_ci
134762306a36Sopenharmony_ci	return !!(data & ADP_DP_CS_2_HDP);
134862306a36Sopenharmony_ci}
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_ci/**
135162306a36Sopenharmony_ci * tb_dp_port_hpd_clear() - Clear HPD from DP IN port
135262306a36Sopenharmony_ci * @port: Port to clear HPD
135362306a36Sopenharmony_ci *
135462306a36Sopenharmony_ci * If the DP IN port has HDP set, this function can be used to clear it.
135562306a36Sopenharmony_ci */
135662306a36Sopenharmony_ciint tb_dp_port_hpd_clear(struct tb_port *port)
135762306a36Sopenharmony_ci{
135862306a36Sopenharmony_ci	u32 data;
135962306a36Sopenharmony_ci	int ret;
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_ci	ret = tb_port_read(port, &data, TB_CFG_PORT,
136262306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_3, 1);
136362306a36Sopenharmony_ci	if (ret)
136462306a36Sopenharmony_ci		return ret;
136562306a36Sopenharmony_ci
136662306a36Sopenharmony_ci	data |= ADP_DP_CS_3_HDPC;
136762306a36Sopenharmony_ci	return tb_port_write(port, &data, TB_CFG_PORT,
136862306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_3, 1);
136962306a36Sopenharmony_ci}
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_ci/**
137262306a36Sopenharmony_ci * tb_dp_port_set_hops() - Set video/aux Hop IDs for DP port
137362306a36Sopenharmony_ci * @port: DP IN/OUT port to set hops
137462306a36Sopenharmony_ci * @video: Video Hop ID
137562306a36Sopenharmony_ci * @aux_tx: AUX TX Hop ID
137662306a36Sopenharmony_ci * @aux_rx: AUX RX Hop ID
137762306a36Sopenharmony_ci *
137862306a36Sopenharmony_ci * Programs specified Hop IDs for DP IN/OUT port. Can be called for USB4
137962306a36Sopenharmony_ci * router DP adapters too but does not program the values as the fields
138062306a36Sopenharmony_ci * are read-only.
138162306a36Sopenharmony_ci */
138262306a36Sopenharmony_ciint tb_dp_port_set_hops(struct tb_port *port, unsigned int video,
138362306a36Sopenharmony_ci			unsigned int aux_tx, unsigned int aux_rx)
138462306a36Sopenharmony_ci{
138562306a36Sopenharmony_ci	u32 data[2];
138662306a36Sopenharmony_ci	int ret;
138762306a36Sopenharmony_ci
138862306a36Sopenharmony_ci	if (tb_switch_is_usb4(port->sw))
138962306a36Sopenharmony_ci		return 0;
139062306a36Sopenharmony_ci
139162306a36Sopenharmony_ci	ret = tb_port_read(port, data, TB_CFG_PORT,
139262306a36Sopenharmony_ci			   port->cap_adap + ADP_DP_CS_0, ARRAY_SIZE(data));
139362306a36Sopenharmony_ci	if (ret)
139462306a36Sopenharmony_ci		return ret;
139562306a36Sopenharmony_ci
139662306a36Sopenharmony_ci	data[0] &= ~ADP_DP_CS_0_VIDEO_HOPID_MASK;
139762306a36Sopenharmony_ci	data[1] &= ~ADP_DP_CS_1_AUX_RX_HOPID_MASK;
139862306a36Sopenharmony_ci	data[1] &= ~ADP_DP_CS_1_AUX_RX_HOPID_MASK;
139962306a36Sopenharmony_ci
140062306a36Sopenharmony_ci	data[0] |= (video << ADP_DP_CS_0_VIDEO_HOPID_SHIFT) &
140162306a36Sopenharmony_ci		ADP_DP_CS_0_VIDEO_HOPID_MASK;
140262306a36Sopenharmony_ci	data[1] |= aux_tx & ADP_DP_CS_1_AUX_TX_HOPID_MASK;
140362306a36Sopenharmony_ci	data[1] |= (aux_rx << ADP_DP_CS_1_AUX_RX_HOPID_SHIFT) &
140462306a36Sopenharmony_ci		ADP_DP_CS_1_AUX_RX_HOPID_MASK;
140562306a36Sopenharmony_ci
140662306a36Sopenharmony_ci	return tb_port_write(port, data, TB_CFG_PORT,
140762306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_0, ARRAY_SIZE(data));
140862306a36Sopenharmony_ci}
140962306a36Sopenharmony_ci
141062306a36Sopenharmony_ci/**
141162306a36Sopenharmony_ci * tb_dp_port_is_enabled() - Is DP adapter port enabled
141262306a36Sopenharmony_ci * @port: DP adapter port to check
141362306a36Sopenharmony_ci */
141462306a36Sopenharmony_cibool tb_dp_port_is_enabled(struct tb_port *port)
141562306a36Sopenharmony_ci{
141662306a36Sopenharmony_ci	u32 data[2];
141762306a36Sopenharmony_ci
141862306a36Sopenharmony_ci	if (tb_port_read(port, data, TB_CFG_PORT, port->cap_adap + ADP_DP_CS_0,
141962306a36Sopenharmony_ci			 ARRAY_SIZE(data)))
142062306a36Sopenharmony_ci		return false;
142162306a36Sopenharmony_ci
142262306a36Sopenharmony_ci	return !!(data[0] & (ADP_DP_CS_0_VE | ADP_DP_CS_0_AE));
142362306a36Sopenharmony_ci}
142462306a36Sopenharmony_ci
142562306a36Sopenharmony_ci/**
142662306a36Sopenharmony_ci * tb_dp_port_enable() - Enables/disables DP paths of a port
142762306a36Sopenharmony_ci * @port: DP IN/OUT port
142862306a36Sopenharmony_ci * @enable: Enable/disable DP path
142962306a36Sopenharmony_ci *
143062306a36Sopenharmony_ci * Once Hop IDs are programmed DP paths can be enabled or disabled by
143162306a36Sopenharmony_ci * calling this function.
143262306a36Sopenharmony_ci */
143362306a36Sopenharmony_ciint tb_dp_port_enable(struct tb_port *port, bool enable)
143462306a36Sopenharmony_ci{
143562306a36Sopenharmony_ci	u32 data[2];
143662306a36Sopenharmony_ci	int ret;
143762306a36Sopenharmony_ci
143862306a36Sopenharmony_ci	ret = tb_port_read(port, data, TB_CFG_PORT,
143962306a36Sopenharmony_ci			  port->cap_adap + ADP_DP_CS_0, ARRAY_SIZE(data));
144062306a36Sopenharmony_ci	if (ret)
144162306a36Sopenharmony_ci		return ret;
144262306a36Sopenharmony_ci
144362306a36Sopenharmony_ci	if (enable)
144462306a36Sopenharmony_ci		data[0] |= ADP_DP_CS_0_VE | ADP_DP_CS_0_AE;
144562306a36Sopenharmony_ci	else
144662306a36Sopenharmony_ci		data[0] &= ~(ADP_DP_CS_0_VE | ADP_DP_CS_0_AE);
144762306a36Sopenharmony_ci
144862306a36Sopenharmony_ci	return tb_port_write(port, data, TB_CFG_PORT,
144962306a36Sopenharmony_ci			     port->cap_adap + ADP_DP_CS_0, ARRAY_SIZE(data));
145062306a36Sopenharmony_ci}
145162306a36Sopenharmony_ci
145262306a36Sopenharmony_ci/* switch utility functions */
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_cistatic const char *tb_switch_generation_name(const struct tb_switch *sw)
145562306a36Sopenharmony_ci{
145662306a36Sopenharmony_ci	switch (sw->generation) {
145762306a36Sopenharmony_ci	case 1:
145862306a36Sopenharmony_ci		return "Thunderbolt 1";
145962306a36Sopenharmony_ci	case 2:
146062306a36Sopenharmony_ci		return "Thunderbolt 2";
146162306a36Sopenharmony_ci	case 3:
146262306a36Sopenharmony_ci		return "Thunderbolt 3";
146362306a36Sopenharmony_ci	case 4:
146462306a36Sopenharmony_ci		return "USB4";
146562306a36Sopenharmony_ci	default:
146662306a36Sopenharmony_ci		return "Unknown";
146762306a36Sopenharmony_ci	}
146862306a36Sopenharmony_ci}
146962306a36Sopenharmony_ci
147062306a36Sopenharmony_cistatic void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
147162306a36Sopenharmony_ci{
147262306a36Sopenharmony_ci	const struct tb_regs_switch_header *regs = &sw->config;
147362306a36Sopenharmony_ci
147462306a36Sopenharmony_ci	tb_dbg(tb, " %s Switch: %x:%x (Revision: %d, TB Version: %d)\n",
147562306a36Sopenharmony_ci	       tb_switch_generation_name(sw), regs->vendor_id, regs->device_id,
147662306a36Sopenharmony_ci	       regs->revision, regs->thunderbolt_version);
147762306a36Sopenharmony_ci	tb_dbg(tb, "  Max Port Number: %d\n", regs->max_port_number);
147862306a36Sopenharmony_ci	tb_dbg(tb, "  Config:\n");
147962306a36Sopenharmony_ci	tb_dbg(tb,
148062306a36Sopenharmony_ci		"   Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n",
148162306a36Sopenharmony_ci	       regs->upstream_port_number, regs->depth,
148262306a36Sopenharmony_ci	       (((u64) regs->route_hi) << 32) | regs->route_lo,
148362306a36Sopenharmony_ci	       regs->enabled, regs->plug_events_delay);
148462306a36Sopenharmony_ci	tb_dbg(tb, "   unknown1: %#x unknown4: %#x\n",
148562306a36Sopenharmony_ci	       regs->__unknown1, regs->__unknown4);
148662306a36Sopenharmony_ci}
148762306a36Sopenharmony_ci
148862306a36Sopenharmony_ci/**
148962306a36Sopenharmony_ci * tb_switch_reset() - reconfigure route, enable and send TB_CFG_PKG_RESET
149062306a36Sopenharmony_ci * @sw: Switch to reset
149162306a36Sopenharmony_ci *
149262306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure.
149362306a36Sopenharmony_ci */
149462306a36Sopenharmony_ciint tb_switch_reset(struct tb_switch *sw)
149562306a36Sopenharmony_ci{
149662306a36Sopenharmony_ci	struct tb_cfg_result res;
149762306a36Sopenharmony_ci
149862306a36Sopenharmony_ci	if (sw->generation > 1)
149962306a36Sopenharmony_ci		return 0;
150062306a36Sopenharmony_ci
150162306a36Sopenharmony_ci	tb_sw_dbg(sw, "resetting switch\n");
150262306a36Sopenharmony_ci
150362306a36Sopenharmony_ci	res.err = tb_sw_write(sw, ((u32 *) &sw->config) + 2,
150462306a36Sopenharmony_ci			      TB_CFG_SWITCH, 2, 2);
150562306a36Sopenharmony_ci	if (res.err)
150662306a36Sopenharmony_ci		return res.err;
150762306a36Sopenharmony_ci	res = tb_cfg_reset(sw->tb->ctl, tb_route(sw));
150862306a36Sopenharmony_ci	if (res.err > 0)
150962306a36Sopenharmony_ci		return -EIO;
151062306a36Sopenharmony_ci	return res.err;
151162306a36Sopenharmony_ci}
151262306a36Sopenharmony_ci
151362306a36Sopenharmony_ci/**
151462306a36Sopenharmony_ci * tb_switch_wait_for_bit() - Wait for specified value of bits in offset
151562306a36Sopenharmony_ci * @sw: Router to read the offset value from
151662306a36Sopenharmony_ci * @offset: Offset in the router config space to read from
151762306a36Sopenharmony_ci * @bit: Bit mask in the offset to wait for
151862306a36Sopenharmony_ci * @value: Value of the bits to wait for
151962306a36Sopenharmony_ci * @timeout_msec: Timeout in ms how long to wait
152062306a36Sopenharmony_ci *
152162306a36Sopenharmony_ci * Wait till the specified bits in specified offset reach specified value.
152262306a36Sopenharmony_ci * Returns %0 in case of success, %-ETIMEDOUT if the @value was not reached
152362306a36Sopenharmony_ci * within the given timeout or a negative errno in case of failure.
152462306a36Sopenharmony_ci */
152562306a36Sopenharmony_ciint tb_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
152662306a36Sopenharmony_ci			   u32 value, int timeout_msec)
152762306a36Sopenharmony_ci{
152862306a36Sopenharmony_ci	ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
152962306a36Sopenharmony_ci
153062306a36Sopenharmony_ci	do {
153162306a36Sopenharmony_ci		u32 val;
153262306a36Sopenharmony_ci		int ret;
153362306a36Sopenharmony_ci
153462306a36Sopenharmony_ci		ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, offset, 1);
153562306a36Sopenharmony_ci		if (ret)
153662306a36Sopenharmony_ci			return ret;
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_ci		if ((val & bit) == value)
153962306a36Sopenharmony_ci			return 0;
154062306a36Sopenharmony_ci
154162306a36Sopenharmony_ci		usleep_range(50, 100);
154262306a36Sopenharmony_ci	} while (ktime_before(ktime_get(), timeout));
154362306a36Sopenharmony_ci
154462306a36Sopenharmony_ci	return -ETIMEDOUT;
154562306a36Sopenharmony_ci}
154662306a36Sopenharmony_ci
154762306a36Sopenharmony_ci/*
154862306a36Sopenharmony_ci * tb_plug_events_active() - enable/disable plug events on a switch
154962306a36Sopenharmony_ci *
155062306a36Sopenharmony_ci * Also configures a sane plug_events_delay of 255ms.
155162306a36Sopenharmony_ci *
155262306a36Sopenharmony_ci * Return: Returns 0 on success or an error code on failure.
155362306a36Sopenharmony_ci */
155462306a36Sopenharmony_cistatic int tb_plug_events_active(struct tb_switch *sw, bool active)
155562306a36Sopenharmony_ci{
155662306a36Sopenharmony_ci	u32 data;
155762306a36Sopenharmony_ci	int res;
155862306a36Sopenharmony_ci
155962306a36Sopenharmony_ci	if (tb_switch_is_icm(sw) || tb_switch_is_usb4(sw))
156062306a36Sopenharmony_ci		return 0;
156162306a36Sopenharmony_ci
156262306a36Sopenharmony_ci	sw->config.plug_events_delay = 0xff;
156362306a36Sopenharmony_ci	res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1);
156462306a36Sopenharmony_ci	if (res)
156562306a36Sopenharmony_ci		return res;
156662306a36Sopenharmony_ci
156762306a36Sopenharmony_ci	res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
156862306a36Sopenharmony_ci	if (res)
156962306a36Sopenharmony_ci		return res;
157062306a36Sopenharmony_ci
157162306a36Sopenharmony_ci	if (active) {
157262306a36Sopenharmony_ci		data = data & 0xFFFFFF83;
157362306a36Sopenharmony_ci		switch (sw->config.device_id) {
157462306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
157562306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_EAGLE_RIDGE:
157662306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
157762306a36Sopenharmony_ci			break;
157862306a36Sopenharmony_ci		default:
157962306a36Sopenharmony_ci			/*
158062306a36Sopenharmony_ci			 * Skip Alpine Ridge, it needs to have vendor
158162306a36Sopenharmony_ci			 * specific USB hotplug event enabled for the
158262306a36Sopenharmony_ci			 * internal xHCI to work.
158362306a36Sopenharmony_ci			 */
158462306a36Sopenharmony_ci			if (!tb_switch_is_alpine_ridge(sw))
158562306a36Sopenharmony_ci				data |= TB_PLUG_EVENTS_USB_DISABLE;
158662306a36Sopenharmony_ci		}
158762306a36Sopenharmony_ci	} else {
158862306a36Sopenharmony_ci		data = data | 0x7c;
158962306a36Sopenharmony_ci	}
159062306a36Sopenharmony_ci	return tb_sw_write(sw, &data, TB_CFG_SWITCH,
159162306a36Sopenharmony_ci			   sw->cap_plug_events + 1, 1);
159262306a36Sopenharmony_ci}
159362306a36Sopenharmony_ci
159462306a36Sopenharmony_cistatic ssize_t authorized_show(struct device *dev,
159562306a36Sopenharmony_ci			       struct device_attribute *attr,
159662306a36Sopenharmony_ci			       char *buf)
159762306a36Sopenharmony_ci{
159862306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
159962306a36Sopenharmony_ci
160062306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", sw->authorized);
160162306a36Sopenharmony_ci}
160262306a36Sopenharmony_ci
160362306a36Sopenharmony_cistatic int disapprove_switch(struct device *dev, void *not_used)
160462306a36Sopenharmony_ci{
160562306a36Sopenharmony_ci	char *envp[] = { "AUTHORIZED=0", NULL };
160662306a36Sopenharmony_ci	struct tb_switch *sw;
160762306a36Sopenharmony_ci
160862306a36Sopenharmony_ci	sw = tb_to_switch(dev);
160962306a36Sopenharmony_ci	if (sw && sw->authorized) {
161062306a36Sopenharmony_ci		int ret;
161162306a36Sopenharmony_ci
161262306a36Sopenharmony_ci		/* First children */
161362306a36Sopenharmony_ci		ret = device_for_each_child_reverse(&sw->dev, NULL, disapprove_switch);
161462306a36Sopenharmony_ci		if (ret)
161562306a36Sopenharmony_ci			return ret;
161662306a36Sopenharmony_ci
161762306a36Sopenharmony_ci		ret = tb_domain_disapprove_switch(sw->tb, sw);
161862306a36Sopenharmony_ci		if (ret)
161962306a36Sopenharmony_ci			return ret;
162062306a36Sopenharmony_ci
162162306a36Sopenharmony_ci		sw->authorized = 0;
162262306a36Sopenharmony_ci		kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp);
162362306a36Sopenharmony_ci	}
162462306a36Sopenharmony_ci
162562306a36Sopenharmony_ci	return 0;
162662306a36Sopenharmony_ci}
162762306a36Sopenharmony_ci
162862306a36Sopenharmony_cistatic int tb_switch_set_authorized(struct tb_switch *sw, unsigned int val)
162962306a36Sopenharmony_ci{
163062306a36Sopenharmony_ci	char envp_string[13];
163162306a36Sopenharmony_ci	int ret = -EINVAL;
163262306a36Sopenharmony_ci	char *envp[] = { envp_string, NULL };
163362306a36Sopenharmony_ci
163462306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock))
163562306a36Sopenharmony_ci		return restart_syscall();
163662306a36Sopenharmony_ci
163762306a36Sopenharmony_ci	if (!!sw->authorized == !!val)
163862306a36Sopenharmony_ci		goto unlock;
163962306a36Sopenharmony_ci
164062306a36Sopenharmony_ci	switch (val) {
164162306a36Sopenharmony_ci	/* Disapprove switch */
164262306a36Sopenharmony_ci	case 0:
164362306a36Sopenharmony_ci		if (tb_route(sw)) {
164462306a36Sopenharmony_ci			ret = disapprove_switch(&sw->dev, NULL);
164562306a36Sopenharmony_ci			goto unlock;
164662306a36Sopenharmony_ci		}
164762306a36Sopenharmony_ci		break;
164862306a36Sopenharmony_ci
164962306a36Sopenharmony_ci	/* Approve switch */
165062306a36Sopenharmony_ci	case 1:
165162306a36Sopenharmony_ci		if (sw->key)
165262306a36Sopenharmony_ci			ret = tb_domain_approve_switch_key(sw->tb, sw);
165362306a36Sopenharmony_ci		else
165462306a36Sopenharmony_ci			ret = tb_domain_approve_switch(sw->tb, sw);
165562306a36Sopenharmony_ci		break;
165662306a36Sopenharmony_ci
165762306a36Sopenharmony_ci	/* Challenge switch */
165862306a36Sopenharmony_ci	case 2:
165962306a36Sopenharmony_ci		if (sw->key)
166062306a36Sopenharmony_ci			ret = tb_domain_challenge_switch_key(sw->tb, sw);
166162306a36Sopenharmony_ci		break;
166262306a36Sopenharmony_ci
166362306a36Sopenharmony_ci	default:
166462306a36Sopenharmony_ci		break;
166562306a36Sopenharmony_ci	}
166662306a36Sopenharmony_ci
166762306a36Sopenharmony_ci	if (!ret) {
166862306a36Sopenharmony_ci		sw->authorized = val;
166962306a36Sopenharmony_ci		/*
167062306a36Sopenharmony_ci		 * Notify status change to the userspace, informing the new
167162306a36Sopenharmony_ci		 * value of /sys/bus/thunderbolt/devices/.../authorized.
167262306a36Sopenharmony_ci		 */
167362306a36Sopenharmony_ci		sprintf(envp_string, "AUTHORIZED=%u", sw->authorized);
167462306a36Sopenharmony_ci		kobject_uevent_env(&sw->dev.kobj, KOBJ_CHANGE, envp);
167562306a36Sopenharmony_ci	}
167662306a36Sopenharmony_ci
167762306a36Sopenharmony_ciunlock:
167862306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
167962306a36Sopenharmony_ci	return ret;
168062306a36Sopenharmony_ci}
168162306a36Sopenharmony_ci
168262306a36Sopenharmony_cistatic ssize_t authorized_store(struct device *dev,
168362306a36Sopenharmony_ci				struct device_attribute *attr,
168462306a36Sopenharmony_ci				const char *buf, size_t count)
168562306a36Sopenharmony_ci{
168662306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
168762306a36Sopenharmony_ci	unsigned int val;
168862306a36Sopenharmony_ci	ssize_t ret;
168962306a36Sopenharmony_ci
169062306a36Sopenharmony_ci	ret = kstrtouint(buf, 0, &val);
169162306a36Sopenharmony_ci	if (ret)
169262306a36Sopenharmony_ci		return ret;
169362306a36Sopenharmony_ci	if (val > 2)
169462306a36Sopenharmony_ci		return -EINVAL;
169562306a36Sopenharmony_ci
169662306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
169762306a36Sopenharmony_ci	ret = tb_switch_set_authorized(sw, val);
169862306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
169962306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
170062306a36Sopenharmony_ci
170162306a36Sopenharmony_ci	return ret ? ret : count;
170262306a36Sopenharmony_ci}
170362306a36Sopenharmony_cistatic DEVICE_ATTR_RW(authorized);
170462306a36Sopenharmony_ci
170562306a36Sopenharmony_cistatic ssize_t boot_show(struct device *dev, struct device_attribute *attr,
170662306a36Sopenharmony_ci			 char *buf)
170762306a36Sopenharmony_ci{
170862306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
170962306a36Sopenharmony_ci
171062306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", sw->boot);
171162306a36Sopenharmony_ci}
171262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(boot);
171362306a36Sopenharmony_ci
171462306a36Sopenharmony_cistatic ssize_t device_show(struct device *dev, struct device_attribute *attr,
171562306a36Sopenharmony_ci			   char *buf)
171662306a36Sopenharmony_ci{
171762306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
171862306a36Sopenharmony_ci
171962306a36Sopenharmony_ci	return sysfs_emit(buf, "%#x\n", sw->device);
172062306a36Sopenharmony_ci}
172162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(device);
172262306a36Sopenharmony_ci
172362306a36Sopenharmony_cistatic ssize_t
172462306a36Sopenharmony_cidevice_name_show(struct device *dev, struct device_attribute *attr, char *buf)
172562306a36Sopenharmony_ci{
172662306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
172762306a36Sopenharmony_ci
172862306a36Sopenharmony_ci	return sysfs_emit(buf, "%s\n", sw->device_name ?: "");
172962306a36Sopenharmony_ci}
173062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(device_name);
173162306a36Sopenharmony_ci
173262306a36Sopenharmony_cistatic ssize_t
173362306a36Sopenharmony_cigeneration_show(struct device *dev, struct device_attribute *attr, char *buf)
173462306a36Sopenharmony_ci{
173562306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
173662306a36Sopenharmony_ci
173762306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", sw->generation);
173862306a36Sopenharmony_ci}
173962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(generation);
174062306a36Sopenharmony_ci
174162306a36Sopenharmony_cistatic ssize_t key_show(struct device *dev, struct device_attribute *attr,
174262306a36Sopenharmony_ci			char *buf)
174362306a36Sopenharmony_ci{
174462306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
174562306a36Sopenharmony_ci	ssize_t ret;
174662306a36Sopenharmony_ci
174762306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock))
174862306a36Sopenharmony_ci		return restart_syscall();
174962306a36Sopenharmony_ci
175062306a36Sopenharmony_ci	if (sw->key)
175162306a36Sopenharmony_ci		ret = sysfs_emit(buf, "%*phN\n", TB_SWITCH_KEY_SIZE, sw->key);
175262306a36Sopenharmony_ci	else
175362306a36Sopenharmony_ci		ret = sysfs_emit(buf, "\n");
175462306a36Sopenharmony_ci
175562306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
175662306a36Sopenharmony_ci	return ret;
175762306a36Sopenharmony_ci}
175862306a36Sopenharmony_ci
175962306a36Sopenharmony_cistatic ssize_t key_store(struct device *dev, struct device_attribute *attr,
176062306a36Sopenharmony_ci			 const char *buf, size_t count)
176162306a36Sopenharmony_ci{
176262306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
176362306a36Sopenharmony_ci	u8 key[TB_SWITCH_KEY_SIZE];
176462306a36Sopenharmony_ci	ssize_t ret = count;
176562306a36Sopenharmony_ci	bool clear = false;
176662306a36Sopenharmony_ci
176762306a36Sopenharmony_ci	if (!strcmp(buf, "\n"))
176862306a36Sopenharmony_ci		clear = true;
176962306a36Sopenharmony_ci	else if (hex2bin(key, buf, sizeof(key)))
177062306a36Sopenharmony_ci		return -EINVAL;
177162306a36Sopenharmony_ci
177262306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock))
177362306a36Sopenharmony_ci		return restart_syscall();
177462306a36Sopenharmony_ci
177562306a36Sopenharmony_ci	if (sw->authorized) {
177662306a36Sopenharmony_ci		ret = -EBUSY;
177762306a36Sopenharmony_ci	} else {
177862306a36Sopenharmony_ci		kfree(sw->key);
177962306a36Sopenharmony_ci		if (clear) {
178062306a36Sopenharmony_ci			sw->key = NULL;
178162306a36Sopenharmony_ci		} else {
178262306a36Sopenharmony_ci			sw->key = kmemdup(key, sizeof(key), GFP_KERNEL);
178362306a36Sopenharmony_ci			if (!sw->key)
178462306a36Sopenharmony_ci				ret = -ENOMEM;
178562306a36Sopenharmony_ci		}
178662306a36Sopenharmony_ci	}
178762306a36Sopenharmony_ci
178862306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
178962306a36Sopenharmony_ci	return ret;
179062306a36Sopenharmony_ci}
179162306a36Sopenharmony_cistatic DEVICE_ATTR(key, 0600, key_show, key_store);
179262306a36Sopenharmony_ci
179362306a36Sopenharmony_cistatic ssize_t speed_show(struct device *dev, struct device_attribute *attr,
179462306a36Sopenharmony_ci			  char *buf)
179562306a36Sopenharmony_ci{
179662306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
179762306a36Sopenharmony_ci
179862306a36Sopenharmony_ci	return sysfs_emit(buf, "%u.0 Gb/s\n", sw->link_speed);
179962306a36Sopenharmony_ci}
180062306a36Sopenharmony_ci
180162306a36Sopenharmony_ci/*
180262306a36Sopenharmony_ci * Currently all lanes must run at the same speed but we expose here
180362306a36Sopenharmony_ci * both directions to allow possible asymmetric links in the future.
180462306a36Sopenharmony_ci */
180562306a36Sopenharmony_cistatic DEVICE_ATTR(rx_speed, 0444, speed_show, NULL);
180662306a36Sopenharmony_cistatic DEVICE_ATTR(tx_speed, 0444, speed_show, NULL);
180762306a36Sopenharmony_ci
180862306a36Sopenharmony_cistatic ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr,
180962306a36Sopenharmony_ci			     char *buf)
181062306a36Sopenharmony_ci{
181162306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
181262306a36Sopenharmony_ci	unsigned int width;
181362306a36Sopenharmony_ci
181462306a36Sopenharmony_ci	switch (sw->link_width) {
181562306a36Sopenharmony_ci	case TB_LINK_WIDTH_SINGLE:
181662306a36Sopenharmony_ci	case TB_LINK_WIDTH_ASYM_TX:
181762306a36Sopenharmony_ci		width = 1;
181862306a36Sopenharmony_ci		break;
181962306a36Sopenharmony_ci	case TB_LINK_WIDTH_DUAL:
182062306a36Sopenharmony_ci		width = 2;
182162306a36Sopenharmony_ci		break;
182262306a36Sopenharmony_ci	case TB_LINK_WIDTH_ASYM_RX:
182362306a36Sopenharmony_ci		width = 3;
182462306a36Sopenharmony_ci		break;
182562306a36Sopenharmony_ci	default:
182662306a36Sopenharmony_ci		WARN_ON_ONCE(1);
182762306a36Sopenharmony_ci		return -EINVAL;
182862306a36Sopenharmony_ci	}
182962306a36Sopenharmony_ci
183062306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", width);
183162306a36Sopenharmony_ci}
183262306a36Sopenharmony_cistatic DEVICE_ATTR(rx_lanes, 0444, rx_lanes_show, NULL);
183362306a36Sopenharmony_ci
183462306a36Sopenharmony_cistatic ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr,
183562306a36Sopenharmony_ci			     char *buf)
183662306a36Sopenharmony_ci{
183762306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
183862306a36Sopenharmony_ci	unsigned int width;
183962306a36Sopenharmony_ci
184062306a36Sopenharmony_ci	switch (sw->link_width) {
184162306a36Sopenharmony_ci	case TB_LINK_WIDTH_SINGLE:
184262306a36Sopenharmony_ci	case TB_LINK_WIDTH_ASYM_RX:
184362306a36Sopenharmony_ci		width = 1;
184462306a36Sopenharmony_ci		break;
184562306a36Sopenharmony_ci	case TB_LINK_WIDTH_DUAL:
184662306a36Sopenharmony_ci		width = 2;
184762306a36Sopenharmony_ci		break;
184862306a36Sopenharmony_ci	case TB_LINK_WIDTH_ASYM_TX:
184962306a36Sopenharmony_ci		width = 3;
185062306a36Sopenharmony_ci		break;
185162306a36Sopenharmony_ci	default:
185262306a36Sopenharmony_ci		WARN_ON_ONCE(1);
185362306a36Sopenharmony_ci		return -EINVAL;
185462306a36Sopenharmony_ci	}
185562306a36Sopenharmony_ci
185662306a36Sopenharmony_ci	return sysfs_emit(buf, "%u\n", width);
185762306a36Sopenharmony_ci}
185862306a36Sopenharmony_cistatic DEVICE_ATTR(tx_lanes, 0444, tx_lanes_show, NULL);
185962306a36Sopenharmony_ci
186062306a36Sopenharmony_cistatic ssize_t nvm_authenticate_show(struct device *dev,
186162306a36Sopenharmony_ci	struct device_attribute *attr, char *buf)
186262306a36Sopenharmony_ci{
186362306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
186462306a36Sopenharmony_ci	u32 status;
186562306a36Sopenharmony_ci
186662306a36Sopenharmony_ci	nvm_get_auth_status(sw, &status);
186762306a36Sopenharmony_ci	return sysfs_emit(buf, "%#x\n", status);
186862306a36Sopenharmony_ci}
186962306a36Sopenharmony_ci
187062306a36Sopenharmony_cistatic ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf,
187162306a36Sopenharmony_ci				      bool disconnect)
187262306a36Sopenharmony_ci{
187362306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
187462306a36Sopenharmony_ci	int val, ret;
187562306a36Sopenharmony_ci
187662306a36Sopenharmony_ci	pm_runtime_get_sync(&sw->dev);
187762306a36Sopenharmony_ci
187862306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock)) {
187962306a36Sopenharmony_ci		ret = restart_syscall();
188062306a36Sopenharmony_ci		goto exit_rpm;
188162306a36Sopenharmony_ci	}
188262306a36Sopenharmony_ci
188362306a36Sopenharmony_ci	if (sw->no_nvm_upgrade) {
188462306a36Sopenharmony_ci		ret = -EOPNOTSUPP;
188562306a36Sopenharmony_ci		goto exit_unlock;
188662306a36Sopenharmony_ci	}
188762306a36Sopenharmony_ci
188862306a36Sopenharmony_ci	/* If NVMem devices are not yet added */
188962306a36Sopenharmony_ci	if (!sw->nvm) {
189062306a36Sopenharmony_ci		ret = -EAGAIN;
189162306a36Sopenharmony_ci		goto exit_unlock;
189262306a36Sopenharmony_ci	}
189362306a36Sopenharmony_ci
189462306a36Sopenharmony_ci	ret = kstrtoint(buf, 10, &val);
189562306a36Sopenharmony_ci	if (ret)
189662306a36Sopenharmony_ci		goto exit_unlock;
189762306a36Sopenharmony_ci
189862306a36Sopenharmony_ci	/* Always clear the authentication status */
189962306a36Sopenharmony_ci	nvm_clear_auth_status(sw);
190062306a36Sopenharmony_ci
190162306a36Sopenharmony_ci	if (val > 0) {
190262306a36Sopenharmony_ci		if (val == AUTHENTICATE_ONLY) {
190362306a36Sopenharmony_ci			if (disconnect)
190462306a36Sopenharmony_ci				ret = -EINVAL;
190562306a36Sopenharmony_ci			else
190662306a36Sopenharmony_ci				ret = nvm_authenticate(sw, true);
190762306a36Sopenharmony_ci		} else {
190862306a36Sopenharmony_ci			if (!sw->nvm->flushed) {
190962306a36Sopenharmony_ci				if (!sw->nvm->buf) {
191062306a36Sopenharmony_ci					ret = -EINVAL;
191162306a36Sopenharmony_ci					goto exit_unlock;
191262306a36Sopenharmony_ci				}
191362306a36Sopenharmony_ci
191462306a36Sopenharmony_ci				ret = nvm_validate_and_write(sw);
191562306a36Sopenharmony_ci				if (ret || val == WRITE_ONLY)
191662306a36Sopenharmony_ci					goto exit_unlock;
191762306a36Sopenharmony_ci			}
191862306a36Sopenharmony_ci			if (val == WRITE_AND_AUTHENTICATE) {
191962306a36Sopenharmony_ci				if (disconnect)
192062306a36Sopenharmony_ci					ret = tb_lc_force_power(sw);
192162306a36Sopenharmony_ci				else
192262306a36Sopenharmony_ci					ret = nvm_authenticate(sw, false);
192362306a36Sopenharmony_ci			}
192462306a36Sopenharmony_ci		}
192562306a36Sopenharmony_ci	}
192662306a36Sopenharmony_ci
192762306a36Sopenharmony_ciexit_unlock:
192862306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
192962306a36Sopenharmony_ciexit_rpm:
193062306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&sw->dev);
193162306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&sw->dev);
193262306a36Sopenharmony_ci
193362306a36Sopenharmony_ci	return ret;
193462306a36Sopenharmony_ci}
193562306a36Sopenharmony_ci
193662306a36Sopenharmony_cistatic ssize_t nvm_authenticate_store(struct device *dev,
193762306a36Sopenharmony_ci	struct device_attribute *attr, const char *buf, size_t count)
193862306a36Sopenharmony_ci{
193962306a36Sopenharmony_ci	int ret = nvm_authenticate_sysfs(dev, buf, false);
194062306a36Sopenharmony_ci	if (ret)
194162306a36Sopenharmony_ci		return ret;
194262306a36Sopenharmony_ci	return count;
194362306a36Sopenharmony_ci}
194462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(nvm_authenticate);
194562306a36Sopenharmony_ci
194662306a36Sopenharmony_cistatic ssize_t nvm_authenticate_on_disconnect_show(struct device *dev,
194762306a36Sopenharmony_ci	struct device_attribute *attr, char *buf)
194862306a36Sopenharmony_ci{
194962306a36Sopenharmony_ci	return nvm_authenticate_show(dev, attr, buf);
195062306a36Sopenharmony_ci}
195162306a36Sopenharmony_ci
195262306a36Sopenharmony_cistatic ssize_t nvm_authenticate_on_disconnect_store(struct device *dev,
195362306a36Sopenharmony_ci	struct device_attribute *attr, const char *buf, size_t count)
195462306a36Sopenharmony_ci{
195562306a36Sopenharmony_ci	int ret;
195662306a36Sopenharmony_ci
195762306a36Sopenharmony_ci	ret = nvm_authenticate_sysfs(dev, buf, true);
195862306a36Sopenharmony_ci	return ret ? ret : count;
195962306a36Sopenharmony_ci}
196062306a36Sopenharmony_cistatic DEVICE_ATTR_RW(nvm_authenticate_on_disconnect);
196162306a36Sopenharmony_ci
196262306a36Sopenharmony_cistatic ssize_t nvm_version_show(struct device *dev,
196362306a36Sopenharmony_ci				struct device_attribute *attr, char *buf)
196462306a36Sopenharmony_ci{
196562306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
196662306a36Sopenharmony_ci	int ret;
196762306a36Sopenharmony_ci
196862306a36Sopenharmony_ci	if (!mutex_trylock(&sw->tb->lock))
196962306a36Sopenharmony_ci		return restart_syscall();
197062306a36Sopenharmony_ci
197162306a36Sopenharmony_ci	if (sw->safe_mode)
197262306a36Sopenharmony_ci		ret = -ENODATA;
197362306a36Sopenharmony_ci	else if (!sw->nvm)
197462306a36Sopenharmony_ci		ret = -EAGAIN;
197562306a36Sopenharmony_ci	else
197662306a36Sopenharmony_ci		ret = sysfs_emit(buf, "%x.%x\n", sw->nvm->major, sw->nvm->minor);
197762306a36Sopenharmony_ci
197862306a36Sopenharmony_ci	mutex_unlock(&sw->tb->lock);
197962306a36Sopenharmony_ci
198062306a36Sopenharmony_ci	return ret;
198162306a36Sopenharmony_ci}
198262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(nvm_version);
198362306a36Sopenharmony_ci
198462306a36Sopenharmony_cistatic ssize_t vendor_show(struct device *dev, struct device_attribute *attr,
198562306a36Sopenharmony_ci			   char *buf)
198662306a36Sopenharmony_ci{
198762306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
198862306a36Sopenharmony_ci
198962306a36Sopenharmony_ci	return sysfs_emit(buf, "%#x\n", sw->vendor);
199062306a36Sopenharmony_ci}
199162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(vendor);
199262306a36Sopenharmony_ci
199362306a36Sopenharmony_cistatic ssize_t
199462306a36Sopenharmony_civendor_name_show(struct device *dev, struct device_attribute *attr, char *buf)
199562306a36Sopenharmony_ci{
199662306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
199762306a36Sopenharmony_ci
199862306a36Sopenharmony_ci	return sysfs_emit(buf, "%s\n", sw->vendor_name ?: "");
199962306a36Sopenharmony_ci}
200062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(vendor_name);
200162306a36Sopenharmony_ci
200262306a36Sopenharmony_cistatic ssize_t unique_id_show(struct device *dev, struct device_attribute *attr,
200362306a36Sopenharmony_ci			      char *buf)
200462306a36Sopenharmony_ci{
200562306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
200662306a36Sopenharmony_ci
200762306a36Sopenharmony_ci	return sysfs_emit(buf, "%pUb\n", sw->uuid);
200862306a36Sopenharmony_ci}
200962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(unique_id);
201062306a36Sopenharmony_ci
201162306a36Sopenharmony_cistatic struct attribute *switch_attrs[] = {
201262306a36Sopenharmony_ci	&dev_attr_authorized.attr,
201362306a36Sopenharmony_ci	&dev_attr_boot.attr,
201462306a36Sopenharmony_ci	&dev_attr_device.attr,
201562306a36Sopenharmony_ci	&dev_attr_device_name.attr,
201662306a36Sopenharmony_ci	&dev_attr_generation.attr,
201762306a36Sopenharmony_ci	&dev_attr_key.attr,
201862306a36Sopenharmony_ci	&dev_attr_nvm_authenticate.attr,
201962306a36Sopenharmony_ci	&dev_attr_nvm_authenticate_on_disconnect.attr,
202062306a36Sopenharmony_ci	&dev_attr_nvm_version.attr,
202162306a36Sopenharmony_ci	&dev_attr_rx_speed.attr,
202262306a36Sopenharmony_ci	&dev_attr_rx_lanes.attr,
202362306a36Sopenharmony_ci	&dev_attr_tx_speed.attr,
202462306a36Sopenharmony_ci	&dev_attr_tx_lanes.attr,
202562306a36Sopenharmony_ci	&dev_attr_vendor.attr,
202662306a36Sopenharmony_ci	&dev_attr_vendor_name.attr,
202762306a36Sopenharmony_ci	&dev_attr_unique_id.attr,
202862306a36Sopenharmony_ci	NULL,
202962306a36Sopenharmony_ci};
203062306a36Sopenharmony_ci
203162306a36Sopenharmony_cistatic umode_t switch_attr_is_visible(struct kobject *kobj,
203262306a36Sopenharmony_ci				      struct attribute *attr, int n)
203362306a36Sopenharmony_ci{
203462306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
203562306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
203662306a36Sopenharmony_ci
203762306a36Sopenharmony_ci	if (attr == &dev_attr_authorized.attr) {
203862306a36Sopenharmony_ci		if (sw->tb->security_level == TB_SECURITY_NOPCIE ||
203962306a36Sopenharmony_ci		    sw->tb->security_level == TB_SECURITY_DPONLY)
204062306a36Sopenharmony_ci			return 0;
204162306a36Sopenharmony_ci	} else if (attr == &dev_attr_device.attr) {
204262306a36Sopenharmony_ci		if (!sw->device)
204362306a36Sopenharmony_ci			return 0;
204462306a36Sopenharmony_ci	} else if (attr == &dev_attr_device_name.attr) {
204562306a36Sopenharmony_ci		if (!sw->device_name)
204662306a36Sopenharmony_ci			return 0;
204762306a36Sopenharmony_ci	} else if (attr == &dev_attr_vendor.attr)  {
204862306a36Sopenharmony_ci		if (!sw->vendor)
204962306a36Sopenharmony_ci			return 0;
205062306a36Sopenharmony_ci	} else if (attr == &dev_attr_vendor_name.attr)  {
205162306a36Sopenharmony_ci		if (!sw->vendor_name)
205262306a36Sopenharmony_ci			return 0;
205362306a36Sopenharmony_ci	} else if (attr == &dev_attr_key.attr) {
205462306a36Sopenharmony_ci		if (tb_route(sw) &&
205562306a36Sopenharmony_ci		    sw->tb->security_level == TB_SECURITY_SECURE &&
205662306a36Sopenharmony_ci		    sw->security_level == TB_SECURITY_SECURE)
205762306a36Sopenharmony_ci			return attr->mode;
205862306a36Sopenharmony_ci		return 0;
205962306a36Sopenharmony_ci	} else if (attr == &dev_attr_rx_speed.attr ||
206062306a36Sopenharmony_ci		   attr == &dev_attr_rx_lanes.attr ||
206162306a36Sopenharmony_ci		   attr == &dev_attr_tx_speed.attr ||
206262306a36Sopenharmony_ci		   attr == &dev_attr_tx_lanes.attr) {
206362306a36Sopenharmony_ci		if (tb_route(sw))
206462306a36Sopenharmony_ci			return attr->mode;
206562306a36Sopenharmony_ci		return 0;
206662306a36Sopenharmony_ci	} else if (attr == &dev_attr_nvm_authenticate.attr) {
206762306a36Sopenharmony_ci		if (nvm_upgradeable(sw))
206862306a36Sopenharmony_ci			return attr->mode;
206962306a36Sopenharmony_ci		return 0;
207062306a36Sopenharmony_ci	} else if (attr == &dev_attr_nvm_version.attr) {
207162306a36Sopenharmony_ci		if (nvm_readable(sw))
207262306a36Sopenharmony_ci			return attr->mode;
207362306a36Sopenharmony_ci		return 0;
207462306a36Sopenharmony_ci	} else if (attr == &dev_attr_boot.attr) {
207562306a36Sopenharmony_ci		if (tb_route(sw))
207662306a36Sopenharmony_ci			return attr->mode;
207762306a36Sopenharmony_ci		return 0;
207862306a36Sopenharmony_ci	} else if (attr == &dev_attr_nvm_authenticate_on_disconnect.attr) {
207962306a36Sopenharmony_ci		if (sw->quirks & QUIRK_FORCE_POWER_LINK_CONTROLLER)
208062306a36Sopenharmony_ci			return attr->mode;
208162306a36Sopenharmony_ci		return 0;
208262306a36Sopenharmony_ci	}
208362306a36Sopenharmony_ci
208462306a36Sopenharmony_ci	return sw->safe_mode ? 0 : attr->mode;
208562306a36Sopenharmony_ci}
208662306a36Sopenharmony_ci
208762306a36Sopenharmony_cistatic const struct attribute_group switch_group = {
208862306a36Sopenharmony_ci	.is_visible = switch_attr_is_visible,
208962306a36Sopenharmony_ci	.attrs = switch_attrs,
209062306a36Sopenharmony_ci};
209162306a36Sopenharmony_ci
209262306a36Sopenharmony_cistatic const struct attribute_group *switch_groups[] = {
209362306a36Sopenharmony_ci	&switch_group,
209462306a36Sopenharmony_ci	NULL,
209562306a36Sopenharmony_ci};
209662306a36Sopenharmony_ci
209762306a36Sopenharmony_cistatic void tb_switch_release(struct device *dev)
209862306a36Sopenharmony_ci{
209962306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
210062306a36Sopenharmony_ci	struct tb_port *port;
210162306a36Sopenharmony_ci
210262306a36Sopenharmony_ci	dma_port_free(sw->dma_port);
210362306a36Sopenharmony_ci
210462306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
210562306a36Sopenharmony_ci		ida_destroy(&port->in_hopids);
210662306a36Sopenharmony_ci		ida_destroy(&port->out_hopids);
210762306a36Sopenharmony_ci	}
210862306a36Sopenharmony_ci
210962306a36Sopenharmony_ci	kfree(sw->uuid);
211062306a36Sopenharmony_ci	kfree(sw->device_name);
211162306a36Sopenharmony_ci	kfree(sw->vendor_name);
211262306a36Sopenharmony_ci	kfree(sw->ports);
211362306a36Sopenharmony_ci	kfree(sw->drom);
211462306a36Sopenharmony_ci	kfree(sw->key);
211562306a36Sopenharmony_ci	kfree(sw);
211662306a36Sopenharmony_ci}
211762306a36Sopenharmony_ci
211862306a36Sopenharmony_cistatic int tb_switch_uevent(const struct device *dev, struct kobj_uevent_env *env)
211962306a36Sopenharmony_ci{
212062306a36Sopenharmony_ci	const struct tb_switch *sw = tb_to_switch(dev);
212162306a36Sopenharmony_ci	const char *type;
212262306a36Sopenharmony_ci
212362306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
212462306a36Sopenharmony_ci		if (add_uevent_var(env, "USB4_VERSION=%u.0",
212562306a36Sopenharmony_ci				   usb4_switch_version(sw)))
212662306a36Sopenharmony_ci			return -ENOMEM;
212762306a36Sopenharmony_ci	}
212862306a36Sopenharmony_ci
212962306a36Sopenharmony_ci	if (!tb_route(sw)) {
213062306a36Sopenharmony_ci		type = "host";
213162306a36Sopenharmony_ci	} else {
213262306a36Sopenharmony_ci		const struct tb_port *port;
213362306a36Sopenharmony_ci		bool hub = false;
213462306a36Sopenharmony_ci
213562306a36Sopenharmony_ci		/* Device is hub if it has any downstream ports */
213662306a36Sopenharmony_ci		tb_switch_for_each_port(sw, port) {
213762306a36Sopenharmony_ci			if (!port->disabled && !tb_is_upstream_port(port) &&
213862306a36Sopenharmony_ci			     tb_port_is_null(port)) {
213962306a36Sopenharmony_ci				hub = true;
214062306a36Sopenharmony_ci				break;
214162306a36Sopenharmony_ci			}
214262306a36Sopenharmony_ci		}
214362306a36Sopenharmony_ci
214462306a36Sopenharmony_ci		type = hub ? "hub" : "device";
214562306a36Sopenharmony_ci	}
214662306a36Sopenharmony_ci
214762306a36Sopenharmony_ci	if (add_uevent_var(env, "USB4_TYPE=%s", type))
214862306a36Sopenharmony_ci		return -ENOMEM;
214962306a36Sopenharmony_ci	return 0;
215062306a36Sopenharmony_ci}
215162306a36Sopenharmony_ci
215262306a36Sopenharmony_ci/*
215362306a36Sopenharmony_ci * Currently only need to provide the callbacks. Everything else is handled
215462306a36Sopenharmony_ci * in the connection manager.
215562306a36Sopenharmony_ci */
215662306a36Sopenharmony_cistatic int __maybe_unused tb_switch_runtime_suspend(struct device *dev)
215762306a36Sopenharmony_ci{
215862306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
215962306a36Sopenharmony_ci	const struct tb_cm_ops *cm_ops = sw->tb->cm_ops;
216062306a36Sopenharmony_ci
216162306a36Sopenharmony_ci	if (cm_ops->runtime_suspend_switch)
216262306a36Sopenharmony_ci		return cm_ops->runtime_suspend_switch(sw);
216362306a36Sopenharmony_ci
216462306a36Sopenharmony_ci	return 0;
216562306a36Sopenharmony_ci}
216662306a36Sopenharmony_ci
216762306a36Sopenharmony_cistatic int __maybe_unused tb_switch_runtime_resume(struct device *dev)
216862306a36Sopenharmony_ci{
216962306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
217062306a36Sopenharmony_ci	const struct tb_cm_ops *cm_ops = sw->tb->cm_ops;
217162306a36Sopenharmony_ci
217262306a36Sopenharmony_ci	if (cm_ops->runtime_resume_switch)
217362306a36Sopenharmony_ci		return cm_ops->runtime_resume_switch(sw);
217462306a36Sopenharmony_ci	return 0;
217562306a36Sopenharmony_ci}
217662306a36Sopenharmony_ci
217762306a36Sopenharmony_cistatic const struct dev_pm_ops tb_switch_pm_ops = {
217862306a36Sopenharmony_ci	SET_RUNTIME_PM_OPS(tb_switch_runtime_suspend, tb_switch_runtime_resume,
217962306a36Sopenharmony_ci			   NULL)
218062306a36Sopenharmony_ci};
218162306a36Sopenharmony_ci
218262306a36Sopenharmony_cistruct device_type tb_switch_type = {
218362306a36Sopenharmony_ci	.name = "thunderbolt_device",
218462306a36Sopenharmony_ci	.release = tb_switch_release,
218562306a36Sopenharmony_ci	.uevent = tb_switch_uevent,
218662306a36Sopenharmony_ci	.pm = &tb_switch_pm_ops,
218762306a36Sopenharmony_ci};
218862306a36Sopenharmony_ci
218962306a36Sopenharmony_cistatic int tb_switch_get_generation(struct tb_switch *sw)
219062306a36Sopenharmony_ci{
219162306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
219262306a36Sopenharmony_ci		return 4;
219362306a36Sopenharmony_ci
219462306a36Sopenharmony_ci	if (sw->config.vendor_id == PCI_VENDOR_ID_INTEL) {
219562306a36Sopenharmony_ci		switch (sw->config.device_id) {
219662306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE:
219762306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_EAGLE_RIDGE:
219862306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_LIGHT_PEAK:
219962306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_2C:
220062306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C:
220162306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
220262306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_REDWOOD_RIDGE_2C_BRIDGE:
220362306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_REDWOOD_RIDGE_4C_BRIDGE:
220462306a36Sopenharmony_ci			return 1;
220562306a36Sopenharmony_ci
220662306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_WIN_RIDGE_2C_BRIDGE:
220762306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_BRIDGE:
220862306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_BRIDGE:
220962306a36Sopenharmony_ci			return 2;
221062306a36Sopenharmony_ci
221162306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_BRIDGE:
221262306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_BRIDGE:
221362306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_BRIDGE:
221462306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_BRIDGE:
221562306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_BRIDGE:
221662306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_BRIDGE:
221762306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_BRIDGE:
221862306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_BRIDGE:
221962306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ICL_NHI0:
222062306a36Sopenharmony_ci		case PCI_DEVICE_ID_INTEL_ICL_NHI1:
222162306a36Sopenharmony_ci			return 3;
222262306a36Sopenharmony_ci		}
222362306a36Sopenharmony_ci	}
222462306a36Sopenharmony_ci
222562306a36Sopenharmony_ci	/*
222662306a36Sopenharmony_ci	 * For unknown switches assume generation to be 1 to be on the
222762306a36Sopenharmony_ci	 * safe side.
222862306a36Sopenharmony_ci	 */
222962306a36Sopenharmony_ci	tb_sw_warn(sw, "unsupported switch device id %#x\n",
223062306a36Sopenharmony_ci		   sw->config.device_id);
223162306a36Sopenharmony_ci	return 1;
223262306a36Sopenharmony_ci}
223362306a36Sopenharmony_ci
223462306a36Sopenharmony_cistatic bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth)
223562306a36Sopenharmony_ci{
223662306a36Sopenharmony_ci	int max_depth;
223762306a36Sopenharmony_ci
223862306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw) ||
223962306a36Sopenharmony_ci	    (sw->tb->root_switch && tb_switch_is_usb4(sw->tb->root_switch)))
224062306a36Sopenharmony_ci		max_depth = USB4_SWITCH_MAX_DEPTH;
224162306a36Sopenharmony_ci	else
224262306a36Sopenharmony_ci		max_depth = TB_SWITCH_MAX_DEPTH;
224362306a36Sopenharmony_ci
224462306a36Sopenharmony_ci	return depth > max_depth;
224562306a36Sopenharmony_ci}
224662306a36Sopenharmony_ci
224762306a36Sopenharmony_ci/**
224862306a36Sopenharmony_ci * tb_switch_alloc() - allocate a switch
224962306a36Sopenharmony_ci * @tb: Pointer to the owning domain
225062306a36Sopenharmony_ci * @parent: Parent device for this switch
225162306a36Sopenharmony_ci * @route: Route string for this switch
225262306a36Sopenharmony_ci *
225362306a36Sopenharmony_ci * Allocates and initializes a switch. Will not upload configuration to
225462306a36Sopenharmony_ci * the switch. For that you need to call tb_switch_configure()
225562306a36Sopenharmony_ci * separately. The returned switch should be released by calling
225662306a36Sopenharmony_ci * tb_switch_put().
225762306a36Sopenharmony_ci *
225862306a36Sopenharmony_ci * Return: Pointer to the allocated switch or ERR_PTR() in case of
225962306a36Sopenharmony_ci * failure.
226062306a36Sopenharmony_ci */
226162306a36Sopenharmony_cistruct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
226262306a36Sopenharmony_ci				  u64 route)
226362306a36Sopenharmony_ci{
226462306a36Sopenharmony_ci	struct tb_switch *sw;
226562306a36Sopenharmony_ci	int upstream_port;
226662306a36Sopenharmony_ci	int i, ret, depth;
226762306a36Sopenharmony_ci
226862306a36Sopenharmony_ci	/* Unlock the downstream port so we can access the switch below */
226962306a36Sopenharmony_ci	if (route) {
227062306a36Sopenharmony_ci		struct tb_switch *parent_sw = tb_to_switch(parent);
227162306a36Sopenharmony_ci		struct tb_port *down;
227262306a36Sopenharmony_ci
227362306a36Sopenharmony_ci		down = tb_port_at(route, parent_sw);
227462306a36Sopenharmony_ci		tb_port_unlock(down);
227562306a36Sopenharmony_ci	}
227662306a36Sopenharmony_ci
227762306a36Sopenharmony_ci	depth = tb_route_length(route);
227862306a36Sopenharmony_ci
227962306a36Sopenharmony_ci	upstream_port = tb_cfg_get_upstream_port(tb->ctl, route);
228062306a36Sopenharmony_ci	if (upstream_port < 0)
228162306a36Sopenharmony_ci		return ERR_PTR(upstream_port);
228262306a36Sopenharmony_ci
228362306a36Sopenharmony_ci	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
228462306a36Sopenharmony_ci	if (!sw)
228562306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
228662306a36Sopenharmony_ci
228762306a36Sopenharmony_ci	sw->tb = tb;
228862306a36Sopenharmony_ci	ret = tb_cfg_read(tb->ctl, &sw->config, route, 0, TB_CFG_SWITCH, 0, 5);
228962306a36Sopenharmony_ci	if (ret)
229062306a36Sopenharmony_ci		goto err_free_sw_ports;
229162306a36Sopenharmony_ci
229262306a36Sopenharmony_ci	sw->generation = tb_switch_get_generation(sw);
229362306a36Sopenharmony_ci
229462306a36Sopenharmony_ci	tb_dbg(tb, "current switch config:\n");
229562306a36Sopenharmony_ci	tb_dump_switch(tb, sw);
229662306a36Sopenharmony_ci
229762306a36Sopenharmony_ci	/* configure switch */
229862306a36Sopenharmony_ci	sw->config.upstream_port_number = upstream_port;
229962306a36Sopenharmony_ci	sw->config.depth = depth;
230062306a36Sopenharmony_ci	sw->config.route_hi = upper_32_bits(route);
230162306a36Sopenharmony_ci	sw->config.route_lo = lower_32_bits(route);
230262306a36Sopenharmony_ci	sw->config.enabled = 0;
230362306a36Sopenharmony_ci
230462306a36Sopenharmony_ci	/* Make sure we do not exceed maximum topology limit */
230562306a36Sopenharmony_ci	if (tb_switch_exceeds_max_depth(sw, depth)) {
230662306a36Sopenharmony_ci		ret = -EADDRNOTAVAIL;
230762306a36Sopenharmony_ci		goto err_free_sw_ports;
230862306a36Sopenharmony_ci	}
230962306a36Sopenharmony_ci
231062306a36Sopenharmony_ci	/* initialize ports */
231162306a36Sopenharmony_ci	sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
231262306a36Sopenharmony_ci				GFP_KERNEL);
231362306a36Sopenharmony_ci	if (!sw->ports) {
231462306a36Sopenharmony_ci		ret = -ENOMEM;
231562306a36Sopenharmony_ci		goto err_free_sw_ports;
231662306a36Sopenharmony_ci	}
231762306a36Sopenharmony_ci
231862306a36Sopenharmony_ci	for (i = 0; i <= sw->config.max_port_number; i++) {
231962306a36Sopenharmony_ci		/* minimum setup for tb_find_cap and tb_drom_read to work */
232062306a36Sopenharmony_ci		sw->ports[i].sw = sw;
232162306a36Sopenharmony_ci		sw->ports[i].port = i;
232262306a36Sopenharmony_ci
232362306a36Sopenharmony_ci		/* Control port does not need HopID allocation */
232462306a36Sopenharmony_ci		if (i) {
232562306a36Sopenharmony_ci			ida_init(&sw->ports[i].in_hopids);
232662306a36Sopenharmony_ci			ida_init(&sw->ports[i].out_hopids);
232762306a36Sopenharmony_ci		}
232862306a36Sopenharmony_ci	}
232962306a36Sopenharmony_ci
233062306a36Sopenharmony_ci	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
233162306a36Sopenharmony_ci	if (ret > 0)
233262306a36Sopenharmony_ci		sw->cap_plug_events = ret;
233362306a36Sopenharmony_ci
233462306a36Sopenharmony_ci	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_TIME2);
233562306a36Sopenharmony_ci	if (ret > 0)
233662306a36Sopenharmony_ci		sw->cap_vsec_tmu = ret;
233762306a36Sopenharmony_ci
233862306a36Sopenharmony_ci	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER);
233962306a36Sopenharmony_ci	if (ret > 0)
234062306a36Sopenharmony_ci		sw->cap_lc = ret;
234162306a36Sopenharmony_ci
234262306a36Sopenharmony_ci	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_CP_LP);
234362306a36Sopenharmony_ci	if (ret > 0)
234462306a36Sopenharmony_ci		sw->cap_lp = ret;
234562306a36Sopenharmony_ci
234662306a36Sopenharmony_ci	/* Root switch is always authorized */
234762306a36Sopenharmony_ci	if (!route)
234862306a36Sopenharmony_ci		sw->authorized = true;
234962306a36Sopenharmony_ci
235062306a36Sopenharmony_ci	device_initialize(&sw->dev);
235162306a36Sopenharmony_ci	sw->dev.parent = parent;
235262306a36Sopenharmony_ci	sw->dev.bus = &tb_bus_type;
235362306a36Sopenharmony_ci	sw->dev.type = &tb_switch_type;
235462306a36Sopenharmony_ci	sw->dev.groups = switch_groups;
235562306a36Sopenharmony_ci	dev_set_name(&sw->dev, "%u-%llx", tb->index, tb_route(sw));
235662306a36Sopenharmony_ci
235762306a36Sopenharmony_ci	return sw;
235862306a36Sopenharmony_ci
235962306a36Sopenharmony_cierr_free_sw_ports:
236062306a36Sopenharmony_ci	kfree(sw->ports);
236162306a36Sopenharmony_ci	kfree(sw);
236262306a36Sopenharmony_ci
236362306a36Sopenharmony_ci	return ERR_PTR(ret);
236462306a36Sopenharmony_ci}
236562306a36Sopenharmony_ci
236662306a36Sopenharmony_ci/**
236762306a36Sopenharmony_ci * tb_switch_alloc_safe_mode() - allocate a switch that is in safe mode
236862306a36Sopenharmony_ci * @tb: Pointer to the owning domain
236962306a36Sopenharmony_ci * @parent: Parent device for this switch
237062306a36Sopenharmony_ci * @route: Route string for this switch
237162306a36Sopenharmony_ci *
237262306a36Sopenharmony_ci * This creates a switch in safe mode. This means the switch pretty much
237362306a36Sopenharmony_ci * lacks all capabilities except DMA configuration port before it is
237462306a36Sopenharmony_ci * flashed with a valid NVM firmware.
237562306a36Sopenharmony_ci *
237662306a36Sopenharmony_ci * The returned switch must be released by calling tb_switch_put().
237762306a36Sopenharmony_ci *
237862306a36Sopenharmony_ci * Return: Pointer to the allocated switch or ERR_PTR() in case of failure
237962306a36Sopenharmony_ci */
238062306a36Sopenharmony_cistruct tb_switch *
238162306a36Sopenharmony_citb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
238262306a36Sopenharmony_ci{
238362306a36Sopenharmony_ci	struct tb_switch *sw;
238462306a36Sopenharmony_ci
238562306a36Sopenharmony_ci	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
238662306a36Sopenharmony_ci	if (!sw)
238762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
238862306a36Sopenharmony_ci
238962306a36Sopenharmony_ci	sw->tb = tb;
239062306a36Sopenharmony_ci	sw->config.depth = tb_route_length(route);
239162306a36Sopenharmony_ci	sw->config.route_hi = upper_32_bits(route);
239262306a36Sopenharmony_ci	sw->config.route_lo = lower_32_bits(route);
239362306a36Sopenharmony_ci	sw->safe_mode = true;
239462306a36Sopenharmony_ci
239562306a36Sopenharmony_ci	device_initialize(&sw->dev);
239662306a36Sopenharmony_ci	sw->dev.parent = parent;
239762306a36Sopenharmony_ci	sw->dev.bus = &tb_bus_type;
239862306a36Sopenharmony_ci	sw->dev.type = &tb_switch_type;
239962306a36Sopenharmony_ci	sw->dev.groups = switch_groups;
240062306a36Sopenharmony_ci	dev_set_name(&sw->dev, "%u-%llx", tb->index, tb_route(sw));
240162306a36Sopenharmony_ci
240262306a36Sopenharmony_ci	return sw;
240362306a36Sopenharmony_ci}
240462306a36Sopenharmony_ci
240562306a36Sopenharmony_ci/**
240662306a36Sopenharmony_ci * tb_switch_configure() - Uploads configuration to the switch
240762306a36Sopenharmony_ci * @sw: Switch to configure
240862306a36Sopenharmony_ci *
240962306a36Sopenharmony_ci * Call this function before the switch is added to the system. It will
241062306a36Sopenharmony_ci * upload configuration to the switch and makes it available for the
241162306a36Sopenharmony_ci * connection manager to use. Can be called to the switch again after
241262306a36Sopenharmony_ci * resume from low power states to re-initialize it.
241362306a36Sopenharmony_ci *
241462306a36Sopenharmony_ci * Return: %0 in case of success and negative errno in case of failure
241562306a36Sopenharmony_ci */
241662306a36Sopenharmony_ciint tb_switch_configure(struct tb_switch *sw)
241762306a36Sopenharmony_ci{
241862306a36Sopenharmony_ci	struct tb *tb = sw->tb;
241962306a36Sopenharmony_ci	u64 route;
242062306a36Sopenharmony_ci	int ret;
242162306a36Sopenharmony_ci
242262306a36Sopenharmony_ci	route = tb_route(sw);
242362306a36Sopenharmony_ci
242462306a36Sopenharmony_ci	tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n",
242562306a36Sopenharmony_ci	       sw->config.enabled ? "restoring" : "initializing", route,
242662306a36Sopenharmony_ci	       tb_route_length(route), sw->config.upstream_port_number);
242762306a36Sopenharmony_ci
242862306a36Sopenharmony_ci	sw->config.enabled = 1;
242962306a36Sopenharmony_ci
243062306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
243162306a36Sopenharmony_ci		/*
243262306a36Sopenharmony_ci		 * For USB4 devices, we need to program the CM version
243362306a36Sopenharmony_ci		 * accordingly so that it knows to expose all the
243462306a36Sopenharmony_ci		 * additional capabilities. Program it according to USB4
243562306a36Sopenharmony_ci		 * version to avoid changing existing (v1) routers behaviour.
243662306a36Sopenharmony_ci		 */
243762306a36Sopenharmony_ci		if (usb4_switch_version(sw) < 2)
243862306a36Sopenharmony_ci			sw->config.cmuv = ROUTER_CS_4_CMUV_V1;
243962306a36Sopenharmony_ci		else
244062306a36Sopenharmony_ci			sw->config.cmuv = ROUTER_CS_4_CMUV_V2;
244162306a36Sopenharmony_ci		sw->config.plug_events_delay = 0xa;
244262306a36Sopenharmony_ci
244362306a36Sopenharmony_ci		/* Enumerate the switch */
244462306a36Sopenharmony_ci		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
244562306a36Sopenharmony_ci				  ROUTER_CS_1, 4);
244662306a36Sopenharmony_ci		if (ret)
244762306a36Sopenharmony_ci			return ret;
244862306a36Sopenharmony_ci
244962306a36Sopenharmony_ci		ret = usb4_switch_setup(sw);
245062306a36Sopenharmony_ci	} else {
245162306a36Sopenharmony_ci		if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
245262306a36Sopenharmony_ci			tb_sw_warn(sw, "unknown switch vendor id %#x\n",
245362306a36Sopenharmony_ci				   sw->config.vendor_id);
245462306a36Sopenharmony_ci
245562306a36Sopenharmony_ci		if (!sw->cap_plug_events) {
245662306a36Sopenharmony_ci			tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
245762306a36Sopenharmony_ci			return -ENODEV;
245862306a36Sopenharmony_ci		}
245962306a36Sopenharmony_ci
246062306a36Sopenharmony_ci		/* Enumerate the switch */
246162306a36Sopenharmony_ci		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
246262306a36Sopenharmony_ci				  ROUTER_CS_1, 3);
246362306a36Sopenharmony_ci	}
246462306a36Sopenharmony_ci	if (ret)
246562306a36Sopenharmony_ci		return ret;
246662306a36Sopenharmony_ci
246762306a36Sopenharmony_ci	return tb_plug_events_active(sw, true);
246862306a36Sopenharmony_ci}
246962306a36Sopenharmony_ci
247062306a36Sopenharmony_ci/**
247162306a36Sopenharmony_ci * tb_switch_configuration_valid() - Set the tunneling configuration to be valid
247262306a36Sopenharmony_ci * @sw: Router to configure
247362306a36Sopenharmony_ci *
247462306a36Sopenharmony_ci * Needs to be called before any tunnels can be setup through the
247562306a36Sopenharmony_ci * router. Can be called to any router.
247662306a36Sopenharmony_ci *
247762306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
247862306a36Sopenharmony_ci */
247962306a36Sopenharmony_ciint tb_switch_configuration_valid(struct tb_switch *sw)
248062306a36Sopenharmony_ci{
248162306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
248262306a36Sopenharmony_ci		return usb4_switch_configuration_valid(sw);
248362306a36Sopenharmony_ci	return 0;
248462306a36Sopenharmony_ci}
248562306a36Sopenharmony_ci
248662306a36Sopenharmony_cistatic int tb_switch_set_uuid(struct tb_switch *sw)
248762306a36Sopenharmony_ci{
248862306a36Sopenharmony_ci	bool uid = false;
248962306a36Sopenharmony_ci	u32 uuid[4];
249062306a36Sopenharmony_ci	int ret;
249162306a36Sopenharmony_ci
249262306a36Sopenharmony_ci	if (sw->uuid)
249362306a36Sopenharmony_ci		return 0;
249462306a36Sopenharmony_ci
249562306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
249662306a36Sopenharmony_ci		ret = usb4_switch_read_uid(sw, &sw->uid);
249762306a36Sopenharmony_ci		if (ret)
249862306a36Sopenharmony_ci			return ret;
249962306a36Sopenharmony_ci		uid = true;
250062306a36Sopenharmony_ci	} else {
250162306a36Sopenharmony_ci		/*
250262306a36Sopenharmony_ci		 * The newer controllers include fused UUID as part of
250362306a36Sopenharmony_ci		 * link controller specific registers
250462306a36Sopenharmony_ci		 */
250562306a36Sopenharmony_ci		ret = tb_lc_read_uuid(sw, uuid);
250662306a36Sopenharmony_ci		if (ret) {
250762306a36Sopenharmony_ci			if (ret != -EINVAL)
250862306a36Sopenharmony_ci				return ret;
250962306a36Sopenharmony_ci			uid = true;
251062306a36Sopenharmony_ci		}
251162306a36Sopenharmony_ci	}
251262306a36Sopenharmony_ci
251362306a36Sopenharmony_ci	if (uid) {
251462306a36Sopenharmony_ci		/*
251562306a36Sopenharmony_ci		 * ICM generates UUID based on UID and fills the upper
251662306a36Sopenharmony_ci		 * two words with ones. This is not strictly following
251762306a36Sopenharmony_ci		 * UUID format but we want to be compatible with it so
251862306a36Sopenharmony_ci		 * we do the same here.
251962306a36Sopenharmony_ci		 */
252062306a36Sopenharmony_ci		uuid[0] = sw->uid & 0xffffffff;
252162306a36Sopenharmony_ci		uuid[1] = (sw->uid >> 32) & 0xffffffff;
252262306a36Sopenharmony_ci		uuid[2] = 0xffffffff;
252362306a36Sopenharmony_ci		uuid[3] = 0xffffffff;
252462306a36Sopenharmony_ci	}
252562306a36Sopenharmony_ci
252662306a36Sopenharmony_ci	sw->uuid = kmemdup(uuid, sizeof(uuid), GFP_KERNEL);
252762306a36Sopenharmony_ci	if (!sw->uuid)
252862306a36Sopenharmony_ci		return -ENOMEM;
252962306a36Sopenharmony_ci	return 0;
253062306a36Sopenharmony_ci}
253162306a36Sopenharmony_ci
253262306a36Sopenharmony_cistatic int tb_switch_add_dma_port(struct tb_switch *sw)
253362306a36Sopenharmony_ci{
253462306a36Sopenharmony_ci	u32 status;
253562306a36Sopenharmony_ci	int ret;
253662306a36Sopenharmony_ci
253762306a36Sopenharmony_ci	switch (sw->generation) {
253862306a36Sopenharmony_ci	case 2:
253962306a36Sopenharmony_ci		/* Only root switch can be upgraded */
254062306a36Sopenharmony_ci		if (tb_route(sw))
254162306a36Sopenharmony_ci			return 0;
254262306a36Sopenharmony_ci
254362306a36Sopenharmony_ci		fallthrough;
254462306a36Sopenharmony_ci	case 3:
254562306a36Sopenharmony_ci	case 4:
254662306a36Sopenharmony_ci		ret = tb_switch_set_uuid(sw);
254762306a36Sopenharmony_ci		if (ret)
254862306a36Sopenharmony_ci			return ret;
254962306a36Sopenharmony_ci		break;
255062306a36Sopenharmony_ci
255162306a36Sopenharmony_ci	default:
255262306a36Sopenharmony_ci		/*
255362306a36Sopenharmony_ci		 * DMA port is the only thing available when the switch
255462306a36Sopenharmony_ci		 * is in safe mode.
255562306a36Sopenharmony_ci		 */
255662306a36Sopenharmony_ci		if (!sw->safe_mode)
255762306a36Sopenharmony_ci			return 0;
255862306a36Sopenharmony_ci		break;
255962306a36Sopenharmony_ci	}
256062306a36Sopenharmony_ci
256162306a36Sopenharmony_ci	if (sw->no_nvm_upgrade)
256262306a36Sopenharmony_ci		return 0;
256362306a36Sopenharmony_ci
256462306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw)) {
256562306a36Sopenharmony_ci		ret = usb4_switch_nvm_authenticate_status(sw, &status);
256662306a36Sopenharmony_ci		if (ret)
256762306a36Sopenharmony_ci			return ret;
256862306a36Sopenharmony_ci
256962306a36Sopenharmony_ci		if (status) {
257062306a36Sopenharmony_ci			tb_sw_info(sw, "switch flash authentication failed\n");
257162306a36Sopenharmony_ci			nvm_set_auth_status(sw, status);
257262306a36Sopenharmony_ci		}
257362306a36Sopenharmony_ci
257462306a36Sopenharmony_ci		return 0;
257562306a36Sopenharmony_ci	}
257662306a36Sopenharmony_ci
257762306a36Sopenharmony_ci	/* Root switch DMA port requires running firmware */
257862306a36Sopenharmony_ci	if (!tb_route(sw) && !tb_switch_is_icm(sw))
257962306a36Sopenharmony_ci		return 0;
258062306a36Sopenharmony_ci
258162306a36Sopenharmony_ci	sw->dma_port = dma_port_alloc(sw);
258262306a36Sopenharmony_ci	if (!sw->dma_port)
258362306a36Sopenharmony_ci		return 0;
258462306a36Sopenharmony_ci
258562306a36Sopenharmony_ci	/*
258662306a36Sopenharmony_ci	 * If there is status already set then authentication failed
258762306a36Sopenharmony_ci	 * when the dma_port_flash_update_auth() returned. Power cycling
258862306a36Sopenharmony_ci	 * is not needed (it was done already) so only thing we do here
258962306a36Sopenharmony_ci	 * is to unblock runtime PM of the root port.
259062306a36Sopenharmony_ci	 */
259162306a36Sopenharmony_ci	nvm_get_auth_status(sw, &status);
259262306a36Sopenharmony_ci	if (status) {
259362306a36Sopenharmony_ci		if (!tb_route(sw))
259462306a36Sopenharmony_ci			nvm_authenticate_complete_dma_port(sw);
259562306a36Sopenharmony_ci		return 0;
259662306a36Sopenharmony_ci	}
259762306a36Sopenharmony_ci
259862306a36Sopenharmony_ci	/*
259962306a36Sopenharmony_ci	 * Check status of the previous flash authentication. If there
260062306a36Sopenharmony_ci	 * is one we need to power cycle the switch in any case to make
260162306a36Sopenharmony_ci	 * it functional again.
260262306a36Sopenharmony_ci	 */
260362306a36Sopenharmony_ci	ret = dma_port_flash_update_auth_status(sw->dma_port, &status);
260462306a36Sopenharmony_ci	if (ret <= 0)
260562306a36Sopenharmony_ci		return ret;
260662306a36Sopenharmony_ci
260762306a36Sopenharmony_ci	/* Now we can allow root port to suspend again */
260862306a36Sopenharmony_ci	if (!tb_route(sw))
260962306a36Sopenharmony_ci		nvm_authenticate_complete_dma_port(sw);
261062306a36Sopenharmony_ci
261162306a36Sopenharmony_ci	if (status) {
261262306a36Sopenharmony_ci		tb_sw_info(sw, "switch flash authentication failed\n");
261362306a36Sopenharmony_ci		nvm_set_auth_status(sw, status);
261462306a36Sopenharmony_ci	}
261562306a36Sopenharmony_ci
261662306a36Sopenharmony_ci	tb_sw_info(sw, "power cycling the switch now\n");
261762306a36Sopenharmony_ci	dma_port_power_cycle(sw->dma_port);
261862306a36Sopenharmony_ci
261962306a36Sopenharmony_ci	/*
262062306a36Sopenharmony_ci	 * We return error here which causes the switch adding failure.
262162306a36Sopenharmony_ci	 * It should appear back after power cycle is complete.
262262306a36Sopenharmony_ci	 */
262362306a36Sopenharmony_ci	return -ESHUTDOWN;
262462306a36Sopenharmony_ci}
262562306a36Sopenharmony_ci
262662306a36Sopenharmony_cistatic void tb_switch_default_link_ports(struct tb_switch *sw)
262762306a36Sopenharmony_ci{
262862306a36Sopenharmony_ci	int i;
262962306a36Sopenharmony_ci
263062306a36Sopenharmony_ci	for (i = 1; i <= sw->config.max_port_number; i++) {
263162306a36Sopenharmony_ci		struct tb_port *port = &sw->ports[i];
263262306a36Sopenharmony_ci		struct tb_port *subordinate;
263362306a36Sopenharmony_ci
263462306a36Sopenharmony_ci		if (!tb_port_is_null(port))
263562306a36Sopenharmony_ci			continue;
263662306a36Sopenharmony_ci
263762306a36Sopenharmony_ci		/* Check for the subordinate port */
263862306a36Sopenharmony_ci		if (i == sw->config.max_port_number ||
263962306a36Sopenharmony_ci		    !tb_port_is_null(&sw->ports[i + 1]))
264062306a36Sopenharmony_ci			continue;
264162306a36Sopenharmony_ci
264262306a36Sopenharmony_ci		/* Link them if not already done so (by DROM) */
264362306a36Sopenharmony_ci		subordinate = &sw->ports[i + 1];
264462306a36Sopenharmony_ci		if (!port->dual_link_port && !subordinate->dual_link_port) {
264562306a36Sopenharmony_ci			port->link_nr = 0;
264662306a36Sopenharmony_ci			port->dual_link_port = subordinate;
264762306a36Sopenharmony_ci			subordinate->link_nr = 1;
264862306a36Sopenharmony_ci			subordinate->dual_link_port = port;
264962306a36Sopenharmony_ci
265062306a36Sopenharmony_ci			tb_sw_dbg(sw, "linked ports %d <-> %d\n",
265162306a36Sopenharmony_ci				  port->port, subordinate->port);
265262306a36Sopenharmony_ci		}
265362306a36Sopenharmony_ci	}
265462306a36Sopenharmony_ci}
265562306a36Sopenharmony_ci
265662306a36Sopenharmony_cistatic bool tb_switch_lane_bonding_possible(struct tb_switch *sw)
265762306a36Sopenharmony_ci{
265862306a36Sopenharmony_ci	const struct tb_port *up = tb_upstream_port(sw);
265962306a36Sopenharmony_ci
266062306a36Sopenharmony_ci	if (!up->dual_link_port || !up->dual_link_port->remote)
266162306a36Sopenharmony_ci		return false;
266262306a36Sopenharmony_ci
266362306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
266462306a36Sopenharmony_ci		return usb4_switch_lane_bonding_possible(sw);
266562306a36Sopenharmony_ci	return tb_lc_lane_bonding_possible(sw);
266662306a36Sopenharmony_ci}
266762306a36Sopenharmony_ci
266862306a36Sopenharmony_cistatic int tb_switch_update_link_attributes(struct tb_switch *sw)
266962306a36Sopenharmony_ci{
267062306a36Sopenharmony_ci	struct tb_port *up;
267162306a36Sopenharmony_ci	bool change = false;
267262306a36Sopenharmony_ci	int ret;
267362306a36Sopenharmony_ci
267462306a36Sopenharmony_ci	if (!tb_route(sw) || tb_switch_is_icm(sw))
267562306a36Sopenharmony_ci		return 0;
267662306a36Sopenharmony_ci
267762306a36Sopenharmony_ci	up = tb_upstream_port(sw);
267862306a36Sopenharmony_ci
267962306a36Sopenharmony_ci	ret = tb_port_get_link_speed(up);
268062306a36Sopenharmony_ci	if (ret < 0)
268162306a36Sopenharmony_ci		return ret;
268262306a36Sopenharmony_ci	if (sw->link_speed != ret)
268362306a36Sopenharmony_ci		change = true;
268462306a36Sopenharmony_ci	sw->link_speed = ret;
268562306a36Sopenharmony_ci
268662306a36Sopenharmony_ci	ret = tb_port_get_link_width(up);
268762306a36Sopenharmony_ci	if (ret < 0)
268862306a36Sopenharmony_ci		return ret;
268962306a36Sopenharmony_ci	if (sw->link_width != ret)
269062306a36Sopenharmony_ci		change = true;
269162306a36Sopenharmony_ci	sw->link_width = ret;
269262306a36Sopenharmony_ci
269362306a36Sopenharmony_ci	/* Notify userspace that there is possible link attribute change */
269462306a36Sopenharmony_ci	if (device_is_registered(&sw->dev) && change)
269562306a36Sopenharmony_ci		kobject_uevent(&sw->dev.kobj, KOBJ_CHANGE);
269662306a36Sopenharmony_ci
269762306a36Sopenharmony_ci	return 0;
269862306a36Sopenharmony_ci}
269962306a36Sopenharmony_ci
270062306a36Sopenharmony_ci/**
270162306a36Sopenharmony_ci * tb_switch_lane_bonding_enable() - Enable lane bonding
270262306a36Sopenharmony_ci * @sw: Switch to enable lane bonding
270362306a36Sopenharmony_ci *
270462306a36Sopenharmony_ci * Connection manager can call this function to enable lane bonding of a
270562306a36Sopenharmony_ci * switch. If conditions are correct and both switches support the feature,
270662306a36Sopenharmony_ci * lanes are bonded. It is safe to call this to any switch.
270762306a36Sopenharmony_ci */
270862306a36Sopenharmony_ciint tb_switch_lane_bonding_enable(struct tb_switch *sw)
270962306a36Sopenharmony_ci{
271062306a36Sopenharmony_ci	struct tb_port *up, *down;
271162306a36Sopenharmony_ci	u64 route = tb_route(sw);
271262306a36Sopenharmony_ci	unsigned int width_mask;
271362306a36Sopenharmony_ci	int ret;
271462306a36Sopenharmony_ci
271562306a36Sopenharmony_ci	if (!route)
271662306a36Sopenharmony_ci		return 0;
271762306a36Sopenharmony_ci
271862306a36Sopenharmony_ci	if (!tb_switch_lane_bonding_possible(sw))
271962306a36Sopenharmony_ci		return 0;
272062306a36Sopenharmony_ci
272162306a36Sopenharmony_ci	up = tb_upstream_port(sw);
272262306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
272362306a36Sopenharmony_ci
272462306a36Sopenharmony_ci	if (!tb_port_is_width_supported(up, TB_LINK_WIDTH_DUAL) ||
272562306a36Sopenharmony_ci	    !tb_port_is_width_supported(down, TB_LINK_WIDTH_DUAL))
272662306a36Sopenharmony_ci		return 0;
272762306a36Sopenharmony_ci
272862306a36Sopenharmony_ci	/*
272962306a36Sopenharmony_ci	 * Both lanes need to be in CL0. Here we assume lane 0 already be in
273062306a36Sopenharmony_ci	 * CL0 and check just for lane 1.
273162306a36Sopenharmony_ci	 */
273262306a36Sopenharmony_ci	if (tb_wait_for_port(down->dual_link_port, false) <= 0)
273362306a36Sopenharmony_ci		return -ENOTCONN;
273462306a36Sopenharmony_ci
273562306a36Sopenharmony_ci	ret = tb_port_lane_bonding_enable(up);
273662306a36Sopenharmony_ci	if (ret) {
273762306a36Sopenharmony_ci		tb_port_warn(up, "failed to enable lane bonding\n");
273862306a36Sopenharmony_ci		return ret;
273962306a36Sopenharmony_ci	}
274062306a36Sopenharmony_ci
274162306a36Sopenharmony_ci	ret = tb_port_lane_bonding_enable(down);
274262306a36Sopenharmony_ci	if (ret) {
274362306a36Sopenharmony_ci		tb_port_warn(down, "failed to enable lane bonding\n");
274462306a36Sopenharmony_ci		tb_port_lane_bonding_disable(up);
274562306a36Sopenharmony_ci		return ret;
274662306a36Sopenharmony_ci	}
274762306a36Sopenharmony_ci
274862306a36Sopenharmony_ci	/* Any of the widths are all bonded */
274962306a36Sopenharmony_ci	width_mask = TB_LINK_WIDTH_DUAL | TB_LINK_WIDTH_ASYM_TX |
275062306a36Sopenharmony_ci		     TB_LINK_WIDTH_ASYM_RX;
275162306a36Sopenharmony_ci
275262306a36Sopenharmony_ci	ret = tb_port_wait_for_link_width(down, width_mask, 100);
275362306a36Sopenharmony_ci	if (ret) {
275462306a36Sopenharmony_ci		tb_port_warn(down, "timeout enabling lane bonding\n");
275562306a36Sopenharmony_ci		return ret;
275662306a36Sopenharmony_ci	}
275762306a36Sopenharmony_ci
275862306a36Sopenharmony_ci	tb_port_update_credits(down);
275962306a36Sopenharmony_ci	tb_port_update_credits(up);
276062306a36Sopenharmony_ci	tb_switch_update_link_attributes(sw);
276162306a36Sopenharmony_ci
276262306a36Sopenharmony_ci	tb_sw_dbg(sw, "lane bonding enabled\n");
276362306a36Sopenharmony_ci	return ret;
276462306a36Sopenharmony_ci}
276562306a36Sopenharmony_ci
276662306a36Sopenharmony_ci/**
276762306a36Sopenharmony_ci * tb_switch_lane_bonding_disable() - Disable lane bonding
276862306a36Sopenharmony_ci * @sw: Switch whose lane bonding to disable
276962306a36Sopenharmony_ci *
277062306a36Sopenharmony_ci * Disables lane bonding between @sw and parent. This can be called even
277162306a36Sopenharmony_ci * if lanes were not bonded originally.
277262306a36Sopenharmony_ci */
277362306a36Sopenharmony_civoid tb_switch_lane_bonding_disable(struct tb_switch *sw)
277462306a36Sopenharmony_ci{
277562306a36Sopenharmony_ci	struct tb_port *up, *down;
277662306a36Sopenharmony_ci	int ret;
277762306a36Sopenharmony_ci
277862306a36Sopenharmony_ci	if (!tb_route(sw))
277962306a36Sopenharmony_ci		return;
278062306a36Sopenharmony_ci
278162306a36Sopenharmony_ci	up = tb_upstream_port(sw);
278262306a36Sopenharmony_ci	if (!up->bonded)
278362306a36Sopenharmony_ci		return;
278462306a36Sopenharmony_ci
278562306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
278662306a36Sopenharmony_ci
278762306a36Sopenharmony_ci	tb_port_lane_bonding_disable(up);
278862306a36Sopenharmony_ci	tb_port_lane_bonding_disable(down);
278962306a36Sopenharmony_ci
279062306a36Sopenharmony_ci	/*
279162306a36Sopenharmony_ci	 * It is fine if we get other errors as the router might have
279262306a36Sopenharmony_ci	 * been unplugged.
279362306a36Sopenharmony_ci	 */
279462306a36Sopenharmony_ci	ret = tb_port_wait_for_link_width(down, TB_LINK_WIDTH_SINGLE, 100);
279562306a36Sopenharmony_ci	if (ret == -ETIMEDOUT)
279662306a36Sopenharmony_ci		tb_sw_warn(sw, "timeout disabling lane bonding\n");
279762306a36Sopenharmony_ci
279862306a36Sopenharmony_ci	tb_port_update_credits(down);
279962306a36Sopenharmony_ci	tb_port_update_credits(up);
280062306a36Sopenharmony_ci	tb_switch_update_link_attributes(sw);
280162306a36Sopenharmony_ci
280262306a36Sopenharmony_ci	tb_sw_dbg(sw, "lane bonding disabled\n");
280362306a36Sopenharmony_ci}
280462306a36Sopenharmony_ci
280562306a36Sopenharmony_ci/**
280662306a36Sopenharmony_ci * tb_switch_configure_link() - Set link configured
280762306a36Sopenharmony_ci * @sw: Switch whose link is configured
280862306a36Sopenharmony_ci *
280962306a36Sopenharmony_ci * Sets the link upstream from @sw configured (from both ends) so that
281062306a36Sopenharmony_ci * it will not be disconnected when the domain exits sleep. Can be
281162306a36Sopenharmony_ci * called for any switch.
281262306a36Sopenharmony_ci *
281362306a36Sopenharmony_ci * It is recommended that this is called after lane bonding is enabled.
281462306a36Sopenharmony_ci *
281562306a36Sopenharmony_ci * Returns %0 on success and negative errno in case of error.
281662306a36Sopenharmony_ci */
281762306a36Sopenharmony_ciint tb_switch_configure_link(struct tb_switch *sw)
281862306a36Sopenharmony_ci{
281962306a36Sopenharmony_ci	struct tb_port *up, *down;
282062306a36Sopenharmony_ci	int ret;
282162306a36Sopenharmony_ci
282262306a36Sopenharmony_ci	if (!tb_route(sw) || tb_switch_is_icm(sw))
282362306a36Sopenharmony_ci		return 0;
282462306a36Sopenharmony_ci
282562306a36Sopenharmony_ci	up = tb_upstream_port(sw);
282662306a36Sopenharmony_ci	if (tb_switch_is_usb4(up->sw))
282762306a36Sopenharmony_ci		ret = usb4_port_configure(up);
282862306a36Sopenharmony_ci	else
282962306a36Sopenharmony_ci		ret = tb_lc_configure_port(up);
283062306a36Sopenharmony_ci	if (ret)
283162306a36Sopenharmony_ci		return ret;
283262306a36Sopenharmony_ci
283362306a36Sopenharmony_ci	down = up->remote;
283462306a36Sopenharmony_ci	if (tb_switch_is_usb4(down->sw))
283562306a36Sopenharmony_ci		return usb4_port_configure(down);
283662306a36Sopenharmony_ci	return tb_lc_configure_port(down);
283762306a36Sopenharmony_ci}
283862306a36Sopenharmony_ci
283962306a36Sopenharmony_ci/**
284062306a36Sopenharmony_ci * tb_switch_unconfigure_link() - Unconfigure link
284162306a36Sopenharmony_ci * @sw: Switch whose link is unconfigured
284262306a36Sopenharmony_ci *
284362306a36Sopenharmony_ci * Sets the link unconfigured so the @sw will be disconnected if the
284462306a36Sopenharmony_ci * domain exists sleep.
284562306a36Sopenharmony_ci */
284662306a36Sopenharmony_civoid tb_switch_unconfigure_link(struct tb_switch *sw)
284762306a36Sopenharmony_ci{
284862306a36Sopenharmony_ci	struct tb_port *up, *down;
284962306a36Sopenharmony_ci
285062306a36Sopenharmony_ci	if (sw->is_unplugged)
285162306a36Sopenharmony_ci		return;
285262306a36Sopenharmony_ci	if (!tb_route(sw) || tb_switch_is_icm(sw))
285362306a36Sopenharmony_ci		return;
285462306a36Sopenharmony_ci
285562306a36Sopenharmony_ci	up = tb_upstream_port(sw);
285662306a36Sopenharmony_ci	if (tb_switch_is_usb4(up->sw))
285762306a36Sopenharmony_ci		usb4_port_unconfigure(up);
285862306a36Sopenharmony_ci	else
285962306a36Sopenharmony_ci		tb_lc_unconfigure_port(up);
286062306a36Sopenharmony_ci
286162306a36Sopenharmony_ci	down = up->remote;
286262306a36Sopenharmony_ci	if (tb_switch_is_usb4(down->sw))
286362306a36Sopenharmony_ci		usb4_port_unconfigure(down);
286462306a36Sopenharmony_ci	else
286562306a36Sopenharmony_ci		tb_lc_unconfigure_port(down);
286662306a36Sopenharmony_ci}
286762306a36Sopenharmony_ci
286862306a36Sopenharmony_cistatic void tb_switch_credits_init(struct tb_switch *sw)
286962306a36Sopenharmony_ci{
287062306a36Sopenharmony_ci	if (tb_switch_is_icm(sw))
287162306a36Sopenharmony_ci		return;
287262306a36Sopenharmony_ci	if (!tb_switch_is_usb4(sw))
287362306a36Sopenharmony_ci		return;
287462306a36Sopenharmony_ci	if (usb4_switch_credits_init(sw))
287562306a36Sopenharmony_ci		tb_sw_info(sw, "failed to determine preferred buffer allocation, using defaults\n");
287662306a36Sopenharmony_ci}
287762306a36Sopenharmony_ci
287862306a36Sopenharmony_cistatic int tb_switch_port_hotplug_enable(struct tb_switch *sw)
287962306a36Sopenharmony_ci{
288062306a36Sopenharmony_ci	struct tb_port *port;
288162306a36Sopenharmony_ci
288262306a36Sopenharmony_ci	if (tb_switch_is_icm(sw))
288362306a36Sopenharmony_ci		return 0;
288462306a36Sopenharmony_ci
288562306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
288662306a36Sopenharmony_ci		int res;
288762306a36Sopenharmony_ci
288862306a36Sopenharmony_ci		if (!port->cap_usb4)
288962306a36Sopenharmony_ci			continue;
289062306a36Sopenharmony_ci
289162306a36Sopenharmony_ci		res = usb4_port_hotplug_enable(port);
289262306a36Sopenharmony_ci		if (res)
289362306a36Sopenharmony_ci			return res;
289462306a36Sopenharmony_ci	}
289562306a36Sopenharmony_ci	return 0;
289662306a36Sopenharmony_ci}
289762306a36Sopenharmony_ci
289862306a36Sopenharmony_ci/**
289962306a36Sopenharmony_ci * tb_switch_add() - Add a switch to the domain
290062306a36Sopenharmony_ci * @sw: Switch to add
290162306a36Sopenharmony_ci *
290262306a36Sopenharmony_ci * This is the last step in adding switch to the domain. It will read
290362306a36Sopenharmony_ci * identification information from DROM and initializes ports so that
290462306a36Sopenharmony_ci * they can be used to connect other switches. The switch will be
290562306a36Sopenharmony_ci * exposed to the userspace when this function successfully returns. To
290662306a36Sopenharmony_ci * remove and release the switch, call tb_switch_remove().
290762306a36Sopenharmony_ci *
290862306a36Sopenharmony_ci * Return: %0 in case of success and negative errno in case of failure
290962306a36Sopenharmony_ci */
291062306a36Sopenharmony_ciint tb_switch_add(struct tb_switch *sw)
291162306a36Sopenharmony_ci{
291262306a36Sopenharmony_ci	int i, ret;
291362306a36Sopenharmony_ci
291462306a36Sopenharmony_ci	/*
291562306a36Sopenharmony_ci	 * Initialize DMA control port now before we read DROM. Recent
291662306a36Sopenharmony_ci	 * host controllers have more complete DROM on NVM that includes
291762306a36Sopenharmony_ci	 * vendor and model identification strings which we then expose
291862306a36Sopenharmony_ci	 * to the userspace. NVM can be accessed through DMA
291962306a36Sopenharmony_ci	 * configuration based mailbox.
292062306a36Sopenharmony_ci	 */
292162306a36Sopenharmony_ci	ret = tb_switch_add_dma_port(sw);
292262306a36Sopenharmony_ci	if (ret) {
292362306a36Sopenharmony_ci		dev_err(&sw->dev, "failed to add DMA port\n");
292462306a36Sopenharmony_ci		return ret;
292562306a36Sopenharmony_ci	}
292662306a36Sopenharmony_ci
292762306a36Sopenharmony_ci	if (!sw->safe_mode) {
292862306a36Sopenharmony_ci		tb_switch_credits_init(sw);
292962306a36Sopenharmony_ci
293062306a36Sopenharmony_ci		/* read drom */
293162306a36Sopenharmony_ci		ret = tb_drom_read(sw);
293262306a36Sopenharmony_ci		if (ret)
293362306a36Sopenharmony_ci			dev_warn(&sw->dev, "reading DROM failed: %d\n", ret);
293462306a36Sopenharmony_ci		tb_sw_dbg(sw, "uid: %#llx\n", sw->uid);
293562306a36Sopenharmony_ci
293662306a36Sopenharmony_ci		ret = tb_switch_set_uuid(sw);
293762306a36Sopenharmony_ci		if (ret) {
293862306a36Sopenharmony_ci			dev_err(&sw->dev, "failed to set UUID\n");
293962306a36Sopenharmony_ci			return ret;
294062306a36Sopenharmony_ci		}
294162306a36Sopenharmony_ci
294262306a36Sopenharmony_ci		for (i = 0; i <= sw->config.max_port_number; i++) {
294362306a36Sopenharmony_ci			if (sw->ports[i].disabled) {
294462306a36Sopenharmony_ci				tb_port_dbg(&sw->ports[i], "disabled by eeprom\n");
294562306a36Sopenharmony_ci				continue;
294662306a36Sopenharmony_ci			}
294762306a36Sopenharmony_ci			ret = tb_init_port(&sw->ports[i]);
294862306a36Sopenharmony_ci			if (ret) {
294962306a36Sopenharmony_ci				dev_err(&sw->dev, "failed to initialize port %d\n", i);
295062306a36Sopenharmony_ci				return ret;
295162306a36Sopenharmony_ci			}
295262306a36Sopenharmony_ci		}
295362306a36Sopenharmony_ci
295462306a36Sopenharmony_ci		tb_check_quirks(sw);
295562306a36Sopenharmony_ci
295662306a36Sopenharmony_ci		tb_switch_default_link_ports(sw);
295762306a36Sopenharmony_ci
295862306a36Sopenharmony_ci		ret = tb_switch_update_link_attributes(sw);
295962306a36Sopenharmony_ci		if (ret)
296062306a36Sopenharmony_ci			return ret;
296162306a36Sopenharmony_ci
296262306a36Sopenharmony_ci		ret = tb_switch_clx_init(sw);
296362306a36Sopenharmony_ci		if (ret)
296462306a36Sopenharmony_ci			return ret;
296562306a36Sopenharmony_ci
296662306a36Sopenharmony_ci		ret = tb_switch_tmu_init(sw);
296762306a36Sopenharmony_ci		if (ret)
296862306a36Sopenharmony_ci			return ret;
296962306a36Sopenharmony_ci	}
297062306a36Sopenharmony_ci
297162306a36Sopenharmony_ci	ret = tb_switch_port_hotplug_enable(sw);
297262306a36Sopenharmony_ci	if (ret)
297362306a36Sopenharmony_ci		return ret;
297462306a36Sopenharmony_ci
297562306a36Sopenharmony_ci	ret = device_add(&sw->dev);
297662306a36Sopenharmony_ci	if (ret) {
297762306a36Sopenharmony_ci		dev_err(&sw->dev, "failed to add device: %d\n", ret);
297862306a36Sopenharmony_ci		return ret;
297962306a36Sopenharmony_ci	}
298062306a36Sopenharmony_ci
298162306a36Sopenharmony_ci	if (tb_route(sw)) {
298262306a36Sopenharmony_ci		dev_info(&sw->dev, "new device found, vendor=%#x device=%#x\n",
298362306a36Sopenharmony_ci			 sw->vendor, sw->device);
298462306a36Sopenharmony_ci		if (sw->vendor_name && sw->device_name)
298562306a36Sopenharmony_ci			dev_info(&sw->dev, "%s %s\n", sw->vendor_name,
298662306a36Sopenharmony_ci				 sw->device_name);
298762306a36Sopenharmony_ci	}
298862306a36Sopenharmony_ci
298962306a36Sopenharmony_ci	ret = usb4_switch_add_ports(sw);
299062306a36Sopenharmony_ci	if (ret) {
299162306a36Sopenharmony_ci		dev_err(&sw->dev, "failed to add USB4 ports\n");
299262306a36Sopenharmony_ci		goto err_del;
299362306a36Sopenharmony_ci	}
299462306a36Sopenharmony_ci
299562306a36Sopenharmony_ci	ret = tb_switch_nvm_add(sw);
299662306a36Sopenharmony_ci	if (ret) {
299762306a36Sopenharmony_ci		dev_err(&sw->dev, "failed to add NVM devices\n");
299862306a36Sopenharmony_ci		goto err_ports;
299962306a36Sopenharmony_ci	}
300062306a36Sopenharmony_ci
300162306a36Sopenharmony_ci	/*
300262306a36Sopenharmony_ci	 * Thunderbolt routers do not generate wakeups themselves but
300362306a36Sopenharmony_ci	 * they forward wakeups from tunneled protocols, so enable it
300462306a36Sopenharmony_ci	 * here.
300562306a36Sopenharmony_ci	 */
300662306a36Sopenharmony_ci	device_init_wakeup(&sw->dev, true);
300762306a36Sopenharmony_ci
300862306a36Sopenharmony_ci	pm_runtime_set_active(&sw->dev);
300962306a36Sopenharmony_ci	if (sw->rpm) {
301062306a36Sopenharmony_ci		pm_runtime_set_autosuspend_delay(&sw->dev, TB_AUTOSUSPEND_DELAY);
301162306a36Sopenharmony_ci		pm_runtime_use_autosuspend(&sw->dev);
301262306a36Sopenharmony_ci		pm_runtime_mark_last_busy(&sw->dev);
301362306a36Sopenharmony_ci		pm_runtime_enable(&sw->dev);
301462306a36Sopenharmony_ci		pm_request_autosuspend(&sw->dev);
301562306a36Sopenharmony_ci	}
301662306a36Sopenharmony_ci
301762306a36Sopenharmony_ci	tb_switch_debugfs_init(sw);
301862306a36Sopenharmony_ci	return 0;
301962306a36Sopenharmony_ci
302062306a36Sopenharmony_cierr_ports:
302162306a36Sopenharmony_ci	usb4_switch_remove_ports(sw);
302262306a36Sopenharmony_cierr_del:
302362306a36Sopenharmony_ci	device_del(&sw->dev);
302462306a36Sopenharmony_ci
302562306a36Sopenharmony_ci	return ret;
302662306a36Sopenharmony_ci}
302762306a36Sopenharmony_ci
302862306a36Sopenharmony_ci/**
302962306a36Sopenharmony_ci * tb_switch_remove() - Remove and release a switch
303062306a36Sopenharmony_ci * @sw: Switch to remove
303162306a36Sopenharmony_ci *
303262306a36Sopenharmony_ci * This will remove the switch from the domain and release it after last
303362306a36Sopenharmony_ci * reference count drops to zero. If there are switches connected below
303462306a36Sopenharmony_ci * this switch, they will be removed as well.
303562306a36Sopenharmony_ci */
303662306a36Sopenharmony_civoid tb_switch_remove(struct tb_switch *sw)
303762306a36Sopenharmony_ci{
303862306a36Sopenharmony_ci	struct tb_port *port;
303962306a36Sopenharmony_ci
304062306a36Sopenharmony_ci	tb_switch_debugfs_remove(sw);
304162306a36Sopenharmony_ci
304262306a36Sopenharmony_ci	if (sw->rpm) {
304362306a36Sopenharmony_ci		pm_runtime_get_sync(&sw->dev);
304462306a36Sopenharmony_ci		pm_runtime_disable(&sw->dev);
304562306a36Sopenharmony_ci	}
304662306a36Sopenharmony_ci
304762306a36Sopenharmony_ci	/* port 0 is the switch itself and never has a remote */
304862306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
304962306a36Sopenharmony_ci		if (tb_port_has_remote(port)) {
305062306a36Sopenharmony_ci			tb_switch_remove(port->remote->sw);
305162306a36Sopenharmony_ci			port->remote = NULL;
305262306a36Sopenharmony_ci		} else if (port->xdomain) {
305362306a36Sopenharmony_ci			tb_xdomain_remove(port->xdomain);
305462306a36Sopenharmony_ci			port->xdomain = NULL;
305562306a36Sopenharmony_ci		}
305662306a36Sopenharmony_ci
305762306a36Sopenharmony_ci		/* Remove any downstream retimers */
305862306a36Sopenharmony_ci		tb_retimer_remove_all(port);
305962306a36Sopenharmony_ci	}
306062306a36Sopenharmony_ci
306162306a36Sopenharmony_ci	if (!sw->is_unplugged)
306262306a36Sopenharmony_ci		tb_plug_events_active(sw, false);
306362306a36Sopenharmony_ci
306462306a36Sopenharmony_ci	tb_switch_nvm_remove(sw);
306562306a36Sopenharmony_ci	usb4_switch_remove_ports(sw);
306662306a36Sopenharmony_ci
306762306a36Sopenharmony_ci	if (tb_route(sw))
306862306a36Sopenharmony_ci		dev_info(&sw->dev, "device disconnected\n");
306962306a36Sopenharmony_ci	device_unregister(&sw->dev);
307062306a36Sopenharmony_ci}
307162306a36Sopenharmony_ci
307262306a36Sopenharmony_ci/**
307362306a36Sopenharmony_ci * tb_sw_set_unplugged() - set is_unplugged on switch and downstream switches
307462306a36Sopenharmony_ci * @sw: Router to mark unplugged
307562306a36Sopenharmony_ci */
307662306a36Sopenharmony_civoid tb_sw_set_unplugged(struct tb_switch *sw)
307762306a36Sopenharmony_ci{
307862306a36Sopenharmony_ci	struct tb_port *port;
307962306a36Sopenharmony_ci
308062306a36Sopenharmony_ci	if (sw == sw->tb->root_switch) {
308162306a36Sopenharmony_ci		tb_sw_WARN(sw, "cannot unplug root switch\n");
308262306a36Sopenharmony_ci		return;
308362306a36Sopenharmony_ci	}
308462306a36Sopenharmony_ci	if (sw->is_unplugged) {
308562306a36Sopenharmony_ci		tb_sw_WARN(sw, "is_unplugged already set\n");
308662306a36Sopenharmony_ci		return;
308762306a36Sopenharmony_ci	}
308862306a36Sopenharmony_ci	sw->is_unplugged = true;
308962306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
309062306a36Sopenharmony_ci		if (tb_port_has_remote(port))
309162306a36Sopenharmony_ci			tb_sw_set_unplugged(port->remote->sw);
309262306a36Sopenharmony_ci		else if (port->xdomain)
309362306a36Sopenharmony_ci			port->xdomain->is_unplugged = true;
309462306a36Sopenharmony_ci	}
309562306a36Sopenharmony_ci}
309662306a36Sopenharmony_ci
309762306a36Sopenharmony_cistatic int tb_switch_set_wake(struct tb_switch *sw, unsigned int flags)
309862306a36Sopenharmony_ci{
309962306a36Sopenharmony_ci	if (flags)
310062306a36Sopenharmony_ci		tb_sw_dbg(sw, "enabling wakeup: %#x\n", flags);
310162306a36Sopenharmony_ci	else
310262306a36Sopenharmony_ci		tb_sw_dbg(sw, "disabling wakeup\n");
310362306a36Sopenharmony_ci
310462306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
310562306a36Sopenharmony_ci		return usb4_switch_set_wake(sw, flags);
310662306a36Sopenharmony_ci	return tb_lc_set_wake(sw, flags);
310762306a36Sopenharmony_ci}
310862306a36Sopenharmony_ci
310962306a36Sopenharmony_ciint tb_switch_resume(struct tb_switch *sw)
311062306a36Sopenharmony_ci{
311162306a36Sopenharmony_ci	struct tb_port *port;
311262306a36Sopenharmony_ci	int err;
311362306a36Sopenharmony_ci
311462306a36Sopenharmony_ci	tb_sw_dbg(sw, "resuming switch\n");
311562306a36Sopenharmony_ci
311662306a36Sopenharmony_ci	/*
311762306a36Sopenharmony_ci	 * Check for UID of the connected switches except for root
311862306a36Sopenharmony_ci	 * switch which we assume cannot be removed.
311962306a36Sopenharmony_ci	 */
312062306a36Sopenharmony_ci	if (tb_route(sw)) {
312162306a36Sopenharmony_ci		u64 uid;
312262306a36Sopenharmony_ci
312362306a36Sopenharmony_ci		/*
312462306a36Sopenharmony_ci		 * Check first that we can still read the switch config
312562306a36Sopenharmony_ci		 * space. It may be that there is now another domain
312662306a36Sopenharmony_ci		 * connected.
312762306a36Sopenharmony_ci		 */
312862306a36Sopenharmony_ci		err = tb_cfg_get_upstream_port(sw->tb->ctl, tb_route(sw));
312962306a36Sopenharmony_ci		if (err < 0) {
313062306a36Sopenharmony_ci			tb_sw_info(sw, "switch not present anymore\n");
313162306a36Sopenharmony_ci			return err;
313262306a36Sopenharmony_ci		}
313362306a36Sopenharmony_ci
313462306a36Sopenharmony_ci		/* We don't have any way to confirm this was the same device */
313562306a36Sopenharmony_ci		if (!sw->uid)
313662306a36Sopenharmony_ci			return -ENODEV;
313762306a36Sopenharmony_ci
313862306a36Sopenharmony_ci		if (tb_switch_is_usb4(sw))
313962306a36Sopenharmony_ci			err = usb4_switch_read_uid(sw, &uid);
314062306a36Sopenharmony_ci		else
314162306a36Sopenharmony_ci			err = tb_drom_read_uid_only(sw, &uid);
314262306a36Sopenharmony_ci		if (err) {
314362306a36Sopenharmony_ci			tb_sw_warn(sw, "uid read failed\n");
314462306a36Sopenharmony_ci			return err;
314562306a36Sopenharmony_ci		}
314662306a36Sopenharmony_ci		if (sw->uid != uid) {
314762306a36Sopenharmony_ci			tb_sw_info(sw,
314862306a36Sopenharmony_ci				"changed while suspended (uid %#llx -> %#llx)\n",
314962306a36Sopenharmony_ci				sw->uid, uid);
315062306a36Sopenharmony_ci			return -ENODEV;
315162306a36Sopenharmony_ci		}
315262306a36Sopenharmony_ci	}
315362306a36Sopenharmony_ci
315462306a36Sopenharmony_ci	err = tb_switch_configure(sw);
315562306a36Sopenharmony_ci	if (err)
315662306a36Sopenharmony_ci		return err;
315762306a36Sopenharmony_ci
315862306a36Sopenharmony_ci	/* Disable wakes */
315962306a36Sopenharmony_ci	tb_switch_set_wake(sw, 0);
316062306a36Sopenharmony_ci
316162306a36Sopenharmony_ci	err = tb_switch_tmu_init(sw);
316262306a36Sopenharmony_ci	if (err)
316362306a36Sopenharmony_ci		return err;
316462306a36Sopenharmony_ci
316562306a36Sopenharmony_ci	/* check for surviving downstream switches */
316662306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
316762306a36Sopenharmony_ci		if (!tb_port_is_null(port))
316862306a36Sopenharmony_ci			continue;
316962306a36Sopenharmony_ci
317062306a36Sopenharmony_ci		if (!tb_port_resume(port))
317162306a36Sopenharmony_ci			continue;
317262306a36Sopenharmony_ci
317362306a36Sopenharmony_ci		if (tb_wait_for_port(port, true) <= 0) {
317462306a36Sopenharmony_ci			tb_port_warn(port,
317562306a36Sopenharmony_ci				     "lost during suspend, disconnecting\n");
317662306a36Sopenharmony_ci			if (tb_port_has_remote(port))
317762306a36Sopenharmony_ci				tb_sw_set_unplugged(port->remote->sw);
317862306a36Sopenharmony_ci			else if (port->xdomain)
317962306a36Sopenharmony_ci				port->xdomain->is_unplugged = true;
318062306a36Sopenharmony_ci		} else {
318162306a36Sopenharmony_ci			/*
318262306a36Sopenharmony_ci			 * Always unlock the port so the downstream
318362306a36Sopenharmony_ci			 * switch/domain is accessible.
318462306a36Sopenharmony_ci			 */
318562306a36Sopenharmony_ci			if (tb_port_unlock(port))
318662306a36Sopenharmony_ci				tb_port_warn(port, "failed to unlock port\n");
318762306a36Sopenharmony_ci			if (port->remote && tb_switch_resume(port->remote->sw)) {
318862306a36Sopenharmony_ci				tb_port_warn(port,
318962306a36Sopenharmony_ci					     "lost during suspend, disconnecting\n");
319062306a36Sopenharmony_ci				tb_sw_set_unplugged(port->remote->sw);
319162306a36Sopenharmony_ci			}
319262306a36Sopenharmony_ci		}
319362306a36Sopenharmony_ci	}
319462306a36Sopenharmony_ci	return 0;
319562306a36Sopenharmony_ci}
319662306a36Sopenharmony_ci
319762306a36Sopenharmony_ci/**
319862306a36Sopenharmony_ci * tb_switch_suspend() - Put a switch to sleep
319962306a36Sopenharmony_ci * @sw: Switch to suspend
320062306a36Sopenharmony_ci * @runtime: Is this runtime suspend or system sleep
320162306a36Sopenharmony_ci *
320262306a36Sopenharmony_ci * Suspends router and all its children. Enables wakes according to
320362306a36Sopenharmony_ci * value of @runtime and then sets sleep bit for the router. If @sw is
320462306a36Sopenharmony_ci * host router the domain is ready to go to sleep once this function
320562306a36Sopenharmony_ci * returns.
320662306a36Sopenharmony_ci */
320762306a36Sopenharmony_civoid tb_switch_suspend(struct tb_switch *sw, bool runtime)
320862306a36Sopenharmony_ci{
320962306a36Sopenharmony_ci	unsigned int flags = 0;
321062306a36Sopenharmony_ci	struct tb_port *port;
321162306a36Sopenharmony_ci	int err;
321262306a36Sopenharmony_ci
321362306a36Sopenharmony_ci	tb_sw_dbg(sw, "suspending switch\n");
321462306a36Sopenharmony_ci
321562306a36Sopenharmony_ci	/*
321662306a36Sopenharmony_ci	 * Actually only needed for Titan Ridge but for simplicity can be
321762306a36Sopenharmony_ci	 * done for USB4 device too as CLx is re-enabled at resume.
321862306a36Sopenharmony_ci	 */
321962306a36Sopenharmony_ci	tb_switch_clx_disable(sw);
322062306a36Sopenharmony_ci
322162306a36Sopenharmony_ci	err = tb_plug_events_active(sw, false);
322262306a36Sopenharmony_ci	if (err)
322362306a36Sopenharmony_ci		return;
322462306a36Sopenharmony_ci
322562306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
322662306a36Sopenharmony_ci		if (tb_port_has_remote(port))
322762306a36Sopenharmony_ci			tb_switch_suspend(port->remote->sw, runtime);
322862306a36Sopenharmony_ci	}
322962306a36Sopenharmony_ci
323062306a36Sopenharmony_ci	if (runtime) {
323162306a36Sopenharmony_ci		/* Trigger wake when something is plugged in/out */
323262306a36Sopenharmony_ci		flags |= TB_WAKE_ON_CONNECT | TB_WAKE_ON_DISCONNECT;
323362306a36Sopenharmony_ci		flags |= TB_WAKE_ON_USB4;
323462306a36Sopenharmony_ci		flags |= TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE | TB_WAKE_ON_DP;
323562306a36Sopenharmony_ci	} else if (device_may_wakeup(&sw->dev)) {
323662306a36Sopenharmony_ci		flags |= TB_WAKE_ON_USB4 | TB_WAKE_ON_USB3 | TB_WAKE_ON_PCIE;
323762306a36Sopenharmony_ci	}
323862306a36Sopenharmony_ci
323962306a36Sopenharmony_ci	tb_switch_set_wake(sw, flags);
324062306a36Sopenharmony_ci
324162306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
324262306a36Sopenharmony_ci		usb4_switch_set_sleep(sw);
324362306a36Sopenharmony_ci	else
324462306a36Sopenharmony_ci		tb_lc_set_sleep(sw);
324562306a36Sopenharmony_ci}
324662306a36Sopenharmony_ci
324762306a36Sopenharmony_ci/**
324862306a36Sopenharmony_ci * tb_switch_query_dp_resource() - Query availability of DP resource
324962306a36Sopenharmony_ci * @sw: Switch whose DP resource is queried
325062306a36Sopenharmony_ci * @in: DP IN port
325162306a36Sopenharmony_ci *
325262306a36Sopenharmony_ci * Queries availability of DP resource for DP tunneling using switch
325362306a36Sopenharmony_ci * specific means. Returns %true if resource is available.
325462306a36Sopenharmony_ci */
325562306a36Sopenharmony_cibool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
325662306a36Sopenharmony_ci{
325762306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
325862306a36Sopenharmony_ci		return usb4_switch_query_dp_resource(sw, in);
325962306a36Sopenharmony_ci	return tb_lc_dp_sink_query(sw, in);
326062306a36Sopenharmony_ci}
326162306a36Sopenharmony_ci
326262306a36Sopenharmony_ci/**
326362306a36Sopenharmony_ci * tb_switch_alloc_dp_resource() - Allocate available DP resource
326462306a36Sopenharmony_ci * @sw: Switch whose DP resource is allocated
326562306a36Sopenharmony_ci * @in: DP IN port
326662306a36Sopenharmony_ci *
326762306a36Sopenharmony_ci * Allocates DP resource for DP tunneling. The resource must be
326862306a36Sopenharmony_ci * available for this to succeed (see tb_switch_query_dp_resource()).
326962306a36Sopenharmony_ci * Returns %0 in success and negative errno otherwise.
327062306a36Sopenharmony_ci */
327162306a36Sopenharmony_ciint tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
327262306a36Sopenharmony_ci{
327362306a36Sopenharmony_ci	int ret;
327462306a36Sopenharmony_ci
327562306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
327662306a36Sopenharmony_ci		ret = usb4_switch_alloc_dp_resource(sw, in);
327762306a36Sopenharmony_ci	else
327862306a36Sopenharmony_ci		ret = tb_lc_dp_sink_alloc(sw, in);
327962306a36Sopenharmony_ci
328062306a36Sopenharmony_ci	if (ret)
328162306a36Sopenharmony_ci		tb_sw_warn(sw, "failed to allocate DP resource for port %d\n",
328262306a36Sopenharmony_ci			   in->port);
328362306a36Sopenharmony_ci	else
328462306a36Sopenharmony_ci		tb_sw_dbg(sw, "allocated DP resource for port %d\n", in->port);
328562306a36Sopenharmony_ci
328662306a36Sopenharmony_ci	return ret;
328762306a36Sopenharmony_ci}
328862306a36Sopenharmony_ci
328962306a36Sopenharmony_ci/**
329062306a36Sopenharmony_ci * tb_switch_dealloc_dp_resource() - De-allocate DP resource
329162306a36Sopenharmony_ci * @sw: Switch whose DP resource is de-allocated
329262306a36Sopenharmony_ci * @in: DP IN port
329362306a36Sopenharmony_ci *
329462306a36Sopenharmony_ci * De-allocates DP resource that was previously allocated for DP
329562306a36Sopenharmony_ci * tunneling.
329662306a36Sopenharmony_ci */
329762306a36Sopenharmony_civoid tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
329862306a36Sopenharmony_ci{
329962306a36Sopenharmony_ci	int ret;
330062306a36Sopenharmony_ci
330162306a36Sopenharmony_ci	if (tb_switch_is_usb4(sw))
330262306a36Sopenharmony_ci		ret = usb4_switch_dealloc_dp_resource(sw, in);
330362306a36Sopenharmony_ci	else
330462306a36Sopenharmony_ci		ret = tb_lc_dp_sink_dealloc(sw, in);
330562306a36Sopenharmony_ci
330662306a36Sopenharmony_ci	if (ret)
330762306a36Sopenharmony_ci		tb_sw_warn(sw, "failed to de-allocate DP resource for port %d\n",
330862306a36Sopenharmony_ci			   in->port);
330962306a36Sopenharmony_ci	else
331062306a36Sopenharmony_ci		tb_sw_dbg(sw, "released DP resource for port %d\n", in->port);
331162306a36Sopenharmony_ci}
331262306a36Sopenharmony_ci
331362306a36Sopenharmony_cistruct tb_sw_lookup {
331462306a36Sopenharmony_ci	struct tb *tb;
331562306a36Sopenharmony_ci	u8 link;
331662306a36Sopenharmony_ci	u8 depth;
331762306a36Sopenharmony_ci	const uuid_t *uuid;
331862306a36Sopenharmony_ci	u64 route;
331962306a36Sopenharmony_ci};
332062306a36Sopenharmony_ci
332162306a36Sopenharmony_cistatic int tb_switch_match(struct device *dev, const void *data)
332262306a36Sopenharmony_ci{
332362306a36Sopenharmony_ci	struct tb_switch *sw = tb_to_switch(dev);
332462306a36Sopenharmony_ci	const struct tb_sw_lookup *lookup = data;
332562306a36Sopenharmony_ci
332662306a36Sopenharmony_ci	if (!sw)
332762306a36Sopenharmony_ci		return 0;
332862306a36Sopenharmony_ci	if (sw->tb != lookup->tb)
332962306a36Sopenharmony_ci		return 0;
333062306a36Sopenharmony_ci
333162306a36Sopenharmony_ci	if (lookup->uuid)
333262306a36Sopenharmony_ci		return !memcmp(sw->uuid, lookup->uuid, sizeof(*lookup->uuid));
333362306a36Sopenharmony_ci
333462306a36Sopenharmony_ci	if (lookup->route) {
333562306a36Sopenharmony_ci		return sw->config.route_lo == lower_32_bits(lookup->route) &&
333662306a36Sopenharmony_ci		       sw->config.route_hi == upper_32_bits(lookup->route);
333762306a36Sopenharmony_ci	}
333862306a36Sopenharmony_ci
333962306a36Sopenharmony_ci	/* Root switch is matched only by depth */
334062306a36Sopenharmony_ci	if (!lookup->depth)
334162306a36Sopenharmony_ci		return !sw->depth;
334262306a36Sopenharmony_ci
334362306a36Sopenharmony_ci	return sw->link == lookup->link && sw->depth == lookup->depth;
334462306a36Sopenharmony_ci}
334562306a36Sopenharmony_ci
334662306a36Sopenharmony_ci/**
334762306a36Sopenharmony_ci * tb_switch_find_by_link_depth() - Find switch by link and depth
334862306a36Sopenharmony_ci * @tb: Domain the switch belongs
334962306a36Sopenharmony_ci * @link: Link number the switch is connected
335062306a36Sopenharmony_ci * @depth: Depth of the switch in link
335162306a36Sopenharmony_ci *
335262306a36Sopenharmony_ci * Returned switch has reference count increased so the caller needs to
335362306a36Sopenharmony_ci * call tb_switch_put() when done with the switch.
335462306a36Sopenharmony_ci */
335562306a36Sopenharmony_cistruct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link, u8 depth)
335662306a36Sopenharmony_ci{
335762306a36Sopenharmony_ci	struct tb_sw_lookup lookup;
335862306a36Sopenharmony_ci	struct device *dev;
335962306a36Sopenharmony_ci
336062306a36Sopenharmony_ci	memset(&lookup, 0, sizeof(lookup));
336162306a36Sopenharmony_ci	lookup.tb = tb;
336262306a36Sopenharmony_ci	lookup.link = link;
336362306a36Sopenharmony_ci	lookup.depth = depth;
336462306a36Sopenharmony_ci
336562306a36Sopenharmony_ci	dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match);
336662306a36Sopenharmony_ci	if (dev)
336762306a36Sopenharmony_ci		return tb_to_switch(dev);
336862306a36Sopenharmony_ci
336962306a36Sopenharmony_ci	return NULL;
337062306a36Sopenharmony_ci}
337162306a36Sopenharmony_ci
337262306a36Sopenharmony_ci/**
337362306a36Sopenharmony_ci * tb_switch_find_by_uuid() - Find switch by UUID
337462306a36Sopenharmony_ci * @tb: Domain the switch belongs
337562306a36Sopenharmony_ci * @uuid: UUID to look for
337662306a36Sopenharmony_ci *
337762306a36Sopenharmony_ci * Returned switch has reference count increased so the caller needs to
337862306a36Sopenharmony_ci * call tb_switch_put() when done with the switch.
337962306a36Sopenharmony_ci */
338062306a36Sopenharmony_cistruct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid)
338162306a36Sopenharmony_ci{
338262306a36Sopenharmony_ci	struct tb_sw_lookup lookup;
338362306a36Sopenharmony_ci	struct device *dev;
338462306a36Sopenharmony_ci
338562306a36Sopenharmony_ci	memset(&lookup, 0, sizeof(lookup));
338662306a36Sopenharmony_ci	lookup.tb = tb;
338762306a36Sopenharmony_ci	lookup.uuid = uuid;
338862306a36Sopenharmony_ci
338962306a36Sopenharmony_ci	dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match);
339062306a36Sopenharmony_ci	if (dev)
339162306a36Sopenharmony_ci		return tb_to_switch(dev);
339262306a36Sopenharmony_ci
339362306a36Sopenharmony_ci	return NULL;
339462306a36Sopenharmony_ci}
339562306a36Sopenharmony_ci
339662306a36Sopenharmony_ci/**
339762306a36Sopenharmony_ci * tb_switch_find_by_route() - Find switch by route string
339862306a36Sopenharmony_ci * @tb: Domain the switch belongs
339962306a36Sopenharmony_ci * @route: Route string to look for
340062306a36Sopenharmony_ci *
340162306a36Sopenharmony_ci * Returned switch has reference count increased so the caller needs to
340262306a36Sopenharmony_ci * call tb_switch_put() when done with the switch.
340362306a36Sopenharmony_ci */
340462306a36Sopenharmony_cistruct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route)
340562306a36Sopenharmony_ci{
340662306a36Sopenharmony_ci	struct tb_sw_lookup lookup;
340762306a36Sopenharmony_ci	struct device *dev;
340862306a36Sopenharmony_ci
340962306a36Sopenharmony_ci	if (!route)
341062306a36Sopenharmony_ci		return tb_switch_get(tb->root_switch);
341162306a36Sopenharmony_ci
341262306a36Sopenharmony_ci	memset(&lookup, 0, sizeof(lookup));
341362306a36Sopenharmony_ci	lookup.tb = tb;
341462306a36Sopenharmony_ci	lookup.route = route;
341562306a36Sopenharmony_ci
341662306a36Sopenharmony_ci	dev = bus_find_device(&tb_bus_type, NULL, &lookup, tb_switch_match);
341762306a36Sopenharmony_ci	if (dev)
341862306a36Sopenharmony_ci		return tb_to_switch(dev);
341962306a36Sopenharmony_ci
342062306a36Sopenharmony_ci	return NULL;
342162306a36Sopenharmony_ci}
342262306a36Sopenharmony_ci
342362306a36Sopenharmony_ci/**
342462306a36Sopenharmony_ci * tb_switch_find_port() - return the first port of @type on @sw or NULL
342562306a36Sopenharmony_ci * @sw: Switch to find the port from
342662306a36Sopenharmony_ci * @type: Port type to look for
342762306a36Sopenharmony_ci */
342862306a36Sopenharmony_cistruct tb_port *tb_switch_find_port(struct tb_switch *sw,
342962306a36Sopenharmony_ci				    enum tb_port_type type)
343062306a36Sopenharmony_ci{
343162306a36Sopenharmony_ci	struct tb_port *port;
343262306a36Sopenharmony_ci
343362306a36Sopenharmony_ci	tb_switch_for_each_port(sw, port) {
343462306a36Sopenharmony_ci		if (port->config.type == type)
343562306a36Sopenharmony_ci			return port;
343662306a36Sopenharmony_ci	}
343762306a36Sopenharmony_ci
343862306a36Sopenharmony_ci	return NULL;
343962306a36Sopenharmony_ci}
344062306a36Sopenharmony_ci
344162306a36Sopenharmony_ci/*
344262306a36Sopenharmony_ci * Can be used for read/write a specified PCIe bridge for any Thunderbolt 3
344362306a36Sopenharmony_ci * device. For now used only for Titan Ridge.
344462306a36Sopenharmony_ci */
344562306a36Sopenharmony_cistatic int tb_switch_pcie_bridge_write(struct tb_switch *sw, unsigned int bridge,
344662306a36Sopenharmony_ci				       unsigned int pcie_offset, u32 value)
344762306a36Sopenharmony_ci{
344862306a36Sopenharmony_ci	u32 offset, command, val;
344962306a36Sopenharmony_ci	int ret;
345062306a36Sopenharmony_ci
345162306a36Sopenharmony_ci	if (sw->generation != 3)
345262306a36Sopenharmony_ci		return -EOPNOTSUPP;
345362306a36Sopenharmony_ci
345462306a36Sopenharmony_ci	offset = sw->cap_plug_events + TB_PLUG_EVENTS_PCIE_WR_DATA;
345562306a36Sopenharmony_ci	ret = tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1);
345662306a36Sopenharmony_ci	if (ret)
345762306a36Sopenharmony_ci		return ret;
345862306a36Sopenharmony_ci
345962306a36Sopenharmony_ci	command = pcie_offset & TB_PLUG_EVENTS_PCIE_CMD_DW_OFFSET_MASK;
346062306a36Sopenharmony_ci	command |= BIT(bridge + TB_PLUG_EVENTS_PCIE_CMD_BR_SHIFT);
346162306a36Sopenharmony_ci	command |= TB_PLUG_EVENTS_PCIE_CMD_RD_WR_MASK;
346262306a36Sopenharmony_ci	command |= TB_PLUG_EVENTS_PCIE_CMD_COMMAND_VAL
346362306a36Sopenharmony_ci			<< TB_PLUG_EVENTS_PCIE_CMD_COMMAND_SHIFT;
346462306a36Sopenharmony_ci	command |= TB_PLUG_EVENTS_PCIE_CMD_REQ_ACK_MASK;
346562306a36Sopenharmony_ci
346662306a36Sopenharmony_ci	offset = sw->cap_plug_events + TB_PLUG_EVENTS_PCIE_CMD;
346762306a36Sopenharmony_ci
346862306a36Sopenharmony_ci	ret = tb_sw_write(sw, &command, TB_CFG_SWITCH, offset, 1);
346962306a36Sopenharmony_ci	if (ret)
347062306a36Sopenharmony_ci		return ret;
347162306a36Sopenharmony_ci
347262306a36Sopenharmony_ci	ret = tb_switch_wait_for_bit(sw, offset,
347362306a36Sopenharmony_ci				     TB_PLUG_EVENTS_PCIE_CMD_REQ_ACK_MASK, 0, 100);
347462306a36Sopenharmony_ci	if (ret)
347562306a36Sopenharmony_ci		return ret;
347662306a36Sopenharmony_ci
347762306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, offset, 1);
347862306a36Sopenharmony_ci	if (ret)
347962306a36Sopenharmony_ci		return ret;
348062306a36Sopenharmony_ci
348162306a36Sopenharmony_ci	if (val & TB_PLUG_EVENTS_PCIE_CMD_TIMEOUT_MASK)
348262306a36Sopenharmony_ci		return -ETIMEDOUT;
348362306a36Sopenharmony_ci
348462306a36Sopenharmony_ci	return 0;
348562306a36Sopenharmony_ci}
348662306a36Sopenharmony_ci
348762306a36Sopenharmony_ci/**
348862306a36Sopenharmony_ci * tb_switch_pcie_l1_enable() - Enable PCIe link to enter L1 state
348962306a36Sopenharmony_ci * @sw: Router to enable PCIe L1
349062306a36Sopenharmony_ci *
349162306a36Sopenharmony_ci * For Titan Ridge switch to enter CLx state, its PCIe bridges shall enable
349262306a36Sopenharmony_ci * entry to PCIe L1 state. Shall be called after the upstream PCIe tunnel
349362306a36Sopenharmony_ci * was configured. Due to Intel platforms limitation, shall be called only
349462306a36Sopenharmony_ci * for first hop switch.
349562306a36Sopenharmony_ci */
349662306a36Sopenharmony_ciint tb_switch_pcie_l1_enable(struct tb_switch *sw)
349762306a36Sopenharmony_ci{
349862306a36Sopenharmony_ci	struct tb_switch *parent = tb_switch_parent(sw);
349962306a36Sopenharmony_ci	int ret;
350062306a36Sopenharmony_ci
350162306a36Sopenharmony_ci	if (!tb_route(sw))
350262306a36Sopenharmony_ci		return 0;
350362306a36Sopenharmony_ci
350462306a36Sopenharmony_ci	if (!tb_switch_is_titan_ridge(sw))
350562306a36Sopenharmony_ci		return 0;
350662306a36Sopenharmony_ci
350762306a36Sopenharmony_ci	/* Enable PCIe L1 enable only for first hop router (depth = 1) */
350862306a36Sopenharmony_ci	if (tb_route(parent))
350962306a36Sopenharmony_ci		return 0;
351062306a36Sopenharmony_ci
351162306a36Sopenharmony_ci	/* Write to downstream PCIe bridge #5 aka Dn4 */
351262306a36Sopenharmony_ci	ret = tb_switch_pcie_bridge_write(sw, 5, 0x143, 0x0c7806b1);
351362306a36Sopenharmony_ci	if (ret)
351462306a36Sopenharmony_ci		return ret;
351562306a36Sopenharmony_ci
351662306a36Sopenharmony_ci	/* Write to Upstream PCIe bridge #0 aka Up0 */
351762306a36Sopenharmony_ci	return tb_switch_pcie_bridge_write(sw, 0, 0x143, 0x0c5806b1);
351862306a36Sopenharmony_ci}
351962306a36Sopenharmony_ci
352062306a36Sopenharmony_ci/**
352162306a36Sopenharmony_ci * tb_switch_xhci_connect() - Connect internal xHCI
352262306a36Sopenharmony_ci * @sw: Router whose xHCI to connect
352362306a36Sopenharmony_ci *
352462306a36Sopenharmony_ci * Can be called to any router. For Alpine Ridge and Titan Ridge
352562306a36Sopenharmony_ci * performs special flows that bring the xHCI functional for any device
352662306a36Sopenharmony_ci * connected to the type-C port. Call only after PCIe tunnel has been
352762306a36Sopenharmony_ci * established. The function only does the connect if not done already
352862306a36Sopenharmony_ci * so can be called several times for the same router.
352962306a36Sopenharmony_ci */
353062306a36Sopenharmony_ciint tb_switch_xhci_connect(struct tb_switch *sw)
353162306a36Sopenharmony_ci{
353262306a36Sopenharmony_ci	struct tb_port *port1, *port3;
353362306a36Sopenharmony_ci	int ret;
353462306a36Sopenharmony_ci
353562306a36Sopenharmony_ci	if (sw->generation != 3)
353662306a36Sopenharmony_ci		return 0;
353762306a36Sopenharmony_ci
353862306a36Sopenharmony_ci	port1 = &sw->ports[1];
353962306a36Sopenharmony_ci	port3 = &sw->ports[3];
354062306a36Sopenharmony_ci
354162306a36Sopenharmony_ci	if (tb_switch_is_alpine_ridge(sw)) {
354262306a36Sopenharmony_ci		bool usb_port1, usb_port3, xhci_port1, xhci_port3;
354362306a36Sopenharmony_ci
354462306a36Sopenharmony_ci		usb_port1 = tb_lc_is_usb_plugged(port1);
354562306a36Sopenharmony_ci		usb_port3 = tb_lc_is_usb_plugged(port3);
354662306a36Sopenharmony_ci		xhci_port1 = tb_lc_is_xhci_connected(port1);
354762306a36Sopenharmony_ci		xhci_port3 = tb_lc_is_xhci_connected(port3);
354862306a36Sopenharmony_ci
354962306a36Sopenharmony_ci		/* Figure out correct USB port to connect */
355062306a36Sopenharmony_ci		if (usb_port1 && !xhci_port1) {
355162306a36Sopenharmony_ci			ret = tb_lc_xhci_connect(port1);
355262306a36Sopenharmony_ci			if (ret)
355362306a36Sopenharmony_ci				return ret;
355462306a36Sopenharmony_ci		}
355562306a36Sopenharmony_ci		if (usb_port3 && !xhci_port3)
355662306a36Sopenharmony_ci			return tb_lc_xhci_connect(port3);
355762306a36Sopenharmony_ci	} else if (tb_switch_is_titan_ridge(sw)) {
355862306a36Sopenharmony_ci		ret = tb_lc_xhci_connect(port1);
355962306a36Sopenharmony_ci		if (ret)
356062306a36Sopenharmony_ci			return ret;
356162306a36Sopenharmony_ci		return tb_lc_xhci_connect(port3);
356262306a36Sopenharmony_ci	}
356362306a36Sopenharmony_ci
356462306a36Sopenharmony_ci	return 0;
356562306a36Sopenharmony_ci}
356662306a36Sopenharmony_ci
356762306a36Sopenharmony_ci/**
356862306a36Sopenharmony_ci * tb_switch_xhci_disconnect() - Disconnect internal xHCI
356962306a36Sopenharmony_ci * @sw: Router whose xHCI to disconnect
357062306a36Sopenharmony_ci *
357162306a36Sopenharmony_ci * The opposite of tb_switch_xhci_connect(). Disconnects xHCI on both
357262306a36Sopenharmony_ci * ports.
357362306a36Sopenharmony_ci */
357462306a36Sopenharmony_civoid tb_switch_xhci_disconnect(struct tb_switch *sw)
357562306a36Sopenharmony_ci{
357662306a36Sopenharmony_ci	if (sw->generation == 3) {
357762306a36Sopenharmony_ci		struct tb_port *port1 = &sw->ports[1];
357862306a36Sopenharmony_ci		struct tb_port *port3 = &sw->ports[3];
357962306a36Sopenharmony_ci
358062306a36Sopenharmony_ci		tb_lc_xhci_disconnect(port1);
358162306a36Sopenharmony_ci		tb_port_dbg(port1, "disconnected xHCI\n");
358262306a36Sopenharmony_ci		tb_lc_xhci_disconnect(port3);
358362306a36Sopenharmony_ci		tb_port_dbg(port3, "disconnected xHCI\n");
358462306a36Sopenharmony_ci	}
358562306a36Sopenharmony_ci}
3586