Revert "drm/dp_mst: Remove single tx msg restriction."
[linux-2.6-microblaze.git] / drivers / gpu / drm / drm_dp_mst_topology.c
index 80067ea..d2c1979 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/seq_file.h>
+#include <linux/iopoll.h>
 
 #if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS)
 #include <linux/stacktrace.h>
@@ -1204,6 +1205,8 @@ static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb,
                    txmsg->state == DRM_DP_SIDEBAND_TX_SENT) {
                        mstb->tx_slots[txmsg->seqno] = NULL;
                }
+               mgr->is_waiting_for_dwn_reply = false;
+
        }
 out:
        if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) {
@@ -1213,6 +1216,7 @@ out:
        }
        mutex_unlock(&mgr->qlock);
 
+       drm_dp_mst_kick_tx(mgr);
        return ret;
 }
 
@@ -1927,7 +1931,7 @@ static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port,
        return parent_lct + 1;
 }
 
-static bool drm_dp_mst_is_dp_mst_end_device(u8 pdt, bool mcs)
+static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs)
 {
        switch (pdt) {
        case DP_PEER_DEVICE_DP_LEGACY_CONV:
@@ -1957,13 +1961,13 @@ drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt,
 
        /* Teardown the old pdt, if there is one */
        if (port->pdt != DP_PEER_DEVICE_NONE) {
-               if (drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) {
+               if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
                        /*
                         * If the new PDT would also have an i2c bus,
                         * don't bother with reregistering it
                         */
                        if (new_pdt != DP_PEER_DEVICE_NONE &&
-                           drm_dp_mst_is_dp_mst_end_device(new_pdt, new_mcs)) {
+                           drm_dp_mst_is_end_device(new_pdt, new_mcs)) {
                                port->pdt = new_pdt;
                                port->mcs = new_mcs;
                                return 0;
@@ -1983,7 +1987,7 @@ drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt,
        port->mcs = new_mcs;
 
        if (port->pdt != DP_PEER_DEVICE_NONE) {
-               if (drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) {
+               if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
                        /* add i2c over sideband */
                        ret = drm_dp_mst_register_i2c_bus(&port->aux);
                } else {
@@ -2162,7 +2166,7 @@ drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb,
        }
 
        if (port->pdt != DP_PEER_DEVICE_NONE &&
-           drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) {
+           drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
                port->cached_edid = drm_get_edid(port->connector,
                                                 &port->aux.ddc);
                drm_connector_set_tile_property(port->connector);
@@ -2292,14 +2296,18 @@ drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb,
                mutex_unlock(&mgr->lock);
        }
 
-       if (old_ddps != port->ddps) {
-               if (port->ddps) {
-                       if (!port->input) {
-                               drm_dp_send_enum_path_resources(mgr, mstb,
-                                                               port);
-                       }
+       /*
+        * Reprobe PBN caps on both hotplug, and when re-probing the link
+        * for our parent mstb
+        */
+       if (old_ddps != port->ddps || !created) {
+               if (port->ddps && !port->input) {
+                       ret = drm_dp_send_enum_path_resources(mgr, mstb,
+                                                             port);
+                       if (ret == 1)
+                               changed = true;
                } else {
-                       port->available_pbn = 0;
+                       port->full_pbn = 0;
                }
        }
 
@@ -2391,11 +2399,10 @@ drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb,
        port->ddps = conn_stat->displayport_device_plug_status;
 
        if (old_ddps != port->ddps) {
-               if (port->ddps) {
-                       dowork = true;
-               } else {
-                       port->available_pbn = 0;
-               }
+               if (port->ddps && !port->input)
+                       drm_dp_send_enum_path_resources(mgr, mstb, port);
+               else
+                       port->full_pbn = 0;
        }
 
        new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type;
@@ -2546,13 +2553,6 @@ static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mg
                if (port->input || !port->ddps)
                        continue;
 
-               if (!port->available_pbn) {
-                       drm_modeset_lock(&mgr->base.lock, NULL);
-                       drm_dp_send_enum_path_resources(mgr, mstb, port);
-                       drm_modeset_unlock(&mgr->base.lock);
-                       changed = true;
-               }
-
                if (port->mstb)
                        mstb_child = drm_dp_mst_topology_get_mstb_validated(
                            mgr, port->mstb);
@@ -2792,9 +2792,11 @@ static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr)
        ret = process_single_tx_qlock(mgr, txmsg, false);
        if (ret == 1) {
                /* txmsg is sent it should be in the slots now */
+               mgr->is_waiting_for_dwn_reply = true;
                list_del(&txmsg->next);
        } else if (ret) {
                DRM_DEBUG_KMS("failed to send msg in q %d\n", ret);
+               mgr->is_waiting_for_dwn_reply = false;
                list_del(&txmsg->next);
                if (txmsg->seqno != -1)
                        txmsg->dst->tx_slots[txmsg->seqno] = NULL;
@@ -2834,7 +2836,8 @@ static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr,
                drm_dp_mst_dump_sideband_msg_tx(&p, txmsg);
        }
 
-       if (list_is_singular(&mgr->tx_msg_downq))
+       if (list_is_singular(&mgr->tx_msg_downq) &&
+           !mgr->is_waiting_for_dwn_reply)
                process_single_down_tx_qlock(mgr);
        mutex_unlock(&mgr->qlock);
 }
@@ -2983,6 +2986,7 @@ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
 
        ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
        if (ret > 0) {
+               ret = 0;
                path_res = &txmsg->reply.u.path_resources;
 
                if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
@@ -2995,14 +2999,22 @@ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
                                      path_res->port_number,
                                      path_res->full_payload_bw_number,
                                      path_res->avail_payload_bw_number);
-                       port->available_pbn =
-                               path_res->avail_payload_bw_number;
+
+                       /*
+                        * If something changed, make sure we send a
+                        * hotplug
+                        */
+                       if (port->full_pbn != path_res->full_payload_bw_number ||
+                           port->fec_capable != path_res->fec_capable)
+                               ret = 1;
+
+                       port->full_pbn = path_res->full_payload_bw_number;
                        port->fec_capable = path_res->fec_capable;
                }
        }
 
        kfree(txmsg);
-       return 0;
+       return ret;
 }
 
 static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb)
@@ -3576,13 +3588,9 @@ drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb)
        /* The link address will need to be re-sent on resume */
        mstb->link_address_sent = false;
 
-       list_for_each_entry(port, &mstb->ports, next) {
-               /* The PBN for each port will also need to be re-probed */
-               port->available_pbn = 0;
-
+       list_for_each_entry(port, &mstb->ports, next)
                if (port->mstb)
                        drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb);
-       }
 }
 
 /**
@@ -3821,6 +3829,7 @@ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr)
        mutex_lock(&mgr->qlock);
        txmsg->state = DRM_DP_SIDEBAND_TX_RX;
        mstb->tx_slots[seqno] = NULL;
+       mgr->is_waiting_for_dwn_reply = false;
        mutex_unlock(&mgr->qlock);
 
        wake_up_all(&mgr->tx_waitq);
@@ -3828,6 +3837,9 @@ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr)
        return 0;
 
 out_clear_reply:
+       mutex_lock(&mgr->qlock);
+       mgr->is_waiting_for_dwn_reply = false;
+       mutex_unlock(&mgr->qlock);
        if (msg)
                memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx));
 out:
@@ -4062,27 +4074,6 @@ out:
 }
 EXPORT_SYMBOL(drm_dp_mst_detect_port);
 
-/**
- * drm_dp_mst_port_has_audio() - Check whether port has audio capability or not
- * @mgr: manager for this port
- * @port: unverified pointer to a port.
- *
- * This returns whether the port supports audio or not.
- */
-bool drm_dp_mst_port_has_audio(struct drm_dp_mst_topology_mgr *mgr,
-                                       struct drm_dp_mst_port *port)
-{
-       bool ret = false;
-
-       port = drm_dp_mst_topology_get_port_validated(mgr, port);
-       if (!port)
-               return ret;
-       ret = port->has_audio;
-       drm_dp_mst_topology_put_port(port);
-       return ret;
-}
-EXPORT_SYMBOL(drm_dp_mst_port_has_audio);
-
 /**
  * drm_dp_mst_get_edid() - get EDID for an MST port
  * @connector: toplevel connector to get EDID for
@@ -4459,42 +4450,58 @@ fail:
        return ret;
 }
 
+static int do_get_act_status(struct drm_dp_aux *aux)
+{
+       int ret;
+       u8 status;
+
+       ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
+       if (ret < 0)
+               return ret;
+
+       return status;
+}
 
 /**
- * drm_dp_check_act_status() - Check ACT handled status.
+ * drm_dp_check_act_status() - Polls for ACT handled status.
  * @mgr: manager to use
  *
- * Check the payload status bits in the DPCD for ACT handled completion.
+ * Tries waiting for the MST hub to finish updating it's payload table by
+ * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really
+ * take that long).
+ *
+ * Returns:
+ * 0 if the ACT was handled in time, negative error code on failure.
  */
 int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr)
 {
-       u8 status;
-       int ret;
-       int count = 0;
-
-       do {
-               ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status);
-
-               if (ret < 0) {
-                       DRM_DEBUG_KMS("failed to read payload table status %d\n", ret);
-                       goto fail;
-               }
-
-               if (status & DP_PAYLOAD_ACT_HANDLED)
-                       break;
-               count++;
-               udelay(100);
-
-       } while (count < 30);
-
-       if (!(status & DP_PAYLOAD_ACT_HANDLED)) {
-               DRM_DEBUG_KMS("failed to get ACT bit %d after %d retries\n", status, count);
-               ret = -EINVAL;
-               goto fail;
+       /*
+        * There doesn't seem to be any recommended retry count or timeout in
+        * the MST specification. Since some hubs have been observed to take
+        * over 1 second to update their payload allocations under certain
+        * conditions, we use a rather large timeout value.
+        */
+       const int timeout_ms = 3000;
+       int ret, status;
+
+       ret = readx_poll_timeout(do_get_act_status, mgr->aux, status,
+                                status & DP_PAYLOAD_ACT_HANDLED || status < 0,
+                                200, timeout_ms * USEC_PER_MSEC);
+       if (ret < 0 && status >= 0) {
+               DRM_ERROR("Failed to get ACT after %dms, last status: %02x\n",
+                         timeout_ms, status);
+               return -EINVAL;
+       } else if (status < 0) {
+               /*
+                * Failure here isn't unexpected - the hub may have
+                * just been unplugged
+                */
+               DRM_DEBUG_KMS("Failed to read payload table status: %d\n",
+                             status);
+               return status;
        }
+
        return 0;
-fail:
-       return ret;
 }
 EXPORT_SYMBOL(drm_dp_check_act_status);
 
