nouveau-y += core/engine/disp/nvd0.o
 nouveau-y += core/engine/disp/nve0.o
 nouveau-y += core/engine/disp/dacnv50.o
+nouveau-y += core/engine/disp/dport.o
 nouveau-y += core/engine/disp/hdanva3.o
 nouveau-y += core/engine/disp/hdanvd0.o
 nouveau-y += core/engine/disp/hdminv84.o
 
--- /dev/null
+/*
+ * Copyright 2013 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Ben Skeggs
+ */
+
+#include <subdev/bios.h>
+#include <subdev/bios/dcb.h>
+#include <subdev/bios/dp.h>
+#include <subdev/bios/init.h>
+#include <subdev/i2c.h>
+
+#include <engine/disp.h>
+
+#include "dport.h"
+
+#define DBG(fmt, args...) nv_debug(dp->disp, "DP:%04x:%04x: " fmt,             \
+                                  dp->outp->hasht, dp->outp->hashm, ##args)
+#define ERR(fmt, args...) nv_error(dp->disp, "DP:%04x:%04x: " fmt,             \
+                                  dp->outp->hasht, dp->outp->hashm, ##args)
+
+/******************************************************************************
+ * link training
+ *****************************************************************************/
+struct dp_state {
+       const struct nouveau_dp_func *func;
+       struct nouveau_disp *disp;
+       struct dcb_output *outp;
+       struct nvbios_dpout info;
+       u8 version;
+       struct nouveau_i2c_port *aux;
+       int head;
+       u8  dpcd[4];
+       int link_nr;
+       u32 link_bw;
+       u8  stat[6];
+       u8  conf[4];
+};
+
+static int
+dp_set_link_config(struct dp_state *dp)
+{
+       struct nouveau_disp *disp = dp->disp;
+       struct nouveau_bios *bios = nouveau_bios(disp);
+       struct nvbios_init init = {
+               .subdev = nv_subdev(dp->disp),
+               .bios = bios,
+               .offset = 0x0000,
+               .outp = dp->outp,
+               .crtc = dp->head,
+               .execute = 1,
+       };
+       u32 lnkcmp;
+       u8 sink[2];
+
+       DBG("%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw);
+
+       /* set desired link configuration on the sink */
+       sink[0] = dp->link_bw / 27000;
+       sink[1] = dp->link_nr;
+       if (dp->dpcd[DPCD_RC02] & DPCD_RC02_ENHANCED_FRAME_CAP)
+               sink[1] |= DPCD_LC01_ENHANCED_FRAME_EN;
+
+       nv_wraux(dp->aux, DPCD_LC00, sink, 2);
+
+       /* set desired link configuration on the source */
+       if ((lnkcmp = dp->info.lnkcmp)) {
+               if (dp->version < 0x30) {
+                       while ((dp->link_bw / 10) < nv_ro16(bios, lnkcmp))
+                               lnkcmp += 4;
+                       init.offset = nv_ro16(bios, lnkcmp + 2);
+               } else {
+                       while ((dp->link_bw / 27000) < nv_ro08(bios, lnkcmp))
+                               lnkcmp += 3;
+                       init.offset = nv_ro16(bios, lnkcmp + 1);
+               }
+
+               nvbios_exec(&init);
+       }
+
+       return dp->func->lnk_ctl(dp->disp, dp->outp, dp->head,
+                                dp->link_nr, dp->link_bw / 27000,
+                                dp->dpcd[DPCD_RC02] &
+                                         DPCD_RC02_ENHANCED_FRAME_CAP);
+}
+
+static void
+dp_set_training_pattern(struct dp_state *dp, u8 pattern)
+{
+       u8 sink_tp;
+
+       DBG("training pattern %d\n", pattern);
+       dp->func->pattern(dp->disp, dp->outp, dp->head, pattern);
+
+       nv_rdaux(dp->aux, DPCD_LC02, &sink_tp, 1);
+       sink_tp &= ~DPCD_LC02_TRAINING_PATTERN_SET;
+       sink_tp |= pattern;
+       nv_wraux(dp->aux, DPCD_LC02, &sink_tp, 1);
+}
+
+static int
+dp_link_train_commit(struct dp_state *dp)
+{
+       int i;
+
+       for (i = 0; i < dp->link_nr; i++) {
+               u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
+               u8 lpre = (lane & 0x0c) >> 2;
+               u8 lvsw = (lane & 0x03) >> 0;
+
+               dp->conf[i] = (lpre << 3) | lvsw;
+               if (lvsw == 3)
+                       dp->conf[i] |= DPCD_LC03_MAX_SWING_REACHED;
+               if (lpre == 3)
+                       dp->conf[i] |= DPCD_LC03_MAX_PRE_EMPHASIS_REACHED;
+
+               DBG("config lane %d %02x\n", i, dp->conf[i]);
+               dp->func->drv_ctl(dp->disp, dp->outp, dp->head, i, lvsw, lpre);
+       }
+
+       return nv_wraux(dp->aux, DPCD_LC03(0), dp->conf, 4);
+}
+
+static int
+dp_link_train_update(struct dp_state *dp, u32 delay)
+{
+       int ret;
+
+       udelay(delay);
+
+       ret = nv_rdaux(dp->aux, DPCD_LS02, dp->stat, 6);
+       if (ret)
+               return ret;
+
+       DBG("status %*ph\n", 6, dp->stat);
+       return 0;
+}
+
+static int
+dp_link_train_cr(struct dp_state *dp)
+{
+       bool cr_done = false, abort = false;
+       int voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
+       int tries = 0, i;
+
+       dp_set_training_pattern(dp, 1);
+
+       do {
+               if (dp_link_train_commit(dp) ||
+                   dp_link_train_update(dp, 100))
+                       break;
+
+               cr_done = true;
+               for (i = 0; i < dp->link_nr; i++) {
+                       u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
+                       if (!(lane & DPCD_LS02_LANE0_CR_DONE)) {
+                               cr_done = false;
+                               if (dp->conf[i] & DPCD_LC03_MAX_SWING_REACHED)
+                                       abort = true;
+                               break;
+                       }
+               }
+
+               if ((dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET) != voltage) {
+                       voltage = dp->conf[0] & DPCD_LC03_VOLTAGE_SWING_SET;
+                       tries = 0;
+               }
+       } while (!cr_done && !abort && ++tries < 5);
+
+       return cr_done ? 0 : -1;
+}
+
+static int
+dp_link_train_eq(struct dp_state *dp)
+{
+       bool eq_done, cr_done = true;
+       int tries = 0, i;
+
+       dp_set_training_pattern(dp, 2);
+
+       do {
+               if (dp_link_train_update(dp, 400))
+                       break;
+
+               eq_done = !!(dp->stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE);
+               for (i = 0; i < dp->link_nr && eq_done; i++) {
+                       u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
+                       if (!(lane & DPCD_LS02_LANE0_CR_DONE))
+                               cr_done = false;
+                       if (!(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) ||
+                           !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED))
+                               eq_done = false;
+               }
+
+               if (dp_link_train_commit(dp))
+                       break;
+       } while (!eq_done && cr_done && ++tries <= 5);
+
+       return eq_done ? 0 : -1;
+}
+
+static void
+dp_link_train_init(struct dp_state *dp, bool spread)
+{
+       struct nvbios_init init = {
+               .subdev = nv_subdev(dp->disp),
+               .bios = nouveau_bios(dp->disp),
+               .outp = dp->outp,
+               .crtc = dp->head,
+               .execute = 1,
+       };
+
+       /* set desired spread */
+       if (spread)
+               init.offset = dp->info.script[2];
+       else
+               init.offset = dp->info.script[3];
+       nvbios_exec(&init);
+
+       /* pre-train script */
+       init.offset = dp->info.script[0];
+       nvbios_exec(&init);
+}
+
+static void
+dp_link_train_fini(struct dp_state *dp)
+{
+       struct nvbios_init init = {
+               .subdev = nv_subdev(dp->disp),
+               .bios = nouveau_bios(dp->disp),
+               .outp = dp->outp,
+               .crtc = dp->head,
+               .execute = 1,
+       };
+
+       /* post-train script */
+       init.offset = dp->info.script[1],
+       nvbios_exec(&init);
+}
+
+int
+nouveau_dp_train(struct nouveau_disp *disp, const struct nouveau_dp_func *func,
+                struct dcb_output *outp, int head, u32 datarate)
+{
+       struct nouveau_bios *bios = nouveau_bios(disp);
+       struct nouveau_i2c *i2c = nouveau_i2c(disp);
+       struct dp_state _dp = {
+               .disp = disp,
+               .func = func,
+               .outp = outp,
+               .head = head,
+       }, *dp = &_dp;
+       const u32 bw_list[] = { 270000, 162000, 0 };
+       const u32 *link_bw = bw_list;
+       u8  hdr, cnt, len;
+       u32 data;
+       int ret;
+
+       /* find the bios displayport data relevant to this output */
+       data = nvbios_dpout_match(bios, outp->hasht, outp->hashm, &dp->version,
+                                &hdr, &cnt, &len, &dp->info);
+       if (!data) {
+               ERR("bios data not found\n");
+               return -EINVAL;
+       }
+
+       /* acquire the aux channel and fetch some info about the display */
+       if (outp->location)
+               dp->aux = i2c->find_type(i2c, NV_I2C_TYPE_EXTAUX(outp->extdev));
+       else
+               dp->aux = i2c->find(i2c, NV_I2C_TYPE_DCBI2C(outp->i2c_index));
+       if (!dp->aux) {
+               ERR("no aux channel?!\n");
+               return -ENODEV;
+       }
+
+       ret = nv_rdaux(dp->aux, 0x00000, dp->dpcd, sizeof(dp->dpcd));
+       if (ret) {
+               ERR("failed to read DPCD\n");
+               return ret;
+       }
+
+       /* adjust required bandwidth for 8B/10B coding overhead */
+       datarate = (datarate / 8) * 10;
+
+       /* enable down-spreading and execute pre-train script from vbios */
+       dp_link_train_init(dp, dp->dpcd[3] & 0x01);
+
+       /* start off at highest link rate supported by encoder and display */
+       while (*link_bw > (dp->dpcd[1] * 27000))
+               link_bw++;
+
+       while (link_bw[0]) {
+               /* find minimum required lane count at this link rate */
+               dp->link_nr = dp->dpcd[2] & DPCD_RC02_MAX_LANE_COUNT;
+               while ((dp->link_nr >> 1) * link_bw[0] > datarate)
+                       dp->link_nr >>= 1;
+
+               /* drop link rate to minimum with this lane count */
+               while ((link_bw[1] * dp->link_nr) > datarate)
+                       link_bw++;
+               dp->link_bw = link_bw[0];
+
+               /* program selected link configuration */
+               ret = dp_set_link_config(dp);
+               if (ret == 0) {
+                       /* attempt to train the link at this configuration */
+                       memset(dp->stat, 0x00, sizeof(dp->stat));
+                       if (!dp_link_train_cr(dp) &&
+                           !dp_link_train_eq(dp))
+                               break;
+               } else
+               if (ret >= 1) {
+                       /* dp_set_link_config() handled training */
+                       break;
+               }
+
+               /* retry at lower rate */
+               link_bw++;
+       }
+
+       /* finish link training */
+       dp_set_training_pattern(dp, 0);
+
+       /* execute post-train script from vbios */
+       dp_link_train_fini(dp);
+       return true;
+}
 
--- /dev/null
+#ifndef __NVKM_DISP_DPORT_H__
+#define __NVKM_DISP_DPORT_H__
+
+/* DPCD Receiver Capabilities */
+#define DPCD_RC00                                                       0x00000
+#define DPCD_RC00_DPCD_REV                                                 0xff
+#define DPCD_RC01                                                       0x00001
+#define DPCD_RC01_MAX_LINK_RATE                                            0xff
+#define DPCD_RC02                                                       0x00002
+#define DPCD_RC02_ENHANCED_FRAME_CAP                                       0x80
+#define DPCD_RC02_MAX_LANE_COUNT                                           0x1f
+#define DPCD_RC03                                                       0x00003
+#define DPCD_RC03_MAX_DOWNSPREAD                                           0x01
+
+/* DPCD Link Configuration */
+#define DPCD_LC00                                                       0x00100
+#define DPCD_LC00_LINK_BW_SET                                              0xff
+#define DPCD_LC01                                                       0x00101
+#define DPCD_LC01_ENHANCED_FRAME_EN                                        0x80
+#define DPCD_LC01_LANE_COUNT_SET                                           0x1f
+#define DPCD_LC02                                                       0x00102
+#define DPCD_LC02_TRAINING_PATTERN_SET                                     0x03
+#define DPCD_LC03(l)                                            ((l) +  0x00103)
+#define DPCD_LC03_MAX_PRE_EMPHASIS_REACHED                                 0x20
+#define DPCD_LC03_PRE_EMPHASIS_SET                                         0x18
+#define DPCD_LC03_MAX_SWING_REACHED                                        0x04
+#define DPCD_LC03_VOLTAGE_SWING_SET                                        0x03
+
+/* DPCD Link/Sink Status */
+#define DPCD_LS02                                                       0x00202
+#define DPCD_LS02_LANE1_SYMBOL_LOCKED                                      0x40
+#define DPCD_LS02_LANE1_CHANNEL_EQ_DONE                                    0x20
+#define DPCD_LS02_LANE1_CR_DONE                                            0x10
+#define DPCD_LS02_LANE0_SYMBOL_LOCKED                                      0x04
+#define DPCD_LS02_LANE0_CHANNEL_EQ_DONE                                    0x02
+#define DPCD_LS02_LANE0_CR_DONE                                            0x01
+#define DPCD_LS03                                                       0x00203
+#define DPCD_LS03_LANE3_SYMBOL_LOCKED                                      0x40
+#define DPCD_LS03_LANE3_CHANNEL_EQ_DONE                                    0x20
+#define DPCD_LS03_LANE3_CR_DONE                                            0x10
+#define DPCD_LS03_LANE2_SYMBOL_LOCKED                                      0x04
+#define DPCD_LS03_LANE2_CHANNEL_EQ_DONE                                    0x02
+#define DPCD_LS03_LANE2_CR_DONE                                            0x01
+#define DPCD_LS04                                                       0x00204
+#define DPCD_LS04_LINK_STATUS_UPDATED                                      0x80
+#define DPCD_LS04_DOWNSTREAM_PORT_STATUS_CHANGED                           0x40
+#define DPCD_LS04_INTERLANE_ALIGN_DONE                                     0x01
+#define DPCD_LS06                                                       0x00206
+#define DPCD_LS06_LANE1_PRE_EMPHASIS                                       0xc0
+#define DPCD_LS06_LANE1_VOLTAGE_SWING                                      0x30
+#define DPCD_LS06_LANE0_PRE_EMPHASIS                                       0x0c
+#define DPCD_LS06_LANE0_VOLTAGE_SWING                                      0x03
+#define DPCD_LS07                                                       0x00207
+#define DPCD_LS07_LANE3_PRE_EMPHASIS                                       0xc0
+#define DPCD_LS07_LANE3_VOLTAGE_SWING                                      0x30
+#define DPCD_LS07_LANE2_PRE_EMPHASIS                                       0x0c
+#define DPCD_LS07_LANE2_VOLTAGE_SWING                                      0x03
+
+struct nouveau_disp;
+struct dcb_output;
+
+struct nouveau_dp_func {
+       int (*pattern)(struct nouveau_disp *, struct dcb_output *,
+                      int head, int pattern);
+       int (*lnk_ctl)(struct nouveau_disp *, struct dcb_output *, int head,
+                      int link_nr, int link_bw, bool enh_frame);
+       int (*drv_ctl)(struct nouveau_disp *, struct dcb_output *, int head,
+                      int lane, int swing, int preem);
+};
+
+extern const struct nouveau_dp_func nv94_sor_dp_func;
+extern const struct nouveau_dp_func nvd0_sor_dp_func;
+
+int nouveau_dp_train(struct nouveau_disp *, const struct nouveau_dp_func *,
+                    struct dcb_output *, int, u32);
+
+#endif
 
        }
 
        data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2);
-       if (data) {
+       if (data && id < 0xff) {
                data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
                if (data) {
                        struct nvbios_init init = {
        head = ffs((super & 0x00000180) >> 7) - 1;
        if (head >= 0) {
                u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-               u32 conf = exec_clkcmp(priv, head, 0, pclk, &outp);
+               u32 conf = exec_clkcmp(priv, head, 0xff, pclk, &outp);
                if (conf != ~0) {
+                       if (outp.location == 0 && outp.type == DCB_OUTPUT_DP) {
+                               u32 soff = (ffs(outp.or) - 1) * 0x08;
+                               u32 ctrl = nv_rd32(priv, 0x610798 + soff);
+                               u32 datarate;
+
+                               switch ((ctrl & 0x000f0000) >> 16) {
+                               case 6: datarate = pclk * 30 / 8; break;
+                               case 5: datarate = pclk * 24 / 8; break;
+                               case 2:
+                               default:
+                                       datarate = pclk * 18 / 8;
+                                       break;
+                               }
+
+                               nouveau_dp_train(&priv->base, priv->sor.dp,
+                                                &outp, head, datarate);
+                       }
+
+                       exec_clkcmp(priv, head, 0, pclk, &outp);
+
                        if (outp.type == DCB_OUTPUT_ANALOG) {
                                addr = 0x614280 + (ffs(outp.or) - 1) * 0x800;
                                mask = 0xffffffff;
        if (head >= 0) {
                struct dcb_output outp;
                u32 pclk = nv_rd32(priv, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-               if (exec_clkcmp(priv, head, 1, pclk, &outp) != ~0) {
-                       if (outp.type == DCB_OUTPUT_TMDS)
+               if (exec_clkcmp(priv, head, 1, pclk, &outp) != ~0)
+                       if (outp.location == 0 && outp.type == DCB_OUTPUT_TMDS)
                                nv50_disp_intr_unk40_tmds(priv, &outp);
-               }
        }
 
        nv_wr32(priv, 0x610030, 0x80000000);
 
 #include <engine/dmaobj.h>
 #include <engine/disp.h>
 
-struct dcb_output;
+#include "dport.h"
 
 struct nv50_disp_priv {
        struct nouveau_disp base;
                int (*power)(struct nv50_disp_priv *, int sor, u32 data);
                int (*hda_eld)(struct nv50_disp_priv *, int sor, u8 *, u32);
                int (*hdmi)(struct nv50_disp_priv *, int head, int sor, u32);
-               int (*dp_train_init)(struct nv50_disp_priv *, int sor, int link,
-                                    int head, u16 type, u16 mask, u32 data,
-                                    struct dcb_output *);
-               int (*dp_train_fini)(struct nv50_disp_priv *, int sor, int link,
-                                    int head, u16 type, u16 mask, u32 data,
-                                    struct dcb_output *);
-               int (*dp_train)(struct nv50_disp_priv *, int sor, int link,
-                               u16 type, u16 mask, u32 data,
-                               struct dcb_output *);
-               int (*dp_lnkctl)(struct nv50_disp_priv *, int sor, int link,
-                                int head, u16 type, u16 mask, u32 data,
-                                struct dcb_output *);
-               int (*dp_drvctl)(struct nv50_disp_priv *, int sor, int link,
-                                int lane, u16 type, u16 mask, u32 data,
-                                struct dcb_output *);
                u32 lvdsconf;
+               const struct nouveau_dp_func *dp;
        } sor;
 };
 
 
        { SOR_MTHD(NV50_DISP_SOR_PWR)         , nv50_sor_mthd },
        { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR)    , nv50_sor_mthd },
        { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN)    , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL)   , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd },
        { DAC_MTHD(NV50_DISP_DAC_PWR)         , nv50_dac_mthd },
        { DAC_MTHD(NV50_DISP_DAC_LOAD)        , nv50_dac_mthd },
        {},
        priv->dac.sense = nv50_dac_sense;
        priv->sor.power = nv50_sor_power;
        priv->sor.hdmi = nv84_hdmi_ctrl;
-       priv->sor.dp_train = nv94_sor_dp_train;
-       priv->sor.dp_train_init = nv94_sor_dp_train_init;
-       priv->sor.dp_train_fini = nv94_sor_dp_train_fini;
-       priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl;
-       priv->sor.dp_drvctl = nv94_sor_dp_drvctl;
+       priv->sor.dp = &nv94_sor_dp_func;
        return 0;
 }
 
 
        { SOR_MTHD(NVA3_DISP_SOR_HDA_ELD)     , nv50_sor_mthd },
        { SOR_MTHD(NV84_DISP_SOR_HDMI_PWR)    , nv50_sor_mthd },
        { SOR_MTHD(NV50_DISP_SOR_LVDS_SCRIPT) , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_TRAIN)    , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_LNKCTL)   , nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(0)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(1)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(2)), nv50_sor_mthd },
-       { SOR_MTHD(NV94_DISP_SOR_DP_DRVCTL(3)), nv50_sor_mthd },
        { DAC_MTHD(NV50_DISP_DAC_PWR)         , nv50_dac_mthd },
        { DAC_MTHD(NV50_DISP_DAC_LOAD)        , nv50_dac_mthd },
        {},
        priv->sor.power = nv50_sor_power;
        priv->sor.hda_eld = nva3_hda_eld;
        priv->sor.hdmi = nva3_hdmi_ctrl;
-       priv->sor.dp_train = nv94_sor_dp_train;
-       priv->sor.dp_train_init = nv94_sor_dp_train_init;
-       priv->sor.dp_train_fini = nv94_sor_dp_train_fini;
-       priv->sor.dp_lnkctl = nv94_sor_dp_lnkctl;
-       priv->sor.dp_drvctl = nv94_sor_dp_drvctl;
+       priv->sor.dp = &nv94_sor_dp_func;
        return 0;
 }
 
 
 
 static u32
 exec_clkcmp(struct nv50_disp_priv *priv, int head, int outp,
-           u32 ctrl, int id, u32 pclk)
+           u32 ctrl, int id, u32 pclk, struct dcb_output *dcb)
 {
        struct nouveau_bios *bios = nouveau_bios(priv);
        struct nvbios_outp info1;
        struct nvbios_ocfg info2;
-       struct dcb_output dcb;
        u8  ver, hdr, cnt, len;
        u32 data, conf = ~0;
 
-       data = exec_lookup(priv, head, outp, ctrl, &dcb, &ver, &hdr, &cnt, &len, &info1);
+       data = exec_lookup(priv, head, outp, ctrl, dcb, &ver, &hdr, &cnt, &len, &info1);
        if (data == 0x0000)
                return conf;
 
-       switch (dcb.type) {
+       switch (dcb->type) {
        case DCB_OUTPUT_TMDS:
                conf = (ctrl & 0x00000f00) >> 8;
                if (pclk >= 165000)
        }
 
        data = nvbios_ocfg_match(bios, data, conf, &ver, &hdr, &cnt, &len, &info2);
-       if (data) {
+       if (data && id < 0xff) {
                data = nvbios_oclk_match(bios, info2.clkcmp[id], pclk);
                if (data) {
                        struct nvbios_init init = {
                                .subdev = nv_subdev(priv),
                                .bios = bios,
                                .offset = data,
-                               .outp = &dcb,
+                               .outp = dcb,
                                .crtc = head,
                                .execute = 1,
                        };
 static void
 nvd0_display_unk2_handler(struct nv50_disp_priv *priv, u32 head, u32 mask)
 {
+       struct dcb_output outp;
        u32 pclk;
        int i;
 
        for (i = 0; mask && i < 8; i++) {
                u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20));
                if (mcp & (1 << head)) {
-                       u32 cfg = exec_clkcmp(priv, head, i, mcp, 0, pclk);
+                       u32 cfg = exec_clkcmp(priv, head, i, mcp, 0xff, pclk, &outp);
                        if (cfg != ~0) {
                                u32 addr, mask, data = 0x00000000;
+
+                               if (outp.type == DCB_OUTPUT_DP) {
+                                       switch ((mcp & 0x000f0000) >> 16) {
+                                       case 6: pclk = pclk * 30 / 8; break;
+                                       case 5: pclk = pclk * 24 / 8; break;
+                                       case 2:
+                                       default:
+                                               pclk = pclk * 18 / 8;
+                                               break;
+                                       }
+
+                                       nouveau_dp_train(&priv->base,
+                                                         priv->sor.dp,
+                                                        &outp, head, pclk);
+                               }
+
+                               exec_clkcmp(priv, head, i, mcp, 0, pclk, &outp);
+
                                if (i < 4) {
                                        addr = 0x612280 + ((i - 0) * 0x800);
                                        mask = 0xffffffff;
 static void
 nvd0_display_unk4_handler(struct nv50_disp_priv *priv, u32 head, u32 mask)
 {
+       struct dcb_output outp;
        int pclk, i;
 
        pclk = nv_rd32(priv, 0x660450 + (head * 0x300)) / 1000;
        for (i = 0; mask && i < 8; i++) {
                u32 mcp = nv_rd32(priv, 0x660180 + (i * 0x20));
                if (mcp & (1 << head))
-                       exec_clkcmp(priv, head, i, mcp, 1, pclk);
+                       exec_clkcmp(priv, head, i, mcp, 1, pclk, &outp);
        }
 
        nv_wr32(priv, 0x6101d4, 0x00000000);
        priv->sor.power = nv50_sor_power;
        priv->sor.hda_eld = nvd0_hda_eld;
        priv->sor.hdmi = nvd0_hdmi_ctrl;
-       priv->sor.dp_train = nvd0_sor_dp_train;
-       priv->sor.dp_train_init = nv94_sor_dp_train_init;
-       priv->sor.dp_train_fini = nv94_sor_dp_train_fini;
-       priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl;
-       priv->sor.dp_drvctl = nvd0_sor_dp_drvctl;
+       priv->sor.dp = &nvd0_sor_dp_func;
        return 0;
 }
 
 
        priv->sor.power = nv50_sor_power;
        priv->sor.hda_eld = nvd0_hda_eld;
        priv->sor.hdmi = nvd0_hdmi_ctrl;
-       priv->sor.dp_train = nvd0_sor_dp_train;
-       priv->sor.dp_train_init = nv94_sor_dp_train_init;
-       priv->sor.dp_train_fini = nv94_sor_dp_train_fini;
-       priv->sor.dp_lnkctl = nvd0_sor_dp_lnkctl;
-       priv->sor.dp_drvctl = nvd0_sor_dp_drvctl;
+       priv->sor.dp = &nvd0_sor_dp_func;
        return 0;
 }
 
 
                priv->sor.lvdsconf = data & NV50_DISP_SOR_LVDS_SCRIPT_ID;
                ret = 0;
                break;
-       case NV94_DISP_SOR_DP_TRAIN:
-               switch (data & NV94_DISP_SOR_DP_TRAIN_OP) {
-               case NV94_DISP_SOR_DP_TRAIN_OP_PATTERN:
-                       ret = priv->sor.dp_train(priv, or, link, type, mask, data, &outp);
-                       break;
-               case NV94_DISP_SOR_DP_TRAIN_OP_INIT:
-                       ret = priv->sor.dp_train_init(priv, or, link, head, type, mask, data, &outp);
-                       break;
-               case NV94_DISP_SOR_DP_TRAIN_OP_FINI:
-                       ret = priv->sor.dp_train_fini(priv, or, link, head, type, mask, data, &outp);
-                       break;
-               default:
-                       break;
-               }
-               break;
-       case NV94_DISP_SOR_DP_LNKCTL:
-               ret = priv->sor.dp_lnkctl(priv, or, link, head, type, mask, data, &outp);
-               break;
-       case NV94_DISP_SOR_DP_DRVCTL(0):
-       case NV94_DISP_SOR_DP_DRVCTL(1):
-       case NV94_DISP_SOR_DP_DRVCTL(2):
-       case NV94_DISP_SOR_DP_DRVCTL(3):
-               ret = priv->sor.dp_drvctl(priv, or, link, (mthd & 0xc0) >> 6,
-                                         type, mask, data, &outp);
-               break;
        default:
                BUG_ON(1);
        }
 
 #include "nv50.h"
 
 static inline u32
-nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane)
+nv94_sor_soff(struct dcb_output *outp)
 {
-       static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */
-       static const u8 nv94[] = { 16, 8, 0, 24 };
-       if (nv_device(priv)->chipset == 0xaf)
-               return nvaf[lane];
-       return nv94[lane];
+       return (ffs(outp->or) - 1) * 0x800;
 }
 
-int
-nv94_sor_dp_train_init(struct nv50_disp_priv *priv, int or, int link, int head,
-                      u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static inline u32
+nv94_sor_loff(struct dcb_output *outp)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       struct nvbios_dpout info;
-       u8  ver, hdr, cnt, len;
-       u16 outp;
-
-       outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info);
-       if (outp) {
-               struct nvbios_init init = {
-                       .subdev = nv_subdev(priv),
-                       .bios = bios,
-                       .outp = dcbo,
-                       .crtc = head,
-                       .execute = 1,
-               };
-
-               if (data & NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON)
-                       init.offset = info.script[2];
-               else
-                       init.offset = info.script[3];
-               nvbios_exec(&init);
-
-               init.offset = info.script[0];
-               nvbios_exec(&init);
-       }
-
-       return 0;
+       return nv94_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80;
 }
 
-int
-nv94_sor_dp_train_fini(struct nv50_disp_priv *priv, int or, int link, int head,
-                      u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static inline u32
+nv94_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       struct nvbios_dpout info;
-       u8  ver, hdr, cnt, len;
-       u16 outp;
-
-       outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info);
-       if (outp) {
-               struct nvbios_init init = {
-                       .subdev = nv_subdev(priv),
-                       .bios = bios,
-                       .offset = info.script[1],
-                       .outp = dcbo,
-                       .crtc = head,
-                       .execute = 1,
-               };
-
-               nvbios_exec(&init);
-       }
-
-       return 0;
+       static const u8 nvaf[] = { 24, 16, 8, 0 }; /* thanks, apple.. */
+       static const u8 nv94[] = { 16, 8, 0, 24 };
+       if (nv_device(priv)->chipset == 0xaf)
+               return nvaf[lane];
+       return nv94[lane];
 }
 
-int
-nv94_sor_dp_train(struct nv50_disp_priv *priv, int or, int link,
-                 u16 type, u16 mask, u32 data, struct dcb_output *info)
+static int
+nv94_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int pattern)
 {
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN);
-       nv_mask(priv, 0x61c10c + loff, 0x0f000000, patt << 24);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 loff = nv94_sor_loff(outp);
+       nv_mask(priv, 0x61c10c + loff, 0x0f000000, pattern << 24);
        return 0;
 }
 
-int
-nv94_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head,
-                  u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static int
+nv94_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int link_nr, int link_bw, bool enh_frame)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u32 soff = (or * 0x800);
-       u16 link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8;
-       u8  link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 soff = nv94_sor_soff(outp);
+       const u32 loff = nv94_sor_loff(outp);
        u32 dpctrl = 0x00000000;
        u32 clksor = 0x00000000;
