summaryrefslogtreecommitdiff
path: root/drivers/thunderbolt/tb.c
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2018-09-17 16:30:49 +0300
committerMika Westerberg <mika.westerberg@linux.intel.com>2019-04-18 11:18:53 +0300
commit4f807e47ee9a75747d042a8eacf398f436da9452 (patch)
treef8bbff8b83730fe4ea3275ebba63d9ff274350ac /drivers/thunderbolt/tb.c
parentc5ee6feb34709da96f9909b8a2e1e42875020efb (diff)
downloadlinux-4f807e47ee9a75747d042a8eacf398f436da9452.tar.xz
thunderbolt: Add support for Display Port tunnels
Display Port tunnels are somewhat more complex than PCIe tunnels as it requires 3 tunnels (AUX Rx/Tx and Video). In addition we are not supposed to create the tunnels immediately when a DP OUT is enumerated. Instead we need to wait until we get hotplug event to that adapter port or check if the port has HPD set before tunnels can be established. This adds Display Port tunneling support to the software connection manager. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Diffstat (limited to 'drivers/thunderbolt/tb.c')
-rw-r--r--drivers/thunderbolt/tb.c129
1 files changed, 108 insertions, 21 deletions
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 903922a16d64..c5e96e7ac37a 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -28,6 +28,32 @@ struct tb_cm {
bool hotplug_active;
};
+struct tb_hotplug_event {
+ struct work_struct work;
+ struct tb *tb;
+ u64 route;
+ u8 port;
+ bool unplug;
+};
+
+static void tb_handle_hotplug(struct work_struct *work);
+
+static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug)
+{
+ struct tb_hotplug_event *ev;
+
+ ev = kmalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ return;
+
+ ev->tb = tb;
+ ev->route = route;
+ ev->port = port;
+ ev->unplug = unplug;
+ INIT_WORK(&ev->work, tb_handle_hotplug);
+ queue_work(tb->wq, &ev->work);
+}
+
/* enumeration & hot plug handling */
static void tb_discover_tunnels(struct tb_switch *sw)
@@ -42,6 +68,10 @@ static void tb_discover_tunnels(struct tb_switch *sw)
port = &sw->ports[i];
switch (port->config.type) {
+ case TB_TYPE_DP_HDMI_IN:
+ tunnel = tb_tunnel_discover_dp(tb, port);
+ break;
+
case TB_TYPE_PCIE_DOWN:
tunnel = tb_tunnel_discover_pci(tb, port);
break;
@@ -50,16 +80,19 @@ static void tb_discover_tunnels(struct tb_switch *sw)
break;
}
- if (tunnel) {
+ if (!tunnel)
+ continue;
+
+ if (tb_tunnel_is_pci(tunnel)) {
struct tb_switch *parent = tunnel->dst_port->sw;
while (parent != tunnel->src_port->sw) {
parent->boot = true;
parent = tb_switch_parent(parent);
}
-
- list_add_tail(&tunnel->list, &tcm->tunnel_list);
}
+
+ list_add_tail(&tunnel->list, &tcm->tunnel_list);
}
for (i = 1; i <= sw->config.max_port_number; i++) {
@@ -91,6 +124,15 @@ static void tb_scan_port(struct tb_port *port)
if (tb_is_upstream_port(port))
return;
+
+ if (tb_port_is_dpout(port) && tb_dp_port_hpd_is_active(port) == 1 &&
+ !tb_dp_port_is_enabled(port)) {
+ tb_port_dbg(port, "DP adapter HPD set, queuing hotplug\n");
+ tb_queue_hotplug(port->sw->tb, tb_route(port->sw), port->port,
+ false);
+ return;
+ }
+
if (port->config.type != TB_TYPE_PORT)
return;
if (port->dual_link_port && port->link_nr)
@@ -139,6 +181,26 @@ static void tb_scan_port(struct tb_port *port)
tb_scan_switch(sw);
}
+static int tb_free_tunnel(struct tb *tb, enum tb_tunnel_type type,
+ struct tb_port *src_port, struct tb_port *dst_port)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_tunnel *tunnel;
+
+ list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
+ if (tunnel->type == type &&
+ ((src_port && src_port == tunnel->src_port) ||
+ (dst_port && dst_port == tunnel->dst_port))) {
+ tb_tunnel_deactivate(tunnel);
+ list_del(&tunnel->list);
+ tb_tunnel_free(tunnel);
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
/**
* tb_free_invalid_tunnels() - destroy tunnels of devices that have gone away
*/
@@ -257,6 +319,44 @@ out:
return tb_find_unused_port(sw, TB_TYPE_PCIE_DOWN);
}
+static int tb_tunnel_dp(struct tb *tb, struct tb_port *out)
+{
+ struct tb_cm *tcm = tb_priv(tb);
+ struct tb_switch *sw = out->sw;
+ struct tb_tunnel *tunnel;
+ struct tb_port *in;
+
+ if (tb_port_is_enabled(out))
+ return 0;
+
+ do {
+ sw = tb_to_switch(sw->dev.parent);
+ if (!sw)
+ return 0;
+ in = tb_find_unused_port(sw, TB_TYPE_DP_HDMI_IN);
+ } while (!in);
+
+ tunnel = tb_tunnel_alloc_dp(tb, in, out);
+ if (!tunnel) {
+ tb_port_dbg(out, "DP tunnel allocation failed\n");
+ return -ENOMEM;
+ }
+
+ if (tb_tunnel_activate(tunnel)) {
+ tb_port_info(out, "DP tunnel activation failed, aborting\n");
+ tb_tunnel_free(tunnel);
+ return -EIO;
+ }
+
+ list_add_tail(&tunnel->list, &tcm->tunnel_list);
+ return 0;
+}
+
+static void tb_teardown_dp(struct tb *tb, struct tb_port *out)
+{
+ tb_free_tunnel(tb, TB_TUNNEL_DP, NULL, out);
+}
+
static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
{
struct tb_port *up, *down, *port;
@@ -295,14 +395,6 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
/* hotplug handling */
-struct tb_hotplug_event {
- struct work_struct work;
- struct tb *tb;
- u64 route;
- u8 port;
- bool unplug;
-};
-
/**
* tb_handle_hotplug() - handle hotplug event
*
@@ -347,6 +439,8 @@ static void tb_handle_hotplug(struct work_struct *work)
port->remote = NULL;
if (port->dual_link_port)
port->dual_link_port->remote = NULL;
+ } else if (tb_port_is_dpout(port)) {
+ tb_teardown_dp(tb, port);
} else {
tb_port_info(port,
"got unplug event for disconnected port, ignoring\n");
@@ -360,6 +454,8 @@ static void tb_handle_hotplug(struct work_struct *work)
tb_scan_port(port);
if (!port->remote)
tb_port_info(port, "hotplug: no switch found\n");
+ } else if (tb_port_is_dpout(port)) {
+ tb_tunnel_dp(tb, port);
}
}
@@ -379,7 +475,6 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
const void *buf, size_t size)
{
const struct cfg_event_pkg *pkg = buf;
- struct tb_hotplug_event *ev;
u64 route;
if (type != TB_CFG_PKG_EVENT) {
@@ -395,15 +490,7 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
pkg->port);
}
- ev = kmalloc(sizeof(*ev), GFP_KERNEL);
- if (!ev)
- return;
- INIT_WORK(&ev->work, tb_handle_hotplug);
- ev->tb = tb;
- ev->route = route;
- ev->port = pkg->port;
- ev->unplug = pkg->unplug;
- queue_work(tb->wq, &ev->work);
+ tb_queue_hotplug(tb, route, pkg->port, pkg->unplug);
}
static void tb_stop(struct tb *tb)