162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Thunderbolt link controller support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019, Intel Corporation 662306a36Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include "tb.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci/** 1262306a36Sopenharmony_ci * tb_lc_read_uuid() - Read switch UUID from link controller common register 1362306a36Sopenharmony_ci * @sw: Switch whose UUID is read 1462306a36Sopenharmony_ci * @uuid: UUID is placed here 1562306a36Sopenharmony_ci */ 1662306a36Sopenharmony_ciint tb_lc_read_uuid(struct tb_switch *sw, u32 *uuid) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci if (!sw->cap_lc) 1962306a36Sopenharmony_ci return -EINVAL; 2062306a36Sopenharmony_ci return tb_sw_read(sw, uuid, TB_CFG_SWITCH, sw->cap_lc + TB_LC_FUSE, 4); 2162306a36Sopenharmony_ci} 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistatic int read_lc_desc(struct tb_switch *sw, u32 *desc) 2462306a36Sopenharmony_ci{ 2562306a36Sopenharmony_ci if (!sw->cap_lc) 2662306a36Sopenharmony_ci return -EINVAL; 2762306a36Sopenharmony_ci return tb_sw_read(sw, desc, TB_CFG_SWITCH, sw->cap_lc + TB_LC_DESC, 1); 2862306a36Sopenharmony_ci} 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic int find_port_lc_cap(struct tb_port *port) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 3362306a36Sopenharmony_ci int start, phys, ret, size; 3462306a36Sopenharmony_ci u32 desc; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci ret = read_lc_desc(sw, &desc); 3762306a36Sopenharmony_ci if (ret) 3862306a36Sopenharmony_ci return ret; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci /* Start of port LC registers */ 4162306a36Sopenharmony_ci start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT; 4262306a36Sopenharmony_ci size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT; 4362306a36Sopenharmony_ci phys = tb_phy_port_from_link(port->port); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci return sw->cap_lc + start + phys * size; 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int tb_lc_set_port_configured(struct tb_port *port, bool configured) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci bool upstream = tb_is_upstream_port(port); 5162306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 5262306a36Sopenharmony_ci u32 ctrl, lane; 5362306a36Sopenharmony_ci int cap, ret; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (sw->generation < 2) 5662306a36Sopenharmony_ci return 0; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci cap = find_port_lc_cap(port); 5962306a36Sopenharmony_ci if (cap < 0) 6062306a36Sopenharmony_ci return cap; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 6362306a36Sopenharmony_ci if (ret) 6462306a36Sopenharmony_ci return ret; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci /* Resolve correct lane */ 6762306a36Sopenharmony_ci if (port->port % 2) 6862306a36Sopenharmony_ci lane = TB_LC_SX_CTRL_L1C; 6962306a36Sopenharmony_ci else 7062306a36Sopenharmony_ci lane = TB_LC_SX_CTRL_L2C; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (configured) { 7362306a36Sopenharmony_ci ctrl |= lane; 7462306a36Sopenharmony_ci if (upstream) 7562306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_UPSTREAM; 7662306a36Sopenharmony_ci } else { 7762306a36Sopenharmony_ci ctrl &= ~lane; 7862306a36Sopenharmony_ci if (upstream) 7962306a36Sopenharmony_ci ctrl &= ~TB_LC_SX_CTRL_UPSTREAM; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci/** 8662306a36Sopenharmony_ci * tb_lc_configure_port() - Let LC know about configured port 8762306a36Sopenharmony_ci * @port: Port that is set as configured 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * Sets the port configured for power management purposes. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_ciint tb_lc_configure_port(struct tb_port *port) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci return tb_lc_set_port_configured(port, true); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci/** 9762306a36Sopenharmony_ci * tb_lc_unconfigure_port() - Let LC know about unconfigured port 9862306a36Sopenharmony_ci * @port: Port that is set as configured 9962306a36Sopenharmony_ci * 10062306a36Sopenharmony_ci * Sets the port unconfigured for power management purposes. 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_civoid tb_lc_unconfigure_port(struct tb_port *port) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci tb_lc_set_port_configured(port, false); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int tb_lc_set_xdomain_configured(struct tb_port *port, bool configure) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 11062306a36Sopenharmony_ci u32 ctrl, lane; 11162306a36Sopenharmony_ci int cap, ret; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (sw->generation < 2) 11462306a36Sopenharmony_ci return 0; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci cap = find_port_lc_cap(port); 11762306a36Sopenharmony_ci if (cap < 0) 11862306a36Sopenharmony_ci return cap; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 12162306a36Sopenharmony_ci if (ret) 12262306a36Sopenharmony_ci return ret; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Resolve correct lane */ 12562306a36Sopenharmony_ci if (port->port % 2) 12662306a36Sopenharmony_ci lane = TB_LC_SX_CTRL_L1D; 12762306a36Sopenharmony_ci else 12862306a36Sopenharmony_ci lane = TB_LC_SX_CTRL_L2D; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (configure) 13162306a36Sopenharmony_ci ctrl |= lane; 13262306a36Sopenharmony_ci else 13362306a36Sopenharmony_ci ctrl &= ~lane; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci/** 13962306a36Sopenharmony_ci * tb_lc_configure_xdomain() - Inform LC that the link is XDomain 14062306a36Sopenharmony_ci * @port: Switch downstream port connected to another host 14162306a36Sopenharmony_ci * 14262306a36Sopenharmony_ci * Sets the lane configured for XDomain accordingly so that the LC knows 14362306a36Sopenharmony_ci * about this. Returns %0 in success and negative errno in failure. 14462306a36Sopenharmony_ci */ 14562306a36Sopenharmony_ciint tb_lc_configure_xdomain(struct tb_port *port) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci return tb_lc_set_xdomain_configured(port, true); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/** 15162306a36Sopenharmony_ci * tb_lc_unconfigure_xdomain() - Unconfigure XDomain from port 15262306a36Sopenharmony_ci * @port: Switch downstream port that was connected to another host 15362306a36Sopenharmony_ci * 15462306a36Sopenharmony_ci * Unsets the lane XDomain configuration. 15562306a36Sopenharmony_ci */ 15662306a36Sopenharmony_civoid tb_lc_unconfigure_xdomain(struct tb_port *port) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci tb_lc_set_xdomain_configured(port, false); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/** 16262306a36Sopenharmony_ci * tb_lc_start_lane_initialization() - Start lane initialization 16362306a36Sopenharmony_ci * @port: Device router lane 0 adapter 16462306a36Sopenharmony_ci * 16562306a36Sopenharmony_ci * Starts lane initialization for @port after the router resumed from 16662306a36Sopenharmony_ci * sleep. Should be called for those downstream lane adapters that were 16762306a36Sopenharmony_ci * not connected (tb_lc_configure_port() was not called) before sleep. 16862306a36Sopenharmony_ci * 16962306a36Sopenharmony_ci * Returns %0 in success and negative errno in case of failure. 17062306a36Sopenharmony_ci */ 17162306a36Sopenharmony_ciint tb_lc_start_lane_initialization(struct tb_port *port) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 17462306a36Sopenharmony_ci int ret, cap; 17562306a36Sopenharmony_ci u32 ctrl; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (!tb_route(sw)) 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (sw->generation < 2) 18162306a36Sopenharmony_ci return 0; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci cap = find_port_lc_cap(port); 18462306a36Sopenharmony_ci if (cap < 0) 18562306a36Sopenharmony_ci return cap; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 18862306a36Sopenharmony_ci if (ret) 18962306a36Sopenharmony_ci return ret; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_SLI; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, cap + TB_LC_SX_CTRL, 1); 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci/** 19762306a36Sopenharmony_ci * tb_lc_is_clx_supported() - Check whether CLx is supported by the lane adapter 19862306a36Sopenharmony_ci * @port: Lane adapter 19962306a36Sopenharmony_ci * 20062306a36Sopenharmony_ci * TB_LC_LINK_ATTR_CPS bit reflects if the link supports CLx including 20162306a36Sopenharmony_ci * active cables (if connected on the link). 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_cibool tb_lc_is_clx_supported(struct tb_port *port) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 20662306a36Sopenharmony_ci int cap, ret; 20762306a36Sopenharmony_ci u32 val; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci cap = find_port_lc_cap(port); 21062306a36Sopenharmony_ci if (cap < 0) 21162306a36Sopenharmony_ci return false; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_ATTR, 1); 21462306a36Sopenharmony_ci if (ret) 21562306a36Sopenharmony_ci return false; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return !!(val & TB_LC_LINK_ATTR_CPS); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci/** 22162306a36Sopenharmony_ci * tb_lc_is_usb_plugged() - Is there USB device connected to port 22262306a36Sopenharmony_ci * @port: Device router lane 0 adapter 22362306a36Sopenharmony_ci * 22462306a36Sopenharmony_ci * Returns true if the @port has USB type-C device connected. 22562306a36Sopenharmony_ci */ 22662306a36Sopenharmony_cibool tb_lc_is_usb_plugged(struct tb_port *port) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 22962306a36Sopenharmony_ci int cap, ret; 23062306a36Sopenharmony_ci u32 val; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (sw->generation != 3) 23362306a36Sopenharmony_ci return false; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci cap = find_port_lc_cap(port); 23662306a36Sopenharmony_ci if (cap < 0) 23762306a36Sopenharmony_ci return false; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_CS_42, 1); 24062306a36Sopenharmony_ci if (ret) 24162306a36Sopenharmony_ci return false; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return !!(val & TB_LC_CS_42_USB_PLUGGED); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci/** 24762306a36Sopenharmony_ci * tb_lc_is_xhci_connected() - Is the internal xHCI connected 24862306a36Sopenharmony_ci * @port: Device router lane 0 adapter 24962306a36Sopenharmony_ci * 25062306a36Sopenharmony_ci * Returns true if the internal xHCI has been connected to @port. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_cibool tb_lc_is_xhci_connected(struct tb_port *port) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 25562306a36Sopenharmony_ci int cap, ret; 25662306a36Sopenharmony_ci u32 val; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (sw->generation != 3) 25962306a36Sopenharmony_ci return false; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci cap = find_port_lc_cap(port); 26262306a36Sopenharmony_ci if (cap < 0) 26362306a36Sopenharmony_ci return false; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); 26662306a36Sopenharmony_ci if (ret) 26762306a36Sopenharmony_ci return false; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci return !!(val & TB_LC_LINK_REQ_XHCI_CONNECT); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic int __tb_lc_xhci_connect(struct tb_port *port, bool connect) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct tb_switch *sw = port->sw; 27562306a36Sopenharmony_ci int cap, ret; 27662306a36Sopenharmony_ci u32 val; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (sw->generation != 3) 27962306a36Sopenharmony_ci return -EINVAL; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci cap = find_port_lc_cap(port); 28262306a36Sopenharmony_ci if (cap < 0) 28362306a36Sopenharmony_ci return cap; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); 28662306a36Sopenharmony_ci if (ret) 28762306a36Sopenharmony_ci return ret; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (connect) 29062306a36Sopenharmony_ci val |= TB_LC_LINK_REQ_XHCI_CONNECT; 29162306a36Sopenharmony_ci else 29262306a36Sopenharmony_ci val &= ~TB_LC_LINK_REQ_XHCI_CONNECT; 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci return tb_sw_write(sw, &val, TB_CFG_SWITCH, cap + TB_LC_LINK_REQ, 1); 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci/** 29862306a36Sopenharmony_ci * tb_lc_xhci_connect() - Connect internal xHCI 29962306a36Sopenharmony_ci * @port: Device router lane 0 adapter 30062306a36Sopenharmony_ci * 30162306a36Sopenharmony_ci * Tells LC to connect the internal xHCI to @port. Returns %0 on success 30262306a36Sopenharmony_ci * and negative errno in case of failure. Can be called for Thunderbolt 3 30362306a36Sopenharmony_ci * routers only. 30462306a36Sopenharmony_ci */ 30562306a36Sopenharmony_ciint tb_lc_xhci_connect(struct tb_port *port) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci int ret; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci ret = __tb_lc_xhci_connect(port, true); 31062306a36Sopenharmony_ci if (ret) 31162306a36Sopenharmony_ci return ret; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci tb_port_dbg(port, "xHCI connected\n"); 31462306a36Sopenharmony_ci return 0; 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci/** 31862306a36Sopenharmony_ci * tb_lc_xhci_disconnect() - Disconnect internal xHCI 31962306a36Sopenharmony_ci * @port: Device router lane 0 adapter 32062306a36Sopenharmony_ci * 32162306a36Sopenharmony_ci * Tells LC to disconnect the internal xHCI from @port. Can be called 32262306a36Sopenharmony_ci * for Thunderbolt 3 routers only. 32362306a36Sopenharmony_ci */ 32462306a36Sopenharmony_civoid tb_lc_xhci_disconnect(struct tb_port *port) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci __tb_lc_xhci_connect(port, false); 32762306a36Sopenharmony_ci tb_port_dbg(port, "xHCI disconnected\n"); 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset, 33162306a36Sopenharmony_ci unsigned int flags) 33262306a36Sopenharmony_ci{ 33362306a36Sopenharmony_ci u32 ctrl; 33462306a36Sopenharmony_ci int ret; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci /* 33762306a36Sopenharmony_ci * Enable wake on PCIe and USB4 (wake coming from another 33862306a36Sopenharmony_ci * router). 33962306a36Sopenharmony_ci */ 34062306a36Sopenharmony_ci ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, 34162306a36Sopenharmony_ci offset + TB_LC_SX_CTRL, 1); 34262306a36Sopenharmony_ci if (ret) 34362306a36Sopenharmony_ci return ret; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci ctrl &= ~(TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD | TB_LC_SX_CTRL_WODPC | 34662306a36Sopenharmony_ci TB_LC_SX_CTRL_WODPD | TB_LC_SX_CTRL_WOP | TB_LC_SX_CTRL_WOU4); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (flags & TB_WAKE_ON_CONNECT) 34962306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_WOC | TB_LC_SX_CTRL_WOD; 35062306a36Sopenharmony_ci if (flags & TB_WAKE_ON_USB4) 35162306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_WOU4; 35262306a36Sopenharmony_ci if (flags & TB_WAKE_ON_PCIE) 35362306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_WOP; 35462306a36Sopenharmony_ci if (flags & TB_WAKE_ON_DP) 35562306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_WODPC | TB_LC_SX_CTRL_WODPD; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci return tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, offset + TB_LC_SX_CTRL, 1); 35862306a36Sopenharmony_ci} 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci/** 36162306a36Sopenharmony_ci * tb_lc_set_wake() - Enable/disable wake 36262306a36Sopenharmony_ci * @sw: Switch whose wakes to configure 36362306a36Sopenharmony_ci * @flags: Wakeup flags (%0 to disable) 36462306a36Sopenharmony_ci * 36562306a36Sopenharmony_ci * For each LC sets wake bits accordingly. 36662306a36Sopenharmony_ci */ 36762306a36Sopenharmony_ciint tb_lc_set_wake(struct tb_switch *sw, unsigned int flags) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci int start, size, nlc, ret, i; 37062306a36Sopenharmony_ci u32 desc; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (sw->generation < 2) 37362306a36Sopenharmony_ci return 0; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (!tb_route(sw)) 37662306a36Sopenharmony_ci return 0; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci ret = read_lc_desc(sw, &desc); 37962306a36Sopenharmony_ci if (ret) 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci /* Figure out number of link controllers */ 38362306a36Sopenharmony_ci nlc = desc & TB_LC_DESC_NLC_MASK; 38462306a36Sopenharmony_ci start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT; 38562306a36Sopenharmony_ci size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci /* For each link controller set sleep bit */ 38862306a36Sopenharmony_ci for (i = 0; i < nlc; i++) { 38962306a36Sopenharmony_ci unsigned int offset = sw->cap_lc + start + i * size; 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci ret = tb_lc_set_wake_one(sw, offset, flags); 39262306a36Sopenharmony_ci if (ret) 39362306a36Sopenharmony_ci return ret; 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci return 0; 39762306a36Sopenharmony_ci} 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci/** 40062306a36Sopenharmony_ci * tb_lc_set_sleep() - Inform LC that the switch is going to sleep 40162306a36Sopenharmony_ci * @sw: Switch to set sleep 40262306a36Sopenharmony_ci * 40362306a36Sopenharmony_ci * Let the switch link controllers know that the switch is going to 40462306a36Sopenharmony_ci * sleep. 40562306a36Sopenharmony_ci */ 40662306a36Sopenharmony_ciint tb_lc_set_sleep(struct tb_switch *sw) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci int start, size, nlc, ret, i; 40962306a36Sopenharmony_ci u32 desc; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci if (sw->generation < 2) 41262306a36Sopenharmony_ci return 0; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci ret = read_lc_desc(sw, &desc); 41562306a36Sopenharmony_ci if (ret) 41662306a36Sopenharmony_ci return ret; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci /* Figure out number of link controllers */ 41962306a36Sopenharmony_ci nlc = desc & TB_LC_DESC_NLC_MASK; 42062306a36Sopenharmony_ci start = (desc & TB_LC_DESC_SIZE_MASK) >> TB_LC_DESC_SIZE_SHIFT; 42162306a36Sopenharmony_ci size = (desc & TB_LC_DESC_PORT_SIZE_MASK) >> TB_LC_DESC_PORT_SIZE_SHIFT; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci /* For each link controller set sleep bit */ 42462306a36Sopenharmony_ci for (i = 0; i < nlc; i++) { 42562306a36Sopenharmony_ci unsigned int offset = sw->cap_lc + start + i * size; 42662306a36Sopenharmony_ci u32 ctrl; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci ret = tb_sw_read(sw, &ctrl, TB_CFG_SWITCH, 42962306a36Sopenharmony_ci offset + TB_LC_SX_CTRL, 1); 43062306a36Sopenharmony_ci if (ret) 43162306a36Sopenharmony_ci return ret; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci ctrl |= TB_LC_SX_CTRL_SLP; 43462306a36Sopenharmony_ci ret = tb_sw_write(sw, &ctrl, TB_CFG_SWITCH, 43562306a36Sopenharmony_ci offset + TB_LC_SX_CTRL, 1); 43662306a36Sopenharmony_ci if (ret) 43762306a36Sopenharmony_ci return ret; 43862306a36Sopenharmony_ci } 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci return 0; 44162306a36Sopenharmony_ci} 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci/** 44462306a36Sopenharmony_ci * tb_lc_lane_bonding_possible() - Is lane bonding possible towards switch 44562306a36Sopenharmony_ci * @sw: Switch to check 44662306a36Sopenharmony_ci * 44762306a36Sopenharmony_ci * Checks whether conditions for lane bonding from parent to @sw are 44862306a36Sopenharmony_ci * possible. 44962306a36Sopenharmony_ci */ 45062306a36Sopenharmony_cibool tb_lc_lane_bonding_possible(struct tb_switch *sw) 45162306a36Sopenharmony_ci{ 45262306a36Sopenharmony_ci struct tb_port *up; 45362306a36Sopenharmony_ci int cap, ret; 45462306a36Sopenharmony_ci u32 val; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if (sw->generation < 2) 45762306a36Sopenharmony_ci return false; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci up = tb_upstream_port(sw); 46062306a36Sopenharmony_ci cap = find_port_lc_cap(up); 46162306a36Sopenharmony_ci if (cap < 0) 46262306a36Sopenharmony_ci return false; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, cap + TB_LC_PORT_ATTR, 1); 46562306a36Sopenharmony_ci if (ret) 46662306a36Sopenharmony_ci return false; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci return !!(val & TB_LC_PORT_ATTR_BE); 46962306a36Sopenharmony_ci} 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic int tb_lc_dp_sink_from_port(const struct tb_switch *sw, 47262306a36Sopenharmony_ci struct tb_port *in) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct tb_port *port; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci /* The first DP IN port is sink 0 and second is sink 1 */ 47762306a36Sopenharmony_ci tb_switch_for_each_port(sw, port) { 47862306a36Sopenharmony_ci if (tb_port_is_dpin(port)) 47962306a36Sopenharmony_ci return in != port; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci return -EINVAL; 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic int tb_lc_dp_sink_available(struct tb_switch *sw, int sink) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci u32 val, alloc; 48862306a36Sopenharmony_ci int ret; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, 49162306a36Sopenharmony_ci sw->cap_lc + TB_LC_SNK_ALLOCATION, 1); 49262306a36Sopenharmony_ci if (ret) 49362306a36Sopenharmony_ci return ret; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* 49662306a36Sopenharmony_ci * Sink is available for CM/SW to use if the allocation valie is 49762306a36Sopenharmony_ci * either 0 or 1. 49862306a36Sopenharmony_ci */ 49962306a36Sopenharmony_ci if (!sink) { 50062306a36Sopenharmony_ci alloc = val & TB_LC_SNK_ALLOCATION_SNK0_MASK; 50162306a36Sopenharmony_ci if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK0_CM) 50262306a36Sopenharmony_ci return 0; 50362306a36Sopenharmony_ci } else { 50462306a36Sopenharmony_ci alloc = (val & TB_LC_SNK_ALLOCATION_SNK1_MASK) >> 50562306a36Sopenharmony_ci TB_LC_SNK_ALLOCATION_SNK1_SHIFT; 50662306a36Sopenharmony_ci if (!alloc || alloc == TB_LC_SNK_ALLOCATION_SNK1_CM) 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci return -EBUSY; 51162306a36Sopenharmony_ci} 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci/** 51462306a36Sopenharmony_ci * tb_lc_dp_sink_query() - Is DP sink available for DP IN port 51562306a36Sopenharmony_ci * @sw: Switch whose DP sink is queried 51662306a36Sopenharmony_ci * @in: DP IN port to check 51762306a36Sopenharmony_ci * 51862306a36Sopenharmony_ci * Queries through LC SNK_ALLOCATION registers whether DP sink is available 51962306a36Sopenharmony_ci * for the given DP IN port or not. 52062306a36Sopenharmony_ci */ 52162306a36Sopenharmony_cibool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci int sink; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci /* 52662306a36Sopenharmony_ci * For older generations sink is always available as there is no 52762306a36Sopenharmony_ci * allocation mechanism. 52862306a36Sopenharmony_ci */ 52962306a36Sopenharmony_ci if (sw->generation < 3) 53062306a36Sopenharmony_ci return true; 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_ci sink = tb_lc_dp_sink_from_port(sw, in); 53362306a36Sopenharmony_ci if (sink < 0) 53462306a36Sopenharmony_ci return false; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci return !tb_lc_dp_sink_available(sw, sink); 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci/** 54062306a36Sopenharmony_ci * tb_lc_dp_sink_alloc() - Allocate DP sink 54162306a36Sopenharmony_ci * @sw: Switch whose DP sink is allocated 54262306a36Sopenharmony_ci * @in: DP IN port the DP sink is allocated for 54362306a36Sopenharmony_ci * 54462306a36Sopenharmony_ci * Allocate DP sink for @in via LC SNK_ALLOCATION registers. If the 54562306a36Sopenharmony_ci * resource is available and allocation is successful returns %0. In all 54662306a36Sopenharmony_ci * other cases returs negative errno. In particular %-EBUSY is returned if 54762306a36Sopenharmony_ci * the resource was not available. 54862306a36Sopenharmony_ci */ 54962306a36Sopenharmony_ciint tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in) 55062306a36Sopenharmony_ci{ 55162306a36Sopenharmony_ci int ret, sink; 55262306a36Sopenharmony_ci u32 val; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci if (sw->generation < 3) 55562306a36Sopenharmony_ci return 0; 55662306a36Sopenharmony_ci 55762306a36Sopenharmony_ci sink = tb_lc_dp_sink_from_port(sw, in); 55862306a36Sopenharmony_ci if (sink < 0) 55962306a36Sopenharmony_ci return sink; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci ret = tb_lc_dp_sink_available(sw, sink); 56262306a36Sopenharmony_ci if (ret) 56362306a36Sopenharmony_ci return ret; 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, 56662306a36Sopenharmony_ci sw->cap_lc + TB_LC_SNK_ALLOCATION, 1); 56762306a36Sopenharmony_ci if (ret) 56862306a36Sopenharmony_ci return ret; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (!sink) { 57162306a36Sopenharmony_ci val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK; 57262306a36Sopenharmony_ci val |= TB_LC_SNK_ALLOCATION_SNK0_CM; 57362306a36Sopenharmony_ci } else { 57462306a36Sopenharmony_ci val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK; 57562306a36Sopenharmony_ci val |= TB_LC_SNK_ALLOCATION_SNK1_CM << 57662306a36Sopenharmony_ci TB_LC_SNK_ALLOCATION_SNK1_SHIFT; 57762306a36Sopenharmony_ci } 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, 58062306a36Sopenharmony_ci sw->cap_lc + TB_LC_SNK_ALLOCATION, 1); 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if (ret) 58362306a36Sopenharmony_ci return ret; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci tb_port_dbg(in, "sink %d allocated\n", sink); 58662306a36Sopenharmony_ci return 0; 58762306a36Sopenharmony_ci} 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci/** 59062306a36Sopenharmony_ci * tb_lc_dp_sink_dealloc() - De-allocate DP sink 59162306a36Sopenharmony_ci * @sw: Switch whose DP sink is de-allocated 59262306a36Sopenharmony_ci * @in: DP IN port whose DP sink is de-allocated 59362306a36Sopenharmony_ci * 59462306a36Sopenharmony_ci * De-allocate DP sink from @in using LC SNK_ALLOCATION registers. 59562306a36Sopenharmony_ci */ 59662306a36Sopenharmony_ciint tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in) 59762306a36Sopenharmony_ci{ 59862306a36Sopenharmony_ci int ret, sink; 59962306a36Sopenharmony_ci u32 val; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci if (sw->generation < 3) 60262306a36Sopenharmony_ci return 0; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci sink = tb_lc_dp_sink_from_port(sw, in); 60562306a36Sopenharmony_ci if (sink < 0) 60662306a36Sopenharmony_ci return sink; 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci /* Needs to be owned by CM/SW */ 60962306a36Sopenharmony_ci ret = tb_lc_dp_sink_available(sw, sink); 61062306a36Sopenharmony_ci if (ret) 61162306a36Sopenharmony_ci return ret; 61262306a36Sopenharmony_ci 61362306a36Sopenharmony_ci ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, 61462306a36Sopenharmony_ci sw->cap_lc + TB_LC_SNK_ALLOCATION, 1); 61562306a36Sopenharmony_ci if (ret) 61662306a36Sopenharmony_ci return ret; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci if (!sink) 61962306a36Sopenharmony_ci val &= ~TB_LC_SNK_ALLOCATION_SNK0_MASK; 62062306a36Sopenharmony_ci else 62162306a36Sopenharmony_ci val &= ~TB_LC_SNK_ALLOCATION_SNK1_MASK; 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_ci ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, 62462306a36Sopenharmony_ci sw->cap_lc + TB_LC_SNK_ALLOCATION, 1); 62562306a36Sopenharmony_ci if (ret) 62662306a36Sopenharmony_ci return ret; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci tb_port_dbg(in, "sink %d de-allocated\n", sink); 62962306a36Sopenharmony_ci return 0; 63062306a36Sopenharmony_ci} 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci/** 63362306a36Sopenharmony_ci * tb_lc_force_power() - Forces LC to be powered on 63462306a36Sopenharmony_ci * @sw: Thunderbolt switch 63562306a36Sopenharmony_ci * 63662306a36Sopenharmony_ci * This is useful to let authentication cycle pass even without 63762306a36Sopenharmony_ci * a Thunderbolt link present. 63862306a36Sopenharmony_ci */ 63962306a36Sopenharmony_ciint tb_lc_force_power(struct tb_switch *sw) 64062306a36Sopenharmony_ci{ 64162306a36Sopenharmony_ci u32 in = 0xffff; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci return tb_sw_write(sw, &in, TB_CFG_SWITCH, TB_LC_POWER, 1); 64462306a36Sopenharmony_ci} 645