Commit a28ec0e1 authored by Gil Fine's avatar Gil Fine Committed by Mika Westerberg
Browse files

thunderbolt: Add TMU uni-directional mode



Up until Titan Ridge (Thunderbolt 3) device routers only supported
bi-directional mode. In this patch we add to TMU a uni-directional mode.
The uni-directional mode is needed for enabling of low power state of
the link (CLx).

Signed-off-by: default avatarGil Fine <gil.fine@intel.com>
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
parent 3cc1c6de
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -221,7 +221,7 @@ static int tb_enable_tmu(struct tb_switch *sw)
	int ret;

	/* If it is already enabled in correct mode, don't touch it */
	if (tb_switch_tmu_is_enabled(sw))
	if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request))
		return 0;

	ret = tb_switch_tmu_disable(sw);
@@ -669,6 +669,7 @@ static void tb_scan_port(struct tb_port *port)
	tb_switch_lane_bonding_enable(sw);
	/* Set the link configured */
	tb_switch_configure_link(sw);
	tb_switch_tmu_configure(sw, TB_SWITCH_TMU_RATE_HIFI, false);

	if (tb_enable_tmu(sw))
		tb_sw_warn(sw, "failed to enable TMU\n");
@@ -1375,6 +1376,7 @@ static int tb_start(struct tb *tb)
		return ret;
	}

	tb_switch_tmu_configure(tb->root_switch, TB_SWITCH_TMU_RATE_HIFI, false);
	/* Enable TMU if it is off */
	tb_switch_tmu_enable(tb->root_switch);
	/* Full scan to discover devices added before the driver was loaded. */
@@ -1418,6 +1420,11 @@ static void tb_restore_children(struct tb_switch *sw)
	if (sw->is_unplugged)
		return;

	/*
	 * tb_switch_tmu_configure() was already called when the switch was
	 * added before entering system sleep or runtime suspend,
	 * so no need to call it again before enabling TMU.
	 */
	if (tb_enable_tmu(sw))
		tb_sw_warn(sw, "failed to restore TMU configuration\n");

