]> git.ipfire.org Git - thirdparty/kernel/linux.git/blobdiff - drivers/gpu/drm/nouveau/dispnv50/disp.c
Merge tag 'drm-misc-next-2023-09-27' of git://anongit.freedesktop.org/drm/drm-misc...
[thirdparty/kernel/linux.git] / drivers / gpu / drm / nouveau / dispnv50 / disp.c
index 4e7c9c353c5112c898a565ea5d388062d9682baf..52f1569ee37c1de3520c14b6cb5fd88d600f289d 100644 (file)
@@ -66,8 +66,6 @@
 #include "nouveau_fence.h"
 #include "nv50_display.h"
 
-#include <subdev/bios/dp.h>
-
 /******************************************************************************
  * EVO channel
  *****************************************************************************/
@@ -477,7 +475,6 @@ nv50_dac_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *st
 
        core->func->dac->ctrl(core, nv_encoder->outp.or.id, ctrl, NULL);
        nv_encoder->crtc = NULL;
-       nvif_outp_release(&nv_encoder->outp);
 }
 
 static void
@@ -502,7 +499,8 @@ nv50_dac_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
 
        ctrl |= NVDEF(NV507D, DAC_SET_CONTROL, PROTOCOL, RGB_CRT);
 
-       nvif_outp_acquire_rgb_crt(&nv_encoder->outp);
+       if (!nvif_outp_acquired(&nv_encoder->outp))
+               nvif_outp_acquire_dac(&nv_encoder->outp);
 
        core->func->dac->ctrl(core, nv_encoder->outp.or.id, ctrl, asyh);
        asyh->or.depth = 0;
@@ -553,34 +551,27 @@ nv50_dac_func = {
 };
 
 static int
-nv50_dac_create(struct drm_connector *connector, struct dcb_output *dcbe)
+nv50_dac_create(struct nouveau_encoder *nv_encoder)
 {
+       struct drm_connector *connector = &nv_encoder->conn->base;
        struct nouveau_drm *drm = nouveau_drm(connector->dev);
-       struct nv50_disp *disp = nv50_disp(connector->dev);
        struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
        struct nvkm_i2c_bus *bus;
-       struct nouveau_encoder *nv_encoder;
        struct drm_encoder *encoder;
+       struct dcb_output *dcbe = nv_encoder->dcb;
        int type = DRM_MODE_ENCODER_DAC;
 
-       nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
-       if (!nv_encoder)
-               return -ENOMEM;
-       nv_encoder->dcb = dcbe;
-
        bus = nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
        if (bus)
                nv_encoder->i2c = &bus->i2c;
 
        encoder = to_drm_encoder(nv_encoder);
-       encoder->possible_crtcs = dcbe->heads;
-       encoder->possible_clones = 0;
        drm_encoder_init(connector->dev, encoder, &nv50_dac_func, type,
                         "dac-%04x-%04x", dcbe->hasht, dcbe->hashm);
        drm_encoder_helper_add(encoder, &nv50_dac_help);
 
        drm_connector_attach_encoder(connector, encoder);
-       return nvif_outp_ctor(disp->disp, nv_encoder->base.base.name, dcbe->id, &nv_encoder->outp);
+       return 0;
 }
 
 /*
@@ -617,7 +608,7 @@ nv50_audio_component_get_eld(struct device *kdev, int port, int dev_id,
                        continue; /* TODO */
 
                nv_encoder = nouveau_encoder(encoder);
-               nv_connector = nouveau_connector(nv_encoder->audio.connector);
+               nv_connector = nv_encoder->conn;
                nv_crtc = nouveau_crtc(nv_encoder->crtc);
 
                if (!nv_crtc || nv_encoder->outp.or.id != port || nv_crtc->index != dev_id)
@@ -713,6 +704,18 @@ nv50_audio_supported(struct drm_encoder *encoder)
            disp->disp->object.oclass == GT206_DISP)
                return false;
 
+       if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
+               struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
+
+               switch (nv_encoder->dcb->type) {
+               case DCB_OUTPUT_TMDS:
+               case DCB_OUTPUT_DP:
+                       break;
+               default:
+                       return false;
+               }
+       }
+
        return true;
 }
 
@@ -729,7 +732,6 @@ nv50_audio_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
        mutex_lock(&drm->audio.lock);
        if (nv_encoder->audio.enabled) {
                nv_encoder->audio.enabled = false;
-               nv_encoder->audio.connector = NULL;
                nvif_outp_hda_eld(&nv_encoder->outp, nv_crtc->index, NULL, 0);
        }
        mutex_unlock(&drm->audio.lock);
@@ -754,7 +756,6 @@ nv50_audio_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
        nvif_outp_hda_eld(&nv_encoder->outp, nv_crtc->index, nv_connector->base.eld,
                          drm_eld_size(nv_connector->base.eld));
        nv_encoder->audio.enabled = true;
-       nv_encoder->audio.connector = &nv_connector->base;
 
        mutex_unlock(&drm->audio.lock);
 
@@ -774,7 +775,6 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
        struct drm_hdmi_info *hdmi = &nv_connector->base.display_info.hdmi;
        union hdmi_infoframe infoframe = { 0 };
        const u8 rekey = 56; /* binary driver, and tegra, constant */