-       u32 outp, lane = 0;
-       u8  ver, hdr, cnt, len;
-       struct nvbios_dpout info;
+       u32 lane = 0;
        int i;
 
-       /* -> 10Khz units */
-       link_bw *= 2700;
-
-       outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info);
-       if (outp && info.lnkcmp) {
-               struct nvbios_init init = {
-                       .subdev = nv_subdev(priv),
-                       .bios = bios,
-                       .offset = 0x0000,
-                       .outp = dcbo,
-                       .crtc = head,
-                       .execute = 1,
-               };
-
-               while (link_bw < nv_ro16(bios, info.lnkcmp))
-                       info.lnkcmp += 4;
-               init.offset = nv_ro16(bios, info.lnkcmp + 2);
-
-               nvbios_exec(&init);
-       }
-
        dpctrl |= ((1 << link_nr) - 1) << 16;
-       if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH)
+       if (enh_frame)
                dpctrl |= 0x00004000;
-       if (link_bw > 16200)
+       if (link_bw > 0x06)
                clksor |= 0x00040000;
 
        for (i = 0; i < link_nr; i++)
        return 0;
 }
 
-int
-nv94_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane,
-                  u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static int
+nv94_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int lane, int swing, int preem)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8;
-       const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE);
+       struct nouveau_bios *bios = nouveau_bios(disp);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 loff = nv94_sor_loff(outp);
        u32 addr, shift = nv94_sor_dp_lane_map(priv, lane);
        u8  ver, hdr, cnt, len;