@@ -4685,7 +4692,7 @@ static void drm_dp_tx_work(struct work_struct *work)
        struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work);
 
        mutex_lock(&mgr->qlock);
-       if (!list_empty(&mgr->tx_msg_downq))
+       if (!list_empty(&mgr->tx_msg_downq) && !mgr->is_waiting_for_dwn_reply)
                process_single_down_tx_qlock(mgr);
        mutex_unlock(&mgr->qlock);
 }
@@ -4865,41 +4872,102 @@ static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port,
        return false;
 }
 
-static inline
-int drm_dp_mst_atomic_check_bw_limit(struct drm_dp_mst_branch *branch,
-                                    struct drm_dp_mst_topology_state *mst_state)
+static int
+drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port,
+                                     struct drm_dp_mst_topology_state *state);
+
+static int
+drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb,
+                                     struct drm_dp_mst_topology_state *state)
 {
-       struct drm_dp_mst_port *port;
        struct drm_dp_vcpi_allocation *vcpi;
-       int pbn_limit = 0, pbn_used = 0;
+       struct drm_dp_mst_port *port;
+       int pbn_used = 0, ret;
+       bool found = false;
 
-       list_for_each_entry(port, &branch->ports, next) {
-               if (port->mstb)
-                       if (drm_dp_mst_atomic_check_bw_limit(port->mstb, mst_state))
-                               return -ENOSPC;
+       /* Check that we have at least one port in our state that's downstream
+        * of this branch, otherwise we can skip this branch
+        */
+       list_for_each_entry(vcpi, &state->vcpis, next) {
+               if (!vcpi->pbn ||
+                   !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb))
+                       continue;
 
-               if (port->available_pbn > 0)
-                       pbn_limit = port->available_pbn;
+               found = true;
+               break;
        }
-       DRM_DEBUG_ATOMIC("[MST BRANCH:%p] branch has %d PBN available\n",
-                        branch, pbn_limit);
+       if (!found)
+               return 0;
 
-       list_for_each_entry(vcpi, &mst_state->vcpis, next) {
-               if (!vcpi->pbn)
-                       continue;
+       if (mstb->port_parent)
+               DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n",
+                                mstb->port_parent->parent, mstb->port_parent,
+                                mstb);
+       else
+               DRM_DEBUG_ATOMIC("[MSTB:%p] Checking bandwidth limits\n",
+                                mstb);
+
+       list_for_each_entry(port, &mstb->ports, next) {
+               ret = drm_dp_mst_atomic_check_port_bw_limit(port, state);
+               if (ret < 0)
+                       return ret;
+
+               pbn_used += ret;
+       }
+
+       return pbn_used;
+}
+
+static int
+drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port,
+                                     struct drm_dp_mst_topology_state *state)
+{
+       struct drm_dp_vcpi_allocation *vcpi;
+       int pbn_used = 0;
+
+       if (port->pdt == DP_PEER_DEVICE_NONE)
+               return 0;
+
+       if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
+               bool found = false;
+
+               list_for_each_entry(vcpi, &state->vcpis, next) {
+                       if (vcpi->port != port)
+                               continue;
+                       if (!vcpi->pbn)
+                               return 0;
 
-               if (drm_dp_mst_port_downstream_of_branch(vcpi->port, branch))
-                       pbn_used += vcpi->pbn;
+                       found = true;
+                       break;
+               }
+               if (!found)
+                       return 0;
+
+               /* This should never happen, as it means we tried to
+                * set a mode before querying the full_pbn
+                */
+               if (WARN_ON(!port->full_pbn))
+                       return -EINVAL;
+
+               pbn_used = vcpi->pbn;
+       } else {
+               pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb,
+                                                                state);
+               if (pbn_used <= 0)
+                       return pbn_used;
        }
-       DRM_DEBUG_ATOMIC("[MST BRANCH:%p] branch used %d PBN\n",
-                        branch, pbn_used);
 
-       if (pbn_used > pbn_limit) {
-               DRM_DEBUG_ATOMIC("[MST BRANCH:%p] No available bandwidth\n",
-                                branch);
+       if (pbn_used > port->full_pbn) {
+               DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n",
+                                port->parent, port, pbn_used,
+                                port->full_pbn);
                return -ENOSPC;
        }
-       return 0;
+
+       DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n",
+                        port->parent, port, pbn_used, port->full_pbn);
+
+       return pbn_used;
 }
 
 static inline int
@@ -5097,9 +5165,15 @@ int drm_dp_mst_atomic_check(struct drm_atomic_state *state)
                ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state);
                if (ret)
                        break;
-               ret = drm_dp_mst_atomic_check_bw_limit(mgr->mst_primary, mst_state);
-               if (ret)
+
+               mutex_lock(&mgr->lock);
+               ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary,
+                                                           mst_state);
+               mutex_unlock(&mgr->lock);
+               if (ret < 0)
                        break;
+               else
+                       ret = 0;
        }
 
        return ret;