summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLyude Paul <lyude@redhat.com>2020-08-26 14:24:53 -0400
committerLyude Paul <lyude@redhat.com>2020-08-31 19:10:09 -0400
commita4efad354cf1b9d5275adc85f67fcaaa1c2912d8 (patch)
treeea0acea868ea033db9ef1bf27b005ad3769532ef
parent4778ff052812029bc076f5e78eff3ba1851cbea7 (diff)
downloadlinux-a4efad354cf1b9d5275adc85f67fcaaa1c2912d8.tar.gz
linux-a4efad354cf1b9d5275adc85f67fcaaa1c2912d8.tar.bz2
linux-a4efad354cf1b9d5275adc85f67fcaaa1c2912d8.zip
drm/nouveau/kms/nv50-: Add support for DP_SINK_COUNT
This is another bit that we never implemented for nouveau: dongle detection. When a "dongle", e.g. an active display adaptor, is hooked up to the system and causes an HPD to be fired, we don't actually know whether or not there's anything plugged into the dongle without checking the sink count. As a result, plugging in a dongle without anything plugged into it currently results in a bogus EDID retrieval error in the kernel log. Additionally, most dongles won't send another long HPD signal if the user suddenly plugs something in, they'll only send a short HPD IRQ with the expectation that the source will check the sink count and reprobe the connector if it's changed - something we don't actually do. As a result, nothing will happen if the user plugs the dongle in before plugging something into the dongle. So, let's fix this by checking the sink count in both nouveau_dp_probe_dpcd() and nouveau_dp_irq(), and reprobing the connector if things change. Signed-off-by: Lyude Paul <lyude@redhat.com> Reviewed-by: Ben Skeggs <bskeggs@redhat.com> Link: https://patchwork.freedesktop.org/patch/msgid/20200826182456.322681-18-lyude@redhat.com
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_dp.c53
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_encoder.h2
2 files changed, 50 insertions, 5 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c
index ad852e572cfe..59be357b17e0 100644
--- a/drivers/gpu/drm/nouveau/nouveau_dp.c
+++ b/drivers/gpu/drm/nouveau/nouveau_dp.c
@@ -36,12 +36,21 @@ MODULE_PARM_DESC(mst, "Enable DisplayPort multi-stream (default: enabled)");
static int nouveau_mst = 1;
module_param_named(mst, nouveau_mst, int, 0400);
+static bool
+nouveau_dp_has_sink_count(struct drm_connector *connector,
+ struct nouveau_encoder *outp)
+{
+ return drm_dp_read_sink_count_cap(connector, outp->dp.dpcd, &outp->dp.desc);
+}
+
static enum drm_connector_status
nouveau_dp_probe_dpcd(struct nouveau_connector *nv_connector,
struct nouveau_encoder *outp)
{
+ struct drm_connector *connector = &nv_connector->base;
struct drm_dp_aux *aux = &nv_connector->aux;
struct nv50_mstm *mstm = NULL;
+ enum drm_connector_status status = connector_status_disconnected;
int ret;
u8 *dpcd = outp->dp.dpcd;
@@ -50,9 +59,9 @@ nouveau_dp_probe_dpcd(struct nouveau_connector *nv_connector,
ret = drm_dp_read_desc(aux, &outp->dp.desc,
drm_dp_is_branch(dpcd));
if (ret < 0)
- return connector_status_disconnected;
+ goto out;
} else {
- return connector_status_disconnected;
+ goto out;
}
if (nouveau_mst) {
@@ -61,12 +70,33 @@ nouveau_dp_probe_dpcd(struct nouveau_connector *nv_connector,
mstm->can_mst = drm_dp_read_mst_cap(aux, dpcd);
}
+ if (nouveau_dp_has_sink_count(connector, outp)) {
+ ret = drm_dp_read_sink_count(aux);
+ if (ret < 0)
+ goto out;
+
+ outp->dp.sink_count = ret;
+
+ /*
+ * Dongle connected, but no display. Don't bother reading
+ * downstream port info
+ */
+ if (!outp->dp.sink_count)
+ return connector_status_disconnected;
+ }
+
ret = drm_dp_read_downstream_info(aux, dpcd,
outp->dp.downstream_ports);
if (ret < 0)
- return connector_status_disconnected;
+ goto out;
- return connector_status_connected;
+ status = connector_status_connected;
+out:
+ if (status != connector_status_connected) {
+ /* Clear any cached info */
+ outp->dp.sink_count = 0;
+ }
+ return status;
}
int
@@ -159,6 +189,8 @@ void nouveau_dp_irq(struct nouveau_drm *drm,
struct drm_connector *connector = &nv_connector->base;
struct nouveau_encoder *outp = find_encoder(connector, DCB_OUTPUT_DP);
struct nv50_mstm *mstm;
+ int ret;
+ bool send_hpd = false;
if (!outp)
return;
@@ -170,12 +202,23 @@ void nouveau_dp_irq(struct nouveau_drm *drm,
if (mstm && mstm->is_mst) {
if (!nv50_mstm_service(drm, nv_connector, mstm))
- nouveau_connector_hpd(connector);
+ send_hpd = true;
} else {
drm_dp_cec_irq(&nv_connector->aux);
+
+ if (nouveau_dp_has_sink_count(connector, outp)) {
+ ret = drm_dp_read_sink_count(&nv_connector->aux);
+ if (ret != outp->dp.sink_count)
+ send_hpd = true;
+ if (ret >= 0)
+ outp->dp.sink_count = ret;
+ }
}
mutex_unlock(&outp->dp.hpd_irq_lock);
+
+ if (send_hpd)
+ nouveau_connector_hpd(connector);
}
/* TODO:
diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h
index c1924a4529a7..21937f1c7dd9 100644
--- a/drivers/gpu/drm/nouveau/nouveau_encoder.h
+++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h
@@ -74,6 +74,8 @@ struct nouveau_encoder {
u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
struct drm_dp_desc desc;
+
+ u8 sink_count;
} dp;
};