Add drm_dp_mst_dsc_caps_for_port and drm_dp_mst_dsc_enable, two helper functions for MST DSC The former, given a port, returns the raw DPCD DSC caps off that port. The latter, given a port, enables or disables DSC on that port. In both cases, the port given as input should be a leaf of the MST tree with an attached display. The logic for this is somewhat complicated, as DSC can be enabled in 4 different ways. Case 1: DP-to-DP peer device if the branch immediately upstream has - PDT = DP_PEER_DEVICE_DP_MST_BRANCHING (2) - DPCD rev. >= DP 1.4 - Exactly one input and one output - The output has PDT = DP_PEER_DEVICE_SST_SINK (3) In this case, DSC could be possible either on the endpoint or the peer device. Prefer the endpoint, which is possible if - The endpoint has DP_DSC_DECOMPRESSION_IS_SUPPORTED bit set - The endpoint has DP_FEC_CAPABLE bit set - The peer device has DSC_PASSTHROUGH_CAPABILITY bit set (from DP v2.0) Otherwise, use the peer device Case 2: DP-to-HDMI peer device If the output port has - PDT = DP_PEER_DEVICE_DP_LEGACY_CONV (4) - DPCD rev. >= DP 1.4 - LDPS = true - MCS = false In this case, DSC can only be attempted on the peer device (the output port) Case 3: Virtual DP Sink (Internal Display Panel) If the output port has - DPCD rev. >= DP 1.4 - port_num >= 8 In this case, DSC can only be attempted on the peer device (the output port) Case 4: Synaptix Workaround If the output has - link DPCD rev. >= DP 1.4 - link branch_dev_id = 0x90CC24 (Synaptix) - There is exactly one branch device between the link and output In this case, DSC can be attempted, but only using the *link* aux device's caps. This is a quirk. Cc: Lyude Paul <lyude@xxxxxxxxxx> Cc: Wenjing Liu <Wenjing.Liu@xxxxxxx> Cc: Nikola Cornij <Nikola.Cornij@xxxxxxx> Signed-off-by: David Francis <David.Francis@xxxxxxx> --- drivers/gpu/drm/drm_dp_mst_topology.c | 195 ++++++++++++++++++++++++++ include/drm/drm_dp_mst_helper.h | 3 + 2 files changed, 198 insertions(+) diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index af4b5cec7c84..00ddc54af65b 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -4186,3 +4186,198 @@ 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 DPCD device? + * @port: The port to check + * + * Returns: + * true if the port is a virtual DPCD 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) + return false; + + /* Virtual DP Sink (Internal Display Panel) */ + if (port->port_num >= 8 && port->dpcd_rev >= DP_DPCD_REV_14) + return true; + + /* DP-to-HDMI Protocol Converter */ + if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && + !port->mcs && + port->ldps && + port->dpcd_rev >= DP_DPCD_REV_14) + return true; + + /* DP-to-DP */ + if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mstb && + port->dpcd_rev >= DP_DPCD_REV_14 && + port->mstb->num_ports == 2) { + list_for_each_entry(downstream_port, &port->mstb->ports, next) { + if (!downstream_port->input && + downstream_port->pdt == DP_PEER_DEVICE_SST_SINK) + return true; + } + } + + return false; +} + +/** + * drm_dp_mst_is_virtual_dpcd() - Test for Synaptix DSC quirk + * @port: The port to check + * + * Some Synaptix MST hubs support DSC even though they do not support virtual + * DPCD. This is a quirk. + * + * Returns: + * true if the Synaptix workaround is required, false otherwise + */ +static bool drm_dp_mst_dsc_synaptix_workaround(struct drm_dp_mst_port *port) +{ + u8 data[3] = { 0 }; + u32 dev_id; + struct drm_dp_aux *phys_aux; + + /* The hub must be directly connected to the connector */ + if (port->mgr->mst_primary != port->parent) + return false; + + phys_aux = port->mgr->aux; + if (drm_dp_dpcd_read(phys_aux, DP_BRANCH_OUI, data, 3) < 0) + return false; + dev_id = (data[0] << 16) & (data[1] << 8) & data[3]; + /* Synaptix device ID */ + if (dev_id != 0x90CC24) + return false; + + if (drm_dp_dpcd_read(phys_aux, DP_DPCD_REV, data, 1) < 0) + return false; + /* Must be DPCD rev. 1.4 or later */ + if (data[0] < DP_DPCD_REV_14) + return false; + + if (drm_dp_dpcd_read(&port->aux, + DP_DOWNSTREAMPORT_PRESENT, data, 1) < 0) + return false; + /* Must not be a VGA converter */ + if ((data[0] & 7) == 3) + return false; + + return true; +} + +/** + * 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. + * + * 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) +{ + u8 upstream_dsc = 0; + u8 endpoint_dsc = 0; + u8 endpoint_fec = 0; + struct drm_dp_mst_port *immediate_upstream_port; + struct drm_dp_mst_port *fec_port; + + if (port && port->parent) + immediate_upstream_port = port->parent->port_parent; + else + immediate_upstream_port = NULL; + + fec_port = immediate_upstream_port; + while (fec_port) { + if (!fec_port->fec_capable) + return NULL; + + fec_port = fec_port->parent->port_parent; + } + + if (immediate_upstream_port) { + if (drm_dp_dpcd_read(&immediate_upstream_port->aux, + DP_DSC_SUPPORT, &upstream_dsc, 1) < 0) + return NULL; + } + + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) < 0) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) < 0) + return NULL; + + /* Enpoint decompression with DP-to-DP peer device */ + if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port) + && (upstream_dsc & 0x2) /* DSC passthrough */ + && (endpoint_fec & DP_FEC_CAPABLE) + && (endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED)) + return &port->aux; + + /* Virtual DPCD decompression with DP-to-DP peer device */ + if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) + 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; + + /* Synaptix workaround */ + if (drm_dp_mst_dsc_synaptix_workaround(port)) + return port->mgr->aux; + + return NULL; +} + +/** + * drm_dp_mst_dsc_aux_for_port() - Retrieve the DSC capability registers + * @port: The port to check. A leaf of the MST tree with an attached display. + * @caps: Output. A pointer to an array at least 16 bytes long + * + * Reads the DSC capability registers (DSC_SUPPORT through + * BITS_PER_PIXEL_INCREMENT) and store them in the given pointer. Use + * the correct aux for DSC on the given port. + * + * Returns: + * The number of bytes read on success, or a negative error code on failure + */ +int drm_dp_mst_dsc_caps_for_port(struct drm_dp_mst_port *port, u8 *caps) +{ + struct drm_dp_aux *aux = drm_dp_mst_dsc_aux_for_port(port); + + if (!aux) + return -EINVAL; + + return drm_dp_dpcd_read(aux, DP_DSC_SUPPORT, caps, 16); +} +EXPORT_SYMBOL(drm_dp_mst_dsc_caps_for_port); + +/** + * drm_dp_mst_dsc_aux_for_port() - Enable DSC on an MST endpoint + * @port: The port to check. A leaf of the MST tree with an attached display. + * @enable: true for turn on DSC, false for turn off DSC + * + * Writes DP_DSC_ENABLE on the correct aux for the given port. + * + * Returns: + * The number of bytes written on success, or a negative error code on failure + */ +int drm_dp_mst_dsc_enable(struct drm_dp_mst_port *port, bool enable) +{ + struct drm_dp_aux *aux = drm_dp_mst_dsc_aux_for_port(port); + u8 enable_dsc = enable ? 1 : 0; + + if (!aux) + return -EINVAL; + + return drm_dp_dpcd_write(aux, DP_DSC_ENABLE, &enable_dsc, 1); +} +EXPORT_SYMBOL(drm_dp_mst_dsc_enable); diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h index fa973773a4a7..0f70dc8dfbeb 100644 --- a/include/drm/drm_dp_mst_helper.h +++ b/include/drm/drm_dp_mst_helper.h @@ -674,6 +674,9 @@ int __must_check drm_dp_mst_atomic_check(struct drm_atomic_state *state); void drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port); void drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port); +int drm_dp_mst_dsc_caps_for_port(struct drm_dp_mst_port *port, u8 *caps); +int drm_dp_mst_dsc_enable(struct drm_dp_mst_port *port, bool enable); + extern const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs; /** -- 2.17.1 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel