162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * CLx support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020 - 2023, Intel Corporation
662306a36Sopenharmony_ci * Authors: Gil Fine <gil.fine@intel.com>
762306a36Sopenharmony_ci *	    Mika Westerberg <mika.westerberg@linux.intel.com>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "tb.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic bool clx_enabled = true;
1562306a36Sopenharmony_cimodule_param_named(clx, clx_enabled, bool, 0444);
1662306a36Sopenharmony_ciMODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)");
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic const char *clx_name(unsigned int clx)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	switch (clx) {
2162306a36Sopenharmony_ci	case TB_CL0S | TB_CL1 | TB_CL2:
2262306a36Sopenharmony_ci		return "CL0s/CL1/CL2";
2362306a36Sopenharmony_ci	case TB_CL1 | TB_CL2:
2462306a36Sopenharmony_ci		return "CL1/CL2";
2562306a36Sopenharmony_ci	case TB_CL0S | TB_CL2:
2662306a36Sopenharmony_ci		return "CL0s/CL2";
2762306a36Sopenharmony_ci	case TB_CL0S | TB_CL1:
2862306a36Sopenharmony_ci		return "CL0s/CL1";
2962306a36Sopenharmony_ci	case TB_CL0S:
3062306a36Sopenharmony_ci		return "CL0s";
3162306a36Sopenharmony_ci	case 0:
3262306a36Sopenharmony_ci		return "disabled";
3362306a36Sopenharmony_ci	default:
3462306a36Sopenharmony_ci		return "unknown";
3562306a36Sopenharmony_ci	}
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic int tb_port_pm_secondary_set(struct tb_port *port, bool secondary)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	u32 phy;
4162306a36Sopenharmony_ci	int ret;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	ret = tb_port_read(port, &phy, TB_CFG_PORT,
4462306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
4562306a36Sopenharmony_ci	if (ret)
4662306a36Sopenharmony_ci		return ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (secondary)
4962306a36Sopenharmony_ci		phy |= LANE_ADP_CS_1_PMS;
5062306a36Sopenharmony_ci	else
5162306a36Sopenharmony_ci		phy &= ~LANE_ADP_CS_1_PMS;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return tb_port_write(port, &phy, TB_CFG_PORT,
5462306a36Sopenharmony_ci			     port->cap_phy + LANE_ADP_CS_1, 1);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int tb_port_pm_secondary_enable(struct tb_port *port)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	return tb_port_pm_secondary_set(port, true);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int tb_port_pm_secondary_disable(struct tb_port *port)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	return tb_port_pm_secondary_set(port, false);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/* Called for USB4 or Titan Ridge routers only */
6862306a36Sopenharmony_cistatic bool tb_port_clx_supported(struct tb_port *port, unsigned int clx)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	u32 val, mask = 0;
7162306a36Sopenharmony_ci	bool ret;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/* Don't enable CLx in case of two single-lane links */
7462306a36Sopenharmony_ci	if (!port->bonded && port->dual_link_port)
7562306a36Sopenharmony_ci		return false;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* Don't enable CLx in case of inter-domain link */
7862306a36Sopenharmony_ci	if (port->xdomain)
7962306a36Sopenharmony_ci		return false;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (tb_switch_is_usb4(port->sw)) {
8262306a36Sopenharmony_ci		if (!usb4_port_clx_supported(port))
8362306a36Sopenharmony_ci			return false;
8462306a36Sopenharmony_ci	} else if (!tb_lc_is_clx_supported(port)) {
8562306a36Sopenharmony_ci		return false;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	if (clx & TB_CL0S)
8962306a36Sopenharmony_ci		mask |= LANE_ADP_CS_0_CL0S_SUPPORT;
9062306a36Sopenharmony_ci	if (clx & TB_CL1)
9162306a36Sopenharmony_ci		mask |= LANE_ADP_CS_0_CL1_SUPPORT;
9262306a36Sopenharmony_ci	if (clx & TB_CL2)
9362306a36Sopenharmony_ci		mask |= LANE_ADP_CS_0_CL2_SUPPORT;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
9662306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_0, 1);
9762306a36Sopenharmony_ci	if (ret)
9862306a36Sopenharmony_ci		return false;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	return !!(val & mask);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic int tb_port_clx_set(struct tb_port *port, unsigned int clx, bool enable)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	u32 phy, mask = 0;
10662306a36Sopenharmony_ci	int ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (clx & TB_CL0S)
10962306a36Sopenharmony_ci		mask |= LANE_ADP_CS_1_CL0S_ENABLE;
11062306a36Sopenharmony_ci	if (clx & TB_CL1)
11162306a36Sopenharmony_ci		mask |= LANE_ADP_CS_1_CL1_ENABLE;
11262306a36Sopenharmony_ci	if (clx & TB_CL2)
11362306a36Sopenharmony_ci		mask |= LANE_ADP_CS_1_CL2_ENABLE;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (!mask)
11662306a36Sopenharmony_ci		return -EOPNOTSUPP;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = tb_port_read(port, &phy, TB_CFG_PORT,
11962306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
12062306a36Sopenharmony_ci	if (ret)
12162306a36Sopenharmony_ci		return ret;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (enable)
12462306a36Sopenharmony_ci		phy |= mask;
12562306a36Sopenharmony_ci	else
12662306a36Sopenharmony_ci		phy &= ~mask;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	return tb_port_write(port, &phy, TB_CFG_PORT,
12962306a36Sopenharmony_ci			     port->cap_phy + LANE_ADP_CS_1, 1);
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int tb_port_clx_disable(struct tb_port *port, unsigned int clx)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	return tb_port_clx_set(port, clx, false);
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int tb_port_clx_enable(struct tb_port *port, unsigned int clx)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	return tb_port_clx_set(port, clx, true);
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int tb_port_clx(struct tb_port *port)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	u32 val;
14562306a36Sopenharmony_ci	int ret;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (!tb_port_clx_supported(port, TB_CL0S | TB_CL1 | TB_CL2))
14862306a36Sopenharmony_ci		return 0;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	ret = tb_port_read(port, &val, TB_CFG_PORT,
15162306a36Sopenharmony_ci			   port->cap_phy + LANE_ADP_CS_1, 1);
15262306a36Sopenharmony_ci	if (ret)
15362306a36Sopenharmony_ci		return ret;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (val & LANE_ADP_CS_1_CL0S_ENABLE)
15662306a36Sopenharmony_ci		ret |= TB_CL0S;
15762306a36Sopenharmony_ci	if (val & LANE_ADP_CS_1_CL1_ENABLE)
15862306a36Sopenharmony_ci		ret |= TB_CL1;
15962306a36Sopenharmony_ci	if (val & LANE_ADP_CS_1_CL2_ENABLE)
16062306a36Sopenharmony_ci		ret |= TB_CL2;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	return ret;
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci/**
16662306a36Sopenharmony_ci * tb_port_clx_is_enabled() - Is given CL state enabled
16762306a36Sopenharmony_ci * @port: USB4 port to check
16862306a36Sopenharmony_ci * @clx: Mask of CL states to check
16962306a36Sopenharmony_ci *
17062306a36Sopenharmony_ci * Returns true if any of the given CL states is enabled for @port.
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_cibool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	return !!(tb_port_clx(port) & clx);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/**
17862306a36Sopenharmony_ci * tb_switch_clx_init() - Initialize router CL states
17962306a36Sopenharmony_ci * @sw: Router
18062306a36Sopenharmony_ci *
18162306a36Sopenharmony_ci * Can be called for any router. Initializes the current CL state by
18262306a36Sopenharmony_ci * reading it from the hardware.
18362306a36Sopenharmony_ci *
18462306a36Sopenharmony_ci * Returns %0 in case of success and negative errno in case of failure.
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_ciint tb_switch_clx_init(struct tb_switch *sw)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct tb_port *up, *down;
18962306a36Sopenharmony_ci	unsigned int clx, tmp;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (tb_switch_is_icm(sw))
19262306a36Sopenharmony_ci		return 0;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	if (!tb_route(sw))
19562306a36Sopenharmony_ci		return 0;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (!tb_switch_clx_is_supported(sw))
19862306a36Sopenharmony_ci		return 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	up = tb_upstream_port(sw);
20162306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	clx = tb_port_clx(up);
20462306a36Sopenharmony_ci	tmp = tb_port_clx(down);
20562306a36Sopenharmony_ci	if (clx != tmp)
20662306a36Sopenharmony_ci		tb_sw_warn(sw, "CLx: inconsistent configuration %#x != %#x\n",
20762306a36Sopenharmony_ci			   clx, tmp);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	tb_sw_dbg(sw, "CLx: current mode: %s\n", clx_name(clx));
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	sw->clx = clx;
21262306a36Sopenharmony_ci	return 0;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic int tb_switch_pm_secondary_resolve(struct tb_switch *sw)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct tb_port *up, *down;
21862306a36Sopenharmony_ci	int ret;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	if (!tb_route(sw))
22162306a36Sopenharmony_ci		return 0;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	up = tb_upstream_port(sw);
22462306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
22562306a36Sopenharmony_ci	ret = tb_port_pm_secondary_enable(up);
22662306a36Sopenharmony_ci	if (ret)
22762306a36Sopenharmony_ci		return ret;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return tb_port_pm_secondary_disable(down);
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic int tb_switch_mask_clx_objections(struct tb_switch *sw)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	int up_port = sw->config.upstream_port_number;
23562306a36Sopenharmony_ci	u32 offset, val[2], mask_obj, unmask_obj;
23662306a36Sopenharmony_ci	int ret, i;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	/* Only Titan Ridge of pre-USB4 devices support CLx states */
23962306a36Sopenharmony_ci	if (!tb_switch_is_titan_ridge(sw))
24062306a36Sopenharmony_ci		return 0;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (!tb_route(sw))
24362306a36Sopenharmony_ci		return 0;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/*
24662306a36Sopenharmony_ci	 * In Titan Ridge there are only 2 dual-lane Thunderbolt ports:
24762306a36Sopenharmony_ci	 * Port A consists of lane adapters 1,2 and
24862306a36Sopenharmony_ci	 * Port B consists of lane adapters 3,4
24962306a36Sopenharmony_ci	 * If upstream port is A, (lanes are 1,2), we mask objections from
25062306a36Sopenharmony_ci	 * port B (lanes 3,4) and unmask objections from Port A and vice-versa.
25162306a36Sopenharmony_ci	 */
25262306a36Sopenharmony_ci	if (up_port == 1) {
25362306a36Sopenharmony_ci		mask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
25462306a36Sopenharmony_ci		unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
25562306a36Sopenharmony_ci		offset = TB_LOW_PWR_C1_CL1;
25662306a36Sopenharmony_ci	} else {
25762306a36Sopenharmony_ci		mask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
25862306a36Sopenharmony_ci		unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
25962306a36Sopenharmony_ci		offset = TB_LOW_PWR_C3_CL1;
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
26362306a36Sopenharmony_ci			 sw->cap_lp + offset, ARRAY_SIZE(val));
26462306a36Sopenharmony_ci	if (ret)
26562306a36Sopenharmony_ci		return ret;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(val); i++) {
26862306a36Sopenharmony_ci		val[i] |= mask_obj;
26962306a36Sopenharmony_ci		val[i] &= ~unmask_obj;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	return tb_sw_write(sw, &val, TB_CFG_SWITCH,
27362306a36Sopenharmony_ci			   sw->cap_lp + offset, ARRAY_SIZE(val));
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci/**
27762306a36Sopenharmony_ci * tb_switch_clx_is_supported() - Is CLx supported on this type of router
27862306a36Sopenharmony_ci * @sw: The router to check CLx support for
27962306a36Sopenharmony_ci */
28062306a36Sopenharmony_cibool tb_switch_clx_is_supported(const struct tb_switch *sw)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	if (!clx_enabled)
28362306a36Sopenharmony_ci		return false;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	if (sw->quirks & QUIRK_NO_CLX)
28662306a36Sopenharmony_ci		return false;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	/*
28962306a36Sopenharmony_ci	 * CLx is not enabled and validated on Intel USB4 platforms
29062306a36Sopenharmony_ci	 * before Alder Lake.
29162306a36Sopenharmony_ci	 */
29262306a36Sopenharmony_ci	if (tb_switch_is_tiger_lake(sw))
29362306a36Sopenharmony_ci		return false;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic bool validate_mask(unsigned int clx)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	/* Previous states need to be enabled */
30162306a36Sopenharmony_ci	if (clx & TB_CL1)
30262306a36Sopenharmony_ci		return (clx & TB_CL0S) == TB_CL0S;
30362306a36Sopenharmony_ci	return true;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci/**
30762306a36Sopenharmony_ci * tb_switch_clx_enable() - Enable CLx on upstream port of specified router
30862306a36Sopenharmony_ci * @sw: Router to enable CLx for
30962306a36Sopenharmony_ci * @clx: The CLx state to enable
31062306a36Sopenharmony_ci *
31162306a36Sopenharmony_ci * CLx is enabled only if both sides of the link support CLx, and if both sides
31262306a36Sopenharmony_ci * of the link are not configured as two single lane links and only if the link
31362306a36Sopenharmony_ci * is not inter-domain link. The complete set of conditions is described in CM
31462306a36Sopenharmony_ci * Guide 1.0 section 8.1.
31562306a36Sopenharmony_ci *
31662306a36Sopenharmony_ci * Returns %0 on success or an error code on failure.
31762306a36Sopenharmony_ci */
31862306a36Sopenharmony_ciint tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	bool up_clx_support, down_clx_support;
32162306a36Sopenharmony_ci	struct tb_switch *parent_sw;
32262306a36Sopenharmony_ci	struct tb_port *up, *down;
32362306a36Sopenharmony_ci	int ret;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	if (!clx || sw->clx == clx)
32662306a36Sopenharmony_ci		return 0;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	if (!validate_mask(clx))
32962306a36Sopenharmony_ci		return -EINVAL;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	parent_sw = tb_switch_parent(sw);
33262306a36Sopenharmony_ci	if (!parent_sw)
33362306a36Sopenharmony_ci		return 0;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	if (!tb_switch_clx_is_supported(parent_sw) ||
33662306a36Sopenharmony_ci	    !tb_switch_clx_is_supported(sw))
33762306a36Sopenharmony_ci		return 0;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	/* Only support CL2 for v2 routers */
34062306a36Sopenharmony_ci	if ((clx & TB_CL2) &&
34162306a36Sopenharmony_ci	    (usb4_switch_version(parent_sw) < 2 ||
34262306a36Sopenharmony_ci	     usb4_switch_version(sw) < 2))
34362306a36Sopenharmony_ci		return -EOPNOTSUPP;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	ret = tb_switch_pm_secondary_resolve(sw);
34662306a36Sopenharmony_ci	if (ret)
34762306a36Sopenharmony_ci		return ret;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	up = tb_upstream_port(sw);
35062306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	up_clx_support = tb_port_clx_supported(up, clx);
35362306a36Sopenharmony_ci	down_clx_support = tb_port_clx_supported(down, clx);
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	tb_port_dbg(up, "CLx: %s %ssupported\n", clx_name(clx),
35662306a36Sopenharmony_ci		    up_clx_support ? "" : "not ");
35762306a36Sopenharmony_ci	tb_port_dbg(down, "CLx: %s %ssupported\n", clx_name(clx),
35862306a36Sopenharmony_ci		    down_clx_support ? "" : "not ");
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (!up_clx_support || !down_clx_support)
36162306a36Sopenharmony_ci		return -EOPNOTSUPP;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	ret = tb_port_clx_enable(up, clx);
36462306a36Sopenharmony_ci	if (ret)
36562306a36Sopenharmony_ci		return ret;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	ret = tb_port_clx_enable(down, clx);
36862306a36Sopenharmony_ci	if (ret) {
36962306a36Sopenharmony_ci		tb_port_clx_disable(up, clx);
37062306a36Sopenharmony_ci		return ret;
37162306a36Sopenharmony_ci	}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	ret = tb_switch_mask_clx_objections(sw);
37462306a36Sopenharmony_ci	if (ret) {
37562306a36Sopenharmony_ci		tb_port_clx_disable(up, clx);
37662306a36Sopenharmony_ci		tb_port_clx_disable(down, clx);
37762306a36Sopenharmony_ci		return ret;
37862306a36Sopenharmony_ci	}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	sw->clx |= clx;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	tb_sw_dbg(sw, "CLx: %s enabled\n", clx_name(clx));
38362306a36Sopenharmony_ci	return 0;
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci/**
38762306a36Sopenharmony_ci * tb_switch_clx_disable() - Disable CLx on upstream port of specified router
38862306a36Sopenharmony_ci * @sw: Router to disable CLx for
38962306a36Sopenharmony_ci *
39062306a36Sopenharmony_ci * Disables all CL states of the given router. Can be called on any
39162306a36Sopenharmony_ci * router and if the states were not enabled already does nothing.
39262306a36Sopenharmony_ci *
39362306a36Sopenharmony_ci * Returns the CL states that were disabled or negative errno in case of
39462306a36Sopenharmony_ci * failure.
39562306a36Sopenharmony_ci */
39662306a36Sopenharmony_ciint tb_switch_clx_disable(struct tb_switch *sw)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	unsigned int clx = sw->clx;
39962306a36Sopenharmony_ci	struct tb_port *up, *down;
40062306a36Sopenharmony_ci	int ret;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	if (!tb_switch_clx_is_supported(sw))
40362306a36Sopenharmony_ci		return 0;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	if (!clx)
40662306a36Sopenharmony_ci		return 0;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	up = tb_upstream_port(sw);
40962306a36Sopenharmony_ci	down = tb_switch_downstream_port(sw);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	ret = tb_port_clx_disable(up, clx);
41262306a36Sopenharmony_ci	if (ret)
41362306a36Sopenharmony_ci		return ret;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	ret = tb_port_clx_disable(down, clx);
41662306a36Sopenharmony_ci	if (ret)
41762306a36Sopenharmony_ci		return ret;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	sw->clx = 0;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	tb_sw_dbg(sw, "CLx: %s disabled\n", clx_name(clx));
42262306a36Sopenharmony_ci	return clx;
42362306a36Sopenharmony_ci}
424