Add utility methods tb_port_state and tb_wait_for_port. Add tb_scan_port
which checks whether a port is connected and if so allocates a
downstream switch.

Signed-off-by: Andreas Noever <andreas.noe...@gmail.com>
---
 drivers/thunderbolt/tb.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/thunderbolt/tb.h |  17 +++++
 2 files changed, 177 insertions(+)

diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 3837f1a..9daff7b 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -84,6 +84,25 @@ static void tb_dump_port(struct tb *tb, struct 
tb_regs_port_header *port)
                port->nfc_credits);
 }
 
+
+/**
+ * tb_downstream_route() - get route to downstream switch
+ *
+ * Port must not be the upstream port (otherwise a loop is created).
+ *
+ * Return: Returns a route to the switch behind @port.
+ */
+static u64 tb_downstream_route(struct tb_port *port)
+{
+       return tb_route(port->sw)
+              | ((u64) port->port << (port->sw->config.depth * 8));
+}
+
+static bool tb_is_upstream_port(struct tb_port *port)
+{
+       return port == tb_upstream_port(port->sw);
+}
+
 static int tb_route_length(u64 route)
 {
        return (fls64(route) + TB_ROUTE_SHIFT - 1) / TB_ROUTE_SHIFT;
@@ -192,6 +211,84 @@ int tb_find_cap(struct tb_port *port, enum tb_cfg_space 
space, enum tb_cap cap)
        return -EIO;
 }
 
+/* thunderbolt port utility functions */
+
+/**
+ * tb_port_state() - get connectedness state of a port
+ *
+ * The port must have a TB_CAP_PHY (i.e. it should be a real port).
+ *
+ * Return: Returns a tb_port_state on success or an error code on failure.
+ */
+static enum tb_port_state tb_port_state(struct tb_port *port)
+{
+       struct tb_cap_phy phy;
+       int res;
+       if (port->cap_phy == 0) {
+               tb_port_WARN(port, "does not have a PHY\n");
+               return -EINVAL;
+       }
+       res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2);
+       if (res)
+               return res;
+       return phy.state;
+}
+
+/**
+ * tb_wait_for_port() - wait for a port to become ready
+ *
+ * Check if the port is connected but not up. If so we wait for some
+ * time to see whether it comes up.
+ *
+ * Return: Returns an error code on failure. Returns 0 if the port is not
+ * connected or failed to reach state TB_PORT_UP within one second. Returns 1
+ * if the port is connected and in state TB_PORT_UP.
+ */
+static int tb_wait_for_port(struct tb_port *port)
+{
+       int retries = 10;
+       enum tb_port_state state;
+       if (!port->cap_phy) {
+               tb_port_WARN(port, "does not have PHY\n");
+               return -EINVAL;
+       }
+       if (tb_is_upstream_port(port)) {
+               tb_port_WARN(port, "is the upstream port\n");
+               return -EINVAL;
+       }
+
+       while (retries--) {
+               state = tb_port_state(port);
+               if (state < 0)
+                       return state;
+               if (state == TB_PORT_DISABLED) {
+                       tb_port_info(port, "is disabled (state: 0)\n");
+                       return 0;
+               }
+               if (state == TB_PORT_UNPLUGGED) {
+                       tb_port_info(port, "is unplugged (state: 7)\n");
+                       return 0;
+               }
+               if (state == TB_PORT_UP) {
+                       tb_port_info(port,
+                                    "is connected, link is up (state: 2)\n");
+                       return 1;
+               }
+
+               /*
+                * After plug-in the state is TB_PORT_CONNECTING. Give it some
+                * time.
+                */
+               tb_port_info(port,
+                            "is connected, link is not up (state: %d), 
retrying...\n",
+                            state);
+               msleep(100);
+       }
+       tb_port_WARN(port,
+                    "failed to reach state TB_PORT_UP. Ignoring port...\n");
+       return 0;
+}
+
 
 /* thunderbolt switch utility functions */
 
@@ -240,13 +337,25 @@ static int tb_plug_events_active(struct tb_switch *sw, 
bool active)
 static int tb_init_port(struct tb_switch *sw, u8 port_nr)
 {
        int res;
+       int cap;
        struct tb_port *port = &sw->ports[port_nr];
        port->sw = sw;
        port->port = port_nr;
+       port->remote = NULL;
        res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8);
        if (res)
                return res;
 