-       struct nvbios_dpout outp;
+       struct nvbios_dpout info;
        struct nvbios_dpcfg ocfg;
 
-       addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp);
+       addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm,
+                                &ver, &hdr, &cnt, &len, &info);
        if (!addr)
                return -ENODEV;
 
-       addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg);
+       addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem,
+                                &ver, &hdr, &cnt, &len, &ocfg);
        if (!addr)
                return -EINVAL;
 
        nv_mask(priv, 0x61c130 + loff, 0x0000ff00, ocfg.unk << 8);
        return 0;
 }
+
+const struct nouveau_dp_func
+nv94_sor_dp_func = {
+       .pattern = nv94_sor_dp_pattern,
+       .lnk_ctl = nv94_sor_dp_lnk_ctl,
+       .drv_ctl = nv94_sor_dp_drv_ctl,
+};
 
 
 #include "nv50.h"
 
+static inline u32
+nvd0_sor_soff(struct dcb_output *outp)
+{
+       return (ffs(outp->or) - 1) * 0x800;
+}
+
+static inline u32
+nvd0_sor_loff(struct dcb_output *outp)
+{
+       return nvd0_sor_soff(outp) + !(outp->sorconf.link & 1) * 0x80;
+}
+
 static inline u32
 nvd0_sor_dp_lane_map(struct nv50_disp_priv *priv, u8 lane)
 {
        return nvd0[lane];
 }
 
-int
-nvd0_sor_dp_train(struct nv50_disp_priv *priv, int or, int link,
-                 u16 type, u16 mask, u32 data, struct dcb_output *info)
+static int
+nvd0_sor_dp_pattern(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int pattern)
 {
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u32 patt = (data & NV94_DISP_SOR_DP_TRAIN_PATTERN);
-       nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * patt);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 loff = nvd0_sor_loff(outp);
+       nv_mask(priv, 0x61c110 + loff, 0x0f0f0f0f, 0x01010101 * pattern);
        return 0;
 }
 
-int
-nvd0_sor_dp_lnkctl(struct nv50_disp_priv *priv, int or, int link, int head,
-                  u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static int
+nvd0_sor_dp_lnk_ctl(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int link_nr, int link_bw, bool enh_frame)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u32 soff = (or * 0x800);
-       const u8  link_bw = (data & NV94_DISP_SOR_DP_LNKCTL_WIDTH) >> 8;
-       const u8  link_nr = (data & NV94_DISP_SOR_DP_LNKCTL_COUNT);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 soff = nvd0_sor_soff(outp);
+       const u32 loff = nvd0_sor_loff(outp);
        u32 dpctrl = 0x00000000;
        u32 clksor = 0x00000000;
-       u32 outp, lane = 0;
-       u8  ver, hdr, cnt, len;
-       struct nvbios_dpout info;
+       u32 lane = 0;
        int i;
 
-       outp = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &info);
-       if (outp && info.lnkcmp) {
-               struct nvbios_init init = {
-                       .subdev = nv_subdev(priv),
-                       .bios = bios,
-                       .offset = 0x0000,
-                       .outp = dcbo,
-                       .crtc = head,
-                       .execute = 1,
-               };
-
-               while (nv_ro08(bios, info.lnkcmp) < link_bw)
-                       info.lnkcmp += 3;
-               init.offset = nv_ro16(bios, info.lnkcmp + 1);
-
-               nvbios_exec(&init);
-       }
-
        clksor |= link_bw << 18;
        dpctrl |= ((1 << link_nr) - 1) << 16;
-       if (data & NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH)
+       if (enh_frame)
                dpctrl |= 0x00004000;
 
        for (i = 0; i < link_nr; i++)
        return 0;
 }
 
-int
-nvd0_sor_dp_drvctl(struct nv50_disp_priv *priv, int or, int link, int lane,
-                  u16 type, u16 mask, u32 data, struct dcb_output *dcbo)
+static int
+nvd0_sor_dp_drv_ctl(struct nouveau_disp *disp, struct dcb_output *outp,
+                   int head, int lane, int swing, int preem)
 {
-       struct nouveau_bios *bios = nouveau_bios(priv);
-       const u32 loff = (or * 0x800) + (link * 0x80);
-       const u8 swing = (data & NV94_DISP_SOR_DP_DRVCTL_VS) >> 8;
-       const u8 preem = (data & NV94_DISP_SOR_DP_DRVCTL_PE);
+       struct nouveau_bios *bios = nouveau_bios(disp);
+       struct nv50_disp_priv *priv = (void *)disp;
+       const u32 loff = nvd0_sor_loff(outp);
        u32 addr, shift = nvd0_sor_dp_lane_map(priv, lane);
        u8  ver, hdr, cnt, len;
-       struct nvbios_dpout outp;
+       struct nvbios_dpout info;
        struct nvbios_dpcfg ocfg;
 
-       addr = nvbios_dpout_match(bios, type, mask, &ver, &hdr, &cnt, &len, &outp);
+       addr = nvbios_dpout_match(bios, outp->hasht, outp->hashm,
+                                &ver, &hdr, &cnt, &len, &info);
        if (!addr)
                return -ENODEV;
 
-       addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem, &ver, &hdr, &cnt, &len, &ocfg);
+       addr = nvbios_dpcfg_match(bios, addr, 0, swing, preem,
+                                &ver, &hdr, &cnt, &len, &ocfg);
        if (!addr)
                return -EINVAL;
 
        nv_mask(priv, 0x61c13c + loff, 0x00000000, 0x00000000);
        return 0;
 }
+
+const struct nouveau_dp_func
+nvd0_sor_dp_func = {
+       .pattern = nvd0_sor_dp_pattern,
+       .lnk_ctl = nvd0_sor_dp_lnk_ctl,
+       .drv_ctl = nvd0_sor_dp_drv_ctl,
+};
 
 #define NV84_DISP_SOR_HDMI_PWR_REKEY                                 0x0000007f
 #define NV50_DISP_SOR_LVDS_SCRIPT                                    0x00013000
 #define NV50_DISP_SOR_LVDS_SCRIPT_ID                                 0x0000ffff
