drm/nouveau/kms/nv50-: Refactor and cleanup DP HPD handling
[linux-2.6-microblaze.git] / drivers / gpu / drm / nouveau / dispnv50 / disp.c
index e787487..8e1effb 100644 (file)
@@ -411,6 +411,40 @@ nv50_outp_atomic_check(struct drm_encoder *encoder,
        return 0;
 }
 
+struct nouveau_connector *
+nv50_outp_get_new_connector(struct nouveau_encoder *outp,
+                           struct drm_atomic_state *state)
+{
+       struct drm_connector *connector;
+       struct drm_connector_state *connector_state;
+       struct drm_encoder *encoder = to_drm_encoder(outp);
+       int i;
+
+       for_each_new_connector_in_state(state, connector, connector_state, i) {
+               if (connector_state->best_encoder == encoder)
+                       return nouveau_connector(connector);
+       }
+
+       return NULL;
+}
+
+struct nouveau_connector *
+nv50_outp_get_old_connector(struct nouveau_encoder *outp,
+                           struct drm_atomic_state *state)
+{
+       struct drm_connector *connector;
+       struct drm_connector_state *connector_state;
+       struct drm_encoder *encoder = to_drm_encoder(outp);
+       int i;
+
+       for_each_old_connector_in_state(state, connector, connector_state, i) {
+               if (connector_state->best_encoder == encoder)
+                       return nouveau_connector(connector);
+       }
+
+       return NULL;
+}
+
 /******************************************************************************
  * DAC
  *****************************************************************************/
@@ -552,16 +586,31 @@ nv50_audio_component_get_eld(struct device *kdev, int port, int dev_id,
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct drm_encoder *encoder;
        struct nouveau_encoder *nv_encoder;
-       struct nouveau_connector *nv_connector;
+       struct drm_connector *connector;
        struct nouveau_crtc *nv_crtc;
+       struct drm_connector_list_iter conn_iter;
        int ret = 0;
 
        *enabled = false;
+
        drm_for_each_encoder(encoder, drm->dev) {
+               struct nouveau_connector *nv_connector = NULL;
+
                nv_encoder = nouveau_encoder(encoder);
-               nv_connector = nouveau_encoder_connector_get(nv_encoder);
+
+               drm_connector_list_iter_begin(drm_dev, &conn_iter);
+               drm_for_each_connector_iter(connector, &conn_iter) {
+                       if (connector->state->best_encoder == encoder) {
+                               nv_connector = nouveau_connector(connector);
+                               break;
+                       }
+               }
+               drm_connector_list_iter_end(&conn_iter);
+               if (!nv_connector)
+                       continue;
+
                nv_crtc = nouveau_crtc(encoder->crtc);
-               if (!nv_connector || !nv_crtc || nv_encoder->or != port ||
+               if (!nv_crtc || nv_encoder->or != port ||
                    nv_crtc->index != dev_id)
                        continue;
                *enabled = nv_encoder->audio;
@@ -572,6 +621,7 @@ nv50_audio_component_get_eld(struct device *kdev, int port, int dev_id,
                }
                break;
        }
+
        return ret;
 }
 
@@ -665,7 +715,8 @@ nv50_audio_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
 }
 
 static void
-nv50_audio_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
+nv50_audio_enable(struct drm_encoder *encoder, struct drm_atomic_state *state,
+                 struct drm_display_mode *mode)
 {
        struct nouveau_drm *drm = nouveau_drm(encoder->dev);
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
@@ -686,7 +737,7 @@ nv50_audio_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
                                     (0x0100 << nv_crtc->index),
        };
 
-       nv_connector = nouveau_encoder_connector_get(nv_encoder);
+       nv_connector = nv50_outp_get_new_connector(nv_encoder, state);
        if (!drm_detect_monitor_audio(nv_connector->edid))
                return;
 
@@ -723,7 +774,8 @@ nv50_hdmi_disable(struct drm_encoder *encoder, struct nouveau_crtc *nv_crtc)
 }
 
 static void
-nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
+nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_atomic_state *state,
+                struct drm_display_mode *mode)
 {
        struct nouveau_drm *drm = nouveau_drm(encoder->dev);
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
@@ -752,7 +804,7 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
        int ret;
        int size;
 
-       nv_connector = nouveau_encoder_connector_get(nv_encoder);
+       nv_connector = nv50_outp_get_new_connector(nv_encoder, state);
        if (!drm_detect_hdmi_monitor(nv_connector->edid))
                return;
 
@@ -798,7 +850,7 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
                + args.pwr.vendor_infoframe_length;
        nvif_mthd(&disp->disp->object, 0, &args, size);
 
-       nv50_audio_enable(encoder, mode);
+       nv50_audio_enable(encoder, state, mode);
 
        /* If SCDC is supported by the downstream monitor, update
         * divider / scrambling settings to what we programmed above.
@@ -827,16 +879,6 @@ nv50_hdmi_enable(struct drm_encoder *encoder, struct drm_display_mode *mode)
 #define nv50_mstc(p) container_of((p), struct nv50_mstc, connector)
 #define nv50_msto(p) container_of((p), struct nv50_msto, encoder)
 
-struct nv50_mstm {
-       struct nouveau_encoder *outp;
-
-       struct drm_dp_mst_topology_mgr mgr;
-
-       bool modified;
-       bool disabled;
-       int links;
-};
-
 struct nv50_mstc {
        struct nv50_mstm *mstm;
        struct drm_dp_mst_port *port;
@@ -1365,41 +1407,51 @@ nv50_mstm = {
        .add_connector = nv50_mstm_add_connector,
 };
 
-void
-nv50_mstm_service(struct nv50_mstm *mstm)
+bool
+nv50_mstm_service(struct nouveau_drm *drm,
+                 struct nouveau_connector *nv_connector,
+                 struct nv50_mstm *mstm)
 {
-       struct drm_dp_aux *aux = mstm ? mstm->mgr.aux : NULL;
-       bool handled = true;
-       int ret;
+       struct drm_dp_aux *aux = &nv_connector->aux;
+       bool handled = true, ret = true;
+       int rc;
        u8 esi[8] = {};
 
-       if (!aux)
-               return;
-
        while (handled) {
-               ret = drm_dp_dpcd_read(aux, DP_SINK_COUNT_ESI, esi, 8);
-               if (ret != 8) {
-                       drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
-                       return;
+               rc = drm_dp_dpcd_read(aux, DP_SINK_COUNT_ESI, esi, 8);
+               if (rc != 8) {
+                       ret = false;
+                       break;
                }
 
                drm_dp_mst_hpd_irq(&mstm->mgr, esi, &handled);
                if (!handled)
                        break;
 
-               drm_dp_dpcd_write(aux, DP_SINK_COUNT_ESI + 1, &esi[1], 3);
+               rc = drm_dp_dpcd_write(aux, DP_SINK_COUNT_ESI + 1, &esi[1],
+                                      3);
+               if (rc != 3) {
+                       ret = false;
+                       break;
+               }
        }
+
+       if (!ret)
+               NV_DEBUG(drm, "Failed to handle ESI on %s: %d\n",
+                        nv_connector->base.name, rc);
+
+       return ret;
 }
 
 void
 nv50_mstm_remove(struct nv50_mstm *mstm)
 {
-       if (mstm)
-               drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
+       mstm->is_mst = false;
+       drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
 }
 
 static int
-nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
+nv50_mstm_enable(struct nv50_mstm *mstm, int state)
 {
        struct nouveau_encoder *outp = mstm->outp;
        struct {
@@ -1414,106 +1466,85 @@ nv50_mstm_enable(struct nv50_mstm *mstm, u8 dpcd, int state)
        };
        struct nouveau_drm *drm = nouveau_drm(outp->base.base.dev);
        struct nvif_object *disp = &drm->display->disp.object;
-       int ret;
-
-       if (dpcd >= 0x12) {
-               /* Even if we're enabling MST, start with disabling the
-                * branching unit to clear any sink-side MST topology state
-                * that wasn't set by us
-                */
-               ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL, 0);
-               if (ret < 0)
-                       return ret;
-
-               if (state) {
-                       /* Now, start initializing */
-                       ret = drm_dp_dpcd_writeb(mstm->mgr.aux, DP_MSTM_CTRL,
-                                                DP_MST_EN);
-                       if (ret < 0)
-                               return ret;
-               }
-       }
 
        return nvif_mthd(disp, 0, &args, sizeof(args));
 }
 
 int
-nv50_mstm_detect(struct nv50_mstm *mstm, u8 dpcd[8], int allow)
+nv50_mstm_detect(struct nouveau_encoder *outp)
 {
+       struct nv50_mstm *mstm = outp->dp.mstm;
        struct drm_dp_aux *aux;
        int ret;
-       bool old_state, new_state;
-       u8 mstm_ctrl;
 
-       if (!mstm)
+       if (!mstm || !mstm->can_mst)
                return 0;
 
-       mutex_lock(&mstm->mgr.lock);
-
-       old_state = mstm->mgr.mst_state;
-       new_state = old_state;
        aux = mstm->mgr.aux;
 
-       if (old_state) {
-               /* Just check that the MST hub is still as we expect it */
-               ret = drm_dp_dpcd_readb(aux, DP_MSTM_CTRL, &mstm_ctrl);
-               if (ret < 0 || !(mstm_ctrl & DP_MST_EN)) {
-                       DRM_DEBUG_KMS("Hub gone, disabling MST topology\n");
-                       new_state = false;
-               }
-       } else if (dpcd[0] >= 0x12) {
-               ret = drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &dpcd[1]);
-               if (ret < 0)
-                       goto probe_error;
-
-               if (!(dpcd[1] & DP_MST_CAP))
-                       dpcd[0] = 0x11;
-               else
-                       new_state = allow;
-       }
-
-       if (new_state == old_state) {
-               mutex_unlock(&mstm->mgr.lock);
-               return new_state;
-       }
-
-       ret = nv50_mstm_enable(mstm, dpcd[0], new_state);
-       if (ret)
-               goto probe_error;
-
-       mutex_unlock(&mstm->mgr.lock);
+       /* Clear any leftover MST state we didn't set ourselves by first
+        * disabling MST if it was already enabled
+        */
+       ret = drm_dp_dpcd_writeb(aux, DP_MSTM_CTRL, 0);
+       if (ret < 0)
+               return ret;
 
-       ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, new_state);
+       /* And start enabling */
+       ret = nv50_mstm_enable(mstm, true);
        if (ret)
-               return nv50_mstm_enable(mstm, dpcd[0], 0);
+               return ret;
 
-       return new_state;
+       ret = drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, true);
+       if (ret) {
+               nv50_mstm_enable(mstm, false);
+               return ret;
+       }
 
-probe_error:
-       mutex_unlock(&mstm->mgr.lock);
-       return ret;
+       mstm->is_mst = true;
+       return 1;
 }
 
 static void
-nv50_mstm_fini(struct nv50_mstm *mstm)
+nv50_mstm_fini(struct nouveau_encoder *outp)
 {
-       if (mstm && mstm->mgr.mst_state)
+       struct nv50_mstm *mstm = outp->dp.mstm;
+
+       if (!mstm)
+               return;
+
+       /* Don't change the MST state of this connector until we've finished
+        * resuming, since we can't safely grab hpd_irq_lock in our resume
+        * path to protect mstm->is_mst without potentially deadlocking
+        */
+       mutex_lock(&outp->dp.hpd_irq_lock);
+       mstm->suspended = true;
+       mutex_unlock(&outp->dp.hpd_irq_lock);
+
+       if (mstm->is_mst)
                drm_dp_mst_topology_mgr_suspend(&mstm->mgr);
 }
 
 static void
-nv50_mstm_init(struct nv50_mstm *mstm, bool runtime)
+nv50_mstm_init(struct nouveau_encoder *outp, bool runtime)
 {
-       int ret;
+       struct nv50_mstm *mstm = outp->dp.mstm;
+       int ret = 0;
 
-       if (!mstm || !mstm->mgr.mst_state)
+       if (!mstm)
                return;
 
-       ret = drm_dp_mst_topology_mgr_resume(&mstm->mgr, !runtime);
-       if (ret == -1) {
-               drm_dp_mst_topology_mgr_set_mst(&mstm->mgr, false);
-               drm_kms_helper_hotplug_event(mstm->mgr.dev);
+       if (mstm->is_mst) {
+               ret = drm_dp_mst_topology_mgr_resume(&mstm->mgr, !runtime);
+               if (ret == -1)
+                       nv50_mstm_remove(mstm);
        }
+
+       mutex_lock(&outp->dp.hpd_irq_lock);
+       mstm->suspended = false;
+       mutex_unlock(&outp->dp.hpd_irq_lock);
+
+       if (ret == -1)
+               drm_kms_helper_hotplug_event(mstm->mgr.dev);
 }
 
 static void
@@ -1535,17 +1566,6 @@ nv50_mstm_new(struct nouveau_encoder *outp, struct drm_dp_aux *aux, int aux_max,
        struct drm_device *dev = outp->base.base.dev;
        struct nv50_mstm *mstm;
        int ret;
-       u8 dpcd;
-
-       /* This is a workaround for some monitors not functioning
-        * correctly in MST mode on initial module load.  I think
-        * some bad interaction with the VBIOS may be responsible.
-        *
-        * A good ol' off and on again seems to work here ;)
-        */
-       ret = drm_dp_dpcd_readb(aux, DP_DPCD_REV, &dpcd);
-       if (ret >= 0 && dpcd >= 0x12)
-               drm_dp_dpcd_writeb(aux, DP_MSTM_CTRL, 0);
 
        if (!(mstm = *pmstm = kzalloc(sizeof(*mstm), GFP_KERNEL)))
                return -ENOMEM;
@@ -1584,23 +1604,27 @@ nv50_sor_update(struct nouveau_encoder *nv_encoder, u8 head,
 }
 
 static void
-nv50_sor_disable(struct drm_encoder *encoder)
+nv50_sor_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 nouveau_connector *nv_connector =
+               nv50_outp_get_old_connector(nv_encoder, state);
 
        nv_encoder->crtc = NULL;
 
        if (nv_crtc) {
-               struct nvkm_i2c_aux *aux = nv_encoder->aux;
+               struct drm_dp_aux *aux = &nv_connector->aux;
                u8 pwr;
 
-               if (aux) {
-                       int ret = nvkm_rdaux(aux, DP_SET_POWER, &pwr, 1);
+               if (nv_encoder->dcb->type == DCB_OUTPUT_DP) {
+                       int ret = drm_dp_dpcd_readb(aux, DP_SET_POWER, &pwr);
+
                        if (ret == 0) {
                                pwr &= ~DP_SET_POWER_MASK;
                                pwr |=  DP_SET_POWER_D3;
-                               nvkm_wraux(aux, DP_SET_POWER, &pwr, 1);
+                               drm_dp_dpcd_writeb(aux, DP_SET_POWER, pwr);
                        }
                }
 
@@ -1612,7 +1636,8 @@ nv50_sor_disable(struct drm_encoder *encoder)
 }
 
 static void
-nv50_sor_enable(struct drm_encoder *encoder)
+nv50_sor_enable(struct drm_encoder *encoder,
+               struct drm_atomic_state *state)
 {
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
@@ -1636,7 +1661,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
        u8 proto = NV507D_SOR_SET_CONTROL_PROTOCOL_CUSTOM;
        u8 depth = NV837D_SOR_SET_CONTROL_PIXEL_DEPTH_DEFAULT;
 
-       nv_connector = nouveau_encoder_connector_get(nv_encoder);
+       nv_connector = nv50_outp_get_new_connector(nv_encoder, state);
        nv_encoder->crtc = encoder->crtc;
 
        if ((disp->disp->object.oclass == GT214_DISP ||
@@ -1663,7 +1688,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
                        proto = NV507D_SOR_SET_CONTROL_PROTOCOL_SINGLE_TMDS_B;
                }
 
-               nv50_hdmi_enable(&nv_encoder->base.base, mode);
+               nv50_hdmi_enable(&nv_encoder->base.base, state, mode);
                break;
        case DCB_OUTPUT_LVDS:
                proto = NV507D_SOR_SET_CONTROL_PROTOCOL_LVDS_CUSTOM;
@@ -1704,7 +1729,7 @@ nv50_sor_enable(struct drm_encoder *encoder)
                else
                        proto = NV887D_SOR_SET_CONTROL_PROTOCOL_DP_B;
 
-               nv50_audio_enable(encoder, mode);
+               nv50_audio_enable(encoder, state, mode);
                break;
        default:
                BUG();
@@ -1717,8 +1742,8 @@ nv50_sor_enable(struct drm_encoder *encoder)
 static const struct drm_encoder_helper_funcs
 nv50_sor_help = {
        .atomic_check = nv50_outp_atomic_check,
-       .enable = nv50_sor_enable,
-       .disable = nv50_sor_disable,
+       .atomic_enable = nv50_sor_enable,
+       .atomic_disable = nv50_sor_disable,
 };
 
 static void
@@ -1727,6 +1752,10 @@ nv50_sor_destroy(struct drm_encoder *encoder)
        struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
        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);
+
        kfree(encoder);
 }
 
@@ -1786,6 +1815,8 @@ nv50_sor_create(struct drm_connector *connector, struct dcb_output *dcbe)
                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
@@ -2077,7 +2108,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
                          outp->clr.mask, outp->set.mask);
 
                if (outp->clr.mask) {
-                       help->disable(encoder);
+                       help->atomic_disable(encoder, state);
                        interlock[NV50_DISP_INTERLOCK_CORE] |= 1;
                        if (outp->flush_disable) {
                                nv50_disp_atomic_commit_wndw(state, interlock);
@@ -2116,7 +2147,7 @@ nv50_disp_atomic_commit_tail(struct drm_atomic_state *state)
                          outp->set.mask, outp->clr.mask);
 
                if (outp->set.mask) {
-                       help->enable(encoder);
+                       help->atomic_enable(encoder, state);
                        interlock[NV50_DISP_INTERLOCK_CORE] = 1;
                }
 
@@ -2484,9 +2515,9 @@ nv50_disp_func = {
  *****************************************************************************/
 
 static void
-nv50_display_fini(struct drm_device *dev, bool suspend)
+nv50_display_fini(struct drm_device *dev, bool runtime, bool suspend)
 {
-       struct nouveau_encoder *nv_encoder;
+       struct nouveau_drm *drm = nouveau_drm(dev);
        struct drm_encoder *encoder;
        struct drm_plane *plane;
 
@@ -2498,11 +2529,12 @@ nv50_display_fini(struct drm_device *dev, bool suspend)
        }
 
        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
-               if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
-                       nv_encoder = nouveau_encoder(encoder);
-                       nv50_mstm_fini(nv_encoder->dp.mstm);
-               }
+               if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST)
+                       nv50_mstm_fini(nouveau_encoder(encoder));
        }
+
+       if (!runtime)
+               cancel_work_sync(&drm->hpd_work);
 }
 
 static int
@@ -2519,7 +2551,7 @@ nv50_display_init(struct drm_device *dev, bool resume, bool runtime)
                if (encoder->encoder_type != DRM_MODE_ENCODER_DPMST) {
                        struct nouveau_encoder *nv_encoder =
                                nouveau_encoder(encoder);
-                       nv50_mstm_init(nv_encoder->dp.mstm, runtime);
+                       nv50_mstm_init(nv_encoder, runtime);
                }
        }