#include "rootnv50.h"
 
 #include <core/client.h>
+#include <core/notify.h>
 #include <core/ramht.h>
 #include <engine/dma.h>
 
 
  */
 #include "ior.h"
 
+static void
+gf119_dac_clock(struct nvkm_ior *dac)
+{
+       struct nvkm_device *device = dac->disp->engine.subdev.device;
+       const u32 doff = nv50_ior_base(dac);
+       nvkm_mask(device, 0x612280 + doff, 0x07070707, 0x00000000);
+}
+
 static void
 gf119_dac_state(struct nvkm_ior *dac, struct nvkm_ior_state *state)
 {
        .state = gf119_dac_state,
        .power = nv50_dac_power,
        .sense = nv50_dac_sense,
+       .clock = gf119_dac_clock,
 };
 
 int
 
 
 #include <subdev/timer.h>
 
+static void
+nv50_dac_clock(struct nvkm_ior *dac)
+{
+       struct nvkm_device *device = dac->disp->engine.subdev.device;
+       const u32 doff = nv50_ior_base(dac);
+       nvkm_mask(device, 0x614280 + doff, 0x07070707, 0x00000000);
+}
+
 int
 nv50_dac_sense(struct nvkm_ior *dac, u32 loadval)
 {
        .state = nv50_dac_state,
        .power = nv50_dac_power,
        .sense = nv50_dac_sense,
+       .clock = nv50_dac_clock,
 };
 
 int
 
        );
 }
 
-int
-nvkm_output_dp_train(struct nvkm_outp *outp, u32 unused)
+static int
+nvkm_dp_acquire(struct nvkm_outp *outp)
 {
        struct nvkm_dp *dp = nvkm_dp(outp);
        struct nvkm_ior *ior = dp->outp.ior;
        OUTP_DBG(&dp->outp, "HPD: %d", line->mask);
        if (line->mask & NVKM_I2C_IRQ) {
                if (atomic_read(&dp->lt.done))
-                       nvkm_output_dp_train(&dp->outp, 0);
+                       dp->outp.func->acquire(&dp->outp);
                rep.mask |= NVIF_NOTIFY_CONN_V0_IRQ;
        } else {
                nvkm_dp_enable(dp, true);
        .dtor = nvkm_dp_dtor,
        .init = nvkm_dp_init,
        .fini = nvkm_dp_fini,
+       .acquire = nvkm_dp_acquire,
        .release = nvkm_dp_release,
 };
 
 
        } lt;
 };
 
-#define nvkm_output_dp nvkm_dp
-
-int nvkm_output_dp_train(struct nvkm_output *, u32 rate);
-
 int nvkm_dp_new(struct nvkm_disp *, int index, struct dcb_output *,
                struct nvkm_outp **);
 
 
        return outp;
 }
 