+25 −5
Original line number Diff line number Diff line
@@ -89,15 +89,24 @@ enum tb_switch_tmu_rate {
 * @cap: Offset to the TMU capability (%0 if not found)
 * @has_ucap: Does the switch support uni-directional mode
 * @rate: TMU refresh rate related to upstream switch. In case of root
 *	  switch this holds the domain rate.
 *	  switch this holds the domain rate. Reflects the HW setting.
 * @unidirectional: Is the TMU in uni-directional or bi-directional mode
 *		    related to upstream switch. Don't case for root switch.
 *		    related to upstream switch. Don't care for root switch.
 *		    Reflects the HW setting.
 * @unidirectional_request: Is the new TMU mode: uni-directional or bi-directional
 *			    that is requested to be set. Related to upstream switch.
 *			    Don't care for root switch.
 * @rate_request: TMU new refresh rate related to upstream switch that is
 *		  requested to be set. In case of root switch, this holds
 *		  the new domain rate that is requested to be set.
 */
struct tb_switch_tmu {
	int cap;
	bool has_ucap;
	enum tb_switch_tmu_rate rate;
	bool unidirectional;
	bool unidirectional_request;
	enum tb_switch_tmu_rate rate_request;
};

/**
@@ -891,11 +900,22 @@ int tb_switch_tmu_init(struct tb_switch *sw);
int tb_switch_tmu_post_time(struct tb_switch *sw);
int tb_switch_tmu_disable(struct tb_switch *sw);
int tb_switch_tmu_enable(struct tb_switch *sw);

static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
void tb_switch_tmu_configure(struct tb_switch *sw,
			     enum tb_switch_tmu_rate rate,
			     bool unidirectional);
/**
 * tb_switch_tmu_hifi_is_enabled() - Checks if the specified TMU mode is enabled
 * @sw: Router whose TMU mode to check
 * @unidirectional: If uni-directional (bi-directional otherwise)
 *
 * Return true if hardware TMU configuration matches the one passed in
 * as parameter. That is HiFi and either uni-directional or bi-directional.
 */
static inline bool tb_switch_tmu_hifi_is_enabled(const struct tb_switch *sw,
						 bool unidirectional)
{
	return sw->tmu.rate == TB_SWITCH_TMU_RATE_HIFI &&
	       !sw->tmu.unidirectional;
	       sw->tmu.unidirectional == unidirectional;
}

int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
+3 −0
Original line number Diff line number Diff line
@@ -246,6 +246,7 @@ enum usb4_switch_op {
#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT	16
#define TMU_RTR_CS_22				0x16
#define TMU_RTR_CS_24				0x18
#define TMU_RTR_CS_25				0x19

enum tb_port_type {
	TB_TYPE_INACTIVE	= 0x000000,
@@ -305,6 +306,8 @@ struct tb_regs_port_header {
/* TMU adapter registers */
#define TMU_ADP_CS_3				0x03
#define TMU_ADP_CS_3_UDM			BIT(29)
#define TMU_ADP_CS_6				0x06
#define TMU_ADP_CS_6_DTS			BIT(1)

/* Lane adapter registers */
#define LANE_ADP_CS_0				0x00
+228 −46
Original line number Diff line number Diff line
@@ -115,6 +115,11 @@ static inline int tb_port_tmu_unidirectional_disable(struct tb_port *port)
	return tb_port_tmu_set_unidirectional(port, false);
}

static inline int tb_port_tmu_unidirectional_enable(struct tb_port *port)
{
	return tb_port_tmu_set_unidirectional(port, true);
}

static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
{
	int ret;
@@ -128,6 +133,23 @@ static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
	return val & TMU_ADP_CS_3_UDM;
}

static int tb_port_tmu_time_sync(struct tb_port *port, bool time_sync)
{
	u32 val = time_sync ? TMU_ADP_CS_6_DTS : 0;

	return tb_port_tmu_write(port, TMU_ADP_CS_6, TMU_ADP_CS_6_DTS, val);
}

static int tb_port_tmu_time_sync_disable(struct tb_port *port)
{
	return tb_port_tmu_time_sync(port, true);
}

static int tb_port_tmu_time_sync_enable(struct tb_port *port)
{
	return tb_port_tmu_time_sync(port, false);
}

static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set)
{
	int ret;
@@ -207,6 +229,7 @@ int tb_switch_tmu_init(struct tb_switch *sw)
 */
int tb_switch_tmu_post_time(struct tb_switch *sw)
{
	unsigned int post_time_high_offset, post_time_high = 0;
	unsigned int post_local_time_offset, post_time_offset;
	struct tb_switch *root_switch = sw->tb->root_switch;
	u64 hi, mid, lo, local_time, post_time;
@@ -247,6 +270,7 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)

	post_local_time_offset = sw->tmu.cap + TMU_RTR_CS_22;
	post_time_offset = sw->tmu.cap + TMU_RTR_CS_24;
	post_time_high_offset = sw->tmu.cap + TMU_RTR_CS_25;

	/*
	 * Write the Grandmaster time to the Post Local Time registers
@@ -258,17 +282,24 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)
		goto out;

	/*
	 * Have the new switch update its local time (by writing 1 to
	 * the post_time registers) and wait for the completion of the
	 * same (post_time register becomes 0). This means the time has
	 * been converged properly.
	 * Have the new switch update its local time by:
	 * 1) writing 0x1 to the Post Time Low register and 0xffffffff to
	 * Post Time High register.
	 * 2) write 0 to Post Time High register and then wait for
	 * the completion of the post_time register becomes 0.
	 * This means the time has been converged properly.
	 */
	post_time = 1;
	post_time = 0xffffffff00000001ULL;

	ret = tb_sw_write(sw, &post_time, TB_CFG_SWITCH, post_time_offset, 2);
	if (ret)
		goto out;

	ret = tb_sw_write(sw, &post_time_high, TB_CFG_SWITCH,
			  post_time_high_offset, 1);
	if (ret)
		goto out;

	do {
		usleep_range(5, 10);
		ret = tb_sw_read(sw, &post_time, TB_CFG_SWITCH,
@@ -297,8 +328,6 @@ int tb_switch_tmu_post_time(struct tb_switch *sw)
 */
int tb_switch_tmu_disable(struct tb_switch *sw)
{
	int ret;

	if (!tb_switch_is_usb4(sw))
		return 0;

@@ -306,21 +335,42 @@ int tb_switch_tmu_disable(struct tb_switch *sw)
	if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF)
		return 0;

	if (sw->tmu.unidirectional) {

	if (tb_route(sw)) {
		bool unidirectional = tb_switch_tmu_hifi_is_enabled(sw, true);
		struct tb_switch *parent = tb_switch_parent(sw);
		struct tb_port *up, *down;
		struct tb_port *down, *up;
		int ret;

		up = tb_upstream_port(sw);
		down = tb_port_at(tb_route(sw), parent);
		up = tb_upstream_port(sw);
		/*
		 * In case of uni-directional time sync, TMU handshake is
		 * initiated by upstream router. In case of bi-directional
		 * time sync, TMU handshake is initiated by downstream router.
		 * Therefore, we change the rate to off in the respective
		 * router.
		 */
		if (unidirectional)
			tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF);
		else
			tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);

		tb_port_tmu_time_sync_disable(up);
		ret = tb_port_tmu_time_sync_disable(down);
		if (ret)
			return ret;

		if (unidirectional) {
			/* The switch may be unplugged so ignore any errors */
			tb_port_tmu_unidirectional_disable(up);
			ret = tb_port_tmu_unidirectional_disable(down);
			if (ret)
				return ret;
		}

	} else {
		tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
	}

	sw->tmu.unidirectional = false;
	sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF;
@@ -329,55 +379,187 @@ int tb_switch_tmu_disable(struct tb_switch *sw)
	return 0;
}

/**
 * tb_switch_tmu_enable() - Enable TMU on a switch
 * @sw: Switch whose TMU to enable
 *
 * Enables TMU of a switch to be in bi-directional, HiFi mode. In this mode
 * all tunneling should work.
static void __tb_switch_tmu_off(struct tb_switch *sw, bool unidirectional)
{
	struct tb_switch *parent = tb_switch_parent(sw);
	struct tb_port *down, *up;

	down = tb_port_at(tb_route(sw), parent);
	up = tb_upstream_port(sw);
	/*
	 * In case of any failure in one of the steps when setting
	 * bi-directional or uni-directional TMU mode, get back to the TMU
	 * configurations in off mode. In case of additional failures in
	 * the functions below, ignore them since the caller shall already
	 * report a failure.
	 */
int tb_switch_tmu_enable(struct tb_switch *sw)
	tb_port_tmu_time_sync_disable(down);
	tb_port_tmu_time_sync_disable(up);
	if (unidirectional)
		tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_OFF);
	else
		tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);

	tb_port_tmu_unidirectional_disable(down);
	tb_port_tmu_unidirectional_disable(up);
}