-#define NV94_DISP_SOR_DP_TRAIN                                       0x00016000
-#define NV94_DISP_SOR_DP_TRAIN_OP                                    0xf0000000
-#define NV94_DISP_SOR_DP_TRAIN_OP_PATTERN                            0x00000000
-#define NV94_DISP_SOR_DP_TRAIN_OP_INIT                               0x10000000
-#define NV94_DISP_SOR_DP_TRAIN_OP_FINI                               0x20000000
-#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD                           0x00000001
-#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF                       0x00000000
-#define NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON                        0x00000001
-#define NV94_DISP_SOR_DP_TRAIN_PATTERN                               0x00000003
-#define NV94_DISP_SOR_DP_TRAIN_PATTERN_DISABLED                      0x00000000
-#define NV94_DISP_SOR_DP_LNKCTL                                      0x00016040
-#define NV94_DISP_SOR_DP_LNKCTL_FRAME                                0x80000000
-#define NV94_DISP_SOR_DP_LNKCTL_FRAME_STD                            0x00000000
-#define NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH                            0x80000000
-#define NV94_DISP_SOR_DP_LNKCTL_WIDTH                                0x00001f00
-#define NV94_DISP_SOR_DP_LNKCTL_COUNT                                0x00000007
-#define NV94_DISP_SOR_DP_DRVCTL(l)                     ((l) * 0x40 + 0x00016100)
-#define NV94_DISP_SOR_DP_DRVCTL_VS                                   0x00000300
-#define NV94_DISP_SOR_DP_DRVCTL_PE                                   0x00000003
 
 #define NV50_DISP_DAC_MTHD                                           0x00020000
 #define NV50_DISP_DAC_MTHD_TYPE                                      0x0000f000
 
 #include <subdev/gpio.h>
 #include <subdev/i2c.h>
 