+       /* Port 0 is the switch itself and has no PHY. */
+       if (port->config.type == TB_TYPE_PORT && port_nr != 0) {
+               cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY);
+
+               if (cap > 0)
+                       port->cap_phy = cap;
+               else
+                       tb_port_WARN(port, "non switch port without a PHY\n");
+       }
+
        tb_dump_port(sw->tb, &port->config);
 
        /* TODO: Read dual link port, DP port and more from EEPROM. */
@@ -259,6 +368,14 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr)
  */
 static void tb_switch_free(struct tb_switch *sw)
 {
+       int i;
+       /* port 0 is the switch itself and never has a remote */
+       for (i = 1; i <= sw->config.max_port_number; i++) {
+               if (tb_is_upstream_port(&sw->ports[i]))
+                       continue;
+               if (sw->ports[i].remote)
+                       tb_switch_free(sw->ports[i].remote->sw);
+       }
        kfree(sw->ports);
        kfree(sw);
 }
@@ -349,6 +466,9 @@ err:
        return NULL;
 }
 
+
+/* startup, enumeration, hot plug handling and shutdown */
+
 /**
  * reset_switch() - send TB_CFG_PKG_RESET and enable switch
  *
@@ -369,6 +489,44 @@ static int tb_switch_reset(struct tb *tb, u64 route)
        return tb_cfg_write(tb->cfg, ((u32 *) &header) + 2, route, 0, 2, 2, 2);
 }
 
+static void tb_link_ports(struct tb_port *down, struct tb_port *up)
+{
+       down->remote = up;
+       up->remote = down;
+}
+
+static void tb_scan_port(struct tb_port *port);
+
+static void tb_scan_ports(struct tb_switch *sw)
+{
+       int i;
+       for (i = 1; i <= sw->config.max_port_number; i++)
+               tb_scan_port(&sw->ports[i]);
+}
+
+/**
+ * tb_scan_port() - check for switches below port
+ *
+ * Checks whether port is connected. If so then we try to create a downstream
+ * switch and recursively scan its ports
+ */
+static void tb_scan_port(struct tb_port *port)
+{
+       struct tb_switch *sw;
+       if (tb_is_upstream_port(port))
+               return;
+       if (port->config.type != TB_TYPE_PORT)
+               return;
+       if (tb_wait_for_port(port) <= 0)
+               return;
+
+       sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port));
+       if (!sw)
+               return;
+       tb_link_ports(port, tb_upstream_port(sw));
+       tb_scan_ports(sw);
+}
+
 struct tb_hotplug_event {
        struct work_struct work;
        struct tb *tb;
@@ -487,6 +645,8 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi)
        if (!tb->root_switch)
                goto err_locked;
 
+       tb_scan_ports(tb->root_switch);
+
        mutex_unlock(&tb->lock);
        return tb;
 
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 2ada42a..fe35d4d 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -32,6 +32,8 @@ struct tb_switch {
 struct tb_port {
        struct tb_regs_port_header config;
        struct tb_switch *sw;
+       struct tb_port *remote; /* remote port, NULL if not connected */
+       int cap_phy; /* offset, zero if not found */
        u8 port; /* port number on switch */
        bool invalid; /* unplugged, will go away */
 };
@@ -54,6 +56,21 @@ struct tb {
                         */
 };
 
+/**
+ * tb_upstream_port() - return the upstream port of a switch
+ *
+ * Every switch has an upstream port (for the root switch it is the NHI).
+ *
+ * During switch alloc/init tb_upstream_port()->remote may be NULL, even for
+ * non root switches (on the NHI port remote is always NULL).
+ *
+ * Return: Returns the upstream port of the switch.
+ */
+static inline struct tb_port *tb_upstream_port(struct tb_switch *sw)
+{
+       return &sw->ports[sw->config.upstream_port_number];
+}
+
 static inline u64 tb_route(struct tb_switch *sw)
 {
        return ((u64) sw->config.route_hi) << 32 | sw->config.route_lo;
-- 
1.8.4.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to