thunderbolt: Add bandwidth management for Display Port tunnels

Titan Ridge supports Display Port 1.4 which adds HBR3 (High Bit Rate)
rates that may be up to 8.1 Gb/s over 4 lanes. This translates to
effective data bandwidth of 25.92 Gb/s (as 8/10 encoding is removed by
the DP adapters when going over Thunderbolt fabric). If another high
rate monitor is connected we may need to reduce the bandwidth it
consumes so that it fits into the total 40 Gb/s available on the
Thunderbolt fabric.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 8f58b9c..bb763a5 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -422,11 +422,51 @@ static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
 	return tb_find_unused_port(sw, TB_TYPE_PCIE_DOWN);
 }
 
+static int tb_available_bw(struct tb_cm *tcm, struct tb_port *in,
+			   struct tb_port *out)
+{
+	struct tb_switch *sw = out->sw;
+	struct tb_tunnel *tunnel;
+	int bw, available_bw = 40000;
+
+	while (sw && sw != in->sw) {
+		bw = sw->link_speed * sw->link_width * 1000; /* Mb/s */
+		/* Leave 10% guard band */
+		bw -= bw / 10;
+
+		/*
+		 * Check for any active DP tunnels that go through this
+		 * switch and reduce their consumed bandwidth from
+		 * available.
+		 */
+		list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
+			int consumed_bw;
+
+			if (!tb_tunnel_switch_on_path(tunnel, sw))
+				continue;
+
+			consumed_bw = tb_tunnel_consumed_bandwidth(tunnel);
+			if (consumed_bw < 0)
+				return consumed_bw;
+
+			bw -= consumed_bw;
+		}
+
+		if (bw < available_bw)
+			available_bw = bw;
+
+		sw = tb_switch_parent(sw);
+	}
+
+	return available_bw;
+}
+
 static void tb_tunnel_dp(struct tb *tb)
 {
 	struct tb_cm *tcm = tb_priv(tb);
 	struct tb_port *port, *in, *out;
 	struct tb_tunnel *tunnel;
+	int available_bw;
 
 	/*
 	 * Find pair of inactive DP IN and DP OUT adapters and then
@@ -464,7 +504,17 @@ static void tb_tunnel_dp(struct tb *tb)
 		return;
 	}
 
-	tunnel = tb_tunnel_alloc_dp(tb, in, out);
+	/* Calculate available bandwidth between in and out */
+	available_bw = tb_available_bw(tcm, in, out);
+	if (available_bw < 0) {
+		tb_warn(tb, "failed to determine available bandwidth\n");
+		return;
+	}
+
+	tb_dbg(tb, "available bandwidth for new DP tunnel %u Mb/s\n",
+	       available_bw);
+
+	tunnel = tb_tunnel_alloc_dp(tb, in, out, available_bw);
 	if (!tunnel) {
 		tb_port_dbg(out, "could not allocate DP tunnel\n");
 		goto dealloc_dp;