/*
 * This function is called when the previous TMU mode was
 * TB_SWITCH_TMU_RATE_OFF.
 */
static int __tb_switch_tmu_enable_bidirectional(struct tb_switch *sw)
{
	struct tb_switch *parent = tb_switch_parent(sw);
	struct tb_port *up, *down;
	int ret;

	if (!tb_switch_is_usb4(sw))
		return 0;
	up = tb_upstream_port(sw);
	down = tb_port_at(tb_route(sw), parent);

	if (tb_switch_tmu_is_enabled(sw))
		return 0;
	ret = tb_port_tmu_unidirectional_disable(up);
	if (ret)
		return ret;

	ret = tb_switch_tmu_set_time_disruption(sw, true);
	ret = tb_port_tmu_unidirectional_disable(down);
	if (ret)
		goto out;

	ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
	if (ret)
		goto out;

	ret = tb_port_tmu_time_sync_enable(up);
	if (ret)
		goto out;

	ret = tb_port_tmu_time_sync_enable(down);
	if (ret)
		goto out;

	return 0;

out:
	__tb_switch_tmu_off(sw, false);
	return ret;
}

	/* Change mode to bi-directional */
	if (tb_route(sw) && sw->tmu.unidirectional) {
/*
 * This function is called when the previous TMU mode was
 * TB_SWITCH_TMU_RATE_OFF.
 */
static int __tb_switch_tmu_enable_unidirectional(struct tb_switch *sw)
{
	struct tb_switch *parent = tb_switch_parent(sw);
	struct tb_port *up, *down;
	int ret;

	up = tb_upstream_port(sw);
	down = tb_port_at(tb_route(sw), parent);
	ret = tb_switch_tmu_rate_write(parent, TB_SWITCH_TMU_RATE_HIFI);
	if (ret)
		return ret;

		ret = tb_port_tmu_unidirectional_disable(down);
	ret = tb_port_tmu_unidirectional_enable(up);
	if (ret)
		goto out;

	ret = tb_port_tmu_time_sync_enable(up);
	if (ret)
		goto out;

	ret = tb_port_tmu_unidirectional_enable(down);
	if (ret)
		goto out;

	ret = tb_port_tmu_time_sync_enable(down);
	if (ret)
		goto out;

	return 0;

out:
	__tb_switch_tmu_off(sw, true);
	return ret;
}

		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
static int tb_switch_tmu_hifi_enable(struct tb_switch *sw)
{
	bool unidirectional = sw->tmu.unidirectional_request;
	int ret;

	if (unidirectional && !sw->tmu.has_ucap)
		return -EOPNOTSUPP;

	if (!tb_switch_is_usb4(sw))
		return 0;

	if (tb_switch_tmu_hifi_is_enabled(sw, sw->tmu.unidirectional_request))
		return 0;

	ret = tb_switch_tmu_set_time_disruption(sw, true);
	if (ret)
		return ret;

		ret = tb_port_tmu_unidirectional_disable(up);
	if (tb_route(sw)) {
		/* The used mode changes are from OFF to HiFi-Uni/HiFi-BiDir */
		if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) {
			if (unidirectional)
				ret = __tb_switch_tmu_enable_unidirectional(sw);
			else
				ret = __tb_switch_tmu_enable_bidirectional(sw);
			if (ret)
				return ret;
		}
		sw->tmu.unidirectional = unidirectional;
	} else {
		/*
		 * Host router port configurations are written as
		 * part of configurations for downstream port of the parent
		 * of the child node - see above.
		 * Here only the host router' rate configuration is written.
		 */
		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
		if (ret)
			return ret;
	}

	sw->tmu.unidirectional = false;
	sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI;
	tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));

	tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));
	return tb_switch_tmu_set_time_disruption(sw, false);
}

