123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Thunderbolt link controller support
- *
- * Copyright (C) 2019, Intel Corporation
- * Author: Mika Westerberg <[email protected]>
- */
- #include "tb.h"
- /**
- * tb_lc_read_uuid() - Read switch UUID from link controller common register
- * @sw: Switch whose UUID is read
- * @uuid: UUID is placed here
- */
- int tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid)
- {
- if (!sw->cap_lc)
- return -EINVAL;
- return tb_sw_read(sw, uuid, TB_CFG_SWITCH, sw->cap_lc + TB_LC_FUSE, 4);
- }
- static int read_lc_desc(struct tb_switch *sw, u32 *desc)
- {
- if (!sw->cap_lc)
- return -EINVAL;
- return tb_sw_read(sw, desc, TB_CFG_SWITCH, sw->cap_lc + TB_LC_DESC, 1);
- }
- static int find_port_lc_cap(struct tb_port *port)
- {
- struct tb_switch *sw = port->sw;
- int start, phys, ret, size;
- u32 desc;
- ret = read_lc_desc(sw, &desc);
- if (ret)
- return ret;
- /* Start of port LC registers */
- start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
- size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
- phys = tb_phy_port_from_link(port->port);
- return sw->cap_lc + start + phys * size;
- }
- static int tb_lc_set_port_configured(struct tb_port *port, bool configured)
- {
- bool upstream = tb_is_upstream_port(port);
- struct tb_switch *sw = port->sw;
- u32 ctrl, lane;
- int cap, ret;
- if (sw->generation < 2)
- return 0;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return cap;
- ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- /* Resolve correct lane */
- if (port->port % 2)
- lane = TB_LC_SX_CTRL_L1C;
- else
- lane = TB_LC_SX_CTRL_L2C;
- if (configured) {
- ctrl |= lane;
- if (upstream)
- ctrl |= TB_LC_SX_CTRL_UPSTREAM;
- } else {
- ctrl &= ~lane;
- if (upstream)
- ctrl &= ~TB_LC_SX_CTRL_UPSTREAM;
- }
- return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- }
- /**
- * tb_lc_configure_port() - Let LC know about configured port
- * @port: Port that is set as configured
- *
- * Sets the port configured for power management purposes.
- */
- int tb_lc_configure_port(struct tb_port *port)
- {
- return tb_lc_set_port_configured(port, true);
- }
- /**
- * tb_lc_unconfigure_port() - Let LC know about unconfigured port
- * @port: Port that is set as configured
- *
- * Sets the port unconfigured for power management purposes.
- */
- void tb_lc_unconfigure_port(struct tb_port *port)
- {
- tb_lc_set_port_configured(port, false);
- }
- static int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure)
- {
- struct tb_switch *sw = port->sw;
- u32 ctrl, lane;
- int cap, ret;
- if (sw->generation < 2)
- return 0;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return cap;
- ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- /* Resolve correct lane */
- if (port->port % 2)
- lane = TB_LC_SX_CTRL_L1D;
- else
- lane = TB_LC_SX_CTRL_L2D;
- if (configure)
- ctrl |= lane;
- else
- ctrl &= ~lane;
- return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- }
- /**
- * tb_lc_configure_xdomain() - Inform LC that the link is XDomain
- * @port: Switch downstream port connected to another host
- *
- * Sets the lane configured for XDomain accordingly so that the LC knows
- * about this. Returns %0 in success and negative errno in failure.
- */
- int tb_lc_configure_xdomain(struct tb_port *port)
- {
- return tb_lc_set_xdomain_configured(port, true);
- }
- /**
- * tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port
- * @port: Switch downstream port that was connected to another host
- *
- * Unsets the lane XDomain configuration.
- */
- void tb_lc_unconfigure_xdomain(struct tb_port *port)
- {
- tb_lc_set_xdomain_configured(port, false);
- }
- /**
- * tb_lc_start_lane_initialization() - Start lane initialization
- * @port: Device router lane 0 adapter
- *
- * Starts lane initialization for @port after the router resumed from
- * sleep. Should be called for those downstream lane adapters that were
- * not connected (tb_lc_configure_port() was not called) before sleep.
- *
- * Returns %0 in success and negative errno in case of failure.
- */
- int tb_lc_start_lane_initialization(struct tb_port *port)
- {
- struct tb_switch *sw = port->sw;
- int ret, cap;
- u32 ctrl;
- if (!tb_route(sw))
- return 0;
- if (sw->generation < 2)
- return 0;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return cap;
- ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- ctrl |= TB_LC_SX_CTRL_SLI;
- return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1);
- }
- /**
- * tb_lc_is_clx_supported() - Check whether CLx is supported by the lane adapter
- * @port: Lane adapter
- *
- * TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including
- * active cables (if connected on the link).
- */
- bool tb_lc_is_clx_supported(struct tb_port *port)
- {
- struct tb_switch *sw = port->sw;
- int cap, ret;
- u32 val;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return false;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_ATTR, 1);
- if (ret)
- return false;
- return !!(val & TB_LC_LINK_ATTR_CPS);
- }
- /**
- * tb_lc_is_usb_plugged() - Is there USB device connected to port
- * @port: Device router lane 0 adapter
- *
- * Returns true if the @port has USB type-C device connected.
- */
- bool tb_lc_is_usb_plugged(struct tb_port *port)
- {
- struct tb_switch *sw = port->sw;
- int cap, ret;
- u32 val;
- if (sw->generation != 3)
- return false;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return false;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_CS_42, 1);
- if (ret)
- return false;
- return !!(val & TB_LC_CS_42_USB_PLUGGED);
- }
- /**
- * tb_lc_is_xhci_connected() - Is the internal xHCI connected
- * @port: Device router lane 0 adapter
- *
- * Returns true if the internal xHCI has been connected to @port.
- */
- bool tb_lc_is_xhci_connected(struct tb_port *port)
- {
- struct tb_switch *sw = port->sw;
- int cap, ret;
- u32 val;
- if (sw->generation != 3)
- return false;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return false;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
- if (ret)
- return false;
- return !!(val & TB_LC_LINK_REQ_XHCI_CONNECT);
- }
- static int __tb_lc_xhci_connect(struct tb_port *port, bool connect)
- {
- struct tb_switch *sw = port->sw;
- int cap, ret;
- u32 val;
- if (sw->generation != 3)
- return -EINVAL;
- cap = find_port_lc_cap(port);
- if (cap < 0)
- return cap;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
- if (ret)
- return ret;
- if (connect)
- val |= TB_LC_LINK_REQ_XHCI_CONNECT;
- else
- val &= ~TB_LC_LINK_REQ_XHCI_CONNECT;
- return tb_sw_write(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1);
- }
- /**
- * tb_lc_xhci_connect() - Connect internal xHCI
- * @port: Device router lane 0 adapter
- *
- * Tells LC to connect the internal xHCI to @port. Returns %0 on success
- * and negative errno in case of failure. Can be called for Thunderbolt 3
- * routers only.
- */
- int tb_lc_xhci_connect(struct tb_port *port)
- {
- int ret;
- ret = __tb_lc_xhci_connect(port, true);
- if (ret)
- return ret;
- tb_port_dbg(port, "xHCI connected\n");
- return 0;
- }
- /**
- * tb_lc_xhci_disconnect() - Disconnect internal xHCI
- * @port: Device router lane 0 adapter
- *
- * Tells LC to disconnect the internal xHCI from @port. Can be called
- * for Thunderbolt 3 routers only.
- */
- void tb_lc_xhci_disconnect(struct tb_port *port)
- {
- __tb_lc_xhci_connect(port, false);
- tb_port_dbg(port, "xHCI disconnected\n");
- }
- static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
- unsigned int flags)
- {
- u32 ctrl;
- int ret;
- /*
- * Enable wake on PCIe and USB4 (wake coming from another
- * router).
- */
- ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
- offset + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WODPC |
- TB_LC_SX_CTRL_WODPD | TB_LC_SX_CTRL_WOP | TB_LC_SX_CTRL_WOU4);
- if (flags & TB_WAKE_ON_CONNECT)
- ctrl |= TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD;
- if (flags & TB_WAKE_ON_USB4)
- ctrl |= TB_LC_SX_CTRL_WOU4;
- if (flags & TB_WAKE_ON_PCIE)
- ctrl |= TB_LC_SX_CTRL_WOP;
- if (flags & TB_WAKE_ON_DP)
- ctrl |= TB_LC_SX_CTRL_WODPC | TB_LC_SX_CTRL_WODPD;
- return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1);
- }
- /**
- * tb_lc_set_wake() - Enable/disable wake
- * @sw: Switch whose wakes to configure
- * @flags: Wakeup flags (%0 to disable)
- *
- * For each LC sets wake bits accordingly.
- */
- int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags)
- {
- int start, size, nlc, ret, i;
- u32 desc;
- if (sw->generation < 2)
- return 0;
- if (!tb_route(sw))
- return 0;
- ret = read_lc_desc(sw, &desc);
- if (ret)
- return ret;
- /* Figure out number of link controllers */
- nlc = desc & TB_LC_DESC_NLC_MASK;
- start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
- size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
- /* For each link controller set sleep bit */
- for (i = 0; i < nlc; i++) {
- unsigned int offset = sw->cap_lc + start + i * size;
- ret = tb_lc_set_wake_one(sw, offset, flags);
- if (ret)
- return ret;
- }
- return 0;
- }
- /**
- * tb_lc_set_sleep() - Inform LC that the switch is going to sleep
- * @sw: Switch to set sleep
- *
- * Let the switch link controllers know that the switch is going to
- * sleep.
- */
- int tb_lc_set_sleep(struct tb_switch *sw)
- {
- int start, size, nlc, ret, i;
- u32 desc;
- if (sw->generation < 2)
- return 0;
- ret = read_lc_desc(sw, &desc);
- if (ret)
- return ret;
- /* Figure out number of link controllers */
- nlc = desc & TB_LC_DESC_NLC_MASK;
- start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT;
- size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT;
- /* For each link controller set sleep bit */
- for (i = 0; i < nlc; i++) {
- unsigned int offset = sw->cap_lc + start + i * size;
- u32 ctrl;
- ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH,
- offset + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- ctrl |= TB_LC_SX_CTRL_SLP;
- ret = tb_sw_write(sw, &ctrl, TB_CFG_SWITCH,
- offset + TB_LC_SX_CTRL, 1);
- if (ret)
- return ret;
- }
- return 0;
- }
- /**
- * tb_lc_lane_bonding_possible() - Is lane bonding possible towards switch
- * @sw: Switch to check
- *
- * Checks whether conditions for lane bonding from parent to @sw are
- * possible.
- */
- bool tb_lc_lane_bonding_possible(struct tb_switch *sw)
- {
- struct tb_port *up;
- int cap, ret;
- u32 val;
- if (sw->generation < 2)
- return false;
- up = tb_upstream_port(sw);
- cap = find_port_lc_cap(up);
- if (cap < 0)
- return false;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_PORT_ATTR, 1);
- if (ret)
- return false;
- return !!(val & TB_LC_PORT_ATTR_BE);
- }
- static int tb_lc_dp_sink_from_port(const struct tb_switch *sw,
- struct tb_port *in)
- {
- struct tb_port *port;
- /* The first DP IN port is sink 0 and second is sink 1 */
- tb_switch_for_each_port(sw, port) {
- if (tb_port_is_dpin(port))
- return in != port;
- }
- return -EINVAL;
- }
- static int tb_lc_dp_sink_available(struct tb_switch *sw, int sink)
- {
- u32 val, alloc;
- int ret;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
- sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
- if (ret)
- return ret;
- /*
- * Sink is available for CM/SW to use if the allocation valie is
- * either 0 or 1.
- */
- if (!sink) {
- alloc = val & TB_LC_SNK_ALLOCATION_SNK0_MASK;
- if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK0_CM)
- return 0;
- } else {
- alloc = (val & TB_LC_SNK_ALLOCATION_SNK1_MASK) >>
- TB_LC_SNK_ALLOCATION_SNK1_SHIFT;
- if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK1_CM)
- return 0;
- }
- return -EBUSY;
- }
- /**
- * tb_lc_dp_sink_query() - Is DP sink available for DP IN port
- * @sw: Switch whose DP sink is queried
- * @in: DP IN port to check
- *
- * Queries through LC SNK_ALLOCATION registers whether DP sink is available
- * for the given DP IN port or not.
- */
- bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in)
- {
- int sink;
- /*
- * For older generations sink is always available as there is no
- * allocation mechanism.
- */
- if (sw->generation < 3)
- return true;
- sink = tb_lc_dp_sink_from_port(sw, in);
- if (sink < 0)
- return false;
- return !tb_lc_dp_sink_available(sw, sink);
- }
- /**
- * tb_lc_dp_sink_alloc() - Allocate DP sink
- * @sw: Switch whose DP sink is allocated
- * @in: DP IN port the DP sink is allocated for
- *
- * Allocate DP sink for @in via LC SNK_ALLOCATION registers. If the
- * resource is available and allocation is successful returns %0. In all
- * other cases returs negative errno. In particular %-EBUSY is returned if
- * the resource was not available.
- */
- int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in)
- {
- int ret, sink;
- u32 val;
- if (sw->generation < 3)
- return 0;
- sink = tb_lc_dp_sink_from_port(sw, in);
- if (sink < 0)
- return sink;
- ret = tb_lc_dp_sink_available(sw, sink);
- if (ret)
- return ret;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
- sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
- if (ret)
- return ret;
- if (!sink) {
- val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK;
- val |= TB_LC_SNK_ALLOCATION_SNK0_CM;
- } else {
- val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK;
- val |= TB_LC_SNK_ALLOCATION_SNK1_CM <<
- TB_LC_SNK_ALLOCATION_SNK1_SHIFT;
- }
- ret = tb_sw_write(sw, &val, TB_CFG_SWITCH,
- sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
- if (ret)
- return ret;
- tb_port_dbg(in, "sink %d allocated\n", sink);
- return 0;
- }
- /**
- * tb_lc_dp_sink_dealloc() - De-allocate DP sink
- * @sw: Switch whose DP sink is de-allocated
- * @in: DP IN port whose DP sink is de-allocated
- *
- * De-allocate DP sink from @in using LC SNK_ALLOCATION registers.
- */
- int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in)
- {
- int ret, sink;
- u32 val;
- if (sw->generation < 3)
- return 0;
- sink = tb_lc_dp_sink_from_port(sw, in);
- if (sink < 0)
- return sink;
- /* Needs to be owned by CM/SW */
- ret = tb_lc_dp_sink_available(sw, sink);
- if (ret)
- return ret;
- ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
- sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
- if (ret)
- return ret;
- if (!sink)
- val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK;
- else
- val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK;
- ret = tb_sw_write(sw, &val, TB_CFG_SWITCH,
- sw->cap_lc + TB_LC_SNK_ALLOCATION, 1);
- if (ret)
- return ret;
- tb_port_dbg(in, "sink %d de-allocated\n", sink);
- return 0;
- }
- /**
- * tb_lc_force_power() - Forces LC to be powered on
- * @sw: Thunderbolt switch
- *
- * This is useful to let authentication cycle pass even without
- * a Thunderbolt link present.
- */
- int tb_lc_force_power(struct tb_switch *sw)
- {
- u32 in = 0xffff;
- return tb_sw_write(sw, &in, TB_CFG_SWITCH, TB_LC_POWER, 1);
- }
|