-       u8 scdc = 0;
        u32 max_ac_packet;
        struct {
                struct nvif_outp_infoframe_v0 infoframe;
@@ -787,8 +787,9 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
        max_ac_packet -= 18; /* constant from tegra */
        max_ac_packet /= 32;
 
-       if (hdmi->scdc.scrambling.supported) {
+       if (nv_encoder->i2c && hdmi->scdc.scrambling.supported) {
                const bool high_tmds_clock_ratio = mode->clock > 340000;
+               u8 scdc;
 
                ret = drm_scdc_readb(nv_encoder->i2c, SCDC_TMDS_CONFIG, &scdc);
                if (ret < 0) {
@@ -808,8 +809,9 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
                                 scdc, ret);
        }
 
-       ret = nvif_outp_acquire_tmds(&nv_encoder->outp, nv_crtc->index, true,
-                                    max_ac_packet, rekey, scdc, hda);
+       ret = nvif_outp_hdmi(&nv_encoder->outp, nv_crtc->index, true, max_ac_packet, rekey,
+                            mode->clock, hdmi->scdc.supported, hdmi->scdc.scrambling.supported,
+                            hdmi->scdc.scrambling.low_rates);
        if (ret)
                return;
 
@@ -838,7 +840,7 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc,
 
        nvif_outp_infoframe(&nv_encoder->outp, NVIF_OUTP_INFOFRAME_V0_VSI, &args.infoframe, size);
 
-       nv50_audio_enable(encoder, nv_crtc, nv_connector, state, mode);
+       nv_encoder->hdmi.enabled = true;
 }
 
 /******************************************************************************
@@ -865,6 +867,8 @@ struct nv50_msto {
        struct nv50_mstc *mstc;
        bool disabled;
        bool enabled;
+
+       u32 display_id;
 };
 
 struct nouveau_encoder *nv50_real_outp(struct drm_encoder *encoder)
@@ -882,21 +886,33 @@ struct nouveau_encoder *nv50_real_outp(struct drm_encoder *encoder)
 
 static void
 nv50_msto_cleanup(struct drm_atomic_state *state,
-                 struct drm_dp_mst_topology_state *mst_state,
+                 struct drm_dp_mst_topology_state *new_mst_state,
                  struct drm_dp_mst_topology_mgr *mgr,
                  struct nv50_msto *msto)
 {
        struct nouveau_drm *drm = nouveau_drm(msto->encoder.dev);
-       struct drm_dp_mst_atomic_payload *payload =
-               drm_atomic_get_mst_payload_state(mst_state, msto->mstc->port);
+       struct drm_dp_mst_atomic_payload *new_payload =
+               drm_atomic_get_mst_payload_state(new_mst_state, msto->mstc->port);
+       struct drm_dp_mst_topology_state *old_mst_state =
+               drm_atomic_get_old_mst_topology_state(state, mgr);
+       const struct drm_dp_mst_atomic_payload *old_payload =
+               drm_atomic_get_mst_payload_state(old_mst_state, msto->mstc->port);
+       struct nv50_mstc *mstc = msto->mstc;
+       struct nv50_mstm *mstm = mstc->mstm;
 
        NV_ATOMIC(drm, "%s: msto cleanup\n", msto->encoder.name);
 
        if (msto->disabled) {
+               if (msto->head->func->display_id) {
+                       nvif_outp_dp_mst_id_put(&mstm->outp->outp, msto->display_id);
+                       msto->display_id = 0;
+               }
+
                msto->mstc = NULL;
                msto->disabled = false;
+               drm_dp_remove_payload_part2(mgr, new_mst_state, old_payload, new_payload);
        } else if (msto->enabled) {
-               drm_dp_add_payload_part2(mgr, state, payload);
+               drm_dp_add_payload_part2(mgr, state, new_payload);
                msto->enabled = false;
        }
 }
@@ -910,28 +926,28 @@ nv50_msto_prepare(struct drm_atomic_state *state,
        struct nouveau_drm *drm = nouveau_drm(msto->encoder.dev);
        struct nv50_mstc *mstc = msto->mstc;
        struct nv50_mstm *mstm = mstc->mstm;
-       struct drm_dp_mst_topology_state *old_mst_state;
-       struct drm_dp_mst_atomic_payload *payload, *old_payload;
+       struct drm_dp_mst_atomic_payload *payload;
+       int ret = 0;
 
        NV_ATOMIC(drm, "%s: msto prepare\n", msto->encoder.name);
 
-       old_mst_state = drm_atomic_get_old_mst_topology_state(state, mgr);
-
        payload = drm_atomic_get_mst_payload_state(mst_state, mstc->port);
-       old_payload = drm_atomic_get_mst_payload_state(old_mst_state, mstc->port);
 
-       // TODO: Figure out if we want to do a better job of handling VCPI allocation failures here?
        if (msto->disabled) {
-               drm_dp_remove_payload(mgr, mst_state, old_payload, payload);
-
+               drm_dp_remove_payload_part1(mgr, mst_state, payload);
                nvif_outp_dp_mst_vcpi(&mstm->outp->outp, msto->head->base.index, 0, 0, 0, 0);
+               ret = 1;
        } else {
                if (msto->enabled)
-                       drm_dp_add_payload_part1(mgr, mst_state, payload);
+                       ret = drm_dp_add_payload_part1(mgr, mst_state, payload);
+       }
 
+       if (ret == 0) {
                nvif_outp_dp_mst_vcpi(&mstm->outp->outp, msto->head->base.index,
                                      payload->vc_start_slot, payload->time_slots,
                                      payload->pbn, payload->time_slots * mst_state->pbn_div);
+       } else {
+               nvif_outp_dp_mst_vcpi(&mstm->outp->outp, msto->head->base.index, 0, 0, 0, 0);
        }
 }
 
@@ -1028,8 +1044,13 @@ nv50_msto_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *st
                return;
 
        if (!mstm->links++) {
-               /*XXX: MST audio. */
-               nvif_outp_acquire_dp(&mstm->outp->outp, mstm->outp->dp.dpcd, 0, 0, false, true);
+               nvif_outp_acquire_sor(&mstm->outp->outp, false /*TODO: MST audio... */);
+               nouveau_dp_train(mstm->outp, true, 0, 0);
+       }
+
+       if (head->func->display_id) {
+               if (!WARN_ON(nvif_outp_dp_mst_id_get(&mstm->outp->outp, &msto->display_id)))
+                       head->func->display_id(head, msto->display_id);
        }
 
        if (mstm->outp->outp.or.link & 1)
@@ -1052,6 +1073,9 @@ nv50_msto_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *s
        struct nv50_mstc *mstc = msto->mstc;
        struct nv50_mstm *mstm = mstc->mstm;
 
+       if (msto->head->func->display_id)
+               msto->head->func->display_id(msto->head, 0);
+
        mstm->outp->update(mstm->outp, msto->head->base.index, NULL, 0, 0);
        mstm->modified = true;
        if (!--mstm->links)
@@ -1290,6 +1314,12 @@ nv50_mstm_cleanup(struct drm_atomic_state *state,
                }
        }
 
+       if (mstm->disabled) {
+               nouveau_dp_power_down(mstm->outp);
+               nvif_outp_release(&mstm->outp->outp);
+               mstm->disabled = false;
+       }
+
        mstm->modified = false;
 }
 
@@ -1324,12 +1354,6 @@ nv50_mstm_prepare(struct drm_atomic_state *state,
                                nv50_msto_prepare(state, mst_state, &mstm->mgr, msto);
                }
        }
-
-       if (mstm->disabled) {
-               if (!mstm->links)
-                       nvif_outp_release(&mstm->outp->outp);
-               mstm->disabled = false;
-       }
 }
 
 static struct drm_connector *
@@ -1535,7 +1559,7 @@ static void
 nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *state)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-       struct nouveau_crtc *nv_crtc = nouveau_crtc(nv_encoder->crtc);
+       struct nv50_head *head = nv50_head(nv_encoder->crtc);
        struct nouveau_connector *nv_connector = nv50_outp_get_old_connector(state, nv_encoder);
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
        struct nouveau_drm *drm = nouveau_drm(nv_encoder->base.base.dev);
@@ -1543,7 +1567,6 @@ nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *st
 #endif
        struct drm_dp_aux *aux = &nv_connector->aux;
        int ret;
-       u8 pwr;
 
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
        if (backlight && backlight->uses_dpcd) {
@@ -1554,19 +1577,20 @@ nv50_sor_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *st
        }
 #endif
 
-       if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
-               ret = drm_dp_dpcd_readb(aux, DP_SET_POWER, &pwr);
-
-               if (ret == 0) {
-                       pwr &= ~DP_SET_POWER_MASK;
-                       pwr |=  DP_SET_POWER_D3;
-                       drm_dp_dpcd_writeb(aux, DP_SET_POWER, pwr);
-               }
+       if (nv_encoder->dcb->type == DCB_OUTPUT_TMDS && nv_encoder->hdmi.enabled) {
+               nvif_outp_hdmi(&nv_encoder->outp, head->base.index,
+                              false, 0, 0, 0, false, false, false);
+               nv_encoder->hdmi.enabled = false;
        }
 
-       nv_encoder->update(nv_encoder, nv_crtc->index, NULL, 0, 0);
-       nv50_audio_disable(encoder, nv_crtc);
-       nvif_outp_release(&nv_encoder->outp);
+       if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
+               nouveau_dp_power_down(nv_encoder);
+
+       if (head->func->display_id)
+               head->func->display_id(head, 0);
+
+       nv_encoder->update(nv_encoder, head->base.index, NULL, 0, 0);
+       nv50_audio_disable(encoder, &head->base);
        nv_encoder->crtc = NULL;
 }
 
@@ -1579,6 +1603,7 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
                nv50_head_atom(drm_atomic_get_new_crtc_state(state, &nv_crtc->base));
        struct drm_display_mode *mode = &asyh->state.adjusted_mode;
        struct nv50_disp *disp = nv50_disp(encoder->dev);
+       struct nv50_head *head = nv50_head(&nv_crtc->base);
        struct nvif_outp *outp = &nv_encoder->outp;
        struct drm_device *dev = encoder->dev;
        struct nouveau_drm *drm = nouveau_drm(dev);
@@ -1596,15 +1621,17 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
 
        if ((disp->disp->object.oclass == GT214_DISP ||
             disp->disp->object.oclass >= GF110_DISP) &&
+           nv_encoder->dcb->type != DCB_OUTPUT_LVDS &&
            drm_detect_monitor_audio(nv_connector->edid))
                hda = true;
 