/**
 * tb_switch_tmu_enable() - Enable TMU on a router
 * @sw: Router whose TMU to enable
 *
 * Enables TMU of a router to be in uni-directional or bi-directional HiFi mode.
 * Calling tb_switch_tmu_configure() is required before calling this function,
 * to select the mode HiFi and directionality (uni-directional/bi-directional).
 * In both modes all tunneling should work. Uni-directional mode is required for
 * CLx (Link Low-Power) to work.
 */
int tb_switch_tmu_enable(struct tb_switch *sw)
{
	if (sw->tmu.rate_request == TB_SWITCH_TMU_RATE_NORMAL)
		return -EOPNOTSUPP;

	return tb_switch_tmu_hifi_enable(sw);
}

/**
 * tb_switch_tmu_configure() - Configure the TMU rate and directionality
 * @sw: Router whose mode to change
 * @rate: Rate to configure Off/LowRes/HiFi
 * @unidirectional: If uni-directional (bi-directional otherwise)
 *
 * Selects the rate of the TMU and directionality (uni-directional or
 * bi-directional). Must be called before tb_switch_tmu_enable().
 */
void tb_switch_tmu_configure(struct tb_switch *sw,
			     enum tb_switch_tmu_rate rate, bool unidirectional)
{
	sw->tmu.unidirectional_request = unidirectional;
	sw->tmu.rate_request = rate;
}