Commit 30a4eca6 authored by Mika Westerberg's avatar Mika Westerberg
Browse files

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: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
parent f1d5ec3e
Loading
Loading
Loading
Loading
+110 −0
Original line number Diff line number Diff line
@@ -217,6 +217,116 @@ bool tb_lc_is_clx_supported(struct tb_port *port)
	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)
{
+70 −1
Original line number Diff line number Diff line
@@ -1528,7 +1528,13 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
		case PCI_DEVICE_ID_INTEL_PORT_RIDGE:
			break;
		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 {
		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 */
	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");
	}
}
+11 −0
Original line number Diff line number Diff line
@@ -1054,6 +1054,8 @@ static int tb_disconnect_pci(struct tb *tb, struct tb_switch *sw)
	if (WARN_ON(!tunnel))
		return -ENODEV;

	tb_switch_xhci_disconnect(sw);

	tb_tunnel_deactivate(tunnel);
	list_del(&tunnel->list);
	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))
		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);
	return 0;
}
@@ -1256,12 +1261,18 @@ static void tb_handle_hotplug(struct work_struct *work)
			tb_port_unconfigure_xdomain(port);
		} else if (tb_port_is_dpout(port) || tb_port_is_dpin(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 {
			tb_port_dbg(port,
				   "got unplug event for disconnected port, ignoring\n");
		}
	} else if (port->remote) {
		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 {
		if (tb_port_is_null(port)) {
			tb_port_dbg(port, "hotplug: scanning\n");
+7 −0
Original line number Diff line number Diff line
@@ -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_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_port_add_nfc_credits(struct tb_port *port, int credits);
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);
int tb_lc_start_lane_initialization(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_sleep(struct tb_switch *sw);
bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
+8 −0
Original line number Diff line number Diff line
@@ -463,6 +463,8 @@ struct tb_regs_hop {
#define TMU_ADP_CS_6_DISABLE_TMU_OBJ_CL2	BIT(3)

/* Plug Events registers */
#define TB_PLUG_EVENTS_USB_DISABLE		BIT(2)

#define TB_PLUG_EVENTS_PCIE_WR_DATA		0x1b
#define TB_PLUG_EVENTS_PCIE_CMD			0x1c
#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

/* 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_BE			BIT(12)

@@ -522,4 +527,7 @@ struct tb_regs_hop {
#define TB_LC_LINK_ATTR				0x97
#define TB_LC_LINK_ATTR_CPS			BIT(18)

#define TB_LC_LINK_REQ				0xad
#define TB_LC_LINK_REQ_XHCI_CONNECT		BIT(31)

#endif