-static void
-gf119_disp_intr_unk2_2_tu(struct nv50_disp *disp, int head,
-                         struct dcb_output *outp)
-{
-       struct nvkm_device *device = disp->base.engine.subdev.device;
-       const int or = ffs(outp->or) - 1;
-       const u32 ctrl = nvkm_rd32(device, 0x660200 + (or   * 0x020));
-       const u32 conf = nvkm_rd32(device, 0x660404 + (head * 0x300));
-       const s32 vactive = nvkm_rd32(device, 0x660414 + (head * 0x300)) & 0xffff;
-       const s32 vblanke = nvkm_rd32(device, 0x66041c + (head * 0x300)) & 0xffff;
-       const s32 vblanks = nvkm_rd32(device, 0x660420 + (head * 0x300)) & 0xffff;
-       const u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-       const u32 link = ((ctrl & 0xf00) == 0x800) ? 0 : 1;
-       const u32 hoff = (head * 0x800);
-       const u32 soff = (  or * 0x800);
-       const u32 loff = (link * 0x080) + soff;
-       const u32 symbol = 100000;
-       const u32 TU = 64;
-       u32 dpctrl = nvkm_rd32(device, 0x61c10c + loff);
-       u32 clksor = nvkm_rd32(device, 0x612300 + soff);
-       u32 datarate, link_nr, link_bw, bits;
-       u64 ratio, value;
-
-       link_nr  = hweight32(dpctrl & 0x000f0000);
-       link_bw  = (clksor & 0x007c0000) >> 18;
-       link_bw *= 27000;
-
-       /* symbols/hblank - algorithm taken from comments in tegra driver */
-       value = vblanke + vactive - vblanks - 7;
-       value = value * link_bw;
-       do_div(value, pclk);
-       value = value - (3 * !!(dpctrl & 0x00004000)) - (12 / link_nr);
-       nvkm_mask(device, 0x616620 + hoff, 0x0000ffff, value);
-
-       /* symbols/vblank - algorithm taken from comments in tegra driver */
-       value = vblanks - vblanke - 25;
-       value = value * link_bw;
-       do_div(value, pclk);
-       value = value - ((36 / link_nr) + 3) - 1;
-       nvkm_mask(device, 0x616624 + hoff, 0x00ffffff, value);
-
-       /* watermark */
-       if      ((conf & 0x3c0) == 0x180) bits = 30;
-       else if ((conf & 0x3c0) == 0x140) bits = 24;
-       else                              bits = 18;
-       datarate = (pclk * bits) / 8;
-
-       ratio  = datarate;
-       ratio *= symbol;
-       do_div(ratio, link_nr * link_bw);
-
-       value  = (symbol - ratio) * TU;
-       value *= ratio;
-       do_div(value, symbol);
-       do_div(value, symbol);
-
-       value += 5;
-       value |= 0x08000000;
-
-       nvkm_wr32(device, 0x616610 + hoff, value);
-}
-
-static void
-gf119_disp_intr_unk2_2(struct nv50_disp *disp, int head)
-{
-       struct nvkm_device *device = disp->base.engine.subdev.device;
-       struct nvkm_output *outp;
-       u32 pclk = nvkm_rd32(device, 0x660450 + (head * 0x300)) / 1000;
-       u32 conf, addr, data;
-
-       outp = exec_clkcmp(disp, head, 0xff, pclk, &conf);
-       if (!outp)
-               return;
-
-       /* see note in nv50_disp_intr_unk20_2() */
-       if (outp->info.type == DCB_OUTPUT_DP) {
-               u32 sync = nvkm_rd32(device, 0x660404 + (head * 0x300));
-               switch ((sync & 0x000003c0) >> 6) {
-               case 6: pclk = pclk * 30; break;
-               case 5: pclk = pclk * 24; break;
-               case 2:
-               default:
-                       pclk = pclk * 18;
-                       break;
-               }
-
-               if (nvkm_output_dp_train(outp, pclk))
-                       OUTP_ERR(outp, "link not trained before attach");
-       }
-
-       exec_clkcmp(disp, head, 0, pclk, &conf);
-
-       if (outp->info.type == DCB_OUTPUT_ANALOG) {
-               addr = 0x612280 + (ffs(outp->info.or) - 1) * 0x800;
-               data = 0x00000000;
-       } else {
-               addr = 0x612300 + (ffs(outp->info.or) - 1) * 0x800;
-               data = (conf & 0x0100) ? 0x00000101 : 0x00000000;
-               switch (outp->info.type) {
-               case DCB_OUTPUT_TMDS:
-                       nvkm_mask(device, addr, 0x007c0000, 0x00280000);
-                       break;
-               case DCB_OUTPUT_DP:
-                       gf119_disp_intr_unk2_2_tu(disp, head, &outp->info);
-                       break;
-               default:
-                       break;
-               }
-       }
-
-       nvkm_mask(device, addr, 0x00000707, data);
-       nvkm_wr32(device, 0x612200 + (head * 0x800), 0x00000000);
-}
-
 static void
 gf119_disp_intr_unk4_0(struct nv50_disp *disp, int head)
 {
                list_for_each_entry(head, &disp->base.head, head) {
                        if (!(mask[head->id] & 0x00001000))
                                continue;
-                       nvkm_debug(subdev, "supervisor 2.2 - head %d\n", head->id);
-                       gf119_disp_intr_unk2_2(disp, head->id);
+                       nv50_disp_super_2_2(disp, head);
                }
        } else
        if (disp->super & 0x00000004) {
 
 struct nvkm_head_func {
        void (*state)(struct nvkm_head *, struct nvkm_head_state *);
        void (*rgpos)(struct nvkm_head *, u16 *hline, u16 *vline);
+       void (*rgclk)(struct nvkm_head *, int div);
        void (*vblank_get)(struct nvkm_head *);
        void (*vblank_put)(struct nvkm_head *);
 };
 
        nvkm_mask(device, 0x6100c0 + hoff, 0x00000001, 0x00000001);
 }
 
+static void
+gf119_head_rgclk(struct nvkm_head *head, int div)
+{
+       struct nvkm_device *device = head->disp->engine.subdev.device;
+       nvkm_mask(device, 0x612200 + (head->id * 0x800), 0x0000000f, div);
+}
+
 static void
 gf119_head_state(struct nvkm_head *head, struct nvkm_head_state *state)
 {
 gf119_head = {
        .state = gf119_head_state,
        .rgpos = nv50_head_rgpos,
+       .rgclk = gf119_head_rgclk,
        .vblank_get = gf119_head_vblank_get,
        .vblank_put = gf119_head_vblank_put,
 };
 
        nvkm_mask(device, 0x61002c, (4 << head->id), (4 << head->id));
 }
 
