forked from Minki/linux
thunderbolt: Add internal xHCI connect flows for Thunderbolt 3 devices
Both Alpine Ridge and Titan Ridge require special flows in order to activate the internal xHCI controller when there is USB device connected to the downstream type-C port. This implements the missing flows for both. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
This commit is contained in:
parent
f1d5ec3e0e
commit
30a4eca69b
@ -217,6 +217,116 @@ bool tb_lc_is_clx_supported(struct tb_port *port)
|
|||||||
return !!(val & TB_LC_LINK_ATTR_CPS);
|
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,
|
static int tb_lc_set_wake_one(struct tb_switch *sw, unsigned int offset,
|
||||||
unsigned int flags)
|
unsigned int flags)
|
||||||
{
|
{
|
||||||
|
@ -1528,7 +1528,13 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
|
|||||||
case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
|
case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
data |= 4;
|
/*
|
||||||
|
* Skip Alpine Ridge, it needs to have vendor
|
||||||
|
* specific USB hotplug event enabled for the
|
||||||
|
* internal xHCI to work.
|
||||||
|
*/
|
||||||
|
if (!tb_switch_is_alpine_ridge(sw))
|
||||||
|
data |= TB_PLUG_EVENTS_USB_DISABLE;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data = data | 0x7c;
|
data = data | 0x7c;
|
||||||
@ -3689,3 +3695,66 @@ int tb_switch_pcie_l1_enable(struct tb_switch *sw)
|
|||||||
/* Write to Upstream PCIe bridge #0 aka Up0 */
|
/* Write to Upstream PCIe bridge #0 aka Up0 */
|
||||||
return tb_switch_pcie_bridge_write(sw, 0, 0x143, 0x0c5806b1);
|
return tb_switch_pcie_bridge_write(sw, 0, 0x143, 0x0c5806b1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tb_switch_xhci_connect() - Connect internal xHCI
|
||||||
|
* @sw: Router whose xHCI to connect
|
||||||
|
*
|
||||||
|
* Can be called to any router. For Alpine Ridge and Titan Ridge
|
||||||
|
* performs special flows that bring the xHCI functional for any device
|
||||||
|
* connected to the type-C port. Call only after PCIe tunnel has been
|
||||||
|
* established. The function only does the connect if not done already
|
||||||
|
* so can be called several times for the same router.
|
||||||
|
*/
|
||||||
|
int tb_switch_xhci_connect(struct tb_switch *sw)
|
||||||
|
{
|
||||||
|
bool usb_port1, usb_port3, xhci_port1, xhci_port3;
|
||||||
|
struct tb_port *port1, *port3;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
port1 = &sw->ports[1];
|
||||||
|
port3 = &sw->ports[3];
|
||||||
|
|
||||||
|
if (tb_switch_is_alpine_ridge(sw)) {
|
||||||
|
usb_port1 = tb_lc_is_usb_plugged(port1);
|
||||||
|
usb_port3 = tb_lc_is_usb_plugged(port3);
|
||||||
|
xhci_port1 = tb_lc_is_xhci_connected(port1);
|
||||||
|
xhci_port3 = tb_lc_is_xhci_connected(port3);
|
||||||
|
|
||||||
|
/* Figure out correct USB port to connect */
|
||||||
|
if (usb_port1 && !xhci_port1) {
|
||||||
|
ret = tb_lc_xhci_connect(port1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (usb_port3 && !xhci_port3)
|
||||||
|
return tb_lc_xhci_connect(port3);
|
||||||
|
} else if (tb_switch_is_titan_ridge(sw)) {
|
||||||
|
ret = tb_lc_xhci_connect(port1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
return tb_lc_xhci_connect(port3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tb_switch_xhci_disconnect() - Disconnect internal xHCI
|
||||||
|
* @sw: Router whose xHCI to disconnect
|
||||||
|
*
|
||||||
|
* The opposite of tb_switch_xhci_connect(). Disconnects xHCI on both
|
||||||
|
* ports.
|
||||||
|
*/
|
||||||
|
void tb_switch_xhci_disconnect(struct tb_switch *sw)
|
||||||
|
{
|
||||||
|
if (sw->generation == 3) {
|
||||||
|
struct tb_port *port1 = &sw->ports[1];
|
||||||
|
struct tb_port *port3 = &sw->ports[3];
|
||||||
|
|
||||||
|
tb_lc_xhci_disconnect(port1);
|
||||||
|
tb_port_dbg(port1, "disconnected xHCI\n");
|
||||||
|
tb_lc_xhci_disconnect(port3);
|
||||||
|
tb_port_dbg(port3, "disconnected xHCI\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1054,6 +1054,8 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw)
|
|||||||
if (WARN_ON(!tunnel))
|
if (WARN_ON(!tunnel))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
|
tb_switch_xhci_disconnect(sw);
|
||||||
|
|
||||||
tb_tunnel_deactivate(tunnel);
|
tb_tunnel_deactivate(tunnel);
|
||||||
list_del(&tunnel->list);
|
list_del(&tunnel->list);
|
||||||
tb_tunnel_free(tunnel);
|
tb_tunnel_free(tunnel);
|
||||||
@ -1099,6 +1101,9 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
|
|||||||
if (tb_switch_pcie_l1_enable(sw))
|
if (tb_switch_pcie_l1_enable(sw))
|
||||||
tb_sw_warn(sw, "failed to enable PCIe L1 for Titan Ridge\n");
|
tb_sw_warn(sw, "failed to enable PCIe L1 for Titan Ridge\n");
|
||||||
|
|
||||||
|
if (tb_switch_xhci_connect(sw))
|
||||||
|
tb_sw_warn(sw, "failed to connect xHCI\n");
|
||||||
|
|
||||||
list_add_tail(&tunnel->list, &tcm->tunnel_list);
|
list_add_tail(&tunnel->list, &tcm->tunnel_list);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1256,12 +1261,18 @@ static void tb_handle_hotplug(struct work_struct *work)
|
|||||||
tb_port_unconfigure_xdomain(port);
|
tb_port_unconfigure_xdomain(port);
|
||||||
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
|
} else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) {
|
||||||
tb_dp_resource_unavailable(tb, port);
|
tb_dp_resource_unavailable(tb, port);
|
||||||
|
} else if (!port->port) {
|
||||||
|
tb_sw_dbg(sw, "xHCI disconnect request\n");
|
||||||
|
tb_switch_xhci_disconnect(sw);
|
||||||
} else {
|
} else {
|
||||||
tb_port_dbg(port,
|
tb_port_dbg(port,
|
||||||
"got unplug event for disconnected port, ignoring\n");
|
"got unplug event for disconnected port, ignoring\n");
|
||||||
}
|
}
|
||||||
} else if (port->remote) {
|
} else if (port->remote) {
|
||||||
tb_port_dbg(port, "got plug event for connected port, ignoring\n");
|
tb_port_dbg(port, "got plug event for connected port, ignoring\n");
|
||||||
|
} else if (!port->port && sw->authorized) {
|
||||||
|
tb_sw_dbg(sw, "xHCI connect request\n");
|
||||||
|
tb_switch_xhci_connect(sw);
|
||||||
} else {
|
} else {
|
||||||
if (tb_port_is_null(port)) {
|
if (tb_port_is_null(port)) {
|
||||||
tb_port_dbg(port, "hotplug: scanning\n");
|
tb_port_dbg(port, "hotplug: scanning\n");
|
||||||
|
@ -988,6 +988,9 @@ int tb_switch_mask_clx_objections(struct tb_switch *sw);
|
|||||||
|
|
||||||
int tb_switch_pcie_l1_enable(struct tb_switch *sw);
|
int tb_switch_pcie_l1_enable(struct tb_switch *sw);
|
||||||
|
|
||||||
|
int tb_switch_xhci_connect(struct tb_switch *sw);
|
||||||
|
void tb_switch_xhci_disconnect(struct tb_switch *sw);
|
||||||
|
|
||||||
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
|
int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
|
||||||
int tb_port_add_nfc_credits(struct tb_port *port, int credits);
|
int tb_port_add_nfc_credits(struct tb_port *port, int credits);
|
||||||
int tb_port_clear_counter(struct tb_port *port, int counter);
|
int tb_port_clear_counter(struct tb_port *port, int counter);
|
||||||
@ -1082,6 +1085,10 @@ int tb_lc_configure_xdomain(struct tb_port *port);
|
|||||||
void tb_lc_unconfigure_xdomain(struct tb_port *port);
|
void tb_lc_unconfigure_xdomain(struct tb_port *port);
|
||||||
int tb_lc_start_lane_initialization(struct tb_port *port);
|
int tb_lc_start_lane_initialization(struct tb_port *port);
|
||||||
bool tb_lc_is_clx_supported(struct tb_port *port);
|
bool tb_lc_is_clx_supported(struct tb_port *port);
|
||||||
|
bool tb_lc_is_usb_plugged(struct tb_port *port);
|
||||||
|
bool tb_lc_is_xhci_connected(struct tb_port *port);
|
||||||
|
int tb_lc_xhci_connect(struct tb_port *port);
|
||||||
|
void tb_lc_xhci_disconnect(struct tb_port *port);
|
||||||
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags);
|
int tb_lc_set_wake(struct tb_switch *sw, unsigned int flags);
|
||||||
int tb_lc_set_sleep(struct tb_switch *sw);
|
int tb_lc_set_sleep(struct tb_switch *sw);
|
||||||
bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
|
bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
|
||||||
|
@ -463,6 +463,8 @@ struct tb_regs_hop {
|
|||||||
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2 BIT(3)
|
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2 BIT(3)
|
||||||
|
|
||||||
/* Plug Events registers */
|
/* Plug Events registers */
|
||||||
|
#define TB_PLUG_EVENTS_USB_DISABLE BIT(2)
|
||||||
|
|
||||||
#define TB_PLUG_EVENTS_PCIE_WR_DATA 0x1b
|
#define TB_PLUG_EVENTS_PCIE_WR_DATA 0x1b
|
||||||
#define TB_PLUG_EVENTS_PCIE_CMD 0x1c
|
#define TB_PLUG_EVENTS_PCIE_CMD 0x1c
|
||||||
#define TB_PLUG_EVENTS_PCIE_CMD_DW_OFFSET_MASK GENMASK(9, 0)
|
#define TB_PLUG_EVENTS_PCIE_CMD_DW_OFFSET_MASK GENMASK(9, 0)
|
||||||
@ -502,6 +504,9 @@ struct tb_regs_hop {
|
|||||||
#define TB_LC_POWER 0x740
|
#define TB_LC_POWER 0x740
|
||||||
|
|
||||||
/* Link controller registers */
|
/* Link controller registers */
|
||||||
|
#define TB_LC_CS_42 0x2a
|
||||||
|
#define TB_LC_CS_42_USB_PLUGGED BIT(31)
|
||||||
|
|
||||||
#define TB_LC_PORT_ATTR 0x8d
|
#define TB_LC_PORT_ATTR 0x8d
|
||||||
#define TB_LC_PORT_ATTR_BE BIT(12)
|
#define TB_LC_PORT_ATTR_BE BIT(12)
|
||||||
|
|
||||||
@ -522,4 +527,7 @@ struct tb_regs_hop {
|
|||||||
#define TB_LC_LINK_ATTR 0x97
|
#define TB_LC_LINK_ATTR 0x97
|
||||||
#define TB_LC_LINK_ATTR_CPS BIT(18)
|
#define TB_LC_LINK_ATTR_CPS BIT(18)
|
||||||
|
|
||||||
|
#define TB_LC_LINK_REQ 0xad
|
||||||
|
#define TB_LC_LINK_REQ_XHCI_CONNECT BIT(31)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user