]> 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 5b3dfceab36529bbfb55240462db4193abb0996f..9f7e714c518256ce6cd16a1429af7017b1f6e427 100644 (file)
@@ -4093,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
@@ -4119,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;
@@ -4152,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,
@@ -4784,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
@@ -5076,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);