+       if (!nvif_outp_acquired(outp))
+               nvif_outp_acquire_sor(outp, hda);
+
        switch (nv_encoder->dcb->type) {
        case DCB_OUTPUT_TMDS:
-               if (disp->disp->object.oclass == NV50_DISP ||
-                   !drm_detect_hdmi_monitor(nv_connector->edid))
-                       nvif_outp_acquire_tmds(outp, nv_crtc->index, false, 0, 0, 0, false);
-               else
+               if (disp->disp->object.oclass != NV50_DISP &&
+                   drm_detect_hdmi_monitor(nv_connector->edid))
                        nv50_hdmi_enable(encoder, nv_crtc, nv_connector, state, mode, hda);
 
                if (nv_encoder->outp.or.link & 1) {
@@ -1650,10 +1677,10 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
                                lvds_8bpc = true;
                }
 
-               nvif_outp_acquire_lvds(&nv_encoder->outp, lvds_dual, lvds_8bpc);
+               nvif_outp_lvds(&nv_encoder->outp, lvds_dual, lvds_8bpc);
                break;
        case DCB_OUTPUT_DP:
-               nvif_outp_acquire_dp(&nv_encoder->outp, nv_encoder->dp.dpcd, 0, 0, hda, false);
+               nouveau_dp_train(nv_encoder, false, mode->clock, asyh->or.bpc);
                depth = nv50_dp_bpc_to_depth(asyh->or.bpc);
 
                if (nv_encoder->outp.or.link & 1)
@@ -1661,8 +1688,6 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
                else
                        proto = NV887D_SOR_SET_CONTROL_PROTOCOL_DP_B;
 
-               nv50_audio_enable(encoder, nv_crtc, nv_connector, state, mode);
-
 #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT
                backlight = nv_connector->backlight;
                if (backlight && backlight->uses_dpcd)
@@ -1676,6 +1701,9 @@ nv50_sor_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *sta
                break;
        }
 
+       if (head->func->display_id)
+               head->func->display_id(head, BIT(nv_encoder->outp.id));
+
        nv_encoder->update(nv_encoder, nv_crtc->index, asyh, proto, depth);
 }
 
@@ -1691,14 +1719,13 @@ nv50_sor_destroy(struct drm_encoder *encoder)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
 
-       nvif_outp_dtor(&nv_encoder->outp);
-
        nv50_mstm_del(&nv_encoder->dp.mstm);
        drm_encoder_cleanup(encoder);
 
        if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
                mutex_destroy(&nv_encoder->dp.hpd_irq_lock);
 
+       nvif_outp_dtor(&nv_encoder->outp);
        kfree(encoder);
 }
 
@@ -1707,24 +1734,15 @@ nv50_sor_func = {
        .destroy = nv50_sor_destroy,
 };
 
-bool nv50_has_mst(struct nouveau_drm *drm)
-{
-       struct nvkm_bios *bios = nvxx_bios(&drm->client.device);
-       u32 data;
-       u8 ver, hdr, cnt, len;
-
-       data = nvbios_dp_table(bios, &ver, &hdr, &cnt, &len);
-       return data && ver >= 0x40 && (nvbios_rd08(bios, data + 0x08) & 0x04);
-}
-
 static int
-nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
+nv50_sor_create(struct nouveau_encoder *nv_encoder)
 {
+       struct drm_connector *connector = &nv_encoder->conn->base;
        struct nouveau_connector *nv_connector = nouveau_connector(connector);
        struct nouveau_drm *drm = nouveau_drm(connector->dev);
        struct nvkm_i2c *i2c = nvxx_i2c(&drm->client.device);
-       struct nouveau_encoder *nv_encoder;
        struct drm_encoder *encoder;
+       struct dcb_output *dcbe = nv_encoder->dcb;
        struct nv50_disp *disp = nv50_disp(connector->dev);
        int type, ret;
 
@@ -1737,15 +1755,9 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
                break;
        }
 
-       nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
-       if (!nv_encoder)
-               return -ENOMEM;
-       nv_encoder->dcb = dcbe;
        nv_encoder->update = nv50_sor_update;
 
        encoder = to_drm_encoder(nv_encoder);
-       encoder->possible_crtcs = dcbe->heads;
-       encoder->possible_clones = 0;
        drm_encoder_init(connector->dev, encoder, &nv50_sor_func, type,
                         "sor-%04x-%04x", dcbe->hasht, dcbe->hashm);
        drm_encoder_helper_add(encoder, &nv50_sor_help);