-/******************************************************************************
- * link training
- *****************************************************************************/
-struct dp_state {
-       struct nouveau_i2c_port *auxch;
-       struct nouveau_object *core;
-       struct dcb_output *dcb;
-       int crtc;
-       u8 *dpcd;
-       int link_nr;
-       u32 link_bw;
-       u8  stat[6];
-       u8  conf[4];
-};
-
-static void
-dp_set_link_config(struct drm_device *dev, struct dp_state *dp)
-{
-       struct nouveau_drm *drm = nouveau_drm(dev);
-       struct dcb_output *dcb = dp->dcb;
-       const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
-       const u32 moff = (dp->crtc << 3) | (link << 2) | or;
-       u8 sink[2];
-       u32 data;
-
-       NV_DEBUG(drm, "%d lanes at %d KB/s\n", dp->link_nr, dp->link_bw);
-
-       /* set desired link configuration on the source */
-       data = ((dp->link_bw / 27000) << 8) | dp->link_nr;
-       if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP)
-               data |= NV94_DISP_SOR_DP_LNKCTL_FRAME_ENH;
-
-       nv_call(dp->core, NV94_DISP_SOR_DP_LNKCTL + moff, data);
-
-       /* inform the sink of the new configuration */
-       sink[0] = dp->link_bw / 27000;
-       sink[1] = dp->link_nr;
-       if (dp->dpcd[2] & DP_ENHANCED_FRAME_CAP)
-               sink[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
-
-       nv_wraux(dp->auxch, DP_LINK_BW_SET, sink, 2);
-}
-
-static void
-dp_set_training_pattern(struct drm_device *dev, struct dp_state *dp, u8 pattern)
-{
-       struct nouveau_drm *drm = nouveau_drm(dev);
-       struct dcb_output *dcb = dp->dcb;
-       const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
-       const u32 moff = (dp->crtc << 3) | (link << 2) | or;
-       u8 sink_tp;
-
-       NV_DEBUG(drm, "training pattern %d\n", pattern);
-
-       nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, pattern);
-
-       nv_rdaux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1);
-       sink_tp &= ~DP_TRAINING_PATTERN_MASK;
-       sink_tp |= pattern;
-       nv_wraux(dp->auxch, DP_TRAINING_PATTERN_SET, &sink_tp, 1);
-}
-
-static int
-dp_link_train_commit(struct drm_device *dev, struct dp_state *dp)
-{
-       struct nouveau_drm *drm = nouveau_drm(dev);
-       struct dcb_output *dcb = dp->dcb;
-       const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
-       const u32 moff = (dp->crtc << 3) | (link << 2) | or;
-       int i;
-
-       for (i = 0; i < dp->link_nr; i++) {
-               u8 lane = (dp->stat[4 + (i >> 1)] >> ((i & 1) * 4)) & 0xf;
-               u8 lpre = (lane & 0x0c) >> 2;
-               u8 lvsw = (lane & 0x03) >> 0;
-
-               dp->conf[i] = (lpre << 3) | lvsw;
-               if (lvsw == DP_TRAIN_VOLTAGE_SWING_1200)
-                       dp->conf[i] |= DP_TRAIN_MAX_SWING_REACHED;
-               if ((lpre << 3) == DP_TRAIN_PRE_EMPHASIS_9_5)
-                       dp->conf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
-
-               NV_DEBUG(drm, "config lane %d %02x\n", i, dp->conf[i]);
-
-               nv_call(dp->core, NV94_DISP_SOR_DP_DRVCTL(i) + moff, (lvsw << 8) | lpre);
-       }
-
-       return nv_wraux(dp->auxch, DP_TRAINING_LANE0_SET, dp->conf, 4);
-}
-
-static int
-dp_link_train_update(struct drm_device *dev, struct dp_state *dp, u32 delay)
-{
-       struct nouveau_drm *drm = nouveau_drm(dev);
-       int ret;
-
-       udelay(delay);
-
-       ret = nv_rdaux(dp->auxch, DP_LANE0_1_STATUS, dp->stat, 6);
-       if (ret)
-               return ret;
-
-       NV_DEBUG(drm, "status %*ph\n", 6, dp->stat);
-       return 0;
-}
-
-static int
-dp_link_train_cr(struct drm_device *dev, struct dp_state *dp)
-{
-       bool cr_done = false, abort = false;
-       int voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
-       int tries = 0, i;
-
-       dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_1);
-
-       do {
-               if (dp_link_train_commit(dev, dp) ||
-                   dp_link_train_update(dev, dp, 100))
-                       break;
-
-               cr_done = true;
-               for (i = 0; i < dp->link_nr; i++) {
-                       u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
-                       if (!(lane & DP_LANE_CR_DONE)) {
-                               cr_done = false;
-                               if (dp->conf[i] & DP_TRAIN_MAX_SWING_REACHED)
-                                       abort = true;
-                               break;
-                       }
-               }
-
-               if ((dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK) != voltage) {
-                       voltage = dp->conf[0] & DP_TRAIN_VOLTAGE_SWING_MASK;
-                       tries = 0;
-               }
-       } while (!cr_done && !abort && ++tries < 5);
-
-       return cr_done ? 0 : -1;
-}
-
-static int
-dp_link_train_eq(struct drm_device *dev, struct dp_state *dp)
-{
-       bool eq_done, cr_done = true;
-       int tries = 0, i;
-
-       dp_set_training_pattern(dev, dp, DP_TRAINING_PATTERN_2);
-
-       do {
-               if (dp_link_train_update(dev, dp, 400))
-                       break;
-
-               eq_done = !!(dp->stat[2] & DP_INTERLANE_ALIGN_DONE);
-               for (i = 0; i < dp->link_nr && eq_done; i++) {
-                       u8 lane = (dp->stat[i >> 1] >> ((i & 1) * 4)) & 0xf;
-                       if (!(lane & DP_LANE_CR_DONE))
-                               cr_done = false;
-                       if (!(lane & DP_LANE_CHANNEL_EQ_DONE) ||
-                           !(lane & DP_LANE_SYMBOL_LOCKED))
-                               eq_done = false;
-               }
-
-               if (dp_link_train_commit(dev, dp))
-                       break;
-       } while (!eq_done && cr_done && ++tries <= 5);
-
-       return eq_done ? 0 : -1;
-}
-
-static void
-dp_link_train_init(struct drm_device *dev, struct dp_state *dp, bool spread)
-{
-       struct dcb_output *dcb = dp->dcb;
-       const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
-       const u32 moff = (dp->crtc << 3) | (link << 2) | or;
-
-       nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff, (spread ?
-                         NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_ON :
-                         NV94_DISP_SOR_DP_TRAIN_INIT_SPREAD_OFF) |
-                         NV94_DISP_SOR_DP_TRAIN_OP_INIT);
-}
-
-static void
-dp_link_train_fini(struct drm_device *dev, struct dp_state *dp)
-{
-       struct dcb_output *dcb = dp->dcb;
-       const u32 or = ffs(dcb->or) - 1, link = !(dcb->sorconf.link & 1);
-       const u32 moff = (dp->crtc << 3) | (link << 2) | or;
-
-       nv_call(dp->core, NV94_DISP_SOR_DP_TRAIN + moff,
-                         NV94_DISP_SOR_DP_TRAIN_OP_FINI);
-}
-
-static bool
-nouveau_dp_link_train(struct drm_encoder *encoder, u32 datarate,
-                     struct nouveau_object *core)
-{
-       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-       struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
-       struct nouveau_connector *nv_connector =
-               nouveau_encoder_connector_get(nv_encoder);
-       struct drm_device *dev = encoder->dev;
-       struct nouveau_drm *drm = nouveau_drm(dev);
-       struct nouveau_gpio *gpio = nouveau_gpio(drm->device);
-       const u32 bw_list[] = { 270000, 162000, 0 };
-       const u32 *link_bw = bw_list;
-       struct dp_state dp;
-
-       dp.auxch = nv_encoder->i2c;
-       if (!dp.auxch)
-               return false;
-
-       dp.core = core;
-       dp.dcb = nv_encoder->dcb;
-       dp.crtc = nv_crtc->index;
-       dp.dpcd = nv_encoder->dp.dpcd;
-
-       /* adjust required bandwidth for 8B/10B coding overhead */
-       datarate = (datarate / 8) * 10;
-
-       /* some sinks toggle hotplug in response to some of the actions
-        * we take during link training (DP_SET_POWER is one), we need
-        * to ignore them for the moment to avoid races.
-        */
-       nouveau_event_put(gpio->events, nv_connector->hpd.line,
-                        &nv_connector->hpd_func);
-
-       /* enable down-spreading and execute pre-train script from vbios */
-       dp_link_train_init(dev, &dp, nv_encoder->dp.dpcd[3] & 1);
-
-       /* start off at highest link rate supported by encoder and display */
-       while (*link_bw > nv_encoder->dp.link_bw)
-               link_bw++;
-
-       while (link_bw[0]) {
-               /* find minimum required lane count at this link rate */
-               dp.link_nr = nv_encoder->dp.link_nr;
-               while ((dp.link_nr >> 1) * link_bw[0] > datarate)
-                       dp.link_nr >>= 1;
-
-               /* drop link rate to minimum with this lane count */
-               while ((link_bw[1] * dp.link_nr) > datarate)
-                       link_bw++;
-               dp.link_bw = link_bw[0];
-
-               /* program selected link configuration */
-               dp_set_link_config(dev, &dp);
-
-               /* attempt to train the link at this configuration */
-               memset(dp.stat, 0x00, sizeof(dp.stat));
-               if (!dp_link_train_cr(dev, &dp) &&
-                   !dp_link_train_eq(dev, &dp))
-                       break;
-
-               /* retry at lower rate */
-               link_bw++;
-       }
-
-       /* finish link training */
-       dp_set_training_pattern(dev, &dp, DP_TRAINING_PATTERN_DISABLE);
-
-       /* execute post-train script from vbios */
-       dp_link_train_fini(dev, &dp);
-
-       /* re-enable hotplug detect */
-       nouveau_event_get(gpio->events, nv_connector->hpd.line,
-                        &nv_connector->hpd_func);
-       return true;
-}
-
-void
-nouveau_dp_dpms(struct drm_encoder *encoder, int mode, u32 datarate,
-               struct nouveau_object *core)
-{
-       struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
-       struct nouveau_i2c_port *auxch;
-       u8 status;
-
-       auxch = nv_encoder->i2c;
-       if (!auxch)
-               return;
-
-       if (mode == DRM_MODE_DPMS_ON)
-               status = DP_SET_POWER_D0;
-       else
-               status = DP_SET_POWER_D3;
-
-       nv_wraux(auxch, DP_SET_POWER, &status, 1);
-
-       if (mode == DRM_MODE_DPMS_ON)
-               nouveau_dp_link_train(encoder, datarate, core);
-}
-
 static void
 nouveau_dp_probe_oui(struct drm_device *dev, struct nouveau_i2c_port *auxch,
                     u8 *dpcd)
 
        }
 
        nv_call(disp->core, NV50_DISP_SOR_PWR + or, (mode == DRM_MODE_DPMS_ON));
-
-       if (nv_encoder->dcb->type == DCB_OUTPUT_DP)
-               nouveau_dp_dpms(encoder, mode, nv_encoder->dp.datarate, disp->core);
 }
 
 static bool