+static void
+nv50_head_rgclk(struct nvkm_head *head, int div)
+{
+       struct nvkm_device *device = head->disp->engine.subdev.device;
+       nvkm_mask(device, 0x614200 + (head->id * 0x800), 0x0000000f, div);
+}
+
 void
 nv50_head_rgpos(struct nvkm_head *head, u16 *hline, u16 *vline)
 {
 nv50_head = {
        .state = nv50_head_state,
        .rgpos = nv50_head_rgpos,
+       .rgclk = nv50_head_rgclk,
        .vblank_get = nv50_head_vblank_get,
        .vblank_put = nv50_head_vblank_put,
 };
 
        void (*power)(struct nvkm_ior *, bool normal, bool pu,
                      bool data, bool vsync, bool hsync);
        int (*sense)(struct nvkm_ior *, u32 loadval);
+       void (*clock)(struct nvkm_ior *);
+       void (*war_2)(struct nvkm_ior *);
 
        struct {
                void (*ctrl)(struct nvkm_ior *, int head, bool enable,
                void (*vcpi)(struct nvkm_ior *, int head, u8 slot,
                             u8 slot_nr, u16 pbn, u16 aligned);
                void (*audio)(struct nvkm_ior *, int head, bool enable);
+               void (*audio_sym)(struct nvkm_ior *, int head, u16 h, u32 v);
+               void (*activesym)(struct nvkm_ior *, int head,
+                                 u8 TU, u8 VTUa, u8 VTUf, u8 VTUi);
+               void (*watermark)(struct nvkm_ior *, int head, u8 watermark);
        } dp;
 
        struct {
 
 void nv50_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
 void nv50_sor_power(struct nvkm_ior *, bool, bool, bool, bool, bool);
+void nv50_sor_clock(struct nvkm_ior *);
 
 void g94_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
 int g94_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
 void g94_sor_dp_power(struct nvkm_ior *, int);
 void g94_sor_dp_pattern(struct nvkm_ior *, int);
 void g94_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
+void g94_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
+void g94_sor_dp_activesym(struct nvkm_ior *, int, u8, u8, u8, u8);
+void g94_sor_dp_watermark(struct nvkm_ior *, int, u8);
 
 void gt215_sor_dp_audio(struct nvkm_ior *, int, bool);
 
 void gf119_sor_state(struct nvkm_ior *, struct nvkm_ior_state *);
+void gf119_sor_clock(struct nvkm_ior *);
 int gf119_sor_dp_links(struct nvkm_ior *, struct nvkm_i2c_aux *);
 void gf119_sor_dp_pattern(struct nvkm_ior *, int);
 void gf119_sor_dp_drive(struct nvkm_ior *, int, int, int, int, int);
 void gf119_sor_dp_vcpi(struct nvkm_ior *, int, u8, u8, u16, u16);
 void gf119_sor_dp_audio(struct nvkm_ior *, int, bool);
+void gf119_sor_dp_audio_sym(struct nvkm_ior *, int, u16, u32);
+void gf119_sor_dp_watermark(struct nvkm_ior *, int, u8);
 
 void gm107_sor_dp_pattern(struct nvkm_ior *, int);
 
 
        return data;
 }
 
+static void
+nv50_disp_super_ied_on(struct nvkm_head *head,
+                      struct nvkm_ior *ior, int id, u32 khz)
+{
+       struct nvkm_subdev *subdev = &head->disp->engine.subdev;
+       struct nvkm_bios *bios = subdev->device->bios;
+       struct nvkm_outp *outp = ior->asy.outp;
+       struct nvbios_ocfg iedtrs;
+       struct nvbios_outp iedt;
+       u8  ver, hdr, cnt, len, flags = 0x00;
+       u32 data;
+
+       if (!outp) {
+               IOR_DBG(ior, "nothing to attach");
+               return;
+       }
+
+       /* Lookup IED table for the device. */
+       data = nv50_disp_super_iedt(head, outp, &ver, &hdr, &cnt, &len, &iedt);
+       if (!data)
+               return;
+
+       /* Lookup IEDT runtime settings for the current configuration. */
+       if (ior->type == SOR) {
+               if (ior->asy.proto == LVDS) {
+                       if (head->asy.or.depth == 24)
+                               flags |= 0x02;
+               }
+               if (ior->asy.link == 3)
+                       flags |= 0x01;
+       }
+
+       data = nvbios_ocfg_match(bios, data, ior->asy.proto_evo, flags,
+                                &ver, &hdr, &cnt, &len, &iedtrs);
+       if (!data) {
+               OUTP_DBG(outp, "missing IEDT RS for %02x:%02x",
+                        ior->asy.proto_evo, flags);
+               return;
+       }
+
+       /* Execute the OnInt[23] script for the current frequency. */
+       data = nvbios_oclk_match(bios, iedtrs.clkcmp[id], khz);
+       if (!data) {
+               OUTP_DBG(outp, "missing IEDT RSS %d for %02x:%02x %d khz",
+                        id, ior->asy.proto_evo, flags, khz);
+               return;
+       }
+
+       nvbios_init(subdev, data,
+               init.outp = &outp->info;
+               init.or   = ior->id;
+               init.link = ior->asy.link;
+               init.head = head->id;
+       );
+}
+
 static void
 nv50_disp_super_ied_off(struct nvkm_head *head, struct nvkm_ior *ior, int id)
 {
        );
 }
 
+static struct nvkm_ior *
+nv50_disp_super_ior_asy(struct nvkm_head *head)
+{
+       struct nvkm_ior *ior;
+       list_for_each_entry(ior, &head->disp->ior, head) {
+               if (ior->asy.head & (1 << head->id)) {
+                       HEAD_DBG(head, "to %s", ior->name);
+                       return ior;
+               }
+       }
+       HEAD_DBG(head, "nothing to attach");
+       return NULL;
+}
+
 static struct nvkm_ior *
 nv50_disp_super_ior_arm(struct nvkm_head *head)
 {
 }
 
 static void
-nv50_disp_intr_unk20_2_dp(struct nv50_disp *disp, int head,
-                         struct dcb_output *outp, u32 pclk)
+nv50_disp_super_2_2_dp(struct nvkm_head *head, struct nvkm_ior *ior)
 {
-       struct nvkm_subdev *subdev = &disp->base.engine.subdev;
-       struct nvkm_device *device = subdev->device;
-       const int link = !(outp->sorconf.link & 1);
-       const int   or = ffs(outp->or) - 1;
-       const u32 soff = (  or * 0x800);
-       const u32 loff = (link * 0x080) + soff;
-       const u32 ctrl = nvkm_rd32(device, 0x610794 + (or * 8));
-       const u32 symbol = 100000;
-       const s32 vactive = nvkm_rd32(device, 0x610af8 + (head * 0x540)) & 0xffff;
-       const s32 vblanke = nvkm_rd32(device, 0x610ae8 + (head * 0x540)) & 0xffff;
-       const s32 vblanks = nvkm_rd32(device, 0x610af0 + (head * 0x540)) & 0xffff;
-       u32 dpctrl = nvkm_rd32(device, 0x61c10c + loff);
-       u32 clksor = nvkm_rd32(device, 0x614300 + soff);
+       struct nvkm_subdev *subdev = &head->disp->engine.subdev;
+       const u32      khz = head->asy.hz / 1000;
+       const u32 linkKBps = ior->dp.bw * 27000;
+       const u32   symbol = 100000;
        int bestTU = 0, bestVTUi = 0, bestVTUf = 0, bestVTUa = 0;
        int TU, VTUi, VTUf, VTUa;
        u64 link_data_rate, link_ratio, unk;
        u32 best_diff = 64 * symbol;
-       u32 link_nr, link_bw, bits;
-       u64 value;
-
-       link_bw = (clksor & 0x000c0000) ? 270000 : 162000;
-       link_nr = hweight32(dpctrl & 0x000f0000);
+       u64 h, v;
 
        /* symbols/hblank - algorithm taken from comments in tegra driver */
-       value = vblanke + vactive - vblanks - 7;
-       value = value * link_bw;
-       do_div(value, pclk);
-       value = value - (3 * !!(dpctrl & 0x00004000)) - (12 / link_nr);
-       nvkm_mask(device, 0x61c1e8 + soff, 0x0000ffff, value);
+       h = head->asy.hblanke + head->asy.htotal - head->asy.hblanks - 7;
+       h = h * linkKBps;
+       do_div(h, khz);
+       h = h - (3 * ior->dp.ef) - (12 / ior->dp.nr);
 
        /* symbols/vblank - algorithm taken from comments in tegra driver */
-       value = vblanks - vblanke - 25;
-       value = value * link_bw;
-       do_div(value, pclk);
-       value = value - ((36 / link_nr) + 3) - 1;
-       nvkm_mask(device, 0x61c1ec + soff, 0x00ffffff, value);
+       v = head->asy.vblanks - head->asy.vblanke - 25;
+       v = v * linkKBps;
+       do_div(v, khz);
+       v = v - ((36 / ior->dp.nr) + 3) - 1;
 
-       /* watermark / activesym */
-       if      ((ctrl & 0xf0000) == 0x60000) bits = 30;
-       else if ((ctrl & 0xf0000) == 0x50000) bits = 24;
-       else                                  bits = 18;
+       ior->func->dp.audio_sym(ior, head->id, h, v);
 
-       link_data_rate = (pclk * bits / 8) / link_nr;
+       /* watermark / activesym */
+       link_data_rate = (khz * head->asy.or.depth / 8) / ior->dp.nr;
 
        /* calculate ratio of packed data rate to link symbol rate */
        link_ratio = link_data_rate * symbol;
-       do_div(link_ratio, link_bw);
+       do_div(link_ratio, linkKBps);
 
-       for (TU = 64; TU >= 32; TU--) {
+       for (TU = 64; ior->func->dp.activesym && TU >= 32; TU--) {
                /* calculate average number of valid symbols in each TU */
                u32 tu_valid = link_ratio * TU;
                u32 calc, diff;
                }
        }
 
-       if (!bestTU) {
-               nvkm_error(subdev, "unable to find suitable dp config\n");
-               return;
+       if (ior->func->dp.activesym) {
+               if (!bestTU) {
+                       nvkm_error(subdev, "unable to determine dp config\n");
+                       return;
+               }
+               ior->func->dp.activesym(ior, head->id, bestTU,
+                                       bestVTUa, bestVTUf, bestVTUi);
+       } else {
+               bestTU = 64;
        }
 
        /* XXX close to vbios numbers, but not right */
        do_div(unk, symbol);
        unk += 6;
 
-       nvkm_mask(device, 0x61c10c + loff, 0x000001fc, bestTU << 2);
-       nvkm_mask(device, 0x61c128 + loff, 0x010f7f3f, bestVTUa << 24 |
-                                                  bestVTUf << 16 |
-                                                  bestVTUi << 8 | unk);
+       ior->func->dp.watermark(ior, head->id, unk);
 }
 
-static void
-nv50_disp_intr_unk20_2(struct nv50_disp *disp, int head)
+void
+nv50_disp_super_2_2(struct nv50_disp *disp, struct nvkm_head *head)
 {
-       struct nvkm_device *device = disp->base.engine.subdev.device;
-       struct nvkm_output *outp;
-       u32 pclk = nvkm_rd32(device, 0x610ad0 + (head * 0x540)) & 0x3fffff;
-       u32 hval, hreg = 0x614200 + (head * 0x800);
-       u32 oval, oreg;
-       u32 mask, conf;
+       const u32 khz = head->asy.hz / 1000;
+       struct nvkm_outp *outp;
+       struct nvkm_ior *ior;
 
-       outp = exec_clkcmp(disp, head, 0xff, pclk, &conf);
-       if (!outp)
+       /* Determine which OR, if any, we're attaching from the head. */
+       HEAD_DBG(head, "supervisor 2.2");
+       ior = nv50_disp_super_ior_asy(head);
+       if (!ior)
                return;
 
-       /* we allow both encoder attach and detach operations to occur
-        * within a single supervisor (ie. modeset) sequence.  the
-        * encoder detach scripts quite often switch off power to the
-        * lanes, which requires the link to be re-trained.
-        *
-        * this is not generally an issue as the sink "must" (heh)
-        * signal an irq when it's lost sync so the driver can
-        * re-train.
+       /* For some reason, NVIDIA decided not to:
         *
-        * however, on some boards, if one does not configure at least
-        * the gpu side of the link *before* attaching, then various
-        * things can go horribly wrong (PDISP disappearing from mmio,
-        * third supervisor never happens, etc).
+        * A) Give dual-link LVDS a separate EVO protocol, like for TMDS.
+        *  and
+        * B) Use SetControlOutputResource.PixelDepth on LVDS.
         *
-        * the solution is simply to retrain here, if necessary.  last
-        * i checked, the binary driver userspace does not appear to
-        * trigger this situation (it forces an UPDATE between steps).
+        * Override the values we usually read from HW with the same
+        * data we pass though an ioctl instead.
         */
-       if (outp->info.type == DCB_OUTPUT_DP) {
-               u32 soff = (ffs(outp->info.or) - 1) * 0x08;
-               u32 ctrl, datarate;
-
-               if (outp->info.location == 0) {
-                       ctrl = nvkm_rd32(device, 0x610794 + soff);
-                       soff = 1;
-               } else {
-                       ctrl = nvkm_rd32(device, 0x610b80 + soff);
-                       soff = 2;
-               }
-
-               switch ((ctrl & 0x000f0000) >> 16) {
-               case 6: datarate = pclk * 30; break;
-               case 5: datarate = pclk * 24; break;
-               case 2:
-               default:
-                       datarate = pclk * 18;
-                       break;
-               }
-
-               if (nvkm_output_dp_train(outp, datarate / soff))
-                       OUTP_ERR(outp, "link not trained before attach");
+       if (ior->type == SOR && ior->asy.proto == LVDS) {
+               head->asy.or.depth = (disp->sor.lvdsconf & 0x0200) ? 24 : 18;
+               ior->asy.link      = (disp->sor.lvdsconf & 0x0100) ? 3  : 1;
        }
 
-       exec_clkcmp(disp, head, 0, pclk, &conf);
+       /* Handle any link training, etc. */
+       if ((outp = ior->asy.outp) && outp->func->acquire)
+               outp->func->acquire(outp);
 
-       if (!outp->info.location && outp->info.type == DCB_OUTPUT_ANALOG) {
-               oreg = 0x614280 + (ffs(outp->info.or) - 1) * 0x800;
-               oval = 0x00000000;
-               hval = 0x00000000;
-               mask = 0xffffffff;
-       } else
-       if (!outp->info.location) {
-               if (outp->info.type == DCB_OUTPUT_DP)
-                       nv50_disp_intr_unk20_2_dp(disp, head, &outp->info, pclk);
-               oreg = 0x614300 + (ffs(outp->info.or) - 1) * 0x800;
-               oval = (conf & 0x0100) ? 0x00000101 : 0x00000000;
-               hval = 0x00000000;
-               mask = 0x00000707;
-       } else {
-               oreg = 0x614380 + (ffs(outp->info.or) - 1) * 0x800;
-               oval = 0x00000001;
-               hval = 0x00000001;
-               mask = 0x00000707;
-       }
+       /* Execute OnInt2 IED script. */
+       nv50_disp_super_ied_on(head, ior, 0, khz);
+
+       /* Program RG clock divider. */
+       head->func->rgclk(head, ior->asy.rgdiv);
 
-       nvkm_mask(device, hreg, 0x0000000f, hval);
-       nvkm_mask(device, oreg, mask, oval);
+       /* Mode-specific internal DP configuration. */
+       if (ior->type == SOR && ior->asy.proto == DP)
+               nv50_disp_super_2_2_dp(head, ior);
 
-       nv50_disp_dptmds_war_2(disp, &outp->info);
+       /* OR-specific handling. */
+       ior->func->clock(ior);
+       if (ior->func->war_2)
+               ior->func->war_2(ior);
 }
 
 void
 nv50_disp_super_2_1(struct nv50_disp *disp, struct nvkm_head *head)
 {
        struct nvkm_devinit *devinit = disp->base.engine.subdev.device->devinit;
-       u32 khz = head->asy.hz / 1000;
+       const u32 khz = head->asy.hz / 1000;
        HEAD_DBG(head, "supervisor 2.1 - %d khz", khz);
        if (khz)
                nvkm_devinit_pll_set(devinit, PLL_VPLL0 + head->id, khz);
                list_for_each_entry(head, &disp->base.head, head) {
                        if (!(super & (0x00000080 << head->id)))
                                continue;
-                       nv50_disp_intr_unk20_2(disp, head->id);
+                       nv50_disp_super_2_2(disp, head);
                }
        } else
        if (disp->super & 0x00000040) {
 
 #define __NV50_DISP_H__
 #define nv50_disp(p) container_of((p), struct nv50_disp, base)
 #include "priv.h"
-#include "dp.h"
 struct nvkm_head;
 
 struct nv50_disp {
 void nv50_disp_super_1_0(struct nv50_disp *, struct nvkm_head *);
 void nv50_disp_super_2_0(struct nv50_disp *, struct nvkm_head *);
 void nv50_disp_super_2_1(struct nv50_disp *, struct nvkm_head *);
+void nv50_disp_super_2_2(struct nv50_disp *, struct nvkm_head *);
 
 int nv50_disp_new_(const struct nv50_disp_func *, struct nvkm_device *,
                   int index, int heads, struct nvkm_disp **);
 
        void *(*dtor)(struct nvkm_outp *);
        void (*init)(struct nvkm_outp *);
        void (*fini)(struct nvkm_outp *);
+       int (*acquire)(struct nvkm_outp *);
        void (*release)(struct nvkm_outp *, struct nvkm_ior *);
 };
 
 
 #include <subdev/i2c.h>
 #include <subdev/timer.h>
 
+static void
+nv50_pior_clock(struct nvkm_ior *pior)
+{
+       struct nvkm_device *device = pior->disp->engine.subdev.device;
+       const u32 poff = nv50_ior_base(pior);
+       nvkm_mask(device, 0x614380 + poff, 0x00000707, 0x00000001);
+}
+
 static int
 nv50_pior_dp_links(struct nvkm_ior *pior, struct nvkm_i2c_aux *aux)
 {
 nv50_pior = {
        .state = nv50_pior_state,
        .power = nv50_pior_power,
+       .clock = nv50_pior_clock,
        .dp = {
                .links = nv50_pior_dp_links,
        },
 
  */
 #include "rootnv50.h"
 #include "dmacnv50.h"
+#include "dp.h"
 #include "head.h"
 #include "ior.h"
 
 
 g84_sor = {
        .state = nv50_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
        .hdmi = {
                .ctrl = g84_hdmi_ctrl,
        },
 
 
 #include <subdev/timer.h>
 
+void
+g94_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 loff = nv50_sor_link(sor);
+       nvkm_mask(device, 0x61c128 + loff, 0x0000003f, watermark);
+}
+
+void
+g94_sor_dp_activesym(struct nvkm_ior *sor, int head,
+                    u8 TU, u8 VTUa, u8 VTUf, u8 VTUi)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 loff = nv50_sor_link(sor);
+       nvkm_mask(device, 0x61c10c + loff, 0x000001fc, TU << 2);
+       nvkm_mask(device, 0x61c128 + loff, 0x010f7f00, VTUa << 24 |
+                                                      VTUf << 16 |
+                                                      VTUi << 8);
+}
+
+void
+g94_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 soff = nv50_ior_base(sor);
+       nvkm_mask(device, 0x61c1e8 + soff, 0x0000ffff, h);
+       nvkm_mask(device, 0x61c1ec + soff, 0x00ffffff, v);
+}
+
 void
 g94_sor_dp_drive(struct nvkm_ior *sor, int ln, int pc, int dc, int pe, int pu)
 {
 
 }
 
+static bool
+g94_sor_war_needed(struct nvkm_ior *sor)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 soff = nv50_ior_base(sor);
+       if (sor->asy.proto == TMDS) {
+               switch (nvkm_rd32(device, 0x614300 + soff) & 0x00030000) {
+               case 0x00000000:
+               case 0x00030000:
+                       return true;
+               default:
+                       break;
+               }
+       }
+       return false;
+}
+
 void
 nv50_disp_update_sppll1(struct nv50_disp *disp)
 {
        }
 }
 
-void
-nv50_disp_dptmds_war_2(struct nv50_disp *disp, struct dcb_output *outp)
+static void
+g94_sor_war_2(struct nvkm_ior *sor)
 {
-       struct nvkm_device *device = disp->base.engine.subdev.device;
-       const u32 soff = __ffs(outp->or) * 0x800;
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 soff = nv50_ior_base(sor);
 
-       if (!nv50_disp_dptmds_war_needed(disp, outp))
+       if (!g94_sor_war_needed(sor))
                return;
 
        nvkm_mask(device, 0x00e840, 0x80000000, 0x80000000);
 g94_sor = {
        .state = g94_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
+       .war_2 = g94_sor_war_2,
        .dp = {
                .lanes = { 2, 1, 0, 3},
                .links = g94_sor_dp_links,
                .power = g94_sor_dp_power,
                .pattern = g94_sor_dp_pattern,
                .drive = g94_sor_dp_drive,
+               .audio_sym = g94_sor_dp_audio_sym,
+               .activesym = g94_sor_dp_activesym,
+               .watermark = g94_sor_dp_watermark,
        },
 };
 
 
 
 #include <subdev/timer.h>
 
+void
+gf119_sor_dp_watermark(struct nvkm_ior *sor, int head, u8 watermark)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 hoff = head * 0x800;
+       nvkm_mask(device, 0x616610 + hoff, 0x0800003f, 0x08000000 | watermark);
+}
+
+void
+gf119_sor_dp_audio_sym(struct nvkm_ior *sor, int head, u16 h, u32 v)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const u32 hoff = head * 0x800;
+       nvkm_mask(device, 0x616620 + hoff, 0x0000ffff, h);
+       nvkm_mask(device, 0x616624 + hoff, 0x00ffffff, v);
+}
+
 void
 gf119_sor_dp_audio(struct nvkm_ior *sor, int head, bool enable)
 {
        return 0;
 }
 
+void
+gf119_sor_clock(struct nvkm_ior *sor)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const int  div = sor->asy.link == 3;
+       const u32 soff = nv50_ior_base(sor);
+       if (sor->asy.proto == TMDS) {
+               /* NFI why, but this sets DP_LINK_BW_2_7 when using TMDS. */
+               nvkm_mask(device, 0x612300 + soff, 0x007c0000, 0x0a << 18);
+       }
+       nvkm_mask(device, 0x612300 + soff, 0x00000707, (div << 8) | div);
+}
+
 void
 gf119_sor_state(struct nvkm_ior *sor, struct nvkm_ior_state *state)
 {
 gf119_sor = {
        .state = gf119_sor_state,
        .power = nv50_sor_power,
+       .clock = gf119_sor_clock,
        .hdmi = {
                .ctrl = gf119_hdmi_ctrl,
        },
                .pattern = gf119_sor_dp_pattern,
                .vcpi = gf119_sor_dp_vcpi,
                .audio = gf119_sor_dp_audio,
+               .audio_sym = gf119_sor_dp_audio_sym,
+               .watermark = gf119_sor_dp_watermark,
        },
        .hda = {
                .hpd = gf119_hda_hpd,
 
 gk104_sor = {
        .state = gf119_sor_state,
        .power = nv50_sor_power,
+       .clock = gf119_sor_clock,
        .hdmi = {
                .ctrl = gk104_hdmi_ctrl,
        },
                .drive = gf119_sor_dp_drive,
                .vcpi = gf119_sor_dp_vcpi,
                .audio = gf119_sor_dp_audio,
+               .audio_sym = gf119_sor_dp_audio_sym,
+               .watermark = gf119_sor_dp_watermark,
        },
        .hda = {
                .hpd = gf119_hda_hpd,
 
 gm107_sor = {
        .state = gf119_sor_state,
        .power = nv50_sor_power,
+       .clock = gf119_sor_clock,
        .hdmi = {
                .ctrl = gk104_hdmi_ctrl,
        },
                .drive = gf119_sor_dp_drive,
                .vcpi = gf119_sor_dp_vcpi,
                .audio = gf119_sor_dp_audio,
+               .audio_sym = gf119_sor_dp_audio_sym,
+               .watermark = gf119_sor_dp_watermark,
        },
        .hda = {
                .hpd = gf119_hda_hpd,
 
        },
        .state = gf119_sor_state,
        .power = nv50_sor_power,
+       .clock = gf119_sor_clock,
        .hdmi = {
                .ctrl = gk104_hdmi_ctrl,
        },
                .drive = gm200_sor_dp_drive,
                .vcpi = gf119_sor_dp_vcpi,
                .audio = gf119_sor_dp_audio,
+               .audio_sym = gf119_sor_dp_audio_sym,
+               .watermark = gf119_sor_dp_watermark,
        },
        .hda = {
                .hpd = gf119_hda_hpd,
 
 gt215_sor = {
        .state = g94_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
        .hdmi = {
                .ctrl = gt215_hdmi_ctrl,
        },
                .pattern = g94_sor_dp_pattern,
                .drive = g94_sor_dp_drive,
                .audio = gt215_sor_dp_audio,
+               .audio_sym = g94_sor_dp_audio_sym,
+               .activesym = g94_sor_dp_activesym,
+               .watermark = g94_sor_dp_watermark,
        },
        .hda = {
                .hpd = gt215_hda_hpd,
 
 mcp77_sor = {
        .state = g94_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
        .hdmi = {
                .ctrl = g84_hdmi_ctrl,
        },
                .power = g94_sor_dp_power,
                .pattern = g94_sor_dp_pattern,
                .drive = g94_sor_dp_drive,
+               .audio_sym = g94_sor_dp_audio_sym,
+               .activesym = g94_sor_dp_activesym,
+               .watermark = g94_sor_dp_watermark,
        },
 };
 
 
 mcp89_sor = {
        .state = g94_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
        .hdmi = {
                .ctrl = gt215_hdmi_ctrl,
        },
                .pattern = g94_sor_dp_pattern,
                .drive = g94_sor_dp_drive,
                .audio = gt215_sor_dp_audio,
+               .audio_sym = g94_sor_dp_audio_sym,
+               .activesym = g94_sor_dp_activesym,
+               .watermark = g94_sor_dp_watermark,
        },
        .hda = {
                .hpd = gt215_hda_hpd,
 
 
 #include <subdev/timer.h>
 
+void
+nv50_sor_clock(struct nvkm_ior *sor)
+{
+       struct nvkm_device *device = sor->disp->engine.subdev.device;
+       const int  div = sor->asy.link == 3;
+       const u32 soff = nv50_ior_base(sor);
+       nvkm_mask(device, 0x614300 + soff, 0x00000707, (div << 8) | div);
+}
+
 static void
 nv50_sor_power_wait(struct nvkm_device *device, u32 soff)
 {
 nv50_sor = {
        .state = nv50_sor_state,
        .power = nv50_sor_power,
+       .clock = nv50_sor_clock,
 };
 
 int