@@ -1756,40 +1768,40 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
        nv50_outp_dump_caps(drm, nv_encoder);
 
        if (dcbe->type == DCB_OUTPUT_DP) {
-               struct nvkm_i2c_aux *aux =
-                       nvkm_i2c_aux_find(i2c, dcbe->i2c_index);
-
                mutex_init(&nv_encoder->dp.hpd_irq_lock);
 
-               if (aux) {
-                       if (disp->disp->object.oclass < GF110_DISP) {
-                               /* HW has no support for address-only
-                                * transactions, so we're required to
-                                * use custom I2C-over-AUX code.
-                                */
-                               nv_encoder->i2c = &aux->i2c;
-                       } else {
-                               nv_encoder->i2c = &nv_connector->aux.ddc;
-                       }
-                       nv_encoder->aux = aux;
+               if (disp->disp->object.oclass < GF110_DISP) {
+                       /* HW has no support for address-only
+                        * transactions, so we're required to
+                        * use custom I2C-over-AUX code.
+                        */
+                       struct nvkm_i2c_aux *aux;
+
+                       aux = nvkm_i2c_aux_find(i2c, dcbe->i2c_index);
+                       if (!aux)
+                               return -EINVAL;
+
+                       nv_encoder->i2c = &aux->i2c;
+               } else {
+                       nv_encoder->i2c = &nv_connector->aux.ddc;
                }
 
-               if (nv_connector->type != DCB_CONNECTOR_eDP &&
-                   nv50_has_mst(drm)) {
+               if (nv_connector->type != DCB_CONNECTOR_eDP && nv_encoder->outp.info.dp.mst) {
                        ret = nv50_mstm_new(nv_encoder, &nv_connector->aux,
                                            16, nv_connector->base.base.id,
                                            &nv_encoder->dp.mstm);
                        if (ret)
                                return ret;
                }
-       } else {
+       } else
+       if (nv_encoder->outp.info.ddc != NVIF_OUTP_DDC_INVALID) {
                struct nvkm_i2c_bus *bus =
                        nvkm_i2c_bus_find(i2c, dcbe->i2c_index);
                if (bus)
                        nv_encoder->i2c = &bus->i2c;
        }
 
-       return nvif_outp_ctor(disp->disp, nv_encoder->base.base.name, dcbe->id, &nv_encoder->outp);
+       return 0;
 }
 
 /******************************************************************************
@@ -1816,7 +1828,6 @@ nv50_pior_atomic_disable(struct drm_encoder *encoder, struct drm_atomic_state *s
 
        core->func->pior->ctrl(core, nv_encoder->outp.or.id, ctrl, NULL);
        nv_encoder->crtc = NULL;
-       nvif_outp_release(&nv_encoder->outp);
 }
 
 static void
@@ -1844,14 +1855,16 @@ nv50_pior_atomic_enable(struct drm_encoder *encoder, struct drm_atomic_state *st
        default: asyh->or.depth = NV837D_PIOR_SET_CONTROL_PIXEL_DEPTH_DEFAULT; break;
        }
 
+       if (!nvif_outp_acquired(&nv_encoder->outp))
+               nvif_outp_acquire_pior(&nv_encoder->outp);
+
        switch (nv_encoder->dcb->type) {
        case DCB_OUTPUT_TMDS:
                ctrl |= NVDEF(NV507D, PIOR_SET_CONTROL, PROTOCOL, EXT_TMDS_ENC);
-               nvif_outp_acquire_tmds(&nv_encoder->outp, false, false, 0, 0, 0, false);
                break;
        case DCB_OUTPUT_DP:
                ctrl |= NVDEF(NV507D, PIOR_SET_CONTROL, PROTOCOL, EXT_TMDS_ENC);
-               nvif_outp_acquire_dp(&nv_encoder->outp, nv_encoder->dp.dpcd, 0, 0, false, false);
+               nouveau_dp_train(nv_encoder, false, asyh->state.adjusted_mode.clock, 6);
                break;
        default:
                BUG();
@@ -1888,8 +1901,9 @@ nv50_pior_func = {
 };
 
 static int
-nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
+nv50_pior_create(struct nouveau_encoder *nv_encoder)
 {
+       struct drm_connector *connector = &nv_encoder->conn->base;
        struct drm_device *dev = connector->dev;
        struct nouveau_drm *drm = nouveau_drm(dev);
        struct nv50_disp *disp = nv50_disp(dev);
@@ -1897,18 +1911,18 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
        struct nvkm_i2c_bus *bus = NULL;
        struct nvkm_i2c_aux *aux = NULL;
        struct i2c_adapter *ddc;
-       struct nouveau_encoder *nv_encoder;
        struct drm_encoder *encoder;
+       struct dcb_output *dcbe = nv_encoder->dcb;
        int type;
 
        switch (dcbe->type) {
        case DCB_OUTPUT_TMDS:
-               bus  = nvkm_i2c_bus_find(i2c, NVKM_I2C_BUS_EXT(dcbe->extdev));
+               bus  = nvkm_i2c_bus_find(i2c, nv_encoder->outp.info.ddc);
                ddc  = bus ? &bus->i2c : NULL;
                type = DRM_MODE_ENCODER_TMDS;
                break;
        case DCB_OUTPUT_DP:
-               aux  = nvkm_i2c_aux_find(i2c, NVKM_I2C_AUX_EXT(dcbe->extdev));
+               aux  = nvkm_i2c_aux_find(i2c, nv_encoder->outp.info.dp.aux);
                ddc  = aux ? &aux->i2c : NULL;
                type = DRM_MODE_ENCODER_TMDS;
                break;
@@ -1916,18 +1930,11 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
                return -ENODEV;
        }
 
-       nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
-       if (!nv_encoder)
-               return -ENOMEM;
-       nv_encoder->dcb = dcbe;
        nv_encoder->i2c = ddc;
-       nv_encoder->aux = aux;
 
        mutex_init(&nv_encoder->dp.hpd_irq_lock);
 
        encoder = to_drm_encoder(nv_encoder);
-       encoder->possible_crtcs = dcbe->heads;
-       encoder->possible_clones = 0;
        drm_encoder_init(connector->dev, encoder, &nv50_pior_func, type,
                         "pior-%04x-%04x", dcbe->hasht, dcbe->hashm);
        drm_encoder_helper_add(encoder, &nv50_pior_help);
@@ -1937,7 +1944,7 @@ nv50_pior_create(struct drm_connector *connector, struct dcb_output *dcbe)
        disp->core->func->pior->get_caps(disp, nv_encoder, ffs(dcbe->or) - 1);
        nv50_outp_dump_caps(drm, nv_encoder);
 
-       return nvif_outp_ctor(disp->disp, nv_encoder->base.base.name, dcbe->id, &nv_encoder->outp);
+       return 0;
 }
 
 /******************************************************************************
@@ -1951,7 +1958,9 @@ nv50_disp_atomic_commit_core(struct drm_atomic_state *state, u32 *interlock)
        struct drm_dp_mst_topology_state *mst_state;
        struct nouveau_drm *drm = nouveau_drm(state->dev);
        struct nv50_disp *disp = nv50_disp(drm->dev);
+       struct nv50_atom *atom = nv50_atom(state);
        struct nv50_core *core = disp->core;
+       struct nv50_outp_atom *outp;
        struct nv50_mstm *mstm;
        int i;
 
@@ -1974,6 +1983,23 @@ nv50_disp_atomic_commit_core(struct drm_atomic_state *state, u32 *interlock)
                if (mstm->modified)
                        nv50_mstm_cleanup(state, mst_state, mstm);
        }
+
+       list_for_each_entry(outp, &atom->outp, head) {
+               if (outp->encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
+                       struct nouveau_encoder *nv_encoder = nouveau_encoder(outp->encoder);
+
+                       if (outp->enabled) {
+                               nv50_audio_enable(outp->encoder, nouveau_crtc(nv_encoder->crtc),
+                                                 nv_encoder->conn, NULL, NULL);
+                               outp->enabled = outp->disabled = false;
+                       } else {
+                               if (outp->disabled) {
+                                       nvif_outp_release(&nv_encoder->outp);
+                                       outp->disabled = false;
+                               }
+                       }
+               }
+       }
 }
 
 static void
@@ -2065,14 +2091,8 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
 
                if (outp->clr.mask) {
                        help->atomic_disable(encoder, state);
+                       outp->disabled = true;
                        interlock[NV50_DISP_INTERLOCK_CORE] |= 1;
-                       if (outp->flush_disable) {
-                               nv50_disp_atomic_commit_wndw(state, interlock);
-                               nv50_disp_atomic_commit_core(state, interlock);
-                               memset(interlock, 0x00, sizeof(interlock));
-
-                               flushed = true;
-                       }
                }
        }
 
@@ -2092,7 +2112,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
        nv50_crc_atomic_init_notifier_contexts(state);
 
        /* Update output path(s). */
-       list_for_each_entry_safe(outp, outt, &atom->outp, head) {
+       list_for_each_entry(outp, &atom->outp, head) {
                const struct drm_encoder_helper_funcs *help;
                struct drm_encoder *encoder;
 
@@ -2104,11 +2124,9 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
 
                if (outp->set.mask) {
                        help->atomic_enable(encoder, state);
+                       outp->enabled = true;
                        interlock[NV50_DISP_INTERLOCK_CORE] = 1;
                }
-
-               list_del(&outp->head);
-               kfree(outp);
        }
 
        /* Update head(s). */
@@ -2206,6 +2224,11 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
        if (atom->lock_core)
                mutex_unlock(&disp->mutex);
 
+       list_for_each_entry_safe(outp, outt, &atom->outp, head) {
+               list_del(&outp->head);
+               kfree(outp);
+       }
+
        /* Wait for HW to signal completion. */
        for_each_new_plane_in_state(state, plane, new_plane_state, i) {
                struct nv50_wndw_atom *asyw = nv50_wndw_atom(new_plane_state);
@@ -2354,10 +2377,9 @@ nv50_disp_outp_atomic_check_clr(struct nv50_atom *atom,
                if (IS_ERR(outp))
                        return PTR_ERR(outp);
 
-               if (outp->encoder->encoder_type == DRM_MODE_ENCODER_DPMST) {
-                       outp->flush_disable = true;
+               if (outp->encoder->encoder_type == DRM_MODE_ENCODER_DPMST ||
+                   nouveau_encoder(outp->encoder)->dcb->type == DCB_OUTPUT_DP)
                        atom->flush_disable = true;
-               }
                outp->clr.ctrl = true;
                atom->lock_core = true;
        }
@@ -2518,6 +2540,104 @@ nv50_display_fini(struct drm_device *dev, bool runtime, bool suspend)
                cancel_work_sync(&drm->hpd_work);
 }
 
+static inline void
+nv50_display_read_hw_or_state(struct drm_device *dev, struct nv50_disp *disp,
+                             struct nouveau_encoder *outp)
+{
+       struct drm_crtc *crtc;
+       struct drm_connector_list_iter conn_iter;
+       struct drm_connector *conn;
+       struct nv50_head_atom *armh;
+       const u32 encoder_mask = drm_encoder_mask(&outp->base.base);
+       bool found_conn = false, found_head = false;
+       u8 proto;
+       int head_idx;
+       int ret;
+
+       switch (outp->dcb->type) {
+       case DCB_OUTPUT_TMDS:
+               ret = nvif_outp_inherit_tmds(&outp->outp, &proto);
+               break;
+       case DCB_OUTPUT_DP:
+               ret = nvif_outp_inherit_dp(&outp->outp, &proto);
+               break;
+       case DCB_OUTPUT_LVDS:
+               ret = nvif_outp_inherit_lvds(&outp->outp, &proto);
+               break;
+       case DCB_OUTPUT_ANALOG:
+               ret = nvif_outp_inherit_rgb_crt(&outp->outp, &proto);
+               break;
+       default:
+               drm_dbg_kms(dev, "Readback for %s not implemented yet, skipping\n",
+                           outp->base.base.name);
+               drm_WARN_ON(dev, true);
+               return;
+       }
+
+       if (ret < 0)
+               return;
+
+       head_idx = ret;
+
+       drm_for_each_crtc(crtc, dev) {
+               if (crtc->index != head_idx)
+                       continue;
+
+               armh = nv50_head_atom(crtc->state);
+               found_head = true;
+               break;
+       }
+       if (drm_WARN_ON(dev, !found_head))
+               return;
+
+       /* Figure out which connector is being used by this encoder */
+       drm_connector_list_iter_begin(dev, &conn_iter);
+       nouveau_for_each_non_mst_connector_iter(conn, &conn_iter) {
+               if (nouveau_connector(conn)->index == outp->dcb->connector) {
+                       found_conn = true;
+                       break;
+               }
+       }
+       drm_connector_list_iter_end(&conn_iter);
+       if (drm_WARN_ON(dev, !found_conn))
+               return;
+
+       armh->state.encoder_mask = encoder_mask;
+       armh->state.connector_mask = drm_connector_mask(conn);
+       armh->state.active = true;
+       armh->state.enable = true;
+       pm_runtime_get_noresume(dev->dev);
+
+       outp->crtc = crtc;
+       outp->ctrl = NVVAL(NV507D, SOR_SET_CONTROL, PROTOCOL, proto) | BIT(crtc->index);
+
+       drm_connector_get(conn);
+       conn->state->crtc = crtc;
+       conn->state->best_encoder = &outp->base.base;
+}
+
+/* Read back the currently programmed display state */
+static void
+nv50_display_read_hw_state(struct nouveau_drm *drm)
+{
+       struct drm_device *dev = drm->dev;
+       struct drm_encoder *encoder;
+       struct drm_modeset_acquire_ctx ctx;
+       struct nv50_disp *disp = nv50_disp(dev);
+       int ret;
+
+       DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
+
+       drm_for_each_encoder(encoder, dev) {
+               if (encoder->encoder_type == DRM_MODE_ENCODER_DPMST)
+                       continue;
+
+               nv50_display_read_hw_or_state(dev, disp, nouveau_encoder(encoder));
+       }
+
+       DRM_MODESET_LOCK_ALL_END(dev, ctx, ret);
+}
+
 static int
 nv50_display_init(struct drm_device *dev, bool resume, bool runtime)
 {
@@ -2535,6 +2655,9 @@ nv50_display_init(struct drm_device *dev, bool resume, bool runtime)
                }
        }
 
+       if (!resume)
+               nv50_display_read_hw_state(nouveau_drm(dev));
+
        return 0;
 }
 
