]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/gpu/drm/drm_dp_mst_topology.c
drm/dp_mst: Add DSC enablement helpers to DRM
[linux.git] / drivers / gpu / drm / drm_dp_mst_topology.c
index ae5809a1f19aac2d23dc318daedb8f343833d4f0..9f7e714c518256ce6cd16a1429af7017b1f6e427 100644 (file)
@@ -76,6 +76,11 @@ static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
 
 static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
                                    struct drm_dp_mst_branch *mstb);
+
+static void
+drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr,
+                                  struct drm_dp_mst_branch *mstb);
+
 static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
                                           struct drm_dp_mst_branch *mstb,
                                           struct drm_dp_mst_port *port);
@@ -517,8 +522,10 @@ drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw,
                        }
 
                        if (failed) {
-                               for (i = 0; i < r->num_transactions; i++)
+                               for (i = 0; i < r->num_transactions; i++) {
+                                       tx = &r->transactions[i];
                                        kfree(tx->bytes);
+                               }
                                return -ENOMEM;
                        }
 
@@ -846,6 +853,7 @@ static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband
 {
        int idx = 1;
        repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf;
+       repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1;
        idx++;
        if (idx > raw->curlen)
                goto fail_len;
@@ -950,6 +958,8 @@ static bool drm_dp_sideband_parse_reply(struct drm_dp_sideband_msg_rx *raw,
        case DP_POWER_DOWN_PHY:
        case DP_POWER_UP_PHY:
                return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg);
+       case DP_CLEAR_PAYLOAD_ID_TABLE:
+               return true; /* since there's nothing to parse */
        default:
                DRM_ERROR("Got unknown reply 0x%02x (%s)\n", msg->req_type,
                          drm_dp_mst_req_type_str(msg->req_type));
@@ -1048,6 +1058,15 @@ static int build_link_address(struct drm_dp_sideband_msg_tx *msg)
        return 0;
 }
 
+static int build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg)
+{
+       struct drm_dp_sideband_msg_req_body req;
+
+       req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE;
+       drm_dp_encode_sideband_req(&req, msg);
+       return 0;
+}
+
 static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, int port_num)
 {
        struct drm_dp_sideband_msg_req_body req;
@@ -2156,6 +2175,7 @@ drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr,
                                struct drm_dp_mst_port *port)
 {
        mutex_lock(&mgr->lock);
+       port->parent->num_ports--;
        list_del(&port->next);
        mutex_unlock(&mgr->lock);
        drm_dp_mst_topology_put_port(port);
@@ -2255,6 +2275,7 @@ drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb,
                mutex_lock(&mgr->lock);
                drm_dp_mst_topology_get_port(port);
                list_add(&port->next, &mstb->ports);
+               mstb->num_ports++;
                mutex_unlock(&mgr->lock);
        }
 
@@ -2520,10 +2541,14 @@ static void drm_dp_mst_link_probe_work(struct work_struct *work)
        struct drm_device *dev = mgr->dev;
        struct drm_dp_mst_branch *mstb;
        int ret;
+       bool clear_payload_id_table;
 
        mutex_lock(&mgr->probe_lock);
 
        mutex_lock(&mgr->lock);
+       clear_payload_id_table = !mgr->payload_id_table_cleared;
+       mgr->payload_id_table_cleared = true;
+
        mstb = mgr->mst_primary;
        if (mstb) {
                ret = drm_dp_mst_topology_try_get_mstb(mstb);
@@ -2536,6 +2561,19 @@ static void drm_dp_mst_link_probe_work(struct work_struct *work)
                return;
        }
 
+       /*
+        * Certain branch devices seem to incorrectly report an available_pbn
+        * of 0 on downstream sinks, even after clearing the
+        * DP_PAYLOAD_ALLOCATE_* registers in
+        * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C
+        * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make
+        * things work again.
+        */
+       if (clear_payload_id_table) {
+               DRM_DEBUG_KMS("Clearing payload ID table\n");
+               drm_dp_send_clear_payload_id_table(mgr, mstb);
+       }
+
        ret = drm_dp_check_and_send_link_address(mgr, mstb);
        drm_dp_mst_topology_put_mstb(mstb);
 
