drm/nouveau/disp: add method to trigger DP link retrain
authorBen Skeggs <bskeggs@redhat.com>
Wed, 1 Jun 2022 10:46:35 +0000 (20:46 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Wed, 9 Nov 2022 00:43:10 +0000 (10:43 +1000)
This moves control of link retraining in response to HPD IRQ to the
KMS driver's HPD IRQ handler.

NVKM still handles checking link status for the moment, this can be
moved to the KMS driver when it takes explicit control of link rate
selection.

v2:
- skip source config on retrain (fixes some retrain failures)

Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
drivers/gpu/drm/nouveau/include/nvif/if0012.h
drivers/gpu/drm/nouveau/include/nvif/outp.h
drivers/gpu/drm/nouveau/nouveau_display.c
drivers/gpu/drm/nouveau/nouveau_dp.c
drivers/gpu/drm/nouveau/nouveau_encoder.h
drivers/gpu/drm/nouveau/nvif/outp.c
drivers/gpu/drm/nouveau/nvkm/engine/disp/dp.c
drivers/gpu/drm/nouveau/nvkm/engine/disp/uoutp.c

index d158327..0d2e9a9 100644 (file)
@@ -16,6 +16,7 @@ union nvif_outp_args {
 #define NVIF_OUTP_V0_INFOFRAME   0x03
 #define NVIF_OUTP_V0_HDA_ELD     0x04
 #define NVIF_OUTP_V0_DP_AUX_PWR  0x05
+#define NVIF_OUTP_V0_DP_RETRAIN  0x06
 
 union nvif_outp_load_detect_args {
        struct nvif_outp_load_detect_v0 {
@@ -100,4 +101,9 @@ union nvif_outp_dp_aux_pwr_args {
                __u8 pad02[6];
        } v0;
 };
+
+union nvif_outp_dp_retrain_args {
+       struct nvif_outp_dp_retrain_vn {
+       } vn;
+};
 #endif
index 1c960f6..ea3e0a9 100644 (file)
@@ -27,4 +27,5 @@ void nvif_outp_release(struct nvif_outp *);
 int nvif_outp_infoframe(struct nvif_outp *, u8 type, struct nvif_outp_infoframe_v0 *, u32 size);
 int nvif_outp_hda_eld(struct nvif_outp *, int head, void *data, u32 size);
 int nvif_outp_dp_aux_pwr(struct nvif_outp *, bool enable);
+int nvif_outp_dp_retrain(struct nvif_outp *);
 #endif
index a21dc2a..965e1c6 100644 (file)
@@ -506,8 +506,10 @@ nouveau_display_hpd_work(struct work_struct *work)
                            !!(bits & NVIF_NOTIFY_CONN_V0_UNPLUG),
                            !!(bits & NVIF_NOTIFY_CONN_V0_IRQ));
 
-               if (bits & NVIF_NOTIFY_CONN_V0_IRQ)
-                       continue;
+               if (bits & NVIF_NOTIFY_CONN_V0_IRQ) {
+                       if (nouveau_dp_link_check(nv_connector))
+                               continue;
+               }
 
                connector->status = drm_helper_probe_detect(connector, NULL, false);
                if (old_epoch_counter == connector->epoch_counter)
index d349dc2..bde58d0 100644 (file)
@@ -211,6 +211,17 @@ out:
        return ret;
 }
 
+bool
+nouveau_dp_link_check(struct nouveau_connector *nv_connector)
+{
+       struct nouveau_encoder *nv_encoder = find_encoder(&nv_connector->base, DCB_OUTPUT_DP);
+
+       if (!nv_encoder || nv_encoder->outp.or.id < 0)
+               return true;
+
+       return nvif_outp_dp_retrain(&nv_encoder->outp) == 0;
+}
+
 void nouveau_dp_irq(struct nouveau_drm *drm,
                    struct nouveau_connector *nv_connector)
 {
index b6c51fc..8524d21 100644 (file)
@@ -141,6 +141,7 @@ enum nouveau_dp_status {
 };
 
 int nouveau_dp_detect(struct nouveau_connector *, struct nouveau_encoder *);
+bool nouveau_dp_link_check(struct nouveau_connector *);
 void nouveau_dp_irq(struct nouveau_drm *drm,
                    struct nouveau_connector *nv_connector);
 enum drm_mode_status nv50_dp_mode_valid(struct drm_connector *,
index 9bd9cc2..56b7c70 100644 (file)
 #include <nvif/class.h>
 
 int
+nvif_outp_dp_retrain(struct nvif_outp *outp)
+{
+       int ret = nvif_object_mthd(&outp->object, NVIF_OUTP_V0_DP_RETRAIN, NULL, 0);
+       NVIF_ERRON(ret, &outp->object, "[DP_RETRAIN]");
+       return ret;
+}
+
+int
 nvif_outp_dp_aux_pwr(struct nvif_outp *outp, bool enable)
 {
        struct nvif_outp_dp_aux_pwr_v0 args;
index 92c9fae..730c3a6 100644 (file)
@@ -274,71 +274,18 @@ nvkm_dp_train_cr(struct lt_state *lt)
 }
 
 static int
-nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
+nvkm_dp_train_link(struct nvkm_outp *outp, int rate)
 {
        struct nvkm_ior *ior = outp->ior;
-       struct nvkm_disp *disp = outp->disp;
-       struct nvkm_subdev *subdev = &disp->engine.subdev;
-       struct nvkm_bios *bios = subdev->device->bios;
        struct lt_state lt = {
                .outp = outp,
+               .pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED,
        };
-       u32 lnkcmp;
        u8 sink[2], data;
        int ret;
 
        OUTP_DBG(outp, "training %dx%02x", ior->dp.nr, ior->dp.bw);
 
-       /* Intersect misc. capabilities of the OR and sink. */
-       if (disp->engine.subdev.device->chipset < 0x110)
-               outp->dp.dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED;
-       if (disp->engine.subdev.device->chipset < 0xd0)
-               outp->dp.dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
-       lt.pc2 = outp->dp.dpcd[DPCD_RC02] & DPCD_RC02_TPS3_SUPPORTED;
-
-       if (AMPERE_IED_HACK(disp) && (lnkcmp = lt.outp->dp.info.script[0])) {
-               /* Execute BeforeLinkTraining script from DP Info table. */
-               while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
-                       lnkcmp += 3;
-               lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
-
-               nvbios_init(&outp->disp->engine.subdev, lnkcmp,
-                       init.outp = &outp->info;
-                       init.or   = ior->id;
-                       init.link = ior->asy.link;
-               );
-       }
-
-       /* Set desired link configuration on the source. */
-       if ((lnkcmp = lt.outp->dp.info.lnkcmp)) {
-               if (outp->dp.version < 0x30) {
-                       while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
-                               lnkcmp += 4;
-                       lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
-               } else {
-                       while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
-                               lnkcmp += 3;
-                       lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
-               }
-
-               nvbios_init(subdev, lnkcmp,
-                       init.outp = &outp->info;
-                       init.or   = ior->id;
-                       init.link = ior->asy.link;
-               );
-       }
-
-       ret = ior->func->dp->links(ior, outp->dp.aux);
-       if (ret) {
-               if (ret < 0) {
-                       OUTP_ERR(outp, "train failed with %d", ret);
-                       return ret;
-               }
-               return 0;
-       }
-
-       ior->func->dp->power(ior, ior->dp.nr);
-
        /* Select LTTPR non-transparent mode if we have a valid configuration,
         * use transparent mode otherwise.
         */
@@ -393,6 +340,71 @@ nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
        return ret;
 }
 
+static int
+nvkm_dp_train_links(struct nvkm_outp *outp, int rate)
+{
+       struct nvkm_ior *ior = outp->ior;
+       struct nvkm_disp *disp = outp->disp;
+       struct nvkm_subdev *subdev = &disp->engine.subdev;
+       struct nvkm_bios *bios = subdev->device->bios;
+       u32 lnkcmp;
+       int ret;
+
+       OUTP_DBG(outp, "programming link for %dx%02x", ior->dp.nr, ior->dp.bw);
+
+       /* Intersect misc. capabilities of the OR and sink. */
+       if (disp->engine.subdev.device->chipset < 0x110)
+               outp->dp.dpcd[DPCD_RC03] &= ~DPCD_RC03_TPS4_SUPPORTED;
+       if (disp->engine.subdev.device->chipset < 0xd0)
+               outp->dp.dpcd[DPCD_RC02] &= ~DPCD_RC02_TPS3_SUPPORTED;
+
+       if (AMPERE_IED_HACK(disp) && (lnkcmp = outp->dp.info.script[0])) {
+               /* Execute BeforeLinkTraining script from DP Info table. */
+               while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
+                       lnkcmp += 3;
+               lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
+
+               nvbios_init(&outp->disp->engine.subdev, lnkcmp,
+                       init.outp = &outp->info;
+                       init.or   = ior->id;
+                       init.link = ior->asy.link;
+               );
+       }
+
+       /* Set desired link configuration on the source. */
+       if ((lnkcmp = outp->dp.info.lnkcmp)) {
+               if (outp->dp.version < 0x30) {
+                       while ((ior->dp.bw * 2700) < nvbios_rd16(bios, lnkcmp))
+                               lnkcmp += 4;
+                       lnkcmp = nvbios_rd16(bios, lnkcmp + 2);
+               } else {
+                       while (ior->dp.bw < nvbios_rd08(bios, lnkcmp))
+                               lnkcmp += 3;
+                       lnkcmp = nvbios_rd16(bios, lnkcmp + 1);
+               }
+
+               nvbios_init(subdev, lnkcmp,
+                       init.outp = &outp->info;
+                       init.or   = ior->id;
+                       init.link = ior->asy.link;
+               );
+       }
+
+       ret = ior->func->dp->links(ior, outp->dp.aux);
+       if (ret) {
+               if (ret < 0) {
+                       OUTP_ERR(outp, "train failed with %d", ret);
+                       return ret;
+               }
+               return 0;
+       }
+
+       ior->func->dp->power(ior, ior->dp.nr);
+
+       /* Attempt to train the link in this configuration. */
+       return nvkm_dp_train_link(outp, rate);
+}
+
 static void
 nvkm_dp_train_fini(struct nvkm_outp *outp)
 {
@@ -439,6 +451,16 @@ nvkm_dp_train(struct nvkm_outp *outp, u32 dataKBps)
        int ret = -EINVAL, nr, rate;
        u8  pwr;
 
+       /* Retraining link?  Skip source configuration, it can mess up the active modeset. */
+       if (atomic_read(&outp->dp.lt.done)) {
+               for (rate = 0; rate < outp->dp.rates; rate++) {
+                       if (outp->dp.rate[rate].rate == ior->dp.bw * 27000)
+                               return nvkm_dp_train_link(outp, ret);
+               }
+               WARN_ON(1);
+               return -EINVAL;
+       }
+
        /* Ensure sink is not in a low-power state. */
        if (!nvkm_rdaux(outp->dp.aux, DPCD_SC00, &pwr, 1)) {
                if ((pwr & DPCD_SC00_SET_POWER) != DPCD_SC00_SET_POWER_D0) {
@@ -726,12 +748,8 @@ nvkm_dp_hpd(struct nvkm_notify *notify)
        struct nvif_notify_conn_rep_v0 rep = {};
 
        OUTP_DBG(outp, "HPD: %d", line->mask);
-       if (line->mask & NVKM_I2C_IRQ) {
-               if (atomic_read(&outp->dp.lt.done))
-                       outp->func->acquire(outp);
+       if (line->mask & NVKM_I2C_IRQ)
                rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ;
-       }
-
        if (line->mask & NVKM_I2C_UNPLUG)
                rep.mask |= NVIF_NOTIFY_CONN_V0_UNPLUG;
        if (line->mask & NVKM_I2C_PLUG)
index 3bfc3e3..e12a31d 100644 (file)
 #include <nvif/if0012.h>
 
 static int
+nvkm_uoutp_mthd_dp_retrain(struct nvkm_outp *outp, void *argv, u32 argc)
+{
+       union nvif_outp_dp_retrain_args *args = argv;
+
+       if (argc != sizeof(args->vn))
+               return -ENOSYS;
+
+       if (!atomic_read(&outp->dp.lt.done))
+               return 0;
+
+       return outp->func->acquire(outp);
+}
+
+static int
 nvkm_uoutp_mthd_dp_aux_pwr(struct nvkm_outp *outp, void *argv, u32 argc)
 {
        union nvif_outp_dp_aux_pwr_args *args = argv;
@@ -251,6 +265,7 @@ nvkm_uoutp_mthd_acquired(struct nvkm_outp *outp, u32 mthd, void *argv, u32 argc)
        case NVIF_OUTP_V0_RELEASE    : return nvkm_uoutp_mthd_release    (outp, argv, argc);
        case NVIF_OUTP_V0_INFOFRAME  : return nvkm_uoutp_mthd_infoframe  (outp, argv, argc);
        case NVIF_OUTP_V0_HDA_ELD    : return nvkm_uoutp_mthd_hda_eld    (outp, argv, argc);
+       case NVIF_OUTP_V0_DP_RETRAIN : return nvkm_uoutp_mthd_dp_retrain (outp, argv, argc);
        default:
                break;
        }