@@ -2561,14 +2684,11 @@ nv50_display_destroy(struct drm_device *dev)
 int
 nv50_display_create(struct drm_device *dev)
 {
-       struct nvif_device *device = &nouveau_drm(dev)->client.device;
        struct nouveau_drm *drm = nouveau_drm(dev);
-       struct dcb_table *dcb = &drm->vbios.dcb;
        struct drm_connector *connector, *tmp;
        struct nv50_disp *disp;
-       struct dcb_output *dcbe;
-       int crtcs, ret, i;
-       bool has_mst = nv50_has_mst(drm);
+       int ret, i;
+       bool has_mst = false;
 
        disp = kzalloc(sizeof(*disp), GFP_KERNEL);
        if (!disp)
@@ -2644,20 +2764,92 @@ nv50_display_create(struct drm_device *dev)
                dev->mode_config.cursor_height = 64;
        }
 
-       /* create crtc objects to represent the hw heads */
-       if (disp->disp->object.oclass >= GV100_DISP)
-               crtcs = nvif_rd32(&device->object, 0x610060) & 0xff;
-       else
-       if (disp->disp->object.oclass >= GF110_DISP)
-               crtcs = nvif_rd32(&device->object, 0x612004) & 0xf;
-       else
-               crtcs = 0x3;
+       /* create encoder/connector objects based on VBIOS DCB table */
+       for_each_set_bit(i, &disp->disp->outp_mask, sizeof(disp->disp->outp_mask) * 8) {
+               struct nouveau_encoder *outp;
 
-       for (i = 0; i < fls(crtcs); i++) {
-               struct nv50_head *head;
+               outp = kzalloc(sizeof(*outp), GFP_KERNEL);
+               if (!outp)
+                       break;
+
+               ret = nvif_outp_ctor(disp->disp, "kmsOutp", i, &outp->outp);
+               if (ret) {
+                       kfree(outp);
+                       continue;
+               }
+
+               connector = nouveau_connector_create(dev, outp->outp.info.conn);
+               if (IS_ERR(connector)) {
+                       nvif_outp_dtor(&outp->outp);
+                       kfree(outp);
+                       continue;
+               }
+
+               outp->base.base.possible_crtcs = outp->outp.info.heads;
+               outp->base.base.possible_clones = 0;
+               outp->conn = nouveau_connector(connector);
+
+               outp->dcb = kzalloc(sizeof(*outp->dcb), GFP_KERNEL);
+               if (!outp->dcb)
+                       break;
+
+               switch (outp->outp.info.proto) {
+               case NVIF_OUTP_RGB_CRT:
+                       outp->dcb->type = DCB_OUTPUT_ANALOG;
+                       outp->dcb->crtconf.maxfreq = outp->outp.info.rgb_crt.freq_max;
+                       break;
+               case NVIF_OUTP_TMDS:
+                       outp->dcb->type = DCB_OUTPUT_TMDS;
+                       outp->dcb->duallink_possible = outp->outp.info.tmds.dual;
+                       break;
+               case NVIF_OUTP_LVDS:
+                       outp->dcb->type = DCB_OUTPUT_LVDS;
+                       outp->dcb->lvdsconf.use_acpi_for_edid = outp->outp.info.lvds.acpi_edid;
+                       break;
+               case NVIF_OUTP_DP:
+                       outp->dcb->type = DCB_OUTPUT_DP;
+                       outp->dcb->dpconf.link_nr = outp->outp.info.dp.link_nr;
+                       outp->dcb->dpconf.link_bw = outp->outp.info.dp.link_bw;
+                       if (outp->outp.info.dp.mst)
+                               has_mst = true;
+                       break;
+               default:
+                       WARN_ON(1);
+                       continue;
+               }
 
-               if (!(crtcs & (1 << i)))
+               outp->dcb->heads = outp->outp.info.heads;
+               outp->dcb->connector = outp->outp.info.conn;
+               outp->dcb->i2c_index = outp->outp.info.ddc;
+
+               switch (outp->outp.info.type) {
+               case NVIF_OUTP_DAC : ret = nv50_dac_create(outp); break;
+               case NVIF_OUTP_SOR : ret = nv50_sor_create(outp); break;
+               case NVIF_OUTP_PIOR: ret = nv50_pior_create(outp); break;
+               default:
+                       WARN_ON(1);
                        continue;
+               }
+
+               if (ret) {
+                       NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n",
+                               i, outp->outp.info.type, outp->outp.info.proto, ret);
+               }
+       }
+
+       /* cull any connectors we created that don't have an encoder */
+       list_for_each_entry_safe(connector, tmp, &dev->mode_config.connector_list, head) {
+               if (connector->possible_encoders)
+                       continue;
+
+               NV_WARN(drm, "%s has no encoders, removing\n",
+                       connector->name);
+               connector->funcs->destroy(connector);
+       }
+
+       /* create crtc objects to represent the hw heads */
+       for_each_set_bit(i, &disp->disp->head_mask, sizeof(disp->disp->head_mask) * 8) {
+               struct nv50_head *head;
 
                head = nv50_head_create(dev, i);
                if (IS_ERR(head)) {
@@ -2683,52 +2875,10 @@ nv50_display_create(struct drm_device *dev)
                         * Once these issues are closed, this should be
                         * removed
                         */
-                       head->msto->encoder.possible_crtcs = crtcs;
-               }
-       }
-
-       /* create encoder/connector objects based on VBIOS DCB table */
-       for (i = 0, dcbe = &dcb->entry[0]; i < dcb->entries; i++, dcbe++) {
-               connector = nouveau_connector_create(dev, dcbe);
-               if (IS_ERR(connector))
-                       continue;
-
-               if (dcbe->location == DCB_LOC_ON_CHIP) {
-                       switch (dcbe->type) {
-                       case DCB_OUTPUT_TMDS:
-                       case DCB_OUTPUT_LVDS:
-                       case DCB_OUTPUT_DP:
-                               ret = nv50_sor_create(connector, dcbe);
-                               break;
-                       case DCB_OUTPUT_ANALOG:
-                               ret = nv50_dac_create(connector, dcbe);
-                               break;
-                       default:
-                               ret = -ENODEV;
-                               break;
-                       }
-               } else {
-                       ret = nv50_pior_create(connector, dcbe);
-               }
-
-               if (ret) {
-                       NV_WARN(drm, "failed to create encoder %d/%d/%d: %d\n",
-                                    dcbe->location, dcbe->type,
-                                    ffs(dcbe->or) - 1, ret);
-                       ret = 0;
+                       head->msto->encoder.possible_crtcs = disp->disp->head_mask;
                }
        }
 
-       /* cull any connectors we created that don't have an encoder */
-       list_for_each_entry_safe(connector, tmp, &dev->mode_config.connector_list, head) {
-               if (connector->possible_encoders)
-                       continue;
-
-               NV_WARN(drm, "%s has no encoders, removing\n",
-                       connector->name);
-               connector->funcs->destroy(connector);
-       }
-
        /* Disable vblank irqs aggressively for power-saving, safe on nv50+ */
        dev->vblank_disable_immediate = true;