| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * CLx support
- *
- * Copyright (C) 2020 - 2023, Intel Corporation
- * Authors: Gil Fine <gil.fine@intel.com>
- * Mika Westerberg <mika.westerberg@linux.intel.com>
- */
- #include <linux/module.h>
- #include "tb.h"
- static bool clx_enabled = true;
- module_param_named(clx, clx_enabled, bool, 0444);
- MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)");
- static const char *clx_name(unsigned int clx)
- {
- switch (clx) {
- case TB_CL0S | TB_CL1 | TB_CL2:
- return "CL0s/CL1/CL2";
- case TB_CL1 | TB_CL2:
- return "CL1/CL2";
- case TB_CL0S | TB_CL2:
- return "CL0s/CL2";
- case TB_CL0S | TB_CL1:
- return "CL0s/CL1";
- case TB_CL0S:
- return "CL0s";
- case 0:
- return "disabled";
- default:
- return "unknown";
- }
- }
- static int tb_port_pm_secondary_set(struct tb_port *port, bool secondary)
- {
- u32 phy;
- int ret;
- ret = tb_port_read(port, &phy, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
- if (ret)
- return ret;
- if (secondary)
- phy |= LANE_ADP_CS_1_PMS;
- else
- phy &= ~LANE_ADP_CS_1_PMS;
- return tb_port_write(port, &phy, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
- }
- static int tb_port_pm_secondary_enable(struct tb_port *port)
- {
- return tb_port_pm_secondary_set(port, true);
- }
- static int tb_port_pm_secondary_disable(struct tb_port *port)
- {
- return tb_port_pm_secondary_set(port, false);
- }
- /* Called for USB4 or Titan Ridge routers only */
- static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx)
- {
- u32 val, mask = 0;
- bool ret;
- /* Don't enable CLx in case of two single-lane links */
- if (!port->bonded && port->dual_link_port)
- return false;
- /* Don't enable CLx in case of inter-domain link */
- if (port->xdomain)
- return false;
- if (tb_switch_is_usb4(port->sw)) {
- if (!usb4_port_clx_supported(port))
- return false;
- } else if (!tb_lc_is_clx_supported(port)) {
- return false;
- }
- if (clx & TB_CL0S)
- mask |= LANE_ADP_CS_0_CL0S_SUPPORT;
- if (clx & TB_CL1)
- mask |= LANE_ADP_CS_0_CL1_SUPPORT;
- if (clx & TB_CL2)
- mask |= LANE_ADP_CS_0_CL2_SUPPORT;
- ret = tb_port_read(port, &val, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_0, 1);
- if (ret)
- return false;
- return !!(val & mask);
- }
- static int tb_port_clx_set(struct tb_port *port, unsigned int clx, bool enable)
- {
- u32 phy, mask = 0;
- int ret;
- if (clx & TB_CL0S)
- mask |= LANE_ADP_CS_1_CL0S_ENABLE;
- if (clx & TB_CL1)
- mask |= LANE_ADP_CS_1_CL1_ENABLE;
- if (clx & TB_CL2)
- mask |= LANE_ADP_CS_1_CL2_ENABLE;
- if (!mask)
- return -EOPNOTSUPP;
- ret = tb_port_read(port, &phy, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
- if (ret)
- return ret;
- if (enable)
- phy |= mask;
- else
- phy &= ~mask;
- return tb_port_write(port, &phy, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
- }
- static int tb_port_clx_disable(struct tb_port *port, unsigned int clx)
- {
- return tb_port_clx_set(port, clx, false);
- }
- static int tb_port_clx_enable(struct tb_port *port, unsigned int clx)
- {
- return tb_port_clx_set(port, clx, true);
- }
- static int tb_port_clx(struct tb_port *port)
- {
- u32 val;
- int ret;
- if (!tb_port_clx_supported(port, TB_CL0S | TB_CL1 | TB_CL2))
- return 0;
- ret = tb_port_read(port, &val, TB_CFG_PORT,
- port->cap_phy + LANE_ADP_CS_1, 1);
- if (ret)
- return ret;
- if (val & LANE_ADP_CS_1_CL0S_ENABLE)
- ret |= TB_CL0S;
- if (val & LANE_ADP_CS_1_CL1_ENABLE)
- ret |= TB_CL1;
- if (val & LANE_ADP_CS_1_CL2_ENABLE)
- ret |= TB_CL2;
- return ret;
- }
- /**
- * tb_port_clx_is_enabled() - Is given CL state enabled
- * @port: USB4 port to check
- * @clx: Mask of CL states to check
- *
- * Returns true if any of the given CL states is enabled for @port.
- */
- bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx)
- {
- return !!(tb_port_clx(port) & clx);
- }
- /**
- * tb_switch_clx_is_supported() - Is CLx supported on this type of router
- * @sw: The router to check CLx support for
- */
- static bool tb_switch_clx_is_supported(const struct tb_switch *sw)
- {
- if (!clx_enabled)
- return false;
- if (sw->quirks & QUIRK_NO_CLX)
- return false;
- /*
- * CLx is not enabled and validated on Intel USB4 platforms
- * before Alder Lake.
- */
- if (tb_switch_is_tiger_lake(sw))
- return false;
- return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
- }
- /**
- * tb_switch_clx_init() - Initialize router CL states
- * @sw: Router
- *
- * Can be called for any router. Initializes the current CL state by
- * reading it from the hardware.
- *
- * Returns %0 in case of success and negative errno in case of failure.
- */
- int tb_switch_clx_init(struct tb_switch *sw)
- {
- struct tb_port *up, *down;
- unsigned int clx, tmp;
- if (tb_switch_is_icm(sw))
- return 0;
- if (!tb_route(sw))
- return 0;
- if (!tb_switch_clx_is_supported(sw))
- return 0;
- up = tb_upstream_port(sw);
- down = tb_switch_downstream_port(sw);
- clx = tb_port_clx(up);
- tmp = tb_port_clx(down);
- if (clx != tmp)
- tb_sw_warn(sw, "CLx: inconsistent configuration %#x != %#x\n",
- clx, tmp);
- tb_sw_dbg(sw, "CLx: current mode: %s\n", clx_name(clx));
- sw->clx = clx;
- return 0;
- }
- static int tb_switch_pm_secondary_resolve(struct tb_switch *sw)
- {
- struct tb_port *up, *down;
- int ret;
- if (!tb_route(sw))
- return 0;
- up = tb_upstream_port(sw);
- down = tb_switch_downstream_port(sw);
- ret = tb_port_pm_secondary_enable(up);
- if (ret)
- return ret;
- return tb_port_pm_secondary_disable(down);
- }
- static int tb_switch_mask_clx_objections(struct tb_switch *sw)
- {
- int up_port = sw->config.upstream_port_number;
- u32 offset, val[2], mask_obj, unmask_obj;
- int ret, i;
- /* Only Titan Ridge of pre-USB4 devices support CLx states */
- if (!tb_switch_is_titan_ridge(sw))
- return 0;
- if (!tb_route(sw))
- return 0;
- /*
- * In Titan Ridge there are only 2 dual-lane Thunderbolt ports:
- * Port A consists of lane adapters 1,2 and
- * Port B consists of lane adapters 3,4
- * If upstream port is A, (lanes are 1,2), we mask objections from
- * port B (lanes 3,4) and unmask objections from Port A and vice-versa.
- */
- if (up_port == 1) {
- mask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
- unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
- offset = TB_LOW_PWR_C1_CL1;
- } else {
- mask_obj = TB_LOW_PWR_C1_PORT_A_MASK;
- unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK;
- offset = TB_LOW_PWR_C3_CL1;
- }
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
- sw->cap_lp + offset, ARRAY_SIZE(val));
- if (ret)
- return ret;
- for (i = 0; i < ARRAY_SIZE(val); i++) {
- val[i] |= mask_obj;
- val[i] &= ~unmask_obj;
- }
- return tb_sw_write(sw, &val, TB_CFG_SWITCH,
- sw->cap_lp + offset, ARRAY_SIZE(val));
- }
- static bool validate_mask(unsigned int clx)
- {
- /* Previous states need to be enabled */
- if (clx & TB_CL1)
- return (clx & TB_CL0S) == TB_CL0S;
- return true;
- }
- /**
- * tb_switch_clx_enable() - Enable CLx on upstream port of specified router
- * @sw: Router to enable CLx for
- * @clx: The CLx state to enable
- *
- * CLx is enabled only if both sides of the link support CLx, and if both sides
- * of the link are not configured as two single lane links and only if the link
- * is not inter-domain link. The complete set of conditions is described in CM
- * Guide 1.0 section 8.1.
- *
- * Returns %0 on success or an error code on failure.
- */
- int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx)
- {
- bool up_clx_support, down_clx_support;
- struct tb_switch *parent_sw;
- struct tb_port *up, *down;
- int ret;
- if (!clx || sw->clx == clx)
- return 0;
- if (!validate_mask(clx))
- return -EINVAL;
- parent_sw = tb_switch_parent(sw);
- if (!parent_sw)
- return 0;
- if (!tb_switch_clx_is_supported(parent_sw) ||
- !tb_switch_clx_is_supported(sw))
- return 0;
- /* Only support CL2 for v2 routers */
- if ((clx & TB_CL2) &&
- (usb4_switch_version(parent_sw) < 2 ||
- usb4_switch_version(sw) < 2))
- return -EOPNOTSUPP;
- ret = tb_switch_pm_secondary_resolve(sw);
- if (ret)
- return ret;
- up = tb_upstream_port(sw);
- down = tb_switch_downstream_port(sw);
- up_clx_support = tb_port_clx_supported(up, clx);
- down_clx_support = tb_port_clx_supported(down, clx);
- tb_port_dbg(up, "CLx: %s %ssupported\n", clx_name(clx),
- up_clx_support ? "" : "not ");
- tb_port_dbg(down, "CLx: %s %ssupported\n", clx_name(clx),
- down_clx_support ? "" : "not ");
- if (!up_clx_support || !down_clx_support)
- return -EOPNOTSUPP;
- ret = tb_port_clx_enable(up, clx);
- if (ret)
- return ret;
- ret = tb_port_clx_enable(down, clx);
- if (ret) {
- tb_port_clx_disable(up, clx);
- return ret;
- }
- ret = tb_switch_mask_clx_objections(sw);
- if (ret) {
- tb_port_clx_disable(up, clx);
- tb_port_clx_disable(down, clx);
- return ret;
- }
- sw->clx |= clx;
- tb_sw_dbg(sw, "CLx: %s enabled\n", clx_name(clx));
- return 0;
- }
- /**
- * tb_switch_clx_disable() - Disable CLx on upstream port of specified router
- * @sw: Router to disable CLx for
- *
- * Disables all CL states of the given router. Can be called on any
- * router and if the states were not enabled already does nothing.
- *
- * Returns the CL states that were disabled or negative errno in case of
- * failure.
- */
- int tb_switch_clx_disable(struct tb_switch *sw)
- {
- unsigned int clx = sw->clx;
- struct tb_port *up, *down;
- int ret;
- if (!tb_switch_clx_is_supported(sw))
- return 0;
- if (!clx)
- return 0;
- if (sw->is_unplugged)
- return clx;
- up = tb_upstream_port(sw);
- down = tb_switch_downstream_port(sw);
- ret = tb_port_clx_disable(up, clx);
- if (ret)
- return ret;
- ret = tb_port_clx_disable(down, clx);
- if (ret)
- return ret;
- sw->clx = 0;
- tb_sw_dbg(sw, "CLx: %s disabled\n", clx_name(clx));
- return clx;
- }
|