@@ -2859,6 +2897,28 @@ static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr,
        return ret < 0 ? ret : changed;
 }
 
+void drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr,
+                                       struct drm_dp_mst_branch *mstb)
+{
+       struct drm_dp_sideband_msg_tx *txmsg;
+       int len, ret;
+
+       txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
+       if (!txmsg)
+               return;
+
+       txmsg->dst = mstb;
+       len = build_clear_payload_id_table(txmsg);
+
+       drm_dp_queue_down_tx(mgr, txmsg);
+
+       ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
+       if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
+               DRM_DEBUG_KMS("clear payload table id nak received\n");
+
+       kfree(txmsg);
+}
+
 static int
 drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
                                struct drm_dp_mst_branch *mstb,
@@ -2894,6 +2954,7 @@ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr,
                                      path_res->avail_payload_bw_number);
                        port->available_pbn =
                                path_res->avail_payload_bw_number;
+                       port->fec_capable = path_res->fec_capable;
                }
        }
 
@@ -3176,9 +3237,11 @@ int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr)
                        drm_dp_mst_topology_put_port(port);
        }
 
-       for (i = 0; i < mgr->max_payloads; i++) {
-               if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL)
+       for (i = 0; i < mgr->max_payloads; /* do nothing */) {
+               if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) {
+                       i++;
                        continue;
+               }
 
                DRM_DEBUG_KMS("removing payload %d\n", i);
                for (j = i; j < mgr->max_payloads - 1; j++) {
@@ -3386,6 +3449,7 @@ static int drm_dp_get_vc_payload_bw(u8 dp_link_bw, u8  dp_link_count)
 int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state)
 {
        int ret = 0;
+       int i = 0;
        struct drm_dp_mst_branch *mstb = NULL;
 
        mutex_lock(&mgr->lock);
@@ -3446,10 +3510,23 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms
                /* this can fail if the device is gone */
                drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0);
                ret = 0;
+               mutex_lock(&mgr->payload_lock);
                memset(mgr->payloads, 0, mgr->max_payloads * sizeof(struct drm_dp_payload));
                mgr->payload_mask = 0;
                set_bit(0, &mgr->payload_mask);
+               for (i = 0; i < mgr->max_payloads; i++) {
+                       struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i];
+
+                       if (vcpi) {
+                               vcpi->vcpi = 0;
+                               vcpi->num_slots = 0;
+                       }
+                       mgr->proposed_vcpis[i] = NULL;
+               }
                mgr->vcpi_mask = 0;
+               mutex_unlock(&mgr->payload_lock);
+
+               mgr->payload_id_table_cleared = false;
        }
 
 out_unlock:
@@ -4016,6 +4093,7 @@ static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr,
  * @mgr: MST topology manager for the port
  * @port: port to find vcpi slots for
  * @pbn: bandwidth required for the mode in PBN
+ * @pbn_div: divider for DSC mode that takes FEC into account
  *
  * Allocates VCPI slots to @port, replacing any previous VCPI allocations it
  * may have had. Any atomic drivers which support MST must call this function
@@ -4042,7 +4120,8 @@ static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr,
  */
 int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
                                  struct drm_dp_mst_topology_mgr *mgr,
-                                 struct drm_dp_mst_port *port, int pbn)
+                                 struct drm_dp_mst_port *port, int pbn,
+                                 int pbn_div)
 {
        struct drm_dp_mst_topology_state *topology_state;
        struct drm_dp_vcpi_allocation *pos, *vcpi = NULL;
@@ -4075,7 +4154,10 @@ int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
        if (!vcpi)
                prev_slots = 0;
 
-       req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
+       if (pbn_div <= 0)
+               pbn_div = mgr->pbn_div;
+
+       req_slots = DIV_ROUND_UP(pbn, pbn_div);
 
        DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n",
                         port->connector->base.id, port->connector->name,
@@ -4342,10 +4424,11 @@ EXPORT_SYMBOL(drm_dp_check_act_status);
  * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode.
  * @clock: dot clock for the mode
  * @bpp: bpp for the mode.
+ * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel
  *
  * This uses the formula in the spec to calculate the PBN value for a mode.
  */
-int drm_dp_calc_pbn_mode(int clock, int bpp)
+int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc)
 {
        /*
         * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006
@@ -4356,7 +4439,16 @@ int drm_dp_calc_pbn_mode(int clock, int bpp)
         * peak_kbps *= (1006/1000)
         * peak_kbps *= (64/54)
         * peak_kbps *= 8    convert to bytes
+        *
+        * If the bpp is in units of 1/16, further divide by 16. Put this
+        * factor in the numerator rather than the denominator to avoid
+        * integer overflow
         */
+
+       if (dsc)
+               return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006),
+                                       8 * 54 * 1000 * 1000);
+
        return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006),
                                8 * 54 * 1000 * 1000);
 }
@@ -4697,6 +4789,67 @@ drm_dp_mst_atomic_check_topology_state(struct drm_dp_mst_topology_mgr *mgr,
        return 0;
 }
 
+/**
+ * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off
+ * @state: Pointer to the new drm_atomic_state
+ * @port: Pointer to the affected MST Port
+ * @pbn: Newly recalculated bw required for link with DSC enabled
+ * @pbn_div: Divider to calculate correct number of pbn per slot
+ * @enable: Boolean flag to enable or disable DSC on the port
+ *
+ * This function enables DSC on the given Port
+ * by recalculating its vcpi from pbn provided
+ * and sets dsc_enable flag to keep track of which
+ * ports have DSC enabled
+ *
+ */
+int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state,
+                                struct drm_dp_mst_port *port,
+                                int pbn, int pbn_div,
+                                bool enable)
+{
+       struct drm_dp_mst_topology_state *mst_state;
+       struct drm_dp_vcpi_allocation *pos;
+       bool found = false;
+       int vcpi = 0;
+
+       mst_state = drm_atomic_get_mst_topology_state(state, port->mgr);
+
+       if (IS_ERR(mst_state))
+               return PTR_ERR(mst_state);
+
+       list_for_each_entry(pos, &mst_state->vcpis, next) {
+               if (pos->port == port) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found) {
+               DRM_DEBUG_ATOMIC("[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n",
+                                port, mst_state);
+               return -EINVAL;
+       }
+
+       if (pos->dsc_enabled == enable) {
+               DRM_DEBUG_ATOMIC("[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n",
+                                port, enable, pos->vcpi);
+               vcpi = pos->vcpi;
+       }
+
+       if (enable) {
+               vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div);
+               DRM_DEBUG_ATOMIC("[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n",
+                                port, vcpi);
+               if (vcpi < 0)
+                       return -EINVAL;
+       }
+
+       pos->dsc_enabled = enable;
+
+       return vcpi;
+}
+EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc);
 /**
  * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an
  * atomic update is valid
@@ -4989,3 +5142,173 @@ static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_aux *aux)
 {
        i2c_del_adapter(&aux->ddc);
 }
+
+/**
+ * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device
+ * @port: The port to check
+ *
+ * A single physical MST hub object can be represented in the topology
+ * by multiple branches, with virtual ports between those branches.
+ *
+ * As of DP1.4, An MST hub with internal (virtual) ports must expose
+ * certain DPCD registers over those ports. See sections 2.6.1.1.1
+ * and 2.6.1.1.2 of Display Port specification v1.4 for details.
+ *
+ * May acquire mgr->lock
+ *
+ * Returns:
+ * true if the port is a virtual DP peer device, false otherwise
+ */
+static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port)
+{
+       struct drm_dp_mst_port *downstream_port;
+
+       if (!port || port->dpcd_rev < DP_DPCD_REV_14)
+               return false;
+
+       /* Virtual DP Sink (Internal Display Panel) */
+       if (port->port_num >= 8)
+               return true;
+
+       /* DP-to-HDMI Protocol Converter */
+       if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV &&
+           !port->mcs &&
+           port->ldps)
+               return true;
+
+       /* DP-to-DP */
+       mutex_lock(&port->mgr->lock);
+       if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING &&
+           port->mstb &&
+           port->mstb->num_ports == 2) {
+               list_for_each_entry(downstream_port, &port->mstb->ports, next) {
+                       if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK &&
+                           !downstream_port->input) {
+                               mutex_unlock(&port->mgr->lock);
+                               return true;
+                       }
+               }
+       }
+       mutex_unlock(&port->mgr->lock);
+
+       return false;
+}
+
+/**
+ * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC
+ * @port: The port to check. A leaf of the MST tree with an attached display.
+ *
+ * Depending on the situation, DSC may be enabled via the endpoint aux,
+ * the immediately upstream aux, or the connector's physical aux.
+ *
+ * This is both the correct aux to read DSC_CAPABILITY and the
+ * correct aux to write DSC_ENABLED.
+ *
+ * This operation can be expensive (up to four aux reads), so
+ * the caller should cache the return.
+ *
+ * Returns:
+ * NULL if DSC cannot be enabled on this port, otherwise the aux device
+ */
+struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port)
+{
+       struct drm_dp_mst_port *immediate_upstream_port;
+       struct drm_dp_mst_port *fec_port;
+       struct drm_dp_desc desc = { 0 };
+       u8 endpoint_fec;
+       u8 endpoint_dsc;
+
+       if (!port)
+               return NULL;
+
+       if (port->parent->port_parent)
+               immediate_upstream_port = port->parent->port_parent;
+       else
+               immediate_upstream_port = NULL;
+
+       fec_port = immediate_upstream_port;
+       while (fec_port) {
+               /*
+                * Each physical link (i.e. not a virtual port) between the
+                * output and the primary device must support FEC
+                */
+               if (!drm_dp_mst_is_virtual_dpcd(fec_port) &&
+                   !fec_port->fec_capable)
+                       return NULL;
+
+               fec_port = fec_port->parent->port_parent;
+       }
+
+       /* DP-to-DP peer device */
+       if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) {
+               u8 upstream_dsc;
+
+               if (drm_dp_dpcd_read(&port->aux,
+                                    DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1)
+                       return NULL;
+               if (drm_dp_dpcd_read(&port->aux,
+                                    DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1)
+                       return NULL;
+               if (drm_dp_dpcd_read(&immediate_upstream_port->aux,
+                                    DP_DSC_SUPPORT, &upstream_dsc, 1) != 1)
+                       return NULL;
+
+               /* Enpoint decompression with DP-to-DP peer device */
+               if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
+                   (endpoint_fec & DP_FEC_CAPABLE) &&
+                   (upstream_dsc & 0x2) /* DSC passthrough */)
+                       return &port->aux;
+
+               /* Virtual DPCD decompression with DP-to-DP peer device */
+               return &immediate_upstream_port->aux;
+       }
+
+       /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */
+       if (drm_dp_mst_is_virtual_dpcd(port))
+               return &port->aux;
+
+       /*
+        * Synaptics quirk
+        * Applies to ports for which:
+        * - Physical aux has Synaptics OUI
+        * - DPv1.4 or higher
+        * - Port is on primary branch device
+        * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG)
+        */
+       if (drm_dp_read_desc(port->mgr->aux, &desc, true))
+               return NULL;
+
+       if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) &&
+           port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 &&
+           port->parent == port->mgr->mst_primary) {
+               u8 downstreamport;
+
+               if (drm_dp_dpcd_read(&port->aux, DP_DOWNSTREAMPORT_PRESENT,
+                                    &downstreamport, 1) < 0)
+                       return NULL;
+
+               if ((downstreamport & DP_DWN_STRM_PORT_PRESENT) &&
+                  ((downstreamport & DP_DWN_STRM_PORT_TYPE_MASK)
+                    != DP_DWN_STRM_PORT_TYPE_ANALOG))
+                       return port->mgr->aux;
+       }
+
+       /*
+        * The check below verifies if the MST sink
+        * connected to the GPU is capable of DSC -
+        * therefore the endpoint needs to be
+        * both DSC and FEC capable.
+        */
+       if (drm_dp_dpcd_read(&port->aux,
+          DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1)
+               return NULL;
+       if (drm_dp_dpcd_read(&port->aux,
+          DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1)
+               return NULL;
+       if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
+          (endpoint_fec & DP_FEC_CAPABLE))
+               return &port->aux;
+
+       return NULL;
